Forem

Michael Lustig - halfjew22@gmail.com
Michael Lustig - halfjew22@gmail.com

Posted on

3 1

βœ…[FIXED]βœ… πŸ—„ Android Room Database is not sending updates upon returning to a Fragment πŸ˜₯

FIXED

Turns out the fix is very simple. I just needed to use the Activity as the lifecycle that was passed to the ViewModel.

ShowsFragment


    // ...
    override fun onAttach(context: Context) {  
        super.onAttach(context)  

        AndroidSupportInjection.inject(this)
        // CHANGED ONE LINE AND IT WORKS
        // (changed this to activity!!)  
        // CHANGED ONE LINE AND IT WORKS
        showsViewModel = ViewModelProviders.of(activity!!, factory).get(ShowsViewModel::class.java)  
    } 
    // ...

}
Enter fullscreen mode Exit fullscreen mode

If a picture is worth 1,000 words, a video is worth, like, a lot of words. Here's a video explanation of the issue.

Here is the StackOverflow post if you think you know how to fix this. I'll be adding a bounty as soon as I can.

FIXED

SEE BELOW

If a picture is worth 1,000 words, a video is worth, like, a lot of words. Here's a video explanation of the issue.

I've included a video as it makes things much clearer. The problem: When I first load up the fragment containing the list of items…

I've included a video as it makes things much clearer. The problem: When I first load up the fragment containing the list of items with state I need to toggle, I can toggle that state just fine. I send the update to the Room database and the changes are emitted back to my ViewModel, who then dispatches them to the Fragment.

However, when I leave the fragment and come back, the changes are no longer dispatched. I don't know if I'm doing something incredibly stupid or if this is a bug.

I'm also using the Jetpack Navigation components if that's relevant. I'll include code below.

Please let me know if you need to see any other code referenced below and I'll add it to the question.

Thank you very much for your time and consideration.

ShowsFragment

class ShowsFragment : Fragment(), ShowClickListener, Observer<Resource<List<ShowDomainModel>>> {  

    @Inject  
    lateinit var factory: ViewModelFactory  
    @Inject  
    lateinit var adapter: ShowsAdapter  

    private lateinit var showsViewModel: ShowsViewModel  

    override fun onAttach(context: Context) {  
        super.onAttach(context)  

        AndroidSupportInjection.inject(this)  
        showsViewModel = ViewModelProviders.of(this, factory).get(ShowsViewModel::class.java)  
    }  

    override fun onCreateView(  
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?  
    ) = inflater.inflate(R.layout.fragment_shows, container, false)!!  

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
        super.onViewCreated(view, savedInstanceState)  

        adapter.clickListener = this  
        shows.adapter = adapter  
        val shows = showsViewModel.getShows()  
        shows.observe(this, this)  
    }  

    override fun onChanged(resource: Resource<List<ShowDomainModel>>) {  
        Timber.d("onChanged")  
        when (resource.state) {  
            State.SUCCESS -> {  
                adapter.shows = resource.data!!  
                adapter.notifyDataSetChanged()  
            }  
            State.LOADING -> Unit  
            State.ERROR -> TODO("Handle error state in ShowsFragment")  
        }  
    }  

    override fun onShowFavoriteClicked(show: ShowDomainModel) {  

        if (show.favorite) {  
            showsViewModel.unfavoriteShow(show.playlistId)  
        } else {  
            showsViewModel.favoriteShow(show.playlistId)  
        }  
    }  

    override fun onShowClicked(show: ShowDomainModel) {  
        findNavController().navigate(  
            ShowsFragmentDirections.showEpisodes(show.name, show.playlistId)  
        )  
    }  
}
Enter fullscreen mode Exit fullscreen mode

ShowsDao

@Dao  
abstract class ShowsDao {  

    @Query("SELECT * FROM $TABLE_NAME")  
    abstract fun getShows(): Observable<List<ShowCacheModel>>  

    @Insert(onConflict = OnConflictStrategy.IGNORE)  
    abstract fun insertShows(shows: List<ShowCacheModel>)  

    @Query("SELECT * from $TABLE_NAME WHERE favorite = 1")  
    abstract fun getFavoriteShows(): Observable<List<ShowCacheModel>>  

    @Query("UPDATE $TABLE_NAME SET favorite = :favorite WHERE $COLUMN_SHOW_ID = :showId")  
    abstract fun setFavorite(showId: String, favorite: Boolean)  
}
Enter fullscreen mode Exit fullscreen mode

ShowsViewModel

@Singleton  
class ShowsViewModel @Inject constructor(  
    private val getShows: GetShows,  
    private val addShowToFavorites: AddShowToFavorites,  
    private val removeShowFromFavorites: RemoveShowFromFavorites  
) : ViewModel() {  

    private val shows: MutableLiveData<Resource<List<ShowDomainModel>>> = MutableLiveData()  

    init {  
        shows.postValue(Resource.loading())  
        getShows.execute(GetShowsObserver())
    }  

    override fun onCleared() {  
        getShows.dispose()  
        super.onCleared()  
    }  

    fun getShows(): LiveData<Resource<List<ShowDomainModel>>> = shows  

    fun favoriteShow(id: String) = addShowToFavorites.execute(  
        AddShowToFavoritesObserver(),  
        AddShowToFavorites.Params.forShow(id)  
    )  

    fun unfavoriteShow(id: String) = removeShowFromFavorites.execute(  
        RemoveShowFromFavoritesObserver(),  
        RemoveShowFromFavorites.Params.forShow(id)  
    )

    inner class GetShowsObserver : DisposableObserver<List<ShowDomainModel>>() {  
        override fun onComplete() {  
            Log.d("ShowsViewModel","onComplete")  
            throw RuntimeException("GetShows should not complete, should be observing changes to data.")  
        }  

        override fun onNext(showList: List<ShowDomainModel>) {
            shows.postValue(Resource.success(showList))  
        }  

        override fun onError(e: Throwable) {  
            shows.postValue(Resource.error(e.localizedMessage))  
        }  
    }  

    inner class AddShowToFavoritesObserver : DisposableCompletableObserver() {  
        override fun onComplete() = Unit  

        override fun onError(e: Throwable) =  
            shows.postValue(Resource.error(e.localizedMessage))  
    }  

    inner class RemoveShowFromFavoritesObserver : DisposableCompletableObserver() {  
        override fun onComplete() = Unit  

        override fun onError(e: Throwable) =  
            shows.postValue(Resource.error(e.localizedMessage))  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (4)

Collapse
 
funkymuse profile image
FunkyMuse β€’ β€’ Edited

Of course that they won't be updated since you're using an observable, try changing observable to Flowable and see the magic.

Collapse
 
technoplato profile image
Michael Lustig - halfjew22@gmail.com β€’

Why do you say of course they won’t be updated? You can see in the video they are being updated. Afaik the key different between Flowable and Observable is that the former has built in back pressure support, which really isn’t relevant to me.

I actually fixed it by changing the LifecycleProvider in the ShowsFragment from this to activity!! See the linked SO answer for the updated code.

Collapse
 
funkymuse profile image
FunkyMuse β€’

Flowable isn't only backpressure related, it constantly emits a flow when the observe is present.

On attach isn't the best place to initiate the view model, onViewCreated is where it should be moved to.

Thread Thread
 
technoplato profile image
Michael Lustig - halfjew22@gmail.com β€’

How is Flowable different than Observable in terms of what you’ve described?

Since this particular ViewMode is a singleton, I’m just getting a reference to it rather than instantiating it, so it shouldn’t matter much where that actually happens.

πŸ‘‹ Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay