Android Architecture Cannot add the same observer with different lifecycles - android

I have application that has MainActivity, that starts InvoiceActivity and that starts InvoicePaymentActivity which finally starts PaymentSuccessActivity.
I started using architecture components and it seems to work fine, bud I have found problem when starting MainActivity from PaymentSuccessActivity.
Up until now, I would just start new Intent and the app would "reset" to main screen. With ViewModel I am getting "Cannot add the same observer with different lifecycles".
I have found 2 solutions, but cannot think which one is better:
Subscribe to observer in onResume, unsubscribe onPause
Finish all previous activities except MainActivity after the next one is started. So when I just finish PaymentSuccessActivity, user will be on MainActivity. This has a drawback of user navigating backwards...
But it seems I cannot add the observer again... how can I
unsibscribe/subscribe? My code does not work right now...
override fun onResume() {
super.onResume()
viewModel.intercom.observe(this, observer)
}
override fun onPause() {
super.onPause()
viewModel.intercom.removeObserver { observer }
}

So this whole problem was base on using anonymous Observer class. Once I created my observer class implementing observer interface, the app startet to work fine, without needing to manually observer/remove. Can anyone explain why this is issue?

Related

Android detect when app is backgrounded OR detect when app is coming from foreground (with no mistake or exception cases)

I have situation where in my app I want to reset ActiveTime variable every time my app goes to background.
To solve this I had idea of using LifecycleObserver. So I defined it:
object VegasLifecycleObserver : DefaultLifecycleObserver {
private var lifecycleHandler: VegasLifecycleHandler? = null
fun registerLifecycle(lifecycle: Lifecycle) {
lifecycle.addObserver(this)
}
fun registerHandler(handler: VegasLifecycleHandler) {
lifecycleHandler = handler
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
lifecycleHandler?.onAppForegrounded()
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
lifecycleHandler?.onAppBackgrounded()
}
}
I registered it in my Application class and added interface to communicate with MainActivity:
interface VegasLifecycleListener {
fun onAppForegrounded()
fun onAppBackgrounded()
}
So in my MainActivity I have these 2 methods that detect when app is going to background and foreground. Looks good, but works really bad.
First regarding detecting when app is going to background:
When I for example click button on android to show all my active apps my onAppBackgrounded() method is called in MainActivity. Inside it I call mainViewmodel.resetActiveTime(). But this happens all so slow that I kill app faster then this code is executed. I tried in debug and it literally needs few seconds to do all of this which is too slow.
After this, I got another idea. What would happen if I reset my ActiveTime in onAppForegrounded(). Not technically identical but hey, if app went to background it has to come back to foreground, no other way.
Detecting foreground works well (at least while I tested) but I found problematic case:
Let's say my app is running and I decide to open active apps and kill it. onAppBackgrounded() is not called because no enough time. But will my onAppForegrounded() be called when starting app?
No it won't. When I stopped and take time to think about it little bit, it makes sense it won't. Since my mainActivity is listening lifecycle of Application class and they communicate by listener, which is initialized in MainActivity of course, at the first start my Application class calls onStart method and tries to communicate to MainActivity but the listener is not yet initialized. Because of this, my onAppForegrounded does not work at first start and all this logic before is useless.
What I would be happy with is nice way to solve this. Nice solution or explanation how I can achieve my goal.
I would not like to implement some "ugly" workarounds. I could for example use SharedPrefferences, save in my application's class at start variable "firstStart" to true and than in MainActivity onCreate() put it to false.
But do I really have to do it that way. If there is some cleaner way here I would be happy to her it :)

removeObserver not working, observer keeps being executed

I have an activity that uploads pictures. Inside this activity I have following observer which is working perfectly:
pictureViewModel.customCreateResult.observeForever { result -> onResponsePostPicture(result!!)}
I need to use observeForever because the user sometimes navigates to other activities. This is working fine and is not the problem. When the user decides to leave this activity with the observer. So when finishing the activity I'm calling:
override fun onDestroy() {
super.onDestroy()
pictureViewModel.customCreateResult.removeObserver{ result -> onResponsePostPicture(result!!)}
}
When for example 2 out of 4 pictures are uploaded and then the user finishes the activity but decides to reopen the activity. I'm getting a respond of the last 2 pictures from the observer. So my removeObserver is not working. What am I doing wrong?
You are not adding and removing same observer each time you are creating anew one . You are passing a lambda which is a new observer every time . Below is an example .
private var observer:Observer<String> = Observer {
onResponsePostPicture(it!!)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
pictureViewModel.customCreateResult.observeForever(observer)
}
override fun onDestroy() {
super.onDestroy()
pictureViewModel.customCreateResult.removeObserver(observer)
}
Alternatively you can use #removeObservers(this) Which will remove all the observers corresponds to Lifecycle Owner.
override fun onDestroy() {
customCreateResult.removeObservers(this)
super.onDestroy()
}
To add to what #ADM said, the issue is specifically that passing a lambda to observeForever/removeObserver creates a new Observer object that the system holds onto. Even if you pass the same lambda instance by holding it as a val, or anything like that, internally it's a new and completely different object.
So by registering an observer in this way, you cannot remove it with removeObserver and it will continue to receive events, and it can create large memory leaks if your lambda has references to things which lead back to something like an Activity.
The documentation doesn't warn you about this, and code completion for observeForever even suggests the lambda version, which is the way the rest of the Kotlin LiveData observer examples are written. It's a huge problem waiting to silently happen, and I wish they'd at least make people aware

Fragment being destroyed when host activity resumes

I'm working on an Android app and have noticed that after pausing and resuming the app (e.g. going to Home page or another app then returning to the original app), I am losing information from the fragment in between the pause and resume. On debugging I found that when I resume the app the fragment is being destroyed by FragmentManager during the host activity's onResume. (There is no code in the host activity's onResume which explicitly destroys the fragment.)
When I try and reproduce this in a simple app, it doesn't happen.
I can't see anything in the Android Lifecycle documentation which says the fragment gets destroyed when the app is resumed.
I'm ploughing through the FragmentManager code to try and work out why this is happening, but I'm wondering, does anyone know of any circumstance when this would happen / could you point me to documentation which explains this?
Let me know if you'd like more information or a link to the code. (I'm not expecting anyone to plough through the code, it's just if you want to get more information.)
Code snippet from host activity:
abstract class MainNavDrawerActivity : AppCompatActivity(), OnNavigationItemSelectedListener, Route.Listener {
...
public override fun onResume() {
...
super.onResume() // (line 190)
...
}
debug stack

How do I handle process recreation in android when application startup relies on a network call?

Say I have a custom application in class in my Android app that makes a network call, asynchronously, on startup. Then I have a Main activity that needs the results of the network call on startup. I can handle this with a Splash activity that waits on app startup. But when the process is recreated, we go straight to the Main activity, which expects the results of the network call to be there, and it crashes.
See the code below for an example.
What's the best way to handle this? I want to keep the splash screen for normal startup situations. But in the second situation, where the app is recreated, I'm not sure how to handle it. Is there a way to show the splash screen again, and wait, before returning to the recreated Main activity?
class MyApplication : Application() {
private val scope = CoroutineScope(Dispatchers.Main)
companion object {
lateinit var version: Integer
var startupFinishedListener: (() -> Unit)? = null
}
override fun onCreate() {
super.onCreate()
scope.launch {
version = getVersionFromNetwork() //Fake suspending network call
startupFinishedListener?.invoke()
}
}
}
class SplashScreen : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.splash)
MyApplication.startupFinishedListener = {
startMainActivity()
}
}
fun startMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
//This line will crash if we are coming from an activity recreation
Timber.d("Version is: ${MyApplication.version}")
}
}
I would prefer not to solve this by doing work in onResume in all of my activities instead of onCreate(). Ideally I could tell Android to launch my Splash activity before it launches the restored activity.
You will have to handle this in your onResume() lifecycle. You can create an onResume and then call the SplashActivity intent and finish() your MainActivity. This way it will just go back to Splash Screen where it will load the data and call the Main Activity again
override fun onResume() {
super.onResume()
val intent = Intent(this,SplashActivity)
startActivity(intent)
}
"This looks like an Architectural Problem."
Calling an API into application class and registering it's listener to your activities is a bad idea!
There are few options for you to handle your requirement:
If the data you want is really that important in MainActivity, you can call the API in MainActivity onCreate() itself with some loading indicator before your actual data is loaded.
What will happen if process restarts in this case?
Your MainActivity will recreate and call the API again. So, you're good.
If you're required to have that data as soon as your MainActivity starts, without waiting, you need to do caching. Call your API in splash activity and save the data to a file, shared preference or database. Then access cached data in your MainActivity.
What will happen if process restarts in this case?
Since you've already cached your data into a persitent storage, it'll be there even after process restart. So, you're good to get your data back.
Bonus
If you don't want to use your cached data everytime you open your app, just clear the cache in your SplashActivity and call the API everytime to have the latest data with you before moving to MainActivity.

Android ViewModel observer not called from background

I'm working on small android app using MVVM pattern.
My issue is that my ViewModel observer in MyActivity not called from the background. I need it to be called even if the app is in background to show system Notification to the user that app calculation process is done and the result is ready.
This is the current implementation located in onCreate in MyActivity:
mainActivityViewModel.getTestResult().observe(MainActivity.this, new Observer<String>() {
#Override
public void onChanged(#Nullable String blogList) {
Toast.makeText(getApplicationContext(), "test...", Toast.LENGTH_SHORT).show();
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)){
//The app is in foreground - showDialog
}else{
//The app is in background - showNotification
}
}
For now, this observer will be called only if the app is in foreground - if the process done while app was in foreground - 'showDialog' will trigger, if the app was in background - the showNotification will trigger - but only after I will open the app again. It's not the behaviour that I try to achieve. Please help! Thanks.
onChanged will only be called if the Activity's current Lifecycle state is at least STARTED. onPause gets called when you leave the Activity, which means it's not at least STARTED.
LiveData is simply not suitable for the behavior you're trying to achieve.
I would recommend you to use a foreground Service instead. Especially if the mentioned "calculation process" is something that the user should be aware of.
edit:
Let's say you're performing some potentially long running task in the background and you want to continue this task even if the user would leave or even close your Activity. Then using a Service is a good option, and especially a foreground Service if the task is the result of a user action. For example, the user clicks an "upload" button, a foreground Service performs the task and the associated Notification says "Upload in progress".
You have the option to either
Always show a new Notification when the task is complete, regardless of if the Activity is shown or not. This is pretty common.
Only show the Notification if the Activity is not currently started, and if it is started, show something in the Activity view instead.
In order to do the latter option, you need to know the current status of the Activity's Lifecycle. You want to be able to do the following check from your service somehow: getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)
The best way to communicate between an Activity and Service is binding to the Service and extending the Binder class in the Service.
After binding, you may store the Activity Lifecycle status in a variable in the Service, or even provide the Activity itself to the Service.
I guess your getTestResult() in ViewModel returning some live data.
So first of all, you are assigning your real data with LiveData using .setValue(some_data) method. And it is working fine while app is open. Btu when your app is in background. You need to use .postValue(some_data) method to assign data with that LiveData.
Check difference below:
setValue()
Sets the value. If there are active observers, the value will be dispatched to them. This method must be called from the main thread.
postValue()
Posts a task to a main thread to set the given value. If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
Conclusion, the key difference would be:
setValue() method must be called from the main thread. But if you need set a value from a background thread, postValue() should be used.
I saw this question researching for the same issue and even though it was asked 2 years ago I was able to let LiveData notify the observer even though the Fragment (or in question's case, an Activity) is either paused or stopped, so I am posting my solution here.
The solution is for a fragment, but can be adapted to activities as well.
On the fragment:
class MyFragment: Fragment() {
private var _lifecycleWrapper: LifecycleOwnerWrapper? = null
val activeLifecycleOwner: LifecycleOwner
get() {
if (_lifecycleWrapper == null)
_lifecycleWrapper = LifecycleOwnerWrapper(viewLifecycleOwner)
return _lifecycleWrapper!!
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
// On the livedata, use "activeLifecycleOwner"
// instead of "viewLifecycleOwner"
myLiveData.observe(activeLifecycleOwner) { value ->
// do processing even when in background
}
}
override fun onDestroyView() {
super.onDestroyView()
_lifecycleWrapper = null
}
}
LifecycleOwnerWrapper:
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
/**
* A special lifecycle owner that lets the livedata
* post values even though the source lifecycle owner is in paused or stopped
* state. It gets destroyed when the source lifecycle owner gets destroyed.
*/
class LifecycleOwnerWrapper(sourceOwner: LifecycleOwner):
LifecycleOwner, LifecycleEventObserver
{
private val lifecycle = LifecycleRegistry(this)
init
{
sourceOwner.lifecycle.addObserver(this)
when (sourceOwner.lifecycle.currentState)
{
Lifecycle.State.DESTROYED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
Lifecycle.State.CREATED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
Lifecycle.State.STARTED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
Lifecycle.State.RESUMED -> lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
else ->
{
// do nothing, the observer will catch up
}
}
}
override fun getLifecycle(): Lifecycle
{
return lifecycle
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event)
{
if (event != Lifecycle.Event.ON_PAUSE && event != Lifecycle.Event.ON_STOP)
lifecycle.handleLifecycleEvent(event)
}
}
The only thing you need to do is to not call this after onDestroy (or for viewLifecycleOwner, after onDestroyView) otherwise the lifecycle owner will be stale.
What you are trying to do is possible but not in the way you are doing it.
The whole purpose of the LiveData API is to link the data layer with the UI in a life cycle aware manner, so when the app is not in foreground then the observer knows that and stop updating the UI.
The first argument on the observer is the lifecycle.
This is a great improvement because without it the crashes because UI was not available were too often or it was too complex to control manually (boilerplate, edge cases, etc).
Service is not a good idea because the services can be killed by the DALVIK or ANT machine if the memory is needed for the foreground app. Services are not in the foreground but that doesn't mean that are bound to background neither that are guaranteed to be working for a undeterminated span of time.
For doing what you wish use the WorkManager. The WorkManager allows you to schedule jobs with or without conditions and from there you are gonna be able to send a Notification to the user.
You can try for a combination of Workmanager and Viewmodel to achieve an foreground/background app functionality.
For this use the Activity life cycle:
Use the onResume method to remove any WorkManager and star using the ViewModel
Use the onPause method to star the WorkManager
To handle the declaration, you can edit or dismiss the declaration from inside the function in your ViewModel class where the data was successfully retrieved.
private fun dataShow(list: List<String>) {
//Notification cancel
NotificationManagerCompat.from(getApplication()).cancel(30)
if (list.isNotEmpty()) {
data.value = list
progressHome.value = false
} else {
progressHome.value = true
}
}

Categories

Resources