0

I'm using mvvm and android architecture component , i'm new in this architecture .

in my application , I get some data from web service and show them in recycleView , it works fine .

then I've a button for adding new data , when the user input the data , it goes into web service , then I have to get the data and update my adapter again.

this is my code in activity:

 private fun getUserCats() {
    vm.getCats().observe(this, Observer {
        if(it!=null) {
            rc_cats.visibility= View.VISIBLE
            pb.visibility=View.GONE
            catAdapter.reloadData(it)

        }
    })
}

this is view model :

class CategoryViewModel(private val model:CategoryModel): ViewModel() {

private lateinit var catsLiveData:MutableLiveData<MutableList<Cat>>

fun getCats():MutableLiveData<MutableList<Cat>>{
    if(!::catsLiveData.isInitialized){
        catsLiveData=model.getCats()
    }
    return catsLiveData;
}

fun addCat(catName:String){
    model.addCat(catName)
}

}

and this is my model class:

class CategoryModel(
    private val netManager: NetManager,
    private val sharedPrefManager: SharedPrefManager) {

private lateinit var categoryDao: CategoryDao
private lateinit var dbConnection: DbConnection
private lateinit var lastUpdate: LastUpdate

fun getCats(): MutableLiveData<MutableList<Cat>> {
    dbConnection = DbConnection.getInstance(MyApp.INSTANCE)!!
    categoryDao = dbConnection.CategoryDao()
    lastUpdate = LastUpdate(MyApp.INSTANCE)

    if (netManager.isConnected!!) {
        return getCatsOnline();
    } else {
        return getCatsOffline();
    }
}

fun addCat(catName: String) {
    val Category = ApiConnection.client.create(Category::class.java)
    Category.newCategory(catName, sharedPrefManager.getUid())
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                    { success ->
                        getCatsOnline()
                    }, { error ->
                Log.v("this", "ErrorNewCat " + error.localizedMessage)
            }
            )
}

private fun getCatsOnline(): MutableLiveData<MutableList<Cat>> {
    Log.v("this", "online ");
    var list: MutableLiveData<MutableList<Cat>> = MutableLiveData()
    list = getCatsOffline()

    val getCats = ApiConnection.client.create(Category::class.java)
    getCats.getCats(sharedPrefManager.getUid(), lastUpdate.getLastCatDate())
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                    { success ->
                        list += success.cats
                        lastUpdate.setLastCatDate()

                        Observable.just(DbConnection)
                                .subscribeOn(Schedulers.io())
                                .subscribe({ db ->
                                    categoryDao.insert(success.cats)
                                })

                    }, { error ->
                Log.v("this", "ErrorGetCats " + error.localizedMessage);
            }
            )

    return list;
}

I call getCat from activity and it goes into model and send it to my web service , after it was successful I call getCatsOnline method to get the data again from webservice .

as I debugged , it gets the data but it doesn't notify my activity , I mean the observer is not triggered in my activity .

how can I fix this ? what is wrong with my code?

Navid Abutorab
  • 1,667
  • 7
  • 31
  • 58

1 Answers1

0

You have made several different mistakes of varying importance in LiveData and RxJava usage, as well as MVVM design itself.


LiveData and RxJava

Note that LiveData and RxJava are streams. They are not one time use, so you need to observe the same LiveData object, and more importantly that same LiveData object needs to get updated.

If you look at getCatsOnline() method, every time the method gets called it's creating a whole new LiveData instance. That instance is different from the previous LiveData object, so whatever that is listening to the previous LiveData object won't get notified to the new change.

And few additional tips:

  • In getCatsOnline() you are subscribing to an Observable inside of another subscriber. That is common mistake from beginners who treat RxJava as a call back. It is not a call back, and you need to chain these calls.

  • Do not subscribe in Model layer, because it breaks the stream and you cannot tell when to unsubscribe.

  • It does not make sense to ever use AndroidSchedulers.mainThread(). There is no need to switch to main thread in Model layer especially since LiveData observers only run on main thread.

  • Do not expose MutableLiveData to other layer. Just return as LiveData.

One last thing I want to point out is that you are using RxJava and LiveData together. Since you are new to both, I recommend you to stick with just one of them. If you must need to use both, use LiveDataReactiveStreams to bridge these two correctly.


Design

How to fix all this? I am guessing that what you are trying to do is to:

(1) view needs category -> (2) get categories from the server -> (3) create/update an observable list object with the new cats, and independently keep the result in DB -> (4) list instance should notify activity automatically.

It is difficult to pull this off correctly because you have this list instance that you have to manually create and update. You also need to worry about where and how long to keep this list instance.

A better design would be:

(1) view needs category -> (2) get a LiveData from DB and observe -> (3) get new categories from the server and update DB with the server response -> (4) view is notified automatically because it's been observing DB!

This is much easier to implement because it has this one way dependency: View -> DB -> Server

Example CategoryModel:

class CategoryModel(
    private val netManager: NetManager,
    private val sharedPrefManager: SharedPrefManager) {

    private val categoryDao: CategoryDao
    private val dbConnection: DbConnection
    private var lastUpdate: LastUpdate // Maybe store this value in more persistent place..


    fun getInstance(netManager: NetManager, sharedPrefManager: SharedPrefManager) {
        // ... singleton
    }


    fun getCats(): Observable<List<Cat>> {
        return getCatsOffline();
    }

    // Notice this method returns just Completable. Any new data should be observed through `getCats()` method.
    fun refreshCats(): Completable {
        val getCats = ApiConnection.client.create(Category::class.java)

        // getCats method may return a Single
        return getCats.getCats(sharedPrefManager.getUid(), lastUpdate.getLastCatDate())
            .flatMap { success -> categoryDao.insert(success.cats) } // insert to db
            .doOnSuccess { lastUpdate.setLastCatDate() }
            .ignoreElement()
            .subscribeOn(Schedulers.io())
    }


    fun addCat(catName: String): Completable {
         val Category = ApiConnection.client.create(Category::class.java)

         // newCategory may return a Single
         return Category.newCategory(catName, sharedPrefManager.getUid())
             .ignoreElement()
             .andThen(refreshCats())
             .subscribeOn(Schedulers.io())
        )
    }
}

I recommend you to read through Guide to App Architecture and one of these livedata-mvvm example app from Google.

Sanlok Lee
  • 3,404
  • 2
  • 15
  • 25