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
Related
Because database fetches usually happen asynchronously by default, a variable that holds the data from the firebase database fetch will be null when used right after the fetch. To solve this I have seen people use the ".await()" feature in Kotlin coroutines but this goes against the purpose of asynchronous database queries. People also call the succeeding code from within 'addOnSuccessListener{}' but this seems to go against the purpose of MVVM, since 'addOnSuccessListener{}' will be called in the model part of MVVM, and the succeeding code that uses the fetched data will be in the ViewModel. The answer I'm looking for is maybe a listener or observer that is activated when the variable (whose value is filled from the fetched data) is given a value.
Edit:
by "succeeding code" I mean what happens after the database fetch using the fetched data.
As #FrankvanPuffelen already mentioned in his comment, that's what the listener does. When the operation for reading the data completes the listener fires. That means you know if you got the data or the operation was rejected by the Firebase servers due to improper security rules.
To solve this I have seen people use the ".await()" feature in Kotlin coroutines but this goes against the purpose of asynchronous database queries.
It doesn't. Using ".await()" is indeed an asynchronous programming technique that can help us prevent our applications from blocking. When it comes to the MVVM architecture pattern, the operation for reading the data should be done in the repository class. Since reading the data is an asynchronous operation, we need to create a suspend function. Assuming that we want to read documents that exist in a collection called "products", the following function is needed:
suspend fun getProductsFirestore(): List<Product> {
var products = listOf<Product>()
try {
products = productsRef.get().await().documents.mapNotNull { snapShot ->
snapShot.toObject(Product::class.java)
}
} catch (e: Exception) {
Log.d("TAG", e.message!!)
}
return products
}
This method can be called from within the ViewModel class:
val productsLiveData = liveData(Dispatchers.IO) {
emit(repository.getProductsFromFirestore())
}
So it can be observed in activity/fragment class:
private fun getProducts() {
viewModel.producsLiveData.observe(this, {
print(it)
//Do what you need to do with the product list
})
}
I have even written an article in which I have explained four ways in which you can read the data from Cloud Firestore:
How to read data from Cloud Firestore using get()?
I am learning Android Architecture Components.
For exemple, and be more easier to understand, If i want to build a TO DO LIST app, my item creation DAO should be
#Dao
public interface ItemDao {
#Insert
long insertItem(Item item);
}
and my viewModel could be use this DAO to insert an item in my TODO list.
But, in architecture component, it is recommanded to NOT manipulate the database by the viewmodel but by the repository.
So, the code should be like that
public class ItemDataRepository {
private final ItemDao itemDao;
public ItemDataRepository(ItemDao itemDao) { this.itemDao = itemDao; }
// --- CREATE ---
public void createItem(Item item){ itemDao.insertItem(item); }
It seems redundant when we cannot understand why.
My question is : why?
I use the Repository for a couple of reasons:
Separation of concern I let the repo be responsible for downloading and storing all the data. That way the ViewModel doesn't have to know any specifics about where the data is coming from, e.g. if it's from an API or a cache. It also makes it easier to write Unit tests for the ViewModel, since all the database and API logic should already be tested in Unit tests for the Repository.
Reusability Lets say you fetch the data from an API and store in a database. If you put the code in the ViewModel and then want to perform the same actions from another place in the app, you need to copy paste the code. By having it in a Repository you can easily share the implementation.
Share data If you have multiple screens that show parts of the same data set, having one Repository passed around between the screens make it easy to share the data. No more trying to pass large amount of data in Bundle and Intent.
Lifecycle Let's say you download data from an API to show in your view. If you fetch the data in the ViewModel you will have to re-download it when the screen is closed and re-opened, since the ViewModel is discarded. However if you store it in the Repository it can use the Application lifecycle, so when you revisit the screen again, the data is already in the cache.
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.
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()
Recently, I've read about how important it is to have a Single-Source-Of-Truth (SSOT) when designing an app´s back-end (repository, not server-side-back-end). https://developer.android.com/topic/libraries/architecture/guide.html
By developing a news-feed app (using the awesome https://newsapi.org/) I am trying to learn more about app architecture. However, I am unsure of how to implement paging. By the way,.: I am using MVVM for my presentation layer. The View subscribes to the ViewModel´s LiveData. The ViewModel subscribes to RxJava streams.
I came up with the following approach:
interface NewsFeedRepository {
fun getNewsFeed(): Observable<List<Article>>
fun refreshFeed(): Completable
fun loadMore(): Completable
}
The ViewModel subscribes to the getNewsFeed() Observable which emits data every time the underlying data in the database (SSOT) changes. However, my question is regarding the loadMore() method.
This method attempts to load more articles from the API and on success insert them into the local database. This causes getNewsFeed() to emit the new feed data.
My questions:
1. Should the repository be responsible for syncing API and local database data? Or should the repository use some "low-level" API that manages syncing network/local data?
2. The method loadMore returns a Completable which might seem weird/misleading. Are there better ways to indicate paging in the repository interface?
3. Paging means storing a current page count and use it when requesting data from the API. Where should the current page count be stored? In the repository? Or in some "lower-level" component that is being used by the repository?
interface SearchArticleRepository {
fun searchArticles(sources: List<NewsSource>? = null, query: String? = null): Flowable<List<Article>>
fun moreArticles(): Completable
}
This repository is used to search articles. The method moreArticles() attempts to load more articles from the API depending on the last call to searchArticles(...). For example: The ViewModel calls repo.searchArticles(q = "bitcoin"). A call to repo.moreArticles() attempts to load more articles that contain the query string "bitcoin" from the API and sync with the local database.
Again my questions:
1. Is it OK to store information about the last request (searchArticles(request) in the repository? I could also think of passing the request parameters in moreArticles() too, e.g. moreArticles(sources: List<NewsSource>? = null, query: String? = null). This way the repository doesn't have to store information about the last request.
2. Again, returning a Completable in moreArticles() might seem weird. Is this OK, or are there better ways?