So my general question is how to call a function from view model for #Query where you have to pass something and then return something.
My simple example:
DAO
#Query ("SELECT * FROM table_name WHERE id = :id LIMIT 1")
fun getItemById (id: Long) : MyItem
Repo
fun getItemById (id: Long) : MyItem {
return itemDao.getItemById(id)
}
I know that it cannot and should not be done on ui thread. For inserting and deleting an item i use viewModelScope job but i cannot (maybe just don`t know how to) use it to return anything.
If i return it everywhere as LiveData, then it works just like that:
ViewModel
fun itemById(id: Long): LiveData<MyItem> {
return itemRepo.getItemById(id)
}
And then i observe it in a Fragment/Activity:
viewModel.itemById(id).observe(this, Observer {
// using it
})
The thing is, that i dont really need it to be an observable livedata. I only need to get it once, check condition and thats it.
So maybe someone could recommend how to do it, without it being a livedata. Or should i leave it a live data?
If you want to get the update only once, then I recommend SingleLiveEvent instead of LiveData.
Here is the class provided by google: Github link
A blog on how to use it: Link
The only drawback of SingleLiveEvent is that it can't have multiple observers.
If you don't like LiveData, you could try RxJava's Single [Observable]
Related
I made a simple example app with using Room and Flows:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val build = Room.databaseBuilder(this, FinanceDatabase::class.java, "database.db")
.fallbackToDestructiveMigration()
.build()
GlobalScope.launch {
build.currencyDao().addCurrency(CurrencyLocalEntity(1))
val toList = build.currencyDao().getAllCurrencies().toList()
Log.d("test", "list - $toList")
}
}
}
#Entity(tableName = "currency")
data class CurrencyLocalEntity(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "currencyId")
var id: Int
) {
constructor() : this(-1)
}
#Dao
interface CurrencyDao {
#Query("SELECT * FROM currency")
fun getAllCurrencies(): Flow<CurrencyLocalEntity>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addCurrency(currency: CurrencyLocalEntity)
}
#Database(entities = [CurrencyLocalEntity::class], version = 1)
abstract class FinanceDatabase : RoomDatabase() {
abstract fun currencyDao(): CurrencyDao
}
I want to use toList() function as in code above but something gets wrong and even Log doesn't print. At the same time using collect() works fine and gives me all records.
Can anybody explain to me what is wrong? Thanks.
There are a couple things wrong here but I'll address the main issue.
Flows returned by room emit the result of the query everytime the database is modified. (This might be scoped to table changes instead of the whole database).
Since the database can change at any point in the future, the Flow will (more or less) never complete because a change can always happen.
Your calling toList() on the returned Flow will suspend forever, since the Flow never completes. This conceptually makes sense since Room cannot give you the list of every change that will happen, without waiting for it to happen.
With this, I'm sure you know why collect gives you the records and toList() doesn't.
What you probably want here is this.
#Query("SELECT * FROM currency")
fun getAllCurrencies(): Flow<List<CurrencyLocalEntity>>
With this you can get the first result of the query with Flow<...>.first().
Flow in Room is for observing Changes in table.
Whenever any changes are made to the table, independent of which row is changed, the query will be re-triggered and the Flow will emit again.
However, this behavior of the database also means that if we update an unrelated row, our Flow will emit again, with the same result. Because SQLite database triggers only allow notifications at table level and not at row level, Room can’t know what exactly has changed in the table data
Make sure that the same doa object you are using for retrieving the list, is used for updating the database.
other than that converting flow to livedata is done using asLivedata extension function
For me below solution works for updating the view with database table changes.
Solution: Same Dao Object should be used when we insert details into the room database and get information from DB.
If you are using a dagger hilt then
#Singleton annotation will work.
I hope this will solve your problem.
**getAllCurrencies()** function should be suspend.
Please check the syntax to collect List from Flow:
suspend fun <T> Flow<T>.toList(
destination: MutableList<T> = ArrayList()
): List<T> (source)
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-list.html
right now I am starting to use LiveData for the first time. First I put all of my code in the viewModel including the code to start a search in the server.
I used LiveData like this:
Fragment onViewCreated()
viewModel.changeNotifierContacts.observe(this, androidx.lifecycle.Observer { value -> value?.let {
recyclerViewAdapter.setData(value)
} })
This was working as expected. Now I add a repository layer following MVVM pattern. (For this I moved my contact search functionality to repository class)
First I implemented the connection between ViewModel and repository like this:
ViewModel code:
fun getContacts(): MutableLiveData<ContactGroup> {
return contactSearchRepository.changeNotifierContacts;
}
fun search(newSearchInput: String) {
contactSearchRepository.searchInRepository(newSearchInput)
}
Now I read this article that told us to not use LiveData like this: https://developer.android.com/topic/libraries/architecture/livedata#merge_livedata
Example from this page:
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
private fun getPostalCode(address: String): LiveData<String> {
// DON'T DO THIS
return repository.getPostCode(address)
}
}
Instead we should use something like this:
var changeNotifierContacts : LiveData<ContactGroup> = Transformations.switchMap(searchInput) {
address -> contactSearchRepository.getPostCode(address) }
Questions:
Did I understand this article correctly or can I use my first implementation?
In my constructor of the viewModel I am creating an instance of my repository object that is starting to observe server data and it is getting initial data. (For example I am getting a list of all my friends). I am getting this initial data if I am using my first implementation. If I am using Transformations.switchMap implementation I am not getting this initial data. I first have to start a search here to get updated data then. This is not what I want, I also need to display "my friends" list without doing a search.
Is there another approach I can use here? Maybe LiveData is not the best solution to connect ViewModel with Repository?
Thanks for responses and suggestions!
Did I understand this article correctly or can I use my first implementation?
I think you did, but I believe you have expanded the concept too much.
If you are expecting the user to enter a search to receive an answer, you should do like they said:
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
private val addressInput = MutableLiveData<String>()
val postalCode: LiveData<String> = Transformations.switchMap(addressInput) {
address -> repository.getPostCode(address) }
fun setInput(address: String) {
addressInput.value = address
}
}
However, if you are loading a default list, you should do it like you did in your first example:
val getContact = contactSearchRepository.changeNotifierContacts
In this case you will have to observe getContact and postalCode.
In my constructor of the viewModel I am creating an instance of my repository object that is starting to observe server data and it is getting initial data. (For example I am getting a list of all my friends). I am getting this initial data if I am using my first implementation. If I am using Transformations.switchMap implementation I am not getting this initial data. I first have to start a search here to get updated data then. This is not what I want, I also need to display "my friends" list without doing a search.
You can start your fragment/activity with a default search, like this:
MyViewModel.setInput("Friends")
This way you do not need to observe two objects as postalCode will provide all answers.
Is there another approach I can use here? Maybe LiveData is not the best solution to connect ViewModel with Repository?
I think live data is your answer. After done with the learning curve it becomes easier to deal with!
I hope it helps!
I'm trying out the new coroutine's flow, my goal is to make a simple repository that can fetch data from a web api and save it to db, also return a flow from the db.
I'm using room and firebase as the web api, now everything seems pretty straight forward until i try to pass errors coming from the api to the ui.
Since i get a flow from the database which only contains the data and no state, what is the correct approach to give it a state (like loading, content, error) by combining it with the web api result?
Some of the code i wrote:
The DAO:
#Query("SELECT * FROM users")
fun getUsers(): Flow<List<UserPojo>>
The Repository:
val users: Flow<List<UserPojo>> = userDao.getUsers()
The Api call:
override fun downloadUsers(filters: UserListFilters, onResult: (result: FailableWrapper<MutableList<UserApiPojo>>) -> Unit) {
val data = Gson().toJson(filters)
functions.getHttpsCallable("users").call(data).addOnSuccessListener {
try {
val type = object : TypeToken<List<UserApiPojo>>() {}.type
val users = Gson().fromJson<List<UserApiPojo>>(it.data.toString(), type)
onResult.invoke(FailableWrapper(users.toMutableList(), null))
} catch (e: java.lang.Exception) {
onResult.invoke(FailableWrapper(null, "Error parsing data"))
}
}.addOnFailureListener {
onResult(FailableWrapper(null, it.localizedMessage))
}
}
I hope the question is clear enough
Thanks for the help
Edit: Since the question wasn't clear i'll try to clarify. My issue is that with the default flow emitted by room you only have the data, so if i were to subscribe to the flow i would only receive the data (eg. In this case i would only receive a list of users). What i need to achieve is some way to notify the state of the app, like loading or error. At the moment the only way i can think of is a "response" object that contains the state, but i can't seem to find a way to implement it.
Something like:
fun getUsers(): Flow<Lce<List<UserPojo>>>{
emit(Loading())
downloadFromApi()
if(downloadSuccessful)
return flowFromDatabase
else
emit(Error(throwable))
}
But the obvious issue i'm running into is that the flow from the database is of type Flow<List<UserPojo>>, i don't know how to "enrich it" with the state editing the flow, without losing the subscription from the database and without running a new network call every time the db is updated (by doing it in a map transformation).
Hope it's clearer
I believe this is more of an architecture question, but let me try to answer some of your questions first.
My issue is that with the default flow emitted by room you only have
the data, so if i were to subscribe to the flow i would only receive
the data
If there is an error with the Flow returned by Room, you can handle it via catch()
What i need to achieve is some way to notify the state of the app,
like loading or error.
I agree with you that having a State object is a good approach. In my mind, it is the ViewModel's responsibility to present the State object to the View. This State object should have a way to expose errors.
At the moment the only way i can think of is a "response" object that
contains the state, but i can't seem to find a way to implement it.
I have found that it is easier to have the State object that the ViewModel controls be responsible for errors instead of an object that bubbles up from the Service layer.
Now with these questions out of the way, let me try to propose one particular "solution" to your issue.
As you mention, it is common practice to have a Repository that handles retrieving data from multiple data sources. In this case, the Repository would take the DAO and an object that represents getting data from the network, let's call it Api. I am assuming that you are using FirebaseFirestore, so the class and method signature would look something like this:
class Api(private val firestore: FirebaseFirestore) {
fun getUsers() : Flow<List<UserApiPojo>
}
Now the question becomes how to turn a callback based API into a Flow. Luckily, we can use callbackFlow() for this. Then Api becomes:
class Api(private val firestore: FirebaseFirestore) {
fun getUsers() : Flow<List<UserApiPojo> = callbackFlow {
val data = Gson().toJson(filters)
functions.getHttpsCallable("users").call(data).addOnSuccessListener {
try {
val type = object : TypeToken<List<UserApiPojo>>() {}.type
val users = Gson().fromJson<List<UserApiPojo>>(it.data.toString(), type)
offer(users.toMutableList())
} catch (e: java.lang.Exception) {
cancel(CancellationException("API Error", e))
}
}.addOnFailureListener {
cancel(CancellationException("Failure", e))
}
}
}
As you can see, callbackFlow allows us to cancel the flow when something goes wrong and have someone donwnstream handle the error.
Moving to the Repository we would now like to do something like:
val users: Flow<List<User>> = Flow.concat(userDao.getUsers().toUsers(), api.getUsers().toUsers()).first()
There are a few caveats here. first() and concat() are operators you will have to come up with it seems. I did not see a version of first() that returns a Flow; it is a terminal operator (Rx used to have a version of first() that returned an Observable, Dan Lew uses it in this post). Flow.concat() does not seem to exist either. The goal of users is to return a Flow that emits the first value emitted by any of the source Flows. Also, note that I am mapping DAO users and Api users to a common User object.
We can now talk about the ViewModel. As I said before, the ViewModel should have something that holds State. This State should represent data, errors and loading states. One way that can be accomplished is with a data class.
data class State(val users: List<User>, val loading: Boolean, val serverError: Boolean)
Since we have access to the Repository the ViewModel can look like:
val state = repo.users.map {users -> State(users, false, false)}.catch {emit(State(emptyList(), false, true)}
Please keep in mind that this is a rough explanation to point you in a direction, there are many ways to accomplish state management and this is by no means a complete implementation. It may not even make sense to turn the API call into a Flow, for example.
The answer from Emmanuel is really close to answering what i need, i need some clarifications about some of it.
It may not even make sense to turn the API call into a Flow
You are totally right, in fact i only want to actually make it a coroutine, i don't really need it to be a flow.
If there is an error with the Flow returned by Room, you can handle it via catch()
Yes i discovered this after posting the question. But my problem is more something like:
I'd like to call a method, say "getData", this method should return the flow from db, start the network call to update the db (so that i'm going to be notified when it's done via the db flow) and somewhere in here, i would need to let the ui know if db or network errored, right?. Or should i maybe do a separate "getDbFlow" and "updateData" and get the errors separately for each one?
val users: Flow> = Flow.concat(userDao.getUsers().toUsers(), api.getUsers().toUsers()).first()
This is a good idea, but i'd like to keep the db as the single source of truth, and never return to the ui any data directly from the network
Recently, I´ve read about how important it is to have a Single-Source-Of-Truth (SSOT) when designing an app´s backend (repository, not server-side-backend). 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 design the repository interface for my app.
Btw.: I am using MVVM for my presentation layer. The View subscribes to the ViewModel´s LiveData. The ViewModel subscribes to RxJava streams.
So I came up with 2 approaches:
Approach 1:
interface NewsFeedRepository {
fun loadFeed(): Flowable<List<Article>>
fun refreshFeed(): Completable
fun loadMore(): Completable
}
interface SearchArticleRepository {
fun searchArticles(sources: List<NewsSource>? = null, query: String? = null): Flowable<List<Article>>
fun moreArticles(): Completable
}
interface BookmarkRepository {
fun getBookmarkedArticles(): Flowable<List<Article>>
fun bookmarkArticle(id: String): Completable
}
This approach is primarily using Flowables which emit data if the corresponding data in the underlying SSOT (database) changes (e.g old data gets replaced with fresh data from API, more data was loaded from API, ...). However, I am unsure if using a Flowable for SearchArticleRepository#searchArticles(...) makes sense. As it is like some request/response thing, where maybe a Single might me be more intuitive.
Approach 2:
interface NewsFeedRepository {
fun loadFeed(): Single<List<Article>>
fun refreshFeed(): Single<List<Article>>
fun loadMore(): Single<List<Article>>
}
interface SearchArticleRepository {
fun searchArticles(sources: List<NewsSource>? = null, query: String? = null): Single<List<Article>>
fun moreArticles(): Single<List<Article>>
}
interface BookmarkRepository {
fun getBookmarkedArticles(): Single<List<Article>>
fun bookmarkArticle(id: String): Single<Article> // Returns the article that was modified. Articles are immutable.
}
This approach is using Singles instead of Flowables. This seems very intuitive but if the data in the SSOT changes, no changes will be emitted. Instead, a call to the repository has to be made again. Another aspect to take into account is that the ViewModel may have to manage its own state.
Let´s take the FeedViewModel for example (pseudo-code).
class FeedViewModel : ViewModel() {
// Variables, Boilerplate, ...
val newsFeed: LiveData<List<Article>>
private val articles = mutableListOf<Article>()
fun loadNewsFeed() {
// ...
repository.loadFeed()
//...
// On success, clear the feed and append the loaded articles.
.subscribe({articles.clear(); articles.addAll(it)})
// ...
}
fun loadMore() {
// ...
repository.loadMore()
//...
// On success, append the newly loaded articles to the feed.
.subscribe({articles.addAll(it)})
// ...
}
}
So this might not be crucial for a smaller app like mine, but it definitely can get a problem for a larger app (see state management: http://hannesdorfmann.com/android/arch-components-purist).
Finally, I wanted to know which approach to take and why. Are there any best-practices? I know many of you have already done some larger software-projects/apps and it would be really awesome if some of you could share some knowledge with me and others.
Thanks a lot!
I'd rather go for the first approach using Observables instead of Flowables in your case:
interface NewsFeedRepository {
fun loadFeed(): Observable<List<Article>>
fun refreshFeed(): Completable
fun loadMore(): Completable
}
interface SearchArticleRepository {
fun searchArticles(sources: List<NewsSource>? = null, query: String? = null): Observable<List<Article>>
fun moreArticles(): Completable
}
interface BookmarkRepository {
fun getBookmarkedArticles(): Observable<List<Article>>
fun bookmarkArticle(id: String): Completable
}
I don't see any reason you should necessarily use Flowable for this purpose since you'll never have any OOME related issues checking your repository changes. In other words, for your use case IMHO backpressure is not necessary at all.
Check this official guide which gives us an advice of when to a Flowable over an Observable.
On the other hand, and not related to the question itself, I have serious doubts of what's the purpose of loadMore or moreArticles methods since they return a Completable. Without knowing the context, it may seem you could either refactor the method name by a better name or change the return type if they do what they seem to do by the name.
I believe the first approach is better, Your repo will update the data whenever the data is changed and your view model will be notified automatically and that's cool, while in your second approach you have to call the repo again and that's not really reactive programming.
Also, assume that the data can be changed by something rather than load more event from the view, like when new data added to the server, or some other part of the app changes the data, Now in the first approach again you get the data automatically while for the second your not even know about the changed data and you don't know when to call the method again.
I'm converting our project to work with Room ORM. It works great when I need a LiveData object updated, and works great for AsyncTasks such as insert, delete, etc., where I do not need a callback. But I'm confused what to use when I need a one-time query that requires a callback. The options are to call AsyncTask to query using the DAO implementation, or LiveData with Observer, and after the first receive, unregister the observer.
I would recommend sticking with the LiveData, particularly if you are using a ViewModel provided by Room. The ArchitectureComponents library really does a great job when it comes to bundling all Room, LiveData and ViewModels together, so try at best to stick with the convention.
The reasons I recommend sticking with LiveData and ViewModels are
ViewModels are Lifecycle aware, meaning they respond appropriately
to Fragment/Activity state changes which would otherwise leave your
AsyncTask either retrieving data for a dead Activity or doing work
when the Activity is no longer there potentially leading to MemoryLeaks
It's best practice (at least for Architecture Components) for a View to observe data/changes to data. If you need just a single callback, unsubscribe after you have received the data. Or use an RxJava single if you are currently using RxJava
If you feel the need to really want to use AsyncTask, I would suggest use an AsyncTaskLoader. This is a more robust/lifecycle aware background thread operation that will cache your data (it is very similar to an AsyncTask so the implementation details won't be too foreign), so if you rotate your device, the data will be cached and is immediately available, and you won't have a memory leak. Also check this Video on loaders by the Android team.
But I advise using the LiveData w/ ViewModels.
I had same issue with Room Implementation. As for LiveData return by room we can observe this on main thread without performing database operation on main thread.
But for fetching Raw object without LiveData we can't do it directly, As database operations on Main thread are not allowed.
I have used LiveData for list of things which I what to keep updated as soon as Database gets Updated. But for some operations I need to fetch it only once and stop observing the database because I know it won't change.
In my case I have used RxJava to Observe the query result only 1 time
Creating Observable for getting User by id from Repository
Observable.create(object : ObservableOnSubscribe<User> {
override fun subscribe(e: ObservableEmitter<User>) {
val user= userRepo.getUserById(currentUserId)
e.onNext(user)
e.onComplete()
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(GetCurrentUserByIdSubscriber())
Repository
fun getUserById(id: Int): User= userDao.getUserById(id)
Dao
#Query("SELECT * FROM users WHERE id= :id")
fun getUserById(id: Int): User
You can see I am returning plain User object instead of LiveData.
In Activity where I want to observe this
private inner class GetCurrentUserByIdSubscriber : Observer<User> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: User) {
// update the view after getting the data from database
user_name.text = t.name
}
override fun onComplete() {}
override fun onError(e: Throwable) {}
}
So this is how I can perform one time database fetch on IO thread with RxJava and get callback as soon as object is fetched from the database.
You can call unsubscribe on Subscription return from subscribe() method after results are obtained.
There is simple solution in Transformations method distinctUntilChanged.expose new data only if data was changed.
in case Event behaviour use this:
https://stackoverflow.com/a/55212795/9381524