Difference between withStateAtLeast and whenStateAtLeast lifeCycle extension methods - android

There are two similar extension method in androidX lifecycle-ktx package with slightly different signature. withStateAtLeast and whenStateAtLeast. I read the doc but couldn't understand the difference in their behavior. An example on when we should use which one, would be appreciated.

Looking at the source code, whenStateAtLeast runs the given block when the lifecycle is at least in the required state and suspends the block if the lifecycle moves to lesser state while block is running.
At the same time withStateAtLeast just waits for the lifecycle to be at least in the required state and runs the block. So it guarantees that the lifecycle state meets the requirement at the time it starts the block, but if the block suspends, by the time it's resumed, the lifecycle can be in lesser state or even destroyed.
So in general withStateAtLeast is a good choice if you need to run a block when lifecycle reaches the state (e.g. user entered the screen) and finish its execution in any case, even if the user leaves the screen before the execution finished. whenStateAtLeast in its turn is useful when the block works with UI, it guarantees, that each time it resumes execution, the lifecycle is in proper state, so you can access UI safely.

Related

Difference between launchWhenStarted and repeatOnLifecycle(STARTED) in collecting flows

As launchWhenStarted and repeatOnLifecycle(STARTED) provide completely different functionality (launchWhenStarted suspends the execution of the coroutine, and repeatOnLifecycle cancels and restarts a new coroutine), if the names of the new APIs were similar (for example, using launchWhenever for the restarting APIs), developers could’ve got confused and even use them interchangeably without noticing.
source
What is a simpler explanation for when to use which?
launchWhenStarted is just a one-time delay.
repeatOnLifecycle creates a suspending point that acts as a handler that runs provided block every time the lifecycle enters provided state and cancels it whenever it falls below it (so for STARTED it happens when it gets stopped).
Update:
For more information: https://medium.com/androiddevelopers/repeatonlifecycle-api-design-story-8670d1a7d333
repeatOnLifecycle restarts its coroutine from scratch on each repeat, and cancels it each time lifecycle falls below the specified state. It’s a natural fit for collecting most flows, because it fully cancels the flow when it’s not needed, which saves resources related to the flow continuing to emit values.
launchWhenX doesn’t cancel the coroutine and restart it. It just postpones when it starts, and pauses execution while below the specified state. They plan to deprecate these functions but I suspect there will need to be some replacement if they do, for the case where you are calling some time consuming suspend function and then want to do something when it’s done, like starting a fragment transaction. Using repeatOnLifecycle for this would result in redoing the time consuming action.
A cold flow backed by a channel or using operators with buffers such as buffer, conflate, flowOn, or shareIn is not safe to collect with some of the existing APIs such as CoroutineScope.launch, Flow<T>.launchIn, or LifecycleCoroutineScope.launchWhenX, unless you manually cancel the Job that started the coroutine when the activity goes to the background. These APIs will keep the underlying flow producer active while emitting items into the buffer in the background, and thus wasting resources.
To solve this issue with these APIs, you’d need to manually cancel collection when the view goes to the background. but it sounds to be a boilerplate code.
That's why Google recommended repeatOnLifecycle(Lifecycle.State.XXX) to make it simple and safe.
repeatOnLifecycle is a suspend function that takes a Lifecycle.State as a parameter which is used to automatically create and launch a new coroutine with the block passed to it when the lifecycle reaches that state, and cancel the ongoing coroutine that’s executing the block when the lifecycle falls below the state.
Learn more from here

LifeCycles getting called when app is forcefully killed

Im using in my app the onDestroy method to clean up data.
Google's documentation says documentation
Note: do not count on this method being called as a place for saving
data! For example, if an activity is editing data in a content
provider, those edits should be committed in either onPause() or
onSaveInstanceState(Bundle), not here. This method is usually
implemented to free resources like threads that are associated with an
activity, so that a destroyed activity does not leave such things
around while the rest of its application is still running. There are
situations where the system will simply kill the activity's hosting
process without calling this method (or any others) in it, so it
should not be used to do things that are intended to remain around
after the process goes away.
Such a situation is swiping the app out of the recent tasks list.
So in this case, data and other user important information must be saved in onPause or orStop().
But according to this link, none of the lifecycles get called.
So where do we save our data?
But according to this link, none of the lifecycles get called.
That answer is somewhat misleading. Your foreground activity should no longer be in the foreground when the user brings up the overview screen (recent tasks list). onPause() and onStop() will be called on that activity as a result. You can test this by overriding onPause() and onStop() and logging their calls.
So where do we save our data?
That depends a lot on what you are building.
In some cases, save the data when the data changes and the user indicates that they want to hold onto this data.
In some cases, save the data periodically. For example, in a game, you might save the game state every so often, so the user can pick up the game from where they left off.
In some cases, using a lifecycle method (e.g., onStop()) as a data-save trigger can be OK, but other times not. Saying "we will save the stuff that the user has typed in when we get called with onStop()" might be fine. Saying "we will go ahead and charge the user's credit card when we get called with onStop()" might not be fine.

Why should Android data be committed/read in onPause() - onResume() rather than in onStop() - onStart()?

The documentation suggests that the data should be committed/read in onPause()/onResume().
Yet when the application is no longer in the foreground, its data structures remain intact, which suggests that one could delay committing/reading data until the application is no longer visible, i.e. in onStop()/onStart(). Particularly since onStop() is guaranteed to be called before onDestroy().
Is it perhaps the case that either approach is suitable? Is the documentation giving here merely a guideline?
Update
Suppose your application needed to save relatively substantial data, say edits to a large image. One would then surely not write/read in onPause()/onResume(), lest the user experience become sluggish. One would in that case choose instead to write/read in onStop()/onStart(). Is that true?
The problem with using onStop is that you have no guarantees on when it will be called since the only sure thing is that it will be called before onDestroy. If you wait until onStop to commit your data it may be to late for another activity to show/use any of those changes. Same thing applies to onStart, your activity may not need to be restarted if it was just in the background so you'll have stale data. Using onResume and onPause guarantees that your data will always be current, commits are made as soon as the activity goes to the background and new data is loaded as soon as it becomes visible.
Yes, it is just a guideline (and generally a good one). It is up to you exactly when you want to commit changes. I personally like to create Store Objects that allow a simplification of Databases or SharedPreferences, and when a change is made, I commit those changes immediately. For simple data storage, this will be quick and invisible to the user. For large data sets, this may take more time, and you may wish to make those writes on a time interval, as well as in onPause.
As for when to read - you can read whenever, but again longer reads will often affect the user experience, unless you have taken care of it in another thread, such as with an AsyncTask.
To Further answer your update:
It depends on the developer, however I would write in onPause() and if necessary, read in a separate thread, probably initialized with onResume(). I may also write data out on a scheduled interval using a Timer thread, depending on how it would affect the user experience for the current session, and if it would be catastrophic for the phone to turn off and lose all data before onPause() is called.
The true answer to this is, onPause is the only method you are guaranteed to get called before Android can destroy your process. If a user leaves your app to take a phone call, and Android decides to close your process, its entirely legal for you to only get an onPause call. If you hadn't saved your state there, when the user hits the back button, you will end up recreating your activity in a different state than the user left it in.

Killing an *unresponsive* thread in Android/Java

I know the accepted, correct solutions for gracefully closing a thread.
But assume my game is supposed to be fault-tolerant, and when a new gamegplay is started, it tries to gracefully close the (now-paused) thread of the old gameplay. If it fails to join it / close it (e.g. because the old thread is buggy and is in an infinite loop), it instantiates the new Thread and starts it. But I don't like the fact that the old thread is still there, eating resources.
Is there an accepted way to kill an unresponsive thread without killing the process? It seems to me there isn't, in fact, I read somewhere that a Thread might not react to Thread.stop() either.
So there is no way dealing with a thread in an infinite loop (e.g. due to a bug), is it? Even if it reacts to Thread.stop(), the docs say that Thread.stop() may leave Dalvik VM in an inconsistent state...
If you need this capability, you must design it and implement it. Obviously, if you don't design and implement a graceful way to shut down a thread, then there will be no way to gracefully shut down a thread. There is no generic solution because the solution is application-specific. For example, it depends on what resources the thread might hold and what shared state the thread may hold locks on or have corrupted.
The canonical answer is this: If you need this capability, don't use threads. Use processes.
The core reason is the way threads work. You acquire a lock and then you manipulate shared data. While you're manipulating that shared data, it can enter an inconsistent state. It is the absolute responsibility of a thread to restore the data to a consistent state before releasing the lock. (Consider, for example, deleting an object from a doubly-linked list. You must adjust the forward link or the reverse link first. In between those two operations, the linked-list is in an inconsistent state.)
Say you have this code:
Acquire a lock or enter a synchronized block.
Begin modifying the shared state the lock protects.
Bug
Return the data the lock protects to a consistent state.
Release the lock.
So, now, what do we do? At step 3, the thread holds a lock and it has encountered a bug and triggered an exception. If we don't release the lock it acquired in step 1, every thread that tries to acquire that same lock will wait forever, and we're doomed. If we do release the lock it acquired in step 1, every thread that acquires the lock will then see the inconsistent shared state the thread failed to clean up because it never got to step 4. Either way, we're doomed.
If a thread encounters an exceptional condition the application programmer did not create a sane way to handle, the process is doomed.

onSaveInstanceState/onPause - wait until state is fully saved before allowing process to be killed

I'm working on my first Android app. It has a model that is persisted to a database as the user makes updates.
When onSaveInsanceState is called, I want to save an id that can be used to load the document the user was working on from the database. But this can only happen once the document has fully hit the database. In some cases, persisting a complex document can take several seconds (I'm hoping this will speed up once I take all the verbose logging out, and in actual use a complex document will be built up by the user in stages, each of which will be persisted to the database, so it's not very likely for a complex document to have to all be saved in one go).
Now, the #1 rule of threading on Android is "don't block the UI thread", so of course the DB interactions happen on a separate thread. But my understanding of the Android lifecycle is that in many cases onSaveInstanceState is being called because the Android system wants to kill the process. That suggests I can't allow this method to return until the DB thread finishes saving the document (and in fact with my current design, I don't actually know what the id number of the document is until it's been saved to the DB, so I can't even put that in the bundle of saved state).
Is it appropriate under these circumstances to block the UI thread waiting for the persist task to be done? When onSaveInstanceState is called because the process is being killed, the app is no longer visible in the foreground, so there's no interface to become unresponsive.
But onSaveInstanceState is also called when the Activity instance is being trashed by a config update, which happens when the screen orientation changes. It's very unfortunate when rotating the screen sideways fails to do anything for several seconds. In this case the process (and therefore memory space) is still around, so I don't strictly need to ensure the document hits the database, if I can just store a reference to it in the Bundle instead of its id. But I'm not aware of a way to tell the difference between these two cases.
Is there an accepted practice for these situations? Should I just block the thread to be safe? Can I just use normal Java thread primitives to block and wait? Is there something I can do that doesn't block the thread but ensures the persist task will be finished before Android closes the process?
All this also applies to onPause, as onSaveInstanceState isn't necessarily going to be called.
You don't have to do to the persistent saving in onSaveInstanceState, you can pretty much just save the instance state in the Bundle so that the instance can be quickly resumed in onRestoreInstanceState.
The Documentation states that you should write crucial persistent data (such as user edits) to storage in onPause, but it also states that it should be fast so that the next activity can start doing whatever it is it wants to do.
I think my recommendation here would be to just save the document text and whatever in the Bundle in onSaveInstanceState and restore it in onRestoreInstanceState. Use onPause to save a "backup copy" somewhere fast (temporary file maybe?) which can then be restored in onResume if it hasn't been saved to the database. And use onStop (which is called when the activity is already in the background) to actually save the data to the database.
Note that the activity might get killed after onPause has been called (never before, unless the system has very very few resources left...), that's why I would save a quick backup before trying to commit it in the database.
Edit - Extra based on comments
To make sure the background thread that's doing the saving process is done saving before the application can be killed by the system, I think it's fine to block and wait for the saving thread to complete before returning from onPause but I recommend using android:configChanges="orientation" to prevent activity restart (and onPause call) when the orientation changes.
You should try AsyncTask, which will block your ui but wont show blank screen, will load the necessary xml elements and then will get call from Async task, in async task save your doc in database.
Follow this link: AyncTask Exmaple

Categories

Resources