MobileNativeFoundation / MobileNativeFoundation/Store

Problems when working with a List<T>

Open
#255 21 comments 3 reactions 1 assignee View on GitHub

@digitalbuddha is already working on this.

Since Nov 16, 2021.

enhancement
Dominant language
Kotlin
Stars
3.4k
Forks
217
Avg merge
18m
Merged PRs (30d)
4

Description

I'm trying to setup a pretty basic Store that works with a List that is backed by a (Room) SoT, but I am running into a few problems, outlined below. The crux of the problem is that a call to the store's get() method never returns in some cases when the list is empty.

My setup is fairly straight forward:

val myStore = StoreBuilder.from(
        fetcher = Fetcher.of { postType: PostType ->

            // Retrofit api call that returns a list of posts
            val response = myApi.getPostsByType(postType) 

            // convert the api response json into a List<Post>
            return when {
                response.isSuccessful -> Post.fromWordpress(response.body())
                else -> listOf()
            }
        },
        sourceOfTruth = SourceOfTruth.of(
            reader = { postType ->
                //Dao method returns a Flow<List<Post>> from the "posts" table
                db.postsDao().postsByTypeObservable(postType)
            },
            writer = { postType, postList ->
                // writing the saved list to the Room DB is a bit more complicated, 
                // because we have some many-many relationships with categories...
                db.postsDao().withTransaction {
                    val posts = postList.map { it.post }
                    val categories = postList.flatMap { it.categories }.distinct()
                    val postCategories = postList.flatMap { it.postCategories }.distinct()

                    db.postsDao().upsert(posts)
                    db.categoriesDao().upsert(categories)
                    db.postCategoriesDao().upsert(postCategories)
                }
            },
            delete = db.postsDao()::deletePostsByType,
            deleteAll = db.postsDao()::deleteAllPosts
        )
    ).build()

This setup works OK, unless/until there are no posts to work with. I've specifically run into 2 problems:

  1. If you call get() with this setup and there is no data in the SoT, the fetcher will not be called.

From reading the source code, it appears that the Fetcher only gets called if the reader returns null. But when working with a list, the reader will return an empty list, and not null. Because of that, the fetcher never gets called.

We can actually fix this problem fairly easily by converting an empty list to null in the reader:

 reader = { postType ->
     //Dao method returns a Flow<List<Post> from the "posts" table
     db.postsDao().postsByTypeObservable(postType).map { entries ->
         when {
             entries.isEmpty() -> null
             else -> entries
     }
 },

Now, an empty list is treated as null, and thus the fetcher gets called.

  1. But now we have a second problem. If you call get() and there are no entries in the SoT, and the Fetcher also returns no entries, the suspended get() method will never finish/complete.

That is because once the fetcher finishes up, there are no new entries to put into the database. The get() is ultimately (via a stream) observing the Flow from the reader, and that flow will not emit anything (since there were no writes). Because of that, there is never any value emitted on the Flow (due to the filters setup in the get()) method. And since there is no value ever emitted, the suspended get() method never finishes/completes.

Is there a way to setup a Store that works with a List and ensure that the get() method will always return?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.