Reputation: 395
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.
Upvotes: 6
Views: 4446
Reputation: 3147
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.
Upvotes: 1
Reputation: 83
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
}
}
Upvotes: 0
Reputation: 10517
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:
Upvotes: 0
Reputation: 2321
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:
Sets the value. If there are active observers, the value will be dispatched to them. This method must be called from the main thread.
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.
Upvotes: 3
Reputation: 116
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
Notification
when the task is complete, regardless of if the Activity
is shown or not. This is pretty common.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
.
Upvotes: 2