Here's flow data in my app:
In view I got method onClick were I call presenter.Method(). In this method on presenter I pass the call to model(Model got his own layer of abstracion -> interface modelHelper. It's getting injected via dagger 2 in Conctructor Presenter).
In Model i got method for Network call :
#Override
public void networkCallForData(String request) {
request = "volumes?q=" + request;
compositeDisposable.add(
api.getBook(request)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
books -> {
items.clear();
items.addAll(books.items);
}
, Throwable::printStackTrace
, () -> {
}
)
);
}
}
I got 2 questions :
1. In MVP architecture should Model layer got injected instance of abstracted presenter and connect it to model just like with view ? if not how should i send data from model to presenter ?
I try connect presenter to model via RxJava2 but got problem with synchronization. In model i create observable from :
private List<Items> items = new ArrayList<>();
and getter method to it :
public Observable<List<Items>> getItemsObservable() {
return itemsObservable;
}
here i create observable :
private Observable<List<Items>> itemsObservable = Observable.fromArray(items);
In presenter i got :
private void getDataFromModel() {
compositeDisposable.add(
findActivityModel.getItemsObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
books -> {
view.setRecycler(books);
}, Throwable::printStackTrace
, () -> {
view.setRecyclerVisible();
}
)
);
}
}
When i click on button to search i got first empty response because i observe on list with got not updated yet(Its getting updated via method network call). If I press button 2nd time thats when I got need data from 1 request. How should i chain those 2 RxJava method from different class ?
1. In MVP architecture should Model layer got injected instance of abstracted presenter and connect it to model just like with view?
No. Model layer should not be directly accessing View nor Presentation layer.
Also note that it doesn't make much sense to put .observeOn(AndroidSchedulers.mainThread()) in any of your Model layer implementation.
if not how should i send data from model to presenter ?
Model should just respond to Presenters' queries. It could be a simple function call. Model does not need to hold Presenter instances to handle that.
2. ...How should i chain those 2 RxJava method from different class ?
Consider this implementation:
Model
#Override
public Observable<List<Items>> networkCallForData(String request) {
request = "volumes?q=" + request;
return api.getBook(request);
}
Presenter
// Call this once in the beginning.
private void getDataFromModel() {
compositeDisposable.add(
findActivityModel.networkCallForData(request)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
books -> {
view.setRecycler(books);
}, Throwable::printStackTrace
, () -> {
view.setRecyclerVisible();
}
)
);
}
Above implementation should be sufficient if you are getting the data only once per screen. But you mentioned something like a refresh button. For that you can make use of BehaviorSubject or BehaviorProcessor.
Presenter
private BehaviorSubject<List<Item>> items =
BehaviorSubject.create(); // You may move this line to the Model layer.
// Call this once in the beginning to setup the recycler view.
private void getDataFromModel() {
// Instead of subscribing to Model, subscribe to BehaviorSubject.
compositeDisposable.add(
items.subscribe(books -> {
// Any change in BehaviorSubject should be notified
view.setRecycler(books);
view.setRecyclerVisible();
});
}
// Trigger this on button clicks
private void refreshData() {
compositeDisposable.add(
findActivityModel.networkCallForData(request)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(books -> {
// Refresh BehaviorSubject
items.onNext(books);
});
}
Even this might not perfectly fit your need but hope you get the idea. Also, few side notes:
Observable.fromArray() returns an observable that emits array items one at a time. Therefore, it is not very useful in this scenario.
It seems you have an object and an observable that wraps the object. If you have these two things in the same place that is usually a sign of a bad design.
private Observable> itemsObservable;
private List items;
This is not the right way to use observable but also violates single source of truth.
One possible way of refactoring this:
private BehaviorSubject<List<Items>> itemsObservable;
// private List<Items> items; // Remove this line
public Observable<List<Items>> getItemsObservable() {
return itemsObservable.hide();
}
// Call this whenever you need to update itemsObservable
private void updateItems(List<Item> newItems) {
itemsObservable.onNext(newItems);
}
Related
I want to get the data of a PagingData<T> object in some in-between class like ViewModel before it is reached to the Adapter and gather its property as a list.
I can access the code by the flatmap but I don't want to apply any changes. Also, this kind of access is useless because the entire block runs after subscribing to the observable is finished in ViewModel so it is unreachable by the ViewModel running.
private fun getAssignedDbList(): Flowable<PagingData<T>> {
var list = mutableListOf<Int>()
return repository.getList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { pagingData ->
Flowable.just(pagingData.map
{ foo ->
list.add(foo.id)
foo })
}
}
Another example, let's assume that we have this scenario:
I'm going to call another API before the Paging data is reached to the view holder. The paging data needs to be filled with a new API call response. How can I wait for the second API call to update the data of paging data?
private fun getListFromAPI(): Flowable<PagingData<T>> {
var list = mutableListOf<Int>()
return repository.getList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { pagingData ->
//call another API calls and waiting for a response to change some of the fields of
//paging data
}
}
I'd like to use the same LiveData with different sources. One from an API call which is an observable and one from a database which is a LiveData. I'd like to be able to do something like this:
private LiveData<List<Items>> items = new MutableLiveData<>();
// this one comes from an API and it's an observable
public void onApiItemsSelected(String name) {
Disposable disposable = repository.getItemsFromApi(name)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(itemsList -> {
items.setValue(itemsList);
});
compositeDisposable.add(disposable);
}
// this one comes from the database and it's a livedata that I want to transform first
public void onDatabaseItemsSelected() {
items = Transformations.map(repository.getItemsFromdatabase(), itemsList -> {
List<Items> finalItemsList = new ArrayList<>();
for (Item item : itemsList) {
finalItemsList.add(itemsList.toSomething());
}
return finalItemsList;
});
}
The problem is that a Transformation.map always returns a LiveData and in order to make a items.setValue(itemsList) items need to be a MutableLiveData. I tried with MediatorLiveData but it's calling the two sources and mixing everything. That's not what I need here. I need one source OR the other. Is it possible?
I think you could delegate this responsibility to your repository.
See for example this open source project and related article with more info.
Basically, the repository handles all the complexity of deciding which source to get the data from (see the JobRepository). It exposes a rx Observable to the ViewModel (see the JobsViewModel). Then all the ViewModel has to do is update the LiveData:
private val presentation: MutableLiveData<Presentation> = MutableLiveData()
fun bind() {
jobRepository.getJobs(true)
.subscribeOn(Schedulers.io())
.subscribe { resource: Resource<List<JobWithRelations>> ->
when (resource) {
// commenting some parts for brevity ...
is Resource.ResourceFound -> {
presentation.postValue(Presentation(getApplication(), resource.data!!))
}
// ...
}
}.addTo(compositeDisposable)
}
Managing multiple sources of data is complex. Among the many aspects involved in architecting this, things to keep in mind include:
In-memory caching expiration strategies
Policies for db caching, for example, when users are offline
Throttling and retry of API calls
Policies for updating the in-memory cache or db when new data comes from the network
One library that could help you is the Dropbox Store. With it, you could build your data sources like in the example below from their documentation:
StoreBuilder
.from(
fetcher = nonFlowValueFetcher { api.fetchSubreddit(it, "10").data.children.map(::toPosts) },
sourceOfTruth = SourceOfTrue.from(
reader = db.postDao()::loadPosts,
writer = db.postDao()::insertPosts,
delete = db.postDao()::clearFeed,
deleteAll = db.postDao()::clearAllFeeds
)
).build()
Then to get the data in your ViewModel:
private val presentation: MutableLiveData<Presentation> = MutableLiveData()
lifecycleScope.launchWhenStarted {
store.stream(StoreRequest.cached(key = key, refresh=true)).collect { response ->
when(response) {
// commenting some parts for brevity ...
is StoreResponse.Data -> {
presentation.postValue(response.value)
}
// ...
}
}
}
I am not sure it is the best practice but I think will do the trick.
public void onDatabaseItemsSelected() {
items.value = repository.getItemsFromdatabase().value;
};
The syntax may not be accurate as I am know little about java, I do mainly kotlin. But the thing is: when the user clicks Api, make your onApiItemsSelected() run, that will set the value of items, when the user clicks database, make onDatabaseItemsSelected() run, so it will replace items value for the result of repository.getItemsFromDatabase()
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?
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.
So, I have a Repository class, which has a search() method which creates a zipped Single and returns data to a listener.
Inside of this method, I need to call 4 services and zip their results. The services return different data types, from which I build the SearchResult object.
The method looks like this:
fun search() {
Single.just(SearchResult.Builder())
.zipWith(
service1.search()
.subscribeOn(Schedulers.io())
.onErrorReturnItem(emptyList()),
{ result, data -> result.apply { this.data1 = data } })
.zipWith(
service2.search()
.subscribeOn(Schedulers.io())
.onErrorReturnItem(emptyList()),
{ result, data -> result.apply { this.data2 = data } })
// Other two services done in the same way
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ listener.onSearchComplete(it.build()) },
{ listener.onSearchFailed() })
}
The implementation of search() in the services looks like this:
fun search(): Single<List<DataTypeX>> =
Single.create<List<DataTypeX>> { subscriber ->
makeNetworkRequest()
?.let{
//logic omitted for clarity
subscriber.onSuccess(it)
} ?: subscriber.onError(IllegalStateException())
The problem is the last line. When this network call fails and the response is null, the IllegalStateException will be propagated and crash the app, instead of being silently caught by onErrorReturnItem(emptyList()).
Is there any reason for this? Am I misunderstanding the idea behind onErrorReturnItem()?
My intention is to create an Observable that can be made to emit an item inside a class.
My goal is to create a ViewModel, which can expose a stream to a View in Android, reactively delivering items.
My approach is as follows:
class MyMVVM {
private lateinit var timeSelectedEmitter: ObservableEmitter<Int>
val timeSelectedObservable: Observable<Int> = Observable.create { emitter -> timeSelectedEmitter = emitter }
}
This should, in theory and as far as I understand it currently, give me access to the emitter, which then can be called at any point to deliver time-events to subscribers of timeSelectedObservable (subscribers which are inside the View)
1) Is this a correct approach or should this be done in another way?
2) Is there a way for the emitter to be created on Observable creation instead of when a subscriber is subscribing to the Observable (to avoid nullpointer exceptions or Kotlin exceptions)?
Thanks.
I usually create the Observable once and return the same Observable to anything that wants to subscribe to it. This way, if there are multiple subscribers, a new Observable doesn't need to be created each time.
So something like:
class ViewModel {
private val timeObservable = PublishSubject.create<Int>()
fun timeObservable(): Observable<Int> = timeObservable
fun update() {
// ...
timeObservable.onNext(time)
}
}
class Activity {
...
viewModel.timeObservable()
.subscribe {
time -> ...
})
}