I have a BaseViewModel that basically has the function to get the user data like so:
abstract class BaseViewModel(
private val repository: BaseRepository
) : ViewModel() {
private var _userResponse: MutableLiveData<Resource<UserResponse>> = MutableLiveData()
val userResponse: LiveData<Resource<UserResponse>> get() = _userResponse
fun getUserData() = viewModelScope.launch {
_userResponse.value = Resource.Loading
_userResponse.value = repository.getLoggedInUserData()
}
}
In my Fragment, I access this data by just calling viewModel.getUserData(). This works. However, I'd like to now be able to edit the data. For example, the data class of UserResponse looks like this:
data class UserResponse(
var id: Int,
var username: String,
var email: String
)
In other fragments, I'd like to edit username and email for example. How do I do access the UserResponse object and edit it? Is this a good way of doing things? The getUserData should be accessed everywhere and that is why I'm including it in the abstract BaseViewModel. Whenever the UserResponse is null, I do the following check:
if (viewModel.userResponse.value == null) {
viewModel.getUserData()
}
If you want to be able to edit the data in userResponse, really what you're talking about is changing the value it holds, right? The best way to do that is through the ViewModel itself:
abstract class BaseViewModel(
private val repository: BaseRepository
) : ViewModel() {
private var _userResponse: MutableLiveData<Resource<UserResponse>> = MutableLiveData()
val userResponse: LiveData<Resource<UserResponse>> get() = _userResponse
fun setUserResponse(response: UserResponse) {
_userResponse.value = response
}
...
}
This has a few advantages - first, the view model is responsible for holding and managing the data, and provides an interface for reading, observing, and updating it. Rather than having lots of places where the data is manipulated, those places just call this one function instead. That makes it a lot easier to change things later, if you need to - the code that calls the function might not need to change at all!
This also means that you can expand the update logic more easily, since it's all centralised in the VM. Need to write the new value to a SavedStateHandle, so it's not lost if the app goes to the background? Just throw that in the update function. Maybe persist it to a database? Throw that in. None of the callers need to know what's happening in there
The other advantage is you're actually setting a new value on the LiveData, which means your update behaviour is consistent and predictable. If the user response changes (either a whole new one, or a change to the current one) then everything observeing that LiveData sees the update, and can decide what to do with it. It's less brittle than this idea that one change to the current response is "new" and another change is "an update" and observers will only care about one of those and don't need to be notified of the other. Consistency in how changes are handled will avoid bugs being introduced later, and just make it easier to reason about what's going on
There's nothing stopping you from updating the properties of the object held in userResponse, just like there's nothing stopping you from holding a List in a LiveData, and adding elements to that list. Everything with a reference to that object will see the new data, but only if they look at it. The point of LiveData and the observer pattern is to push updates to observers, so they can react to changes (like, say, updating text displayed in a UI). If you change one of the vars in that data class, how are you going to make sure everything that needs to see those changes definitely sees them? How can you ensure that will always happen, as the app gets developed, possibly by other people? The observer pattern is about simplifying that logic - update happens, observers are notified, the end
If you are going to do things this way, then I'd still recommend putting an update function in your VM, and let that update the vars. You get the same benefits - centralising the logic, enabling things like persistence if it ever becomes necessary, etc. It could be as simple as
fun setUserResponse(response: UserResponse) {
_userResponse.value?.run {
id = response.id
username = response.username
email = response.email
}
}
and if you do decide to go with the full observer pattern for all changes later, everything is already calling the function the right way, no need for changes there. Or you could just make separate updateEmail(email: String) etc functions, whatever you want to do. But putting all that logic in the VM is a good idea, it's kinda what it's there for
Oh and you access that object through userResponse.value if you want to poke at it - but like I said, better to do that inside a function in the VM, keep that implementation detail, null-safety etc in one place, so callers don't need to mess with it
The ideal way to update userResponse you should change/edit _userResponse so that your userResponse we'll give you the updated data.
it should be something like this
_userResponse.value = Resource<UserResponse>()
Related
I am trying first handle the response from API by using observe. Later after observing the handled variable I want to save it to database.
The variable tokenFromApi is updated inside tokenResponseFromApi's observer. Is it possible to observe tokenFromApi outside the observer of tokenResponseFromApi? When debugged, the code did not enter inside tokenFromApi observer when the app started.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
var tokenResponseFromApi: LiveData<String>? = MutableLiveData<String>()
var tokenFromApi: LiveData<TokenEntity>? = MutableLiveData<TokenEntity>()
tokenResponseFromApi?.observe(viewLifecycleOwner, Observer {
tokenResponseFromApi ->
if (tokenResponseFromApi != null) {
tokenFromApi = viewModel.convertTokenResponseToEntity(tokenResponseFromApi, dh.asDate)
}
})
tokenFromApi?.observe(viewLifecycleOwner, Observer {
tokenFromApi ->
if (tokenFromApi != null) {
viewModel.saveTokenToDB(repo, tokenFromApi)
}
})
}
Your problem is that you're registering the observer on tokenFromApi during setup, and when you get your API response, you're replacing tokenFromApi without registering an observer on it. So if it ever emits a value, you'll never know about it. The only observer you have registered is the one on the discarded tokenFromApi which is never used by anything
Honestly your setup here isn't how you're supposed to use LiveData. Instead of creating a whole new tokenFromApi for each response, you'd just have a single LiveData that things can observe. When there's a new value (like an API token) you set that on the LiveData, and all the observers see it and react to it. Once that's wired up, it's done and it all works.
The way you're doing it right now, you have a data source that needs to be taken apart, replaced with a new one, and then everything reconnected to it - every time there's a new piece of data, if you see what I mean.
Ideally the Fragment is the UI, so it reacts to events (by observing a data source like a LiveData and pushes UI events to the view model (someone clicked this thing, etc). That API fetching and DB storing really belongs in the VM - and you're already half doing that with those functions in the VM you're calling here, right? The LiveDatas belong in the VM because they're a source of data about what's going on inside the VM, and the rest of the app - they expose info the UI needs to react to. Having the LiveData instances in your fragment and trying to wire them up when something happens is part of your problem
Have a look at the App Architecture guide (that's the UI Layer page but it's worth being familiar with the rest), but this is a basic sketch of how I'd do it:
class SomeViewModel ViewModel() {
// private mutable version, public immutable version
private val _tokenFromApi = MutableLiveData<TokenEntity>()
val tokenFromApi: LiveData<TokenEntity> get() = _tokenFromApi
fun callApi() {
// Do your API call here
// Whatever callback/observer function you're using, do this
// with the result:
result?.let { reponse ->
convertTokenResponseToEntity(response, dh.asDate)
}?.let { token ->
saveTokenToDb(repo, token)
_tokenFromApi.setValue(token)
}
}
private fun convertTokenResponseToEntity(response: String, date: Date): TokenEntity? {
// whatever goes on in here
}
private fun saveTokenToDb(repo: Repository, token: TokenEntity) {
// whatever goes on in here too
}
}
so it's basically all contained within the VM - the UI stuff like fragments doesn't need to know anything about API calls, whether something is being stored, how it's being stored. The VM can update one of its exposed LiveData objects when it needs to emit some new data, update some state, or whatever - stuff that's interesting to things outside the VM, not its internal workings. The Fragment just observes whichever one it's interested in, and updates the UI as required.
(I know the callback situation might be more complex than that, like saving to the DB might involve a Flow or something. But the idea is the same - in its callback/result function, push a value to your LiveData as appropriate so observers can receive it. And there's nothing wrong with using LiveData or Flow objects inside the VM, and wiring those up so a new TokenEntity gets pushed to an observer that calls saveTokenToDb, if that kind of pipeline setup makes sense! But keep that stuff private if the outside world doesn't need to know about those intermediate steps
I am trying to figure out how jobs with coroutines work. Basically, I want to launch this coroutine from FirstFragment and after that navigate to SecondFragment and get notified when this job is done. I call getData() in FirstFragment onViewCreated() and navigate to SecondFragment. Whether I write getData().isCompleted or getData().invokeOnCompletion { } in SecondFragment nothing happens. I don't know if I am missing something or not starting job correctly or something else.
private val _data = MutableStateFlow<GetResource<String>?>(null)
val data: StateFlow<GetResource<String>?> = _data
fun getData() = viewModelScope.launch {
repository.getData().collect {
_data.value = it
}
}
A Flow from a database never completes because it is supposed to monitor the database for changes indefinitely. It only stops when the coroutine is cancelled. Therefore the Job that collects such a Flow will never complete. Also, if you call getData() on the repo again, you are getting a new Flow instance each time.
Regardless of what you're doing, you need to be sure you are using the same ViewModel instance between both fragments by scoping it to the Activity. (Use by activityViewModels() for example.) This is so the viewModelScope won't be cancelled during the transition between Fragments.
If all you need is a single item from the repo one time, probably the simplest thing to do would be to expose a suspend function from the repo instead of a Flow. Then turn it into a Deferred. Maybe by making it a Lazy, you can selectively decide when to start retrieving the value. Omit the lazy if you just want to start retrieving the value immediately when the first Fragment starts.
// In the shared view model:
val data: Deferred<GetResource<String>> by lazy {
viewModelScope.async {
repository.getData() // suspend function returning GetResource<String>
}
}
fun startDataRetrieval() { data } // access the lazy property to start its coroutine
// In second fragment:
lifecycleScope.launch {
val value = mySharedViewModel.data.await()
// do something with value
}
But if you have to have the Flow because you’re using it for other purposes:
If you just want the first available value from the Flow, have the second Fragment monitor your data StateFlow for its first valid value.
lifecycleScope.launch {
val value = mySharedViewModel.data.filterNotNull().first()
// do something with first arrived value
}
And you can use SharedFlow so you don’t have to make the data type nullable. If you do this you can omit filterNotNull() above. In your ViewModel, it’s easier to do this with shareIn than your code that has to use a backing property and manually collect the source.
val data: SharedFlow<GetResource<String>> = repository.getData()
.shareIn(viewModelScope, replay = 1, SharingStarted.Eagerly)
If you need to wait before starting the collection to the SharedFlow, then you could make the property lazy.
Agreed with #Tenfour04 's answer, I would like to contribute a little more.
If you really want to control over the jobs or Structured Concurrency, i would suggest use custom way of handling the coroutine rather than coupled your code with the viewModelScope.
There are couple of things you need to make sure:
1- What happen when cancellation or exception occurrs
2- you have to manage the lifecycle of the coroutine(CoroutineScope)
3- Cancelling scope, depends on usecase like problem facing you are right now
4- Scope of ViewModel e.g: Either it is tied to activity(Shared ViewModel) or for specific fragment.
If you are not handling either of these carefully specifically first 3, your are more likely to leaking the coroutine your are gurenteed gonna get misbehavior of you app.
Whenever you start any coroutine in Custom way you have to make sure, what is going to be the lifecycle, when it gonna end, This is so important, it can cause real problems
Luckily, i have this sample of CustomViewModel using Jobs: Structured Concurrency sample code
So currently I have a Dao with a function that emits a Flow<>
#Query("SELECT * FROM ${Constants.Redacted}")
fun loadAllContacts(): Flow<List<Redacted>>
I am calling this from a repository like so
val loadAllContacts: Flow<List<Redacted>> = contactDao.loadAllContacts()
I am injecting the repository into the viewModel's constructor, and then at the top of my viewModel I have a val like so
val contacts: LiveData<List<Redacted>> = contactRepository.loadAllContacts.asLiveData()
Which is being observed in my Activity like so
viewModel.contacts.observe(this) { contacts ->
viewModel.onContactsChange(contacts)
}
My thinking is that the Flow is converted to a LiveData, and then I can observe this LiveData from my activity and kick off this function to actually update the viewModel upon the data being updated.
For now onContactsChange just looks like
fun onContactsChange(list: List<Redacted>) {
Timber.i("VIEW UPDATE")
}
The problem is that I only see this Timber log upon opening the activity, and never again. I verified that data IS going into my database, and I verified that an insert occurred successfully while the activity & viewModel are open. But I never see the log from onContactsChange again. When I close the activity, and reopen it, I do see my new data, so that is another reason I know my insert is working correctly.
I would like to add that I am using a single instance (singleton) of my repository, and I think I can verify this by the fact that I can see my data at all, at least when the view is first made.
Figured it out:
Note: If your app runs in a single process, you should follow the singleton design pattern when instantiating an AppDatabase object. Each RoomDatabase instance is fairly expensive, and you rarely need access to multiple instances within a single process.
If your app runs in multiple processes, include enableMultiInstanceInvalidation() in your database builder invocation. That way, when you have an instance of AppDatabase in each process, you can invalidate the shared database file in one process, and this invalidation automatically propagates to the instances of AppDatabase within other processes.
It's a little bit hard to follow your question, but I think I see the overall problem with your Flow object not updating the way you want it too.
Following this quick tutorial, it seems that first you should declare your Flow object inside your Repository the same way you're already doing
val loadAllContacts: Flow<List<Redacted>> = contactDao.loadAllContacts()
and have your VM 'subscribe' to it by using the collect coroutine which would then allow you to dump all this data into a MutableLiveData State
data class YourState(..)
val state = MutableLiveData<YourState>()
init {
contactRepository.loadAllContacts().collect {
if (it.isNotEmpty()) {
state.postValue(YourState(
...
)
}
}
}
that your Activity/Fragment could then observe for changes
viewModel.state.observe(.. { state ->
// DO SOMETHING
})
P.S. The tutorial also mentions that because of how Dao's work, you might be getting updates for even the slightest of changes, but that you can use the distinctUntilChanged() Flow extension function to get more specific results.
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
I've got a question about Flowables. I already have a few solutions for this issue, but I would like to double-check if these are the best possible solutions or not.
Context
I have an Interactor that is supposed to bookmark recipes on the DB. It looks like this:
/**
* This Interactor marks a recipe as "bookmarked" on the DB. The Interactor actually switches
* the isBookmarked value of the related recipeId. If it was marked as true, it switches its value
* to false. If it was false, then it switches its value to true.
*/
class BookmarkRecipeInteractorImpl(
private val recipesCacheRepository: RecipesCacheRepository
) : BookmarkRecipeInteractor {
override fun execute(recipeId: Int, callback: BookmarkRecipeInteractor.Callback) {
// Fetches the recipe from DB. The getRecipeById(recipeId) function returns a Flowable.
// Internally, within the RecipesCacheRepository, I'm using room.
recipesCacheRepository.getRecipeById(recipeId).flatMap { originalRecipe ->
// Switches the isBookmarked value
val updatedRecipe = originalRecipe.copy(
isBookmarked = !originalRecipe.isBookmarked
)
// Update the DB
recipesCacheRepository.updateRecipe(updatedRecipe)
// Here's the issue, since I'm updating a DB record and the getRecipeById returns
// a Flowable, as soon as I update the DB, the getRecipeById is going to get triggered
// again, and switch the value again, and again, and again...
}
.subscribe(
{
callback.onSuccessfullyBookmarkedRecipe(it.response)
},
{
callback.onErrorFetchingRecipes()
}
)
}
}
So, if you follow the code, the error is pretty straightforward. I get stuck on a loop, where I constantly change the recipe record.
Possible solutions
1) Have two different functions on my DAO, one called getRecipeByIdFlowable(id) that returns a Flowable, and another called getRecipeByIdSingle(id) that returns a rx.Single. That way I can expose the getRecipeByIdSingle(id) through the Repository and use it instead of the function that returns the Flowable. That way I cut the loop.
Pro: It works.
Con: I don't like having functions like this on my DAO.
2) Save the Disposable on a lateinit property and dispose it as soon as the subscriber triggers the onNext().
Pro: It works.
Con: I don't like having to do something like this, feels hacky.
3) Using ...getRecipeById(recipeId).take(1).flatMap... so it only handles the first emitted object.
Pro: It works, it looks tidy.
Con: I'm not sure if there's a better way to do it.
Question
Ideally, I would like to call some function that just allows me to disable the Flowable behavior and prevent it from emitting more items if the DB changes. So far the solution that I like the most is #3, but I'm not really sure if this is the right way to do it.
Thanks!
Edit 1
I'm just adding a bit more of information about the use case here. I need an Interactor that given a recipeId changes the isBookmarked value on DB to its oposite.
The DB records look like:
data class DbRecipeDto(
#PrimaryKey
val id: Int,
val name: String,
val ingredients: List<String>,
val isBookmarked: Boolean = false
)
I know that maybe there's some other ways in which I could tackle this issue differently. Maybe I could pass the recipeId arg and a bookmark (Boolean) argument and just run the update query.
But this use case it is totally made up, just an example; The thing that I'm trying to figure out how to prevent a Flowable from emitting more items if something changes on the DB.
You should probably call .take(1).singleOrError() on the end of getRecipeById(recipeId).
This will take the first item (or the error) emitted by the Flowable retrieved by calling getRecipeById and wrap it in a Single. In my opinion this correctly matches the semantics of what you want to achieve.
In addition, if I recall correctly, because you will be subscribing on a Single by doing this, your Flowable will not continue to do work after the first item is consumed by the downstream call to singleOrError.