I have an activity that performs multiple background tasks, for example:
getJWTToken(), sendFCMTokenToServer(), isPromoAvailable(), isForcedUpgradeRequired(), fetchNewsFromServer(), sendUserLatLngToServer()
These all are network calls, and take some time.
This is what i have done for 1 method.
HomeViewModel homeViewModel = ViewModelProviders.of(this).get(HomeViewModel.class);
homeViewModel.sendFCMTokenToServer().observe(this, isFCMSendToServer -> {
Toast.makeText(this, "Home FCM Observer called", Toast.LENGTH_SHORT).show();
});
Now my question is do i need to repeat the above line homeViewModel.observer(this, ) for each method, or there is some other way for achieving this.
I think, You should not!
You don't need to observe these changes separately.
You can do these:
make ViewState Data model class which contains all these data (jwt token, other booleans and all)
observe ViewModel based on this model from activity.
In ViewModel, you can just change/update these ViewState model values using LiveData.postValue() as well.
Related
I need to use flow.collectLatest {} in my fragment in OnViewCreated, and then in a loop under some condition multiple times (I made a filter, so each time different data should be retrieved).
This is my code for calling collectLatest:
viewLifecycleOwner.lifecycleScope.launch {
myViewModel.myFlow.collectLatest { pagingData ->
myAdapter.submitData(pagingData)
myAdapter.notifyDataSetChanged()
}
}
I use this block of code in both onViewCreated and in the loop.
However, it gets called only once, in OnViewCreated.
In the loop, sometimes it gets called, and then it needs 2-3min to retrieve data, but most of the time nothing changes.
I guess it could be an issue related to needing much more time to retrieve data, or it just shouldn't be used this way.
Some of the possible solutions I tried, but didn't work:
using delay
adding flowOn(Despatchers.IO) in the end of the flow
switching flow call to a different thread
You don't need a loop for this, should only collect it on onViewCreated() once time. You should have two flows(one for your filter and another for your data) and to use switcMap(), and your adapter/view should call viewModel to notify any change that to be done.
Here an example:
//you can use any object type for your filter, in this example i used a sealed class
private val _transactionFilter = MutableLiveData<TransactionFilter>(
TransactionFilter.TransactionsByDate(Date())
)
val transactions: LiveData<Data> = _transactionFilter.switchMap { filter ->
//code to return data
}
Kotlin Flow's have a switchMap() too, i used liveData because flow.switchMap was experimental yet.
Another thing: you don't need call notifiyDataSetChanged() when using ListAdapter
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
I'm using MVVM architecture components by Android. For information that is going to be displayed in the View (Activity), they recommend to expose LiveData from ViewModel. The Observers in the Activity will consume that data and react to their changes.
But I don't know what happens when you want to fetch information (maybe other tables in your database) that you need to do some business validation for example. You definitely don't want to display that information in the UI, so it doesn't make sense to include Observers in the Activity for that.
It is possible to use observeForever in the ViewModel, so you can consume LiveData from Repository that you don't need to display in the Activity. However, the documentation says that ViewModel should noy contain observers to LiveData.
I cannot find any good example that deals with this situation so far.
EXAMPLE
I have one MainActivity that will create a Tournament record, which contains the Integer "numberOfRounds", selected by the user on the screen.
Now, in Activity PairingsView, I display the following AlertDialog when the user clicks the "finish current Round" button:
new AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.continues, (dialogInterface, i) -> {
if (mViewModel.hasNextRond()) {
mViewModel.calculateNewPairings();
}
In ViewModel:
public boolean hasNextRound() {
return currentRound <= numberOfRounds;
}
Here, I've access to currentRound value, since I exposed Round table information in PairingsView. But I also need to know the numberOfRounds to check if I must generate pairings for next round. This value is stored in Tournament table, and I don't need to display any information from that table in my View, so I think I shouldn't include an Observer for it.
Yes you can use observeForever in the ViewModel but you should remove the observer in onCleared method of ViewModel in order to prevent from memory leaking
I'm learning Kotlin and I'm trying to use the same ViewModel for display a list of users and for edit of a user.
I'm using room so I have a "getPersonnelById() which needs to be Observed. The problem is that I would like to Observe only Once and I don't know how to do...
Here's my function
private fun retrievePersonnelData(id: Long){
if(id != -1L){
val observer = dataSource.getPersonnelById(id).observeForever{
newPersonnel.value = it
Timber.e("Valeur newPersonnel = ${newPersonnel.value}")
}
}
}
I've used as recommended a observeForever but I don't know how to use removeObserver in this case...
Thank you very much
If you need to get data once - consider using suspend functions in Room and get data by demand.
If you need to get a particular Personnel object and observe changes in DB of it, store value of getPersonnelById(id) in LiveData<Personnel> and observe it from Activity/Fragment
observeForever is mostly needed in testing purposes, you should better use observe function to not manually remove an observer every time.