Android : Does viewmodelscope cancel all jobs on detach from window of fragment - android

I want to know if oncleared of viewmodel is called when onDetach of a fragment is called. This is to make sure that all coroutines will be cancelled. I was getting a IllegalStateException: Fragment not attached to a context before refactoring to kotlin and coroutines. Now I am using viemodelscope to do these tasks.

If any Context or UI related logic has to be executed on the result of any asynchronous or API calls, can result in this issue even if onDetach is called. You should make safe calls like null checking to get rid of that exception.

As Google notes on the official documentation:
Figure 1 illustrates the various lifecycle states of an activity as it
undergoes a rotation and then is finished. The illustration also shows
the lifetime of the ViewModel next to the associated activity
lifecycle. This particular diagram illustrates the states of an
activity. The same basic states apply to the lifecycle of a fragment.
And this is the image:
So the answer is: ViewModel's onCleared is called when onDestroy is called from Activity/Fragment .
As for your coroutines, you should cancel() the job on the onCleared()

Related

Android - kotlin: What is the best way to add a delay in a custom view?

I have a custom view let's call it CustomTV, and I need to call notifydatasetchanged after 5 seconds inside method loadDetails(). I know there are 3 ways to do this.
Using Handlers with postdelay. I cannot use this because this is not lifecycle aware safe. the main thread will still execute my Runnable even if there is no View, i.e. user goes the background causing a crash.
Using Observable.timer, I am not 100% sure how this can be done from a View. Where would I dispose the disposable safely?
disposable = Observable.timer(
1000L,
TimeUnit.MILLISECONDS,
AndroidSchedulers.mainThread()
).subscribe {
// myadapter.notifydatasetchanged()
}
Coroutines. Issue with this is that I am inside a view. Not sure if I need to have the scope of the fragment of the View or the View.
Is loadDetails() a method in the CustomTV class or in your fragment?
I would suggest using a coroutine in your fragment to update the child view.
Ideally you would push this type of business logic down into a view model and test it - that's probably out of scope for your question though
Regarding 1: I don't think it will cause a crash. The Runnable itself will keep the View reference alive. But this would keep your View alive longer than necessary. Handlers are dangerous when they try to use a Fragment's attached Activity/Context assuming they are not null, because it can be null at the time the Runnable is called.
I don't use Rx, so can't comment on that.
Regarding 3: I would make loadDetails a suspend function with internal delay() call. Then the Fragment or Activity that calls loadDetails() can use its own coroutine launched from its own lifecycleScope to call it. The Fragment can use launchWhenStarted if applicable.

Android kotlin coroutines, viewModelScope behavior

I am having this interesting problem. I need to do some work immediately after insertion, but viewModelScope randomly, or at least it looks like randomly, skips functions except for the first one.
Example:
fun insertItem(item: SingleItem) = viewModelScope.launch {
itemsRepository.insertItem(item)
increaseAmount(item.catId)
}
So in this example everything runs ok only after fresh app install, but then on the next app launches second function "increaseAmount" will be randomly skipped and i don`t know why.
And it doesn't matter what goes after first function. I tried simple "Log" and it gets skipped as well. Is it normal for viewModelScope?
EDIT
Checked for exceptions. Second function throws an exception that the job was cancelled:
kotlinx.coroutines.JobCancellationException: Job was cancelled; job=SupervisorJobImpl{Cancelling}#2d87ff
Also, in my Fragment it is called like this:
viewModel.insertItem(newItem)
root.findNavController().popBackStack()
So after calling this function i go back to previous Fragment. Is it possible that viewModel gets destroyed before it finishes executing all work?
Is it normal for viewModelScope?
No, it is not. In coroutines functions call must be sequential. Functions itemsRepository.insertItem(item) and increaseAmount(item.catId) must be called one after another. I see a couple of reasons why second function is not called:
Function itemsRepository.insertItem(item) throws some exception.
Current coroutine scope is cancelled before second function call.
Edit:
ViewModel objects are scoped to the Lifecycle passed to the ViewModelProvider when getting the ViewModel. The ViewModel remains in memory until the Lifecycle it's scoped to goes away permanently: in the case of an activity, when it finishes, while in the case of a fragment, when it's detached.
After you call root.findNavController().popBackStack() your fragment will be detached, the ViewModel cleared and coroutine job cancelled.
You can initialize the ViewModel in the fragment in the following way:
private val viewModel: YourViewModel by activityViewModels()
Initializing viewModel in this way it will be scoped to the Lifecycle of an Activity.
To use activityViewModels() add next line to the dependencies of app's build.gradle file:
implementation "androidx.fragment:fragment-ktx:1.2.5"

commit() fragment transaction after activity.onSaveInstanceState() has been called

In my application I have an Activity that holds 3 Fragments. The very first time the Activity is created, Fragment 1 is displayed. Next, all fragment transactions will be executed after a network operation. For example: Fragment 1 has a button to make a request to the server and when the result is ready, Fragment 1 uses a listener to call a method defined inside the parent activity, to replace fragment 1 with fragment 2.
This works fine, except when the parent activity receives the callback after its state has been saved by onSaveInstanceState(). An IllegalStateException is thrown.
I've read some answers about this problem, for example this post and I understood why this exception happens thanks to this blog.
I also take an example that I found here to try to solve the problem. This post suggests to always check if the activity is running before call commit(). So I declared a Boolean variable in the parent activity and I put its value to false in onPause() and to true in onResume().
The parent activity callback called after network operations has been completed is something like this piece of Kotlin code, where next is the number of the replacing fragment:
private fun changeFragment(next:Int){
// get the instance of the next fragment
val currentFragment = createFragment(next)
// do other stuff here
if(isRunning){
// prepare a replace fragment transaction and then commit
ft.commit()
}else{
// store this transaction to be executed when the activity state become running
}
}
This code is working fine and now I'm not getting the Exception anymore, but my question is: it's possible that onSaveInstanceState() is called after I check if(isRunning) and before I call ft.commit(), so that the commit() happens after the activity state has been saved causing IllegalStateException again?
I'm not sure if onSaveInstanceState() could interrupt my changeFragment() method at any point in time. Is it possible?
If the possibility exists and my code may be interrupted between if(isRunning) and ft.commit(), what I can do?
It could be solved adding a try{}catch(){} block like this?:
if(isRunning){
try{
ft.commit()
}catch(ie:IllegalStateException){
// store the transaction and execute it when the activity become running
}
}else{
// store the transaction and execute it when the activity become running
}
Its a bit late but as of API 26+ we can use following to check if we need to do a normal commit or commitAllowingStateLoss().
getSupportFragmentManager().isStateSaved();
Are you storing anything when you're changing states?
If not, then you can try commitAllowingStateLoss().
onSaveInstanceState() would not be able to interrupt your method if your method is being called on the main (UI) thread.
Another approach that tends to make your life easier is to not use callbacks, but rather adopt a reactive pattern like MVVM. In that pattern, your Activity or Fragment subscribe to an observable when they are interested in e.g. network responses and unsubscribe typically in the onStop or onPause lifecycle callbacks so that your methods never get called after onSaveInstanceState. For a good starting place, check the official LiveData overview.

RxAndroid, event bus and Activity lifecycle

I found a few articles talking about how RxJava/RxAndroid can replace event busses (such as otto)
https://lorentzos.com/rxjava-as-event-bus-the-right-way-10a36bdd49ba#.7a4619qva
https://medium.com/mobiwise-blog/use-rxjava-instead-of-event-bus-libraries-aa78b5023097#.ew28h2urf
A quote from the first article:
Otto from Square got officially deprecated the previous days. In the Android world we can cheer now something like “EventBusses are dead long live RxJava”.
There is one thing I am missing though:
One of the perks of event buses is that they help a lot with the Activity lifecycle in that you don't need to manage registering/unregistering to callbacks manually (and thus avoiding memory leaks easily)
Example flow:
Activity subscribes to an event for getting songs (say SongsAvailableEvent)
We request songs (we make a network request)
We change the device's orientation mid-request
The Activity dies and a new one is built, that is also subscribed to the SongsAvailableEvent
The new activity gets the event and updates the UI, and the old Activity (which is now dead) does not get the event (yay!)
The articles above make it look like this flow is "solved" by RxAndroid/RxJava, but using Rx you still need to subscribe/unsubscribe on an Observable manually when you change the device's orientation. Moreover, if I want to "reuse" the request made in an Observable, I need to somehow persist it so that I will subscribe on that same Observable in the new Activity (I'm not quite sure how to do that, but it is not the point :) ).
My question is: is this problem easily solvable with pure RxAndroid/RxJava, or do I still need to use Rx with an event bus / extend Rx using something like RxLifecycle (which complicates things since I do not manage my Observables in the presentation layer)?
Your Activity's onDestroy can always call unsubscribe.
As for making things work to reuse request- Look into Loaders and LoaderManager. EventBus and RxJava to solve that was never needed.
I would venture to say that there isn't any way out of the fact that at some point in the chain, the Observable has to be tied to the lifecycle of some Android platform object, such as an Activity. Also, because you have not mentioned it as a partial solution, I assume you are avoiding using retained Fragments. If you are creating and holding a reference to the Observable only within your Activity, it is not possible for the results of a request in-flight to survive destruction of the Activity and be automatically subscribed to the new one. In addition, at some point, either during an orientation change, or the Activity finishing in the middle of a network request, your Observable will leak a reference to the Activity (via its subscribe() callback) if it is not unsubscribed on the Activity's onDestroy().
I have found RxLifecycle to be simple to use. My base Activity class has a method on it:
public <T> Observable.Transformer<T,T> bindLifecycleOnMainThread() {
return o -> o.compose(lifecycleProvider.bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread());
}
lifecycleProvider is created as per the instructions for RxLifecycle, depending on how you create your provider. This particular implementation uses bindToLifecycle() rather than specifying an explicit lifecycle event, so its use is contextual. Calling it during onResume will cause it to end on onPause. Calling it during onStart will cause it to end on onStop. Calling it other other times will cause it to end on onDestroy. Since this subscription will be updating the UI, it must only be observed on the UI thread.
This can then then used in the Activity as follows:
yourObservable.compose(bindLifecycleOnMainThread())
.subscribe(event -> handleEvent(event));
Now, where does this observable come from? Well, there's still no magic, and if you want an Observable to have a longer lifespan than the Activity, that means the Observable must be held by a component that lives longer than the Activity. There are many, many ways to do this, but your particular use case maps well to the new ViewModel library included in the Android Architecture framework. If you were to use ViewModels, your ViewModel would have a method that begins the network request, and would have a PublishSubject or PublishRelay that would emit SongsAvailableEvent objects (though I recommend exposing it to your Activity as only an Observable<SongsAvailableEvent>, not a Subject, for good encapsulation!). Your ViewModel would make the network call and forward the results to your Subject.
Finally, your Activity, when created, will immediately get its ViewModel from the ViewModel registry and subscribe to the Observable<SongsAvailableEvent> (which is a Subject/Relay) exposed by the ViewModel, and then bind it to the Activity's lifecycle, as in the example above. The ViewModel will survive any orientation changes of the Activity, and therefore so will the observable. The Observable will then never attempt to deliver an event to a destroyed Activity and the new Activity will immediately begin listening for events.
I believe this strategy promotes good encapsulation, since the Activity does not concern itself with how the network request gets made, and does not concern itself with how the source Observable is created. The only way that the Activity manipulates the Observable is by choosing what happens when it receives an event, and binding the subscription to the lifecycle of the Activity.
This can be endlessly tweaked and refined by composing your Observables but this should get you on the way.

Setting data on Android retained Fragment before onCreate() has been called

I have an Activity that creates a retained Fragment in order to persist a long running network operation in the event that the Activity goes through any configuration changes during the long running operation. So basically I'm trying to use the retained Fragment like a Singleton that lives until Activity's onDestroy() is called.
My goal is to have Activity's onCreate() trigger the long running network operation because I only want it to run when the activity is created not every time it starts again (otherwise I'd put it in onStart()).
To do this I first create a retained fragment in Activity's onCreate, then use FragmentManager to add the retained fragment then I kick off the network call in Activity's onCreate method and pass the networking object to the retained Fragment to hold onto.
This works, however I'm concerned because if I log what's going on I can see that the Activity first sets data on the retained Fragment and then the retained Fragment's onCreate() method is called. This looks wrong and seems out of order, however it works.
Is it bad practice to utilize the retained Fragment instance before the fragment has run its onCreate() method?
EDIT
After reading the responses and thinking a bit more about this, I'm realizing that initiating the network call from Activity onCreate() although convenient, is risky to do. As noted in the responses there's a chance that the long running operation returns very quickly and attempts to manipulate the Activity's view which may not have been initialized yet. So for my specific case I am resorting to initiating the long running operation from Activity's onStart() method and then using the retainedFragment to cache the response. This way even if onStart() is called multiple times and attempts to kick off the long running operation again, the result from the first attempt will be cached and can be returned.
I will admit that's unusual, but I can't think of a reason off the top of my head why it would be bad. The advantage of a retained Fragment is the reference to the Fragment is not destroyed so the references it holds on to are also retained. onCreate() and onDestroy() are called once throughout the lifetime of the Fragment (when the Fragment is added and removed respectively).
The danger might be that the asynchronous operation finishes before onCreate() is called. Likewise, the operation could finish after onDestroy() is called and when you expected the Fragment to be running. There are some methods like Fragment#setArguments() that can not be called during some parts of the Fragment's lifecycle. If you were to call these methods when you expected the Fragment to be running, then you will get an Exception thrown. So you end up having to put in a bunch of checks like if(isAttached()) { /* do this */ }. Putting the operation in onCreate() will ensure that it at least started before the operation finished.
But if you are not actually relying on any of the Fragment's functionality, then it should be fine. The lifecycles are only meant to tell you what's going on with it.
As long as you are not relying on Activity/Fragment views being available while Fragment's onCreate, you will be fine:
Note that this can be called while the fragment's activity is still in the process of being created. As such, you can not rely on things like the activity's content view hierarchy being initialized at this point.
(https://developer.android.com/reference/android/app/Fragment.html#onCreate(android.os.Bundle)
Still, you need to secure possible edge case that long-running operation may be finished before Activity and Fragment are fully created (if you depend on their views, this might be an issue).
Therefore, think about what you will do when long-running operation has finished and the results need to be presented somewhere.

Categories

Resources