I am using RxJava in my Android project and I'm happy about it. I'm currently using it to make all my DAO methods asynchronous and make UI listens on them.
But I have a big problem, that is, when I retrieve some data from database using Observable<List<User>> getLists(), I need to use List<User> in my ViewModels, but I cannot extract it from the observable.
I would like to know what is the common approach to solve this kind of problem ? I searched on Internet and people said it's not recommended to extract the objects, but in this case how can I use the data from database and at the same time still enable the observers listening ?
Should I create another method using AsyncTask ??
Thanks.
In my UserRepo.java
public Observable<List<User>> getUsers() {
return colisDao.getUsers();
}
In HomeScreenViewModel.java:
public List<User> getUsers() {
return userRepo.getUsers(); // do not work because I need a List<User>
}
In HomeActivity.java:
UserListAdapter userListAdapter = new UserListAdapter(this,
vm.getUsers());
Central idea of reactive extensions is to make use of events' streams observation and timely processing.
So actually, if you need to retrieve data in a straightforward way, I'd say you don't need RxJava2 at all. Still, if you want to stick to the reactive approach, the data stream should be listened to instead.
All RxJava2 types provide a subscribe method that "notifies" the source of data that's lazy by nature that here is an observer that wants to receive the data, so all the data processing flow described by use of RxJava2 operators will become alive.
The most painless approach is to change HomeActivity's code to this:
vm.getUsers()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(userListAdapter::populateWithNewDataSet);
, assuming that adapter will have the mentioned method that will update the UI data set using something like notifyDataSetChanged() (or DiffUtil, for instance) internally.
By doing that the data source is now observed and every time the update is emitted the UI will be repopulated with the most recent data.
P.S.: I've just demonstrated the simplest way to do the thing, but it is up to the developer where to place RxJava-related code: be it ViewModel, Activity, or even some other component. RxJava is a convenient tool to use and it can make complicated asynchronous flow simple, but the problem with RxJava arises when all the code base is dependent on it. The code base can then quickly become unmanageable, fragile and rigid if the tool was used in an improper place.
Adding on #AndreyIlyunin very good answer, You could also use MutableLivedata in your Viewmodel to save the List in the viewmodel as Livedata and observe changes to it in your Activity. This is suggested by Google as a way to maintain MVVM architecture. Something like:
In HomeScreenViewModel.java:
private final MutableLivedata<List<User>> users = new MutableLivedata<>();
public void getUsers() {
return userRepo.getUsers()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onUsers)
}
private void onUsers(List<> list){
users.setValue(list);
}
public MutableLivedata<List<User>> getUserList(){
return users;
}
In HomeActivity.java, in onCreate() add:
vm.getUserList().observe(this,this::onUserList);
and add following methods to activity:
private void onUserList(List<> list){
userListAdapter = new UserListAdapter(this,list);
}
and then from your activity call:
vm.getUsers();
The getUsers() call is made asynchronously in the background, and you get the userList reactivly.
Related
The company I just started working at uses a so called Navigator, which I for now interpreted as a stateless ViewModel. My Navigator receives some usecases, with each contains 1 suspend function. The result of any of those usecases could end up in a single LiveData. The Navigator has no coroutine scope, so I pass the responsibility of scoping suspending to the Fragment using fetchValue().
Most current code in project has LiveData in the data layer, which I tried not to. Because of that, their livedata is linked from view to dao.
My simplified classes:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
val url = MediatorLiveData<String>()
fun goToUrl1() {
url.fetchValue { getUrl1() }
}
fun goToUrl2() {
url.fetchValue { getUrl2() }
}
fun <T> MediatorLiveData<T>.fetchValue(provideValue: suspend () -> T) {
val liveData = liveData { emit(provideValue()) }
addSource(liveData) {
removeSource(liveData)
value = it
}
}
}
class MyFeatureFragment : Fragment {
val viewModel: MyFeatureViewModel by viewModel()
val navigator: MyFeatureNavigator by inject()
fun onViewCreated() {
button.setOnClickListener { navigator.goToUrl1() }
navigator.url.observe(viewLifecycleOwner, Observer { url ->
openUrl(url)
})
}
}
My two questions:
Is fetchValue() a good way to link a suspend function to LiveData? Could it leak? Any other concerns?
My main reason to only use coroutines (and flow) in the data layer, is 'because Google said so'. What's a better reason for this? And: what's the best trade off in being consistent with the project and current good coding practices?
Is fetchValue() a good way to link a suspend function to LiveData?
Could it leak? Any other concerns?
Generally it should work. You probably should remove the previous source of the MediatorLiveData before adding new one, otherwise if you get two calls to fetchValue in a row, the first url can be slower to fetch, so it will come later and win.
I don't see any other correctness concerns, but this code is pretty complicated, creates a couple of intermediate objects and generally difficult to read.
My main reason to only use coroutines (and flow) in the data layer,
is 'because Google said so'. What's a better reason for this?
Google has provided a lot of useful extensions to use coroutines in the UI layer, e.g. take a look at this page. So obviously they encourage people to use it.
Probably you mean the recommendation to use LiveData instead of the Flow in the UI layer. That's not a strict rule and it has one reason: LiveData is a value holder, it keeps its value and provides it immediately to new subscribers without doing any work. That's particularly useful in the UI/ViewModel layer - when a configuration change happens and activity/fragment is recreated, the newly created activity/fragment uses the same view model, subscribes to the same LiveData and receives the value at no cost.
At the same time Flow is 'cold' and if you expose a flow from your view model, each reconfiguration will trigger a new flow collection and the flow will be to execute from scratch.
So e.g. if you fetch data from db or network, LiveData will just provide the last value to new subscriber and Flow will execute the costly db/network operation again.
So as I said there is no strict rule, it depends on the particular use-case. Also I find it very useful to use Flow in view models - it provides a lot of operators and makes the code clean and concise. But than I convert it to a LiveData with help of extensions like asLiveData() and expose this LiveData to the UI. This way I get best from both words - LiveData catches value between reconfigurations and Flow makes the code of view models nice and clean.
Also you can use latest StateFlow and SharedFlow often they also can help to overcome the mentioned Flow issue in the UI layer.
Back to your code, I would implement it like this:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
private val currentUseCase = MutableStateFlow<UseCase?>(null)
val url = currentUseCase.filterNotNull().mapLatest { source -> source.getData()}.asLiveData()
fun goToUrl1() {
currentUseCase.value = getUrl1
}
fun goToUrl2() {
currentUseCase.value = getUrl2
}
}
This way there are no race conditions to care about and code is clean.
And: what's the best trade off in being consistent with the project
and current good coding practices?
That's an arguable question and it should be primarily team decision. In most projects I participated we adopted this rule: when fixing bugs, doing maintenance of existing code, one should follow the same style. When doing big refactoring/implementing new features one should use latest practices adopted by the team.
I am getting a complex json from an api and I am saving the values with Architect component(Room).
Question:
Can I compare current values with last values that I saved in SQlite and if in compare I find a difference, update the RecyclerView?
Is this method logical?
Do you have a better way to offer?
If you have a better way to offer get me a sample(url sample)
Yes, you can do that and it is actually the recommended way. In order to do so, I think you should leverage the use of two other Architecture Components that were introduced with Android Jetpack, not only Room database: ViewModel and LiveData, but it is not mandatory.
The important thing is to add an extra layer to your app called Repository:
Repository modules handle data operations. They provide a clean API so
that the rest of the app can retrieve this data easily. They know
where to get the data from and what API calls to make when data is
updated. You can consider repositories to be mediators between
different data sources, such as persistent models, web services, and
caches.
So basically, the suggested architecture to handle this will look something like this:
With that in mind, an example of a Repository that retrieves User data from a web service and save it to a local Room Database will look something like this:
public class UserRepository {
private final Webservice webservice;
private final UserDao userDao;
private final Executor executor;
public UserRepository(Webservice webservice, UserDao userDao, Executor executor) {
this.webservice = webservice;
this.userDao = userDao;
this.executor = executor;
}
public LiveData<User> getUser(String userId) {
refreshUser(userId);
// Returns a LiveData object directly from the database.
return userDao.load(userId);
}
private void refreshUser(final String userId) {
// Runs in a background thread.
executor.execute(() -> {
// Check if user data was fetched recently.
boolean userExists = userDao.hasUser(FRESH_TIMEOUT);
if (!userExists) {
// Refreshes the data.
Response<User> response = webservice.getUser(userId).execute();
// Check for errors here.
// Updates the database. The LiveData object automatically
// refreshes, so we don't need to do anything else here.
userDao.save(response.body());
}
});
}
}
Then, your ViewModel will get the User Live Data doing something like this:
...
user = userRepo.getUser(userId);
...
And it will provide that LiveData to the UI layer with a public method:
...
public LiveData<User> getUser() {
return this.user;
}
...
Finally, from your UI layer (an Activity or Fragment) you can observe the LiveData in the ViewModel and adapt the UI accordingly.
viewModel.getUser().observe(this, user -> {
// Update UI.
});
For a more complete explanation I suggest that you take a look to:
Guide to app architecture in Android`s Developers website.
This Github project with a basic example.
This other Github project with a more complex example.
You can merge multiple live data source from server and sqlite with MediatorLiveData which is a subclass of LiveData.
For example, if you have a LiveData object in your UI that can be updated from a local database or a network, then you can add the following sources to the MediatorLiveData object:
A LiveData object associated with the data stored in the database.
A LiveData object associated with the data accessed from the network.
Documentation
I just started using android room. Only problem is,
It takes several layers for db interaction.
Table Class -> Dao Interface -> Database Repo -> ViewModels
And in each layer, code repetition is there.
As if I directly call queries from Repo, without viewModels, it will not allow. Because call without viewModel observer becomes synchronous, which blocks main thread.
Either there must be standard way to call repo asynchronously, or some hacks.
May be we can use some Async generic class, which lets you pass queries and return result to main thread.
Possible hack. Don't knwo if it is correct way.
AsyncTask.execute(new Runnable() {
#Override
public void run() {
List<User> users = apiService.getAllUsers();
if(users.size()>0)
{
System.out.println("Total users:"+users.size());
System.out.println("Email:"+users.get(0).getEmail());
}
}
});
You can use an AsyncTask for this without the need for ViewModels.
AsyncTask.execute {
val entity = daoInterface.queryFunction()
...
}
If you are just testing Room then just call
.allowMainThreadQueries()
If you are building a real app there is no point in skipping this Android Architecture.
The layers that you see explained here or in your app do not introduce code repetition, it may seem like so, but it makes your app modular. If your application scales and you need to reuse or change something it will be much easier.
And additionally, ViewModel does not make the calls asynchronous. What makes them work is LiveData (when you wrap you return type in LiveData in Dao class).
ViewModel serves to abstract away the non-view related logic from the View (Activity or Fragment), and lets the data survive configuration change, additionally with ViewModel you will avoid having a God Activity that handles everything.
You have several options:
1) You can use AsyncTask mentioned by #EarlOfEgo in order to perform insert. And when you query your database, just wrap the return type in LiveData and that's it. A small example of an AsyncTask, taken from the codelab page 8:
private static class insertAsyncTask extends AsyncTask<Word, Void, Void> {
private WordDao mAsyncTaskDao;
insertAsyncTask(WordDao dao) {
mAsyncTaskDao = dao;
}
#Override
protected Void doInBackground(final Word... params) {
mAsyncTaskDao.insert(params[0]);
return null;
}
}
2) If you don't need to observe the changes in your database then you can avoid LiveData altogether and handle the execution of all the queries and inserts manually on a separate thread. Or another option is to receive only one update from the LiveData and unregister the listener (or I believe there is an implementation of a LiveData that receives only a single update).
3) Or you can just .allowMainThreadQueries()
I don't know if this is a stupid question. This may defeat the purpose of LiveData/ViewModel.
Can I make the LiveData static? My reason is I have a listener from a Service which updates the information. So I need to have a way from a service to "set/change" the LiveData.
I used to do following and it works:
1. Service changes the DB
2. ViewModel listens to the DB change
3. UI updates from the liveData change
I found this way is too slow. To increase the performance, I want something like:
1. Service changes the class object directly
2. ViewModel listens to the the class object changes
3. UI updates from the liveData change
In order to achieve what I want, either I need to make the MutableLiveData static or make the ViewModel class to share the same instance of ViewModel between Activities.
Is this good idea?
public class MyViewModel extends AndroidViewModel {
// Note: this MutableLiveData is static
private static MutableLiveData<MyModel> mutableLiveData;
public MyViewModel(#NonNull Application application) {
super(application);
}
LiveData<MyModel> getLiveDataList() {
if (mutableLiveData == null) {
mutableLiveData = new MutableLiveData<>();
loadDataFromDb();
}
return mutableLiveData;
}
private void loadDataFromDb() {
// load data from DB
// mutableLiveData.setValue(MyModelFromDb); // Omit the real implementation
}
// Note: this method is static
public static void setData(MyModel newData) {
mutableLiveData.setValue(newData);
}
#Override
protected void onCleared() {
super.onCleared();
}
}
The whole point of ViewModel from Android Jetpack (as opposed to other versions) is for the ViewModel to be lifecycle aware and perform magic like destroying itself when observer is destroyed (activity/fragment), or surviving configuration changes (for example, orientation) without initialising itself all over again thereby making it much easier to deal with issues related to configuration changes.
So if you made the ViewModel or LiveData static you would actually beat their purpose and most likely leak ViewModel's data, though the need to do this is understandable. So this requires you to engineer your way around it, and the first way you mentioned is probably the best way you can do it. I don't understand why you have an issue with the first solution. The way I see it, it provides the best user experience:
You init ViewModel in your fragment or activity in onCreate and add an Observer to the data.
If database already has some data, your observer will receive it instantly and UI will be updated with existing data instantly.
Service makes the API request and changes the DB
DB changes triggers an update to the data in ViewModel
Observer refreshes received data and you pass this to your views/adapters
UI updates with latest data with some nice animations that indicate addition/removal of items.
From what I can see it cant get better than this. Since your question is from months ago, I am curious to know what you ended up doing?
I think if MyViewModel will have lots of LiveData fields it will grow with large amount of getters and setters. And what even worst, as for me, you will break the testablity of your code, because if you will create a new instance of MyViewModel you will expect that your LiveData objects are stateless at this point of time, but as it's a static object you don't know in what exactly state it is after simple creation.
As well static methods can't be overriden. And about fields: if you will want to have common field, suppose errorMessage, in class A and class B while both of them extend class C(which contains your common field) you can have unexpected behavior. On the other hand you can duplicate this code in other classes(what is bad).
The memory issue: if a large number of static variables/methods are used. Because they will not be GC until program ends.
But it just my opinion.
In my application I am accessing my business objects using a Repository and RxJava. From my Presenter layer I can ask the Repository to get me ObjectA or get me ObjectB and the Respository will return an Observable or Observable respectively.
Sometimes I have the need to get both ObjectA and ObjectB. I'm wondering what the options are for fetching both objects using RxJava that also allows unit testing of my Presenter layer.
The way I originally implemented this was to use the flatMap() operator. So I'd do something like this:
//In Presenter layer
Observable<ObjectA> obsA = repository.getObjectA();
Observable<ObjectB> obsB = repository.getObjectB();
Observable<ObjectB> obsResult = obsA.flatMap(new Func1<ObjectA, Observable<ObjectB>>() {
#Override
public Observable<ObjectB> call(ObjectA a) {
mObjectA = a;
return obsB;
}
});
When I subscribe to the obsResult, obsA runs and I can access its result in the flatMap() operator. I get a handle to its result and store it as a field in my Presenter. Then obsB runs and I get its result in my Subscriber. This works just fine but I can't help but think I'm not really doing it right. One issue is that although I may have 100% test coverage in my Repository class, now I'm manipulating the observables that come out of the Repository class and I'm having a hard time figuring out how to test the obsResult or to verify that the code I write in flatMap() is correct.
Should I be adding methods to my Repository layer such as getObjectAandObjectB and return an Observable and do all of the work in the Repository layer? This would allow me to test the resultant Observable in the Repository layer tests rather than trying to create a new Observable from two separate Observables and figure out how to test it in my Presenter layer.
Another thing I've looked at is using the zip() operator. This would look something like this:
Observable<ObjectA> obsA = repository.getObjectA();
Observable<ObjectB> obsB = repository.getObjectB();
Observable<CombinedObjectAandObjectB> resultObs = obsA.zipWith(obsB, new Func2<ObjectA, ObjectB, CombinedObjectAandObjectB>() {
#Override
public Object call(ObjectA a, ObjectB b) {
return new CombinedObjectAandObjectB(a,b);
}
});
Now when I subscribe to my resultObs I get a composite object returned with both ObjectA and Object B. This is pretty slick but still requires me to write code in Func2 that needs to be tested.
What are some ways that I can achieve my goals of combining calls for my business objects while also allowing for testability of the resultant Observable?
You almost anwser your question yourself. According to Clean Architecture it's better to pass ready-to-use models to presentation layer. Combining logic belongs to Domain layer. So use zip() in your Repository (although it's Data layer). E.g.:
Observable.zip(modelAObservable, modelBOsbervable, (A, B) -> {
combineModels(A, B);
})...
private CombinedModel combineModels(modelA, modelB) {
//combining logic
}
And test it as usual. E.g.:
ModelA A = new ModelA...
ModelB B = new ModelB...
CombinedModel expectedResult = new CombinedModel...
assertEquals(combineModels(A, B), expectedResult);