Android equivalent to NSNotificationCenter - android

In the process of porting an iPhone application over to android, I am looking for the best way to communicate within the app. Intents seem to be the way to go, is this the best (only) option? NSUserDefaults seems much lighter weight than Intents do in both performance and coding.
I should also add I have an application subclass for state, but I need to make another activity aware of an event.

The best equivalent I found is LocalBroadcastManager which is part of the Android Support Package.
From the LocalBroadcastManager documentation:
Helper to register for and send broadcasts of Intents to local objects within your process. This is has a number of advantages over sending global broadcasts with sendBroadcast(Intent):
You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data.
It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit.
It is more efficient than sending a global broadcast through the system.
When using this, you can say that an Intent is an equivalent to an NSNotification. Here is an example:
ReceiverActivity.java
An activity that watches for notifications for the event named "custom-event-name".
#Override
public void onCreate(Bundle savedInstanceState) {
...
// Register to receive messages.
// This is just like [[NSNotificationCenter defaultCenter] addObserver:...]
// We are registering an observer (mMessageReceiver) to receive Intents
// with actions named "custom-event-name".
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter("custom-event-name"));
}
// Our handler for received Intents. This will be called whenever an Intent
// with an action named "custom-event-name" is broadcasted.
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Get extra data included in the Intent
String message = intent.getStringExtra("message");
Log.d("receiver", "Got message: " + message);
}
};
#Override
protected void onDestroy() {
// Unregister since the activity is about to be closed.
// This is somewhat like [[NSNotificationCenter defaultCenter] removeObserver:name:object:]
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
super.onDestroy();
}
SenderActivity.java
The second activity that sends/broadcasts notifications.
#Override
public void onCreate(Bundle savedInstanceState) {
...
// Every time a button is clicked, we want to broadcast a notification.
findViewById(R.id.button_send).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
sendMessage();
}
});
}
// Send an Intent with an action named "custom-event-name". The Intent sent should
// be received by the ReceiverActivity.
private void sendMessage() {
Log.d("sender", "Broadcasting message");
Intent intent = new Intent("custom-event-name");
// You can also include some extra data.
intent.putExtra("message", "This is my message!");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
With the code above, every time the button R.id.button_send is clicked, an Intent is broadcasted and is received by mMessageReceiver in ReceiverActivity.
The debug output should look like this:
01-16 10:35:42.413: D/sender(356): Broadcasting message
01-16 10:35:42.421: D/receiver(356): Got message: This is my message!

Here is something similar to #Shiki answer, but from the angle of iOS developers and Notification center.
First create some kind of NotificationCenter service:
public class NotificationCenter {
public static void addObserver(Context context, NotificationType notification, BroadcastReceiver responseHandler) {
LocalBroadcastManager.getInstance(context).registerReceiver(responseHandler, new IntentFilter(notification.name()));
}
public static void removeObserver(Context context, BroadcastReceiver responseHandler) {
LocalBroadcastManager.getInstance(context).unregisterReceiver(responseHandler);
}
public static void postNotification(Context context, NotificationType notification, HashMap<String, String> params) {
Intent intent = new Intent(notification.name());
// insert parameters if needed
for(Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
intent.putExtra(key, value);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
}
Then, you will also need some enum type to be secure of mistakes in coding with strings - (NotificationType):
public enum NotificationType {
LoginResponse;
// Others
}
Here is usage(add/remove observers) for example in activities:
public class LoginActivity extends AppCompatActivity{
private BroadcastReceiver loginResponseReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// do what you need to do with parameters that you sent with notification
//here is example how to get parameter "isSuccess" that is sent with notification
Boolean result = Boolean.valueOf(intent.getStringExtra("isSuccess"));
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//subscribe to notifications listener in onCreate of activity
NotificationCenter.addObserver(this, NotificationType.LoginResponse, loginResponseReceiver);
}
#Override
protected void onDestroy() {
// Don't forget to unsubscribe from notifications listener
NotificationCenter.removeObserver(this, loginResponseReceiver);
super.onDestroy();
}
}
and here is finally how we post notification to NotificationCenter from some callback or rest service or whatever:
public void loginService(final Context context, String username, String password) {
//do some async work, or rest call etc.
//...
//on response, when we want to trigger and send notification that our job is finished
HashMap<String,String> params = new HashMap<String, String>();
params.put("isSuccess", String.valueOf(false));
NotificationCenter.postNotification(context, NotificationType.LoginResponse, params);
}
that's it, cheers!

You could try this: http://developer.android.com/reference/java/util/Observer.html

I found that the usage of EventBus of Guava lib is the simplest way for publish-subscribe-style communication between components without requiring the components to explicitly register with one another
see their sample on https://code.google.com/p/guava-libraries/wiki/EventBusExplained
// Class is typically registered by the container.
class EventBusChangeRecorder {
#Subscribe public void recordCustomerChange(ChangeEvent e) {
recordChange(e.getChange());
}
// somewhere during initialization
eventBus.register(this);
}
// much later
public void changeCustomer() {
eventBus.post(new ChangeEvent("bla bla") );
}
you can add this lib simply on Android Studio by adding a dependency to your build.gradle:
compile 'com.google.guava:guava:17.0'

You could use this: http://developer.android.com/reference/android/content/BroadcastReceiver.html, which gives a similar behavior.
You can register receivers programmatically through Context.registerReceiver(BroadcastReceiver, IntentFilter) and it will capture intents sent through Context.sendBroadcast(Intent).
Note, though, that a receiver will not get notifications if its activity (context) has been paused.

Kotlin: Here's a #Shiki's version in Kotlin with a little bit refactor in a fragment.
Register the observer in Fragment.
Fragment.kt
class MyFragment : Fragment() {
private var mContext: Context? = null
private val mMessageReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
//Do something here after you get the notification
myViewModel.reloadData()
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
}
override fun onStart() {
super.onStart()
registerSomeUpdate()
}
override fun onDestroy() {
LocalBroadcastManager.getInstance(mContext!!).unregisterReceiver(mMessageReceiver)
super.onDestroy()
}
private fun registerSomeUpdate() {
LocalBroadcastManager.getInstance(mContext!!).registerReceiver(mMessageReceiver, IntentFilter(Constant.NOTIFICATION_SOMETHING_HAPPEN))
}
}
Post notification anywhere. Only you need the context.
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(Constant.NOTIFICATION_SOMETHING_HAPPEN))```
PS:
you can add a Constant.kt like me for well organize the notifications.
Constant.kt
object Constant {
const val NOTIFICATION_SOMETHING_HAPPEN = "notification_something_happened_locally"
}
For the context in a fragment, you can use activity (sometimes null) or conext like what I used.

I wrote a wrapper that can do this same job, equivalent to iOS using LiveData
Wrapper:
class ObserverNotify {
private val liveData = MutableLiveData<Nothing>()
fun postNotification() {
GlobalScope.launch {
withContext(Dispatchers.Main) {
liveData.value = liveData.value
}
}
}
fun observeForever(observer: () -> Unit) {
liveData.observeForever { observer() }
}
fun observe(owner: LifecycleOwner, observer: () -> Unit) {
liveData.observe(owner) { observer()}
}
}
class ObserverNotifyWithData<T> {
private val liveData = MutableLiveData<T>()
fun postNotification(data: T) {
GlobalScope.launch {
withContext(Dispatchers.Main) {
liveData.value = data
}
}
}
fun observeForever(observer: (T) -> Unit) {
liveData.observeForever { observer(it) }
}
fun observe(owner: LifecycleOwner, observer: (T) -> Unit) {
liveData.observe(owner) { observer(it) }
}
}
Declaring observer types:
object ObserverCenter {
val moveMusicToBeTheNextOne: ObserverNotifyWithData<Music> by lazy { ObserverNotifyWithData() }
val playNextMusic: ObserverNotify by lazy { ObserverNotify() }
val newFCMTokenDidHandle: ObserverNotifyWithData<String?> by lazy { ObserverNotifyWithData() }
}
In the activity to observe:
ObserverCenter.newFCMTokenDidHandle.observe(this) {
// Do stuff
}
To notify:
ObserverCenter.playNextMusic.postNotification()
ObserverCenter.newFCMTokenDidHandle.postNotification("MyData")

Answer of #Shiki could be right in June 2020, but in January 2022, LocalBroadcastManager happened to be deprecated.
After two days of research, I ended up finding that SharedFlow was indicated by Android to "send ticks to the rest of the app so that all the content refreshes periodically at the same time".
Meaning, more or less, what we could expect from the NSNotificationCenter of Swift.
And here is the way I implemented the Shared Flow in my app:
First, you need to create an InAppNotif Singleton, which is actually a shared ViewModel for your activity (be caution to this last point: shared for your activity, not your all app^^)
enum class InAppNotifName {
NotifNameNumber1,
NotifNameNumber2,
NotifNameNumber3
}
object InAppNotif: ViewModel() {
private val _sharedNotif = MutableSharedFlow<InAppNotifName>(0)
val sharedNotif: SharedFlow<InAppNotifName> = _sharedNotif.asSharedFlow()
private fun sendNotif(name: InAppNotifName) {
CoroutineScope(Default).launch {
_sharedNotif.emit(name)
}
}
public fun notifyNotif1() {
sendNotif(InAppNotifName.NotifNameNumber1)
}
public fun notifyNotif2() {
sendNotif(InAppNotifName.NotifNameNumber1)
}
public fun notifyNotif3() {
sendNotif(InAppNotifName.NotifNameNumber1)
}
}
Second Step, only required if you have many Fragments receiving in app notifications, and you don't want to repeat yourself, would be to create an "Receiving Notif" interface
fun AnyReceivingNotif.observeInAppNotif() {
CoroutineScope(Default).launch {
InAppNotif.sharedNotif.collect {
onReceivingInAppNotif(it)
}
}
}
interface AnyReceivingNotif {
suspend fun onReceivingInAppNotif(value: InAppNotifName)
}
By the way, the "suspend" word is useful only if you need to update the UI upon receiving the notification.
Finally, from any object which is to receive InAppNotif, all you would need to do is get it be conform to your AnyReceivingNotif interface, and then complete the onReceivingInAppNotif function
class MyFragment: Fragment(), AnyReceivingNotif {
override suspend fun onReceivingInAppNotif(value: InAppNotifName) {
when (value) {
InAppNotifName.NotifNameNumber1 -> { /* Do complicated things */ }
InAppNotifName.NotifNameNumber2 -> { /* Do some stuff */ }
InAppNotifName.NotifNameNumber3 -> {
withContext(Default){
/* Update the UI */
}
}
}
}
}

You could use weak references.
This way you could manage the memory yourself and add and remove observers as you please.
When you addObserver add these parameters - cast that context from the activity you are adding it in to the empty interface, add a notification name, and call the method to run interface.
The method to run interface would have a function that is called run to return the data that you are passing something like this
public static interface Themethodtorun {
void run(String notification_name, Object additional_data);
}
Create a observation class that invokes a reference with a empty interface.
Also construct your Themethodtorun interface from the context being passed in the addobserver.
Add the observation to a data structure.
To call it would be the same method however all you need to do is find the specific notification name in the data structure, use the Themethodtorun.run(notification_name, data).
This will send a callback to where ever you created an observer with a specific notification name.
Dont forget to remove them when your done!
This is good reference for weak references.
http://learningviacode.blogspot.co.nz/2014/02/weak-references-in-java.html
I am in the process of uploading this code to github. Keep eyes open!

Related

Unable to schedule Alarm using JobIntentService

I have been researching how to schedule an alarm targeting devices Oreo +.
My current approach is using AlarmManager, a Ringtone service and broadcast receiver. I got the alarm to ring and display notification but the notification and the alarm does not fire for Oreo + devices.
I learned that using JobIntentService is a good practice due to Doze and other background limitations. Currently, when using a JobIntentService, I get an error:
java.lang.RuntimeException: An error occurred while executing doInBackground()
Am I doing this wrong? Do I really need to implement an AsyncTask?
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val alarmStatus: String = intent!!.getStringExtra("alarmStatus")
val serviceIntent = Intent(context, RingtoneService::class.java)
serviceIntent.putExtra("alarmStatus", alarmStatus)
//context!!.startService(serviceIntent)
RingtoneService.enqueueWork(context, serviceIntent)
}
}
class RingtoneService : JobIntentService() {
// Enqueing work to do in this service.
companion object {
val SERVICE_JOB_ID = 1
fun enqueueWork(context: Context?, work: Intent) {
if (context != null) {
enqueueWork(context, RingtoneService::class.java, RingtoneService.SERVICE_JOB_ID, work)
}
}
}
override fun onHandleWork(intent: Intent) {
onHandleIntent(intent)
}
private fun onHandleIntent(intent: Intent) {
//handling of notification goes here
}
}
you can try to update your support libraries to the latest and greatest and put some number > 1 (to avoid having two different intent services with the same Id). If it doesn't help you might want to go deeper to this rabbit hole: https://issuetracker.google.com/issues/63622293
One of the suggested fixes there is to use a wrapper class around the JobIntentService:
package android.support.v4.app;
import android.content.Intent;
public abstract class FixedJobIntentService extends JobIntentService {
#Override
GenericWorkItem dequeueWork() {
try {
return new FixedGenericWorkItem(super.dequeueWork());
} catch (SecurityException ignored) {
}
return null;
}
private class FixedGenericWorkItem implements GenericWorkItem {
final GenericWorkItem mGenericWorkItem;
FixedGenericWorkItem(GenericWorkItem genericWorkItem) {
mGenericWorkItem = genericWorkItem;
}
#Override
public Intent getIntent() {
if (mGenericWorkItem != null) {
return mGenericWorkItem.getIntent();
}
return null;
}
#Override
public void complete() {
try {
if (mGenericWorkItem != null) {
mGenericWorkItem.complete();
}
} catch (IllegalArgumentException ignored) {
}
}
}
}
Or you can try to use the new shiny WorkManager from the architecture components (may have issues if you have an app widget (the one for the android home screen) in your app).
https://developer.android.com/topic/libraries/architecture/workmanager/

RxJava Alternate to CompletableFuture

I am new to Rx java and trying to find a solution to my problem. I want to return a Single to caller but the data is not available when that method is called, it will get filled either before that call is made or after that call is made. A naive example of what I am trying to do is below. CompletableFuture partially solves it but I am looking for Rx solution, possibly with backpressure.
val receiver = Receiver()
class Receiver {
var data = ""
// this returns a single but does not have data, but will be available after call to onComplete
request(): Single<Data> {
return Single.fromFuture(completableFuture)
}
onNext(data: String) {
data.append(data)
}
onComplete() {
completableFuture.complete(data)
}
}
class Processor {
fun process() {
receiver.onNext("1")
receiver.onNext("2")
receiver.onComplete()
}
}
class Caller {
fun call() {
// This should get "12" result
// Processor().process() can be called before or after caller subscibe
receiver.request()..subscribe(...)
}
}
Instead of CompletableFuture, you can use PublishSubject. In order to be able to supply the value either before or after the subscribe call, you should also used .cache() on the Single. Put it all together like this:
class Receiver {
var data = ""
val subject = PublishSubject.create<Data>()
val single = Single.fromObservable(subject).cache()
// this ensures there is at least one subscription when the event is published - otherwise because there is no subscriber, the event is lost, despite cache().
val dummyObserver = single.subscribe()
// this returns a single but does not have data, but will be available after call to onComplete
fun request(): Single<Data> {
return single
}
fun onNext(data: String) {
// as before
}
fun onComplete() {
subject.onNext(data)
subject.onComplete(data)
}
}

Kotlin: Call a function to update UI from BroadcastReceiver onReceive

I am new to Kotlin, and it seems awesome! Though today, I've been trying to do something that in Java was super simple, but I've got totally stuck.
I am using a broadcast receiver to determine when the device is connected/ disconnected from a power source. And all I need to do it update my UI accordingly.
My Code
Here's my BroadcastReceiver classs, and it seems to work fine.
class PlugInReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action == Intent.ACTION_POWER_CONNECTED) {
// Do stuff when power connected
}
else if (action == Intent.ACTION_POWER_DISCONNECTED) {
// Do more stuff when power disconnected
}
}
}
Now in my MainActivity (but somewhere else, later on), I want to update my UI when the intent is fired, for example in the function below, the background color changes.
private fun updateBackgroundColor( newBgColorId: Int = R.color.colorAccent){
val mainLayout = findViewById<View>(R.id.mainLayout)
val colorFade = ObjectAnimator.ofObject(
mainLayout, "backgroundColor", ArgbEvaluator(), oldBgColor, newBgColor)
colorFade.start()
}
The Question
How can I call a function on the MainActivity, or update my UI when the BroadcastReceiver fires an event?
What I've tried so far
I looked into having a static variable somewhere, storing the result of the BroadcastReciever, then an observable in my UI class, watching and calling appropriate function accordingly. Though after Googling how to do this, looks like that's not really a good approach in Kotlin.
Considered trying to run the BroadcastReciever on the UI thread, but that sounds like a terrible idea.
Tried mixing a Java implementation with my Kotlin class, but couldn't get it to work.
Frustratingly I found several very similar questions on SO. However their implementations seem to all use Java-specific features:
Android BroadcastReceiver onReceive Update TextView in MainActivity
How to update UI in a BroadcastReceiver
Calling a Activity method from BroadcastReceiver in Android
How to update UI from BroadcastReceiver after screenshot
I'm sure this is a trivial question for most Android developers, but I am lost! Let me know if you need any more details. Thanks very much in advance!
Sharing the info to register BroadcastReceiver in Kotlin
Step 1. Create BroadcastReceiver in MainActivity.kt
private val mPlugInReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_POWER_CONNECTED -> {
//update your main background color
updateBackgroundColor()
}
Intent.ACTION_POWER_DISCONNECTED -> {
//update your main background color
updateBackgroundColor()
}
}
}
}
Step 2. Create IntentFilter
private fun getIntentFilter(): IntentFilter {
val iFilter = IntentFilter()
iFilter.addAction(Intent.ACTION_POWER_CONNECTED)
iFilter.addAction(Intent.ACTION_POWER_DISCONNECTED)
return iFilter
}
Step 3. Register receiver at onStart()
override fun onStart() {
super.onStart()
registerReceiver(mPlugInReceiver, getIntentFilter())
}
Step 4. Unregister receiver at onStop()
override fun onStop() {
super.onStop()
unregisterReceiver(mPlugInReceiver)
}
If you have custom BroadcastReceiver, you can register using LocalBroadcastManager and create your local IntentFilter
private val mLocalBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
AnyService.UPDATE_ANY -> {
}
}
}
}
private fun getLocalIntentFilter(): IntentFilter {
val iFilter = IntentFilter()
iFilter.addAction(AnyService.UPDATE_ANY)
return iFilter
}
Register local receiver
LocalBroadcastManager.getInstance(applicationContext).registerReceiver(mLocalBroadcastReceiver, getLocalIntentFilter())
Unregister local receiver LocalBroadcastManager.getInstance(applicationContext).unregisterReceiver(mLocalBroadcastReceiver)
The best way to achieve that is to create an abstract method in the BroadcastReceiver, and when onReceive() method is called, you can invoke that method that will be implemented by your activity.
BroadcastReceiver example:
abstract class ConnectionBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//Do the checks or whatever you want
var isConnected = true
broadcastResult(isConnected)
}
protected abstract fun broadcastResult(connected: Boolean)
}
And the code in your activity (in the onCreate or onStart for example). Here you register the broadcast receiver with the method implementation, and here you can update the UI:
var connectionBroadcastReceiver = object : ConnectionBroadcastReceiver() {
override fun broadcastResult(connected: Boolean) {
if(isConnected){
refreshList()
}
}
}
val intentFilter = IntentFilter()
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
this.registerReceiver(connectionBroadcastReceiver, intentFilter)
Don't forget to unregister the receiver in (onPause||onStop||onDestroy), but it's not strictly necessary.
The onReceive(...) method runs on the main thread. You can register your Activity in onStart() and unregister it in onStop(), which will guarantee that your UI is present when the event is received.

LiveData remove Observer after first callback

How do I remove the observer after I receive the first result? Below are two code ways I've tried, but they both keep receiving updates even though I have removed the observer.
Observer observer = new Observer<DownloadItem>() {
#Override
public void onChanged(#Nullable DownloadItem downloadItem) {
if(downloadItem!= null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
}
};
model.getDownloadByContentId(contentId).observeForever(observer);
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, downloadItem-> {
if(downloadItem!= null) {
this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObserver(downloadItem-> {});
} );
There is a more convenient solution for Kotlin with extensions:
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
This extension permit us to do that:
liveData.observeOnce(this, Observer<Password> {
if (it != null) {
// do something
}
})
So to answer your original question, we can do that:
val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce((AppCompatActivity) context, Observer<T> {
if (it != null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
}
startDownload();
})
The original source is here: https://code.luasoftware.com/tutorials/android/android-livedata-observe-once-only-kotlin/
Update: #Hakem-Zaied is right, we need to use observe instead of observeForever.
Your first one will not work, because observeForever() is not tied to any LifecycleOwner.
Your second one will not work, because you are not passing the existing registered observer to removeObserver().
You first need to settle on whether you are using LiveData with a LifecycleOwner (your activity) or not. My assumption is that you should be using a LifecycleOwner. In that case, use:
Observer observer = new Observer<DownloadItem>() {
#Override
public void onChanged(#Nullable DownloadItem downloadItem) {
if(downloadItem!= null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
}
};
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);
I love the generic solutions by Vince and Hakem Zaied, but to me the lambda version seems even better:
fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit) {
observeForever(object: Observer<T> {
override fun onChanged(value: T) {
removeObserver(this)
observer(value)
}
})
}
fun <T> LiveData<T>.observeOnce(owner: LifecycleOwner, observer: (T) -> Unit) {
observe(owner, object: Observer<T> {
override fun onChanged(value: T) {
removeObserver(this)
observer(value)
}
})
}
So you end up with:
val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce(context as AppCompatActivity) {
if (it != null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists")
}
startDownload();
}
Which I find cleaner.
Also, removeObserver() is called first-thing as the observer is dispatched, which makes it safer (i.e. copes with potential runtime error throws from within the user's observer code).
Following on CommonsWare answer, instead of calling removeObservers() which will remove all the observers attached to the LiveData, you can simply call removeObserver(this) to only remove this observer:
Observer observer = new Observer<DownloadItem>() {
#Override
public void onChanged(#Nullable DownloadItem downloadItem) {
if(downloadItem!= null) {
DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
model.getDownloadByContentId(contentId).removeObserver(this);
}
};
model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);
Note: in removeObserver(this), this refers to the observer instance and this works only in the case of an anonymous inner class. If you use a lambda, then this will refer to the activity instance.
I agree with Vince above, but I believe that we either skip passing lifecycleOwner and use observerForever as below:
fun <T> LiveData<T>.observeOnce(observer: Observer<T>) {
observeForever(object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
Or, using the lifecycleOwner with observe as below:
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(lifecycleOwner, object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
})
}
Java version of observeOnce method is already suggested by many users. But here we'll se the implementation in the main code.
First, we need to create Util class method
public class LiveDataUtil {
public static <T> void observeOnce(final LiveData<T> liveData, final Observer<T> observer) {
liveData.observeForever(new Observer<T>() {
#Override
public void onChanged(T t) {
liveData.removeObserver(this);
observer.onChanged(t);
}
});
}}
Now, we need to call this class where we need our ViewModel.
LiveDataUtil.observeOnce(viewModel.getUserDetails(), response-> {
if(response.isSuccessful()){
//Do your task
}
}
That's All!
Here's a Java version of the observeOnce method suggested in the other answers (an util class method instead of a Kotlin extension function) :
public class LiveDataUtil {
public static <T> void observeOnce(final LiveData<T> liveData, final Observer<T> observer) {
liveData.observeForever(new Observer<T>() {
#Override
public void onChanged(T t) {
liveData.removeObserver(this);
observer.onChanged(t);
}
});
}
}
You are creating live data instance (model.getDownloadByContentId(contentId)) more than one time that is the problem here.
Try this:
LiveData myLiveData =model.getDownloadByContentId(contentId);
myLiveData.observe(getViewLifecycleOwner(), downloadItem-> {
if(downloadItem!= null) {
this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
return;
}
startDownload();
myLiveData.removeObservers(getViewLifecycleOwner());
} );
The solution proposed by #CommonsWare and #Toni Joe didn't solve the issue for me when I needed to remove the observers after receiving the first result from a DAO query in my ViewModel. However, the following solution found at Livedata keeps observer after calling removeObserer did the trick for me with a little of my own intuition.
The process is as follows, create a variable in your ViewModel where the LiveData is stored upon request, retrieve it in a create observer function call in the activity after doing a null check, and call a remove observers function before calling the flushToDB routine in an imported class. That is, the code in my ViewModel looks as follows:
public class GameDataModel extends AndroidViewModel {
private LiveData<Integer> lastMatchNum = null;
.
.
.
private void initLastMatchNum(Integer player1ID, Integer player2ID) {
List<Integer> playerIDs = new ArrayList<>();
playerIDs.add(player1ID);
playerIDs.add(player2ID);
lastMatchNum = mRepository.getLastMatchNum(playerIDs);
}
public LiveData<Integer> getLastMatchNum(Integer player1ID, Integer player2ID) {
if (lastMatchNum == null) { initLastMatchNum(player1ID, player2ID); }
return lastMatchNum;
}
In the above, if there is no data in the LiveData variable in the ViewModel, I call initLastMatchNum() to retrieve the data from a function within the view model. The function to be called from the activity is getLastMatchNum(). This routine retrieves the data in the variable in the ViewModel (which is retrieved via the repository via the DAO).
The following code I have in my Activity
public class SomeActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
.
.
.
setupLastMatchNumObserver();
.
.
.
}
private void setupLastMatchNumObserver() {
if (mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).hasObservers()) {
Log.v("Observers", "setupLastMatchNumObserver has observers...returning");
return;
}
Log.v("Setting up Observers", "running mGameDataViewModel.get...observer()");
mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer MatchNumber) {
if (MatchNumber == null ) {
matchNumber = 1;
Log.v( "null MatchNumber", "matchNumber: " + matchNumber.toString());
}
else {
matchNumber = MatchNumber; matchNumber++;
Log.v( "matchNumber", "Incrementing match number: " + matchNumber.toString());
}
MatchNumberText.setText(matchNumber.toString());
}
});
}
private void removeObservers() {
final LiveData<Integer> observable = mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID);
if (observable != null && observable.hasObservers()) {
Log.v("removeObserver", "Removing Observers");
observable.removeObservers(this);
}
}
What's going on in the above, is 1.) I call the setupLastMatchNumObserver() routine in the onCreate method of the activity, to update the class's variable matchNum. This keeps track of the match numbers between players in my game which is stored in a database. Every set of players will have a different match number in the database based upon how often they play new matches with each other. The first solutions in this thread seemed a little weary to me as calling remove observers in the onChanged seems strange to me and would constantly change the TextView object after every database flush of each move of the players. So matchNumber was getting incremented after every move because there was a new value in the database after the first move (namely the one matchNumber++ value) and onChanged kept being called because removeObservers was not working as intended. setupLastMatchNumObserver() checks to see if there are observers of the live data and if so does not instantiate a new call each round. As you can see I am setting a TextView object to reflect the current matchnumber of the players.
The next part is a little trick on when to call removeObservers(). At first I thought if I called it directly after setupLastMatchNumObserver() in the onCreate override of the activity that all would be fine. But it removed the observer before the observer could grab the data. I found out that if I called removeObservers() directly prior to the call to flush the new data collected in the activity to the database (in separate routines throughout the activity) it worked like a charm. i.e.,
public void addListenerOnButton() {
.
.
.
#Override
public void onClick(View v) {
.
.
.
removeObservers();
updateMatchData(data);
}
}
I also call removeObservers(); and updateMatchData(data) in other places in my activity in the above fashion. The beauty is removeObservers() can be called as many times as needed since there is a check to return if there are no observers present.
LiveData class has 2 similar methods to remove Observers. First is named,
removeObserver(#NonNull final Observer<T> observer) (see carefully the name of the method, it's singular) which takes in the observer you want to be removed from the list of Observers of the same LifecycleOwner.
Second method is
removeObservers(#NonNull final LifecycleOwner owner) (see the plural method name). This method takes in the LifecycleOwner itself and removes all the Observers of the specified LifecycleOwner.
Now in your case, you can remove your Observer by 2 ways (there might be many ways), one is told by #ToniJoe in the previous answer.
Another way is just have a MutableLiveData of boolean in your ViewModel which stores true when it's been Observed the first time and just observe that Livedata as well. So whenever it turns to true, you'll be notified and there you can remove your observer by passing that particular observer.
Vince and Hakem Zaied solutions worked well, but in my case, I was trying to get the livedata instance and update a local DB, but the livedata was to be updated from a remote API first, hence I was getting a NullPointer, so I switched to observeForever and I was able to get the data when it was updated, but now I had to dispose of the observer after getting the data, so I modified Vince solution to only observe and emit data when the livedata contained data.
fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit) {
observeForever(object : Observer<T> {
override fun onChanged(value: T) {
//Resource is my data class response wrapper, with this i was able to
//only update the observer when the livedata had values
//the idea is to cast the value to the expected type and check for nulls
val resource = value as Resource<*>
if (resource.data != null) {
observer(value)
removeObserver(this)
}}
})
}
Here is a androidx.lifecycle.Observer Java example:
Observer <? super List<MyEntity>> observer = new Observer<List<MyEntity>>() {
#Override
public void onChanged(List<MyEntity> myEntities) {
Log.d(TAG, "observer changed");
MySearchViewModel.getMyList().removeObserver(this);
}
};
MySearchViewModel.getMyList().observe(MainActivity.this, observer);
In my opinion, Livedata is designed to continually receive oncoming data. If you just want it to be executed only once for the purpose of, say, requesting data from server to initialize UI, I would recommend you design your code in this way:
1、Define your time-consuming method as non-Livedata type inside a Viewmodel. You do not have to start a new thread in this process.
2、Start a new Thread in an Activity, and inside the new Thread, call the method defined above, followed by runOnUiThread() where you write your logic of utilizing the requested data. In thie way the time-consuming method will not block the UI thread, while it blocks the new thread so the runOnUiThread() only runs after your requested data is received successfully.
So consider a replacement of Livedata, if this is what you want.
I read some documentation and saw at the observer the remove method and so I came to this solution:
1: first declare the observer:
// observer for selecting chip in view
View actionView;
Observer selectChipFunction = (action) -> selectChip(actionView, action);
2: then use the observer:
// select an action if set before
if (sharedViewModel.getAction() != null)
sharedViewModel.getAction().observe(getViewLifecycleOwner(), selectChipFunction);
3: then in the selectChip observer remove the observer:
/**
* select action chip
* #param actionView - view to use for selecting action chip
* #param actionObject - action chip to select
*/
private void selectChip(View actionView, Object actionObject)
{
// no need for observing when action is changed so remove.
sharedViewModel.getAction().removeObserver(selectChipFunction);
This way its only triggered once and after that its removed. In my case I needed this because I was setting the "action" that triggered the Observer in the selectChipFunction and if I dont do this you will end in a cyclic observer triggering.
How about this:
fun <T> LiveData<T>.observeOnCondition(lifecycleOwner: LifecycleOwner,
observer: Observer<T>,
condition: () -> Boolean) {
observe(lifecycleOwner) { t ->
if (condition()) {
observer.onChanged(t)
}
}
}
This way you can define a more generic condition if you might want to pick up the data again at a later stage.
you can use such function to observe once then remove the observer
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
var ob: Observer<T>? = null
ob = Observer { value ->
ob?.let {
removeObserver(it)
}
observer.onChanged(value)
}
observe(lifecycleOwner, ob)
}
Unfortunately, all solution didn't work for me. The only example that worked for me see that link.
https://gist.github.com/kobeumut/edb3edd9a2ae9abf6984a42bb2de0441

How to clear LiveData stored value?

According to LiveData documentation:
The LiveData class provides the following advantages:
...
Always up to date data: If a Lifecycle starts again (like an activity going back to started state from the back stack) it receives the latest location data (if it didn’t already).
But sometimes I don't need this feature.
For example, I have following LiveData in ViewModel and Observer in Activity:
//LiveData
val showDialogLiveData = MutableLiveData<String>()
//Activity
viewModel.showMessageLiveData.observe(this, android.arch.lifecycle.Observer { message ->
AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK") { _, _ -> }
.show()
})
Now after every rotation old dialog will appear.
Is there a way to clear stored value after it's handled or is it wrong usage of LiveData at all?
Update
There are actually a few ways to resolve this issue. They are summarized nicely in the article LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case). This is written by a fellow Googler who works with the Architecture Components team.
TL;DR A more robust approach is to use an Event wrapper class, which you can see an example of at the bottom of the article.
This pattern has made it's way into numerous Android samples, for example:
Plaid
Architecture Blueprints
IOSched
Why is an Event wrapper preferred over SingleLiveEvent?
One issue with SingleLiveEvent is that if there are multiple observers to a SingleLiveEvent, only one of them will be notified when that data has changed - this can introduce subtle bugs and is hard to work around.
Using an Event wrapper class, all of your observers will be notified as normal. You can then choose to either explicitly "handle" the content (content is only "handled" once) or peek at the content, which always returns whatever the latest "content" was. In the dialog example, this means you can always see what the last message was with peek, but ensure that for every new message, the dialog only is triggered once, using getContentIfNotHandled.
Old Response
Alex's response in the comments is I think exactly what you're looking for. There's sample code for a class called SingleLiveEvent. The purpose of this class is described as:
A lifecycle-aware observable that sends only new updates after
subscription, used for events like navigation and Snackbar messages.
This avoids a common problem with events: on configuration change
(like rotation) an update can be emitted if the observer is active.
This LiveData only calls the observable if there's an explicit call to
setValue() or call().
If you need simple solution, try this one:
class SingleLiveData<T> : MutableLiveData<T?>() {
override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
super.observe(owner, Observer { t ->
if (t != null) {
observer.onChanged(t)
postValue(null)
}
})
}
}
Use it like a regular MutableLiveData
I`m not sure if it will work in your case, but in my case (increasing/decreasing items amount in Room by click on views) removing Observer and checking if there is active observers let me do the job:
LiveData<MenuItem> menuitem = mViewModel.getMenuItemById(menuid);
menuitem.observe(this, (MenuItem menuItemRoom) ->{
menuitem.removeObservers(this);
if(menuitem.hasObservers())return;
// Do your single job here
});
});
UPDATE 20/03/2019:
Now i prefer this:
EventWraper class from Google Samples inside MutableLiveData
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
public class Event<T> {
private T mContent;
private boolean hasBeenHandled = false;
public Event( T content) {
if (content == null) {
throw new IllegalArgumentException("null values in Event are not allowed.");
}
mContent = content;
}
#Nullable
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return mContent;
}
}
public boolean hasBeenHandled() {
return hasBeenHandled;
}
}
In ViewModel :
/** expose Save LiveData Event */
public void newSaveEvent() {
saveEvent.setValue(new Event<>(true));
}
private final MutableLiveData<Event<Boolean>> saveEvent = new MutableLiveData<>();
LiveData<Event<Boolean>> onSaveEvent() {
return saveEvent;
}
In Activity/Fragment
mViewModel
.onSaveEvent()
.observe(
getViewLifecycleOwner(),
booleanEvent -> {
if (booleanEvent != null)
final Boolean shouldSave = booleanEvent.getContentIfNotHandled();
if (shouldSave != null && shouldSave) saveData();
}
});
In my case SingleLiveEvent doesn't help. I use this code:
private MutableLiveData<Boolean> someLiveData;
private final Observer<Boolean> someObserver = new Observer<Boolean>() {
#Override
public void onChanged(#Nullable Boolean aBoolean) {
if (aBoolean != null) {
// doing work
...
// reset LiveData value
someLiveData.postValue(null);
}
}
};
You need to use SingleLiveEvent for this case
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer<T> { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
#MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}
companion object {
private const val TAG = "SingleLiveEvent"
}
}
And inside you viewmodel class create object like:
val snackbarMessage = SingleLiveEvent<Int>()
I solved it like that. Live data will clear itself when there is no observer
class SelfCleaningLiveData<T> : MutableLiveData<T>(){
override fun onInactive() {
super.onInactive()
value = null
}
}
The best solution I found is live event library which works perfectly if you have multiple observers:
class LiveEventViewModel : ViewModel() {
private val clickedState = LiveEvent<String>()
val state: LiveData<String> = clickedState
fun clicked() {
clickedState.value = ...
}
}
Might be an ugly hack but... Note: it requires RxJava
menuRepository
.getMenuTypeAndMenuEntity(menuId)
.flatMap { Single.fromCallable { menuTypeAndId.postValue(Pair(it.first, menuId)) } }
.flatMap { Single.timer(200, TimeUnit.MILLISECONDS) }
.subscribe(
{ menuTypeAndId.postValue(null) },
{ Log.d(MenuViewModel.TAG, "onError: ${it.printStackTrace()}") }
)
I know It's not the best way or even a professional way but if you do not hav time to do it the right way you can recreate the MutableLiveDataa after you observed it. it would be like :
private void purchaseAllResultFun() {
viewModel.isAllPurchaseSuccess().observe(getViewLifecycleOwner(), isSuccess -> {
if (!isSuccess) {
failedPurchaseToast();
}else {
successfulPurchaseToast();
}
//reset mutableLiveData after you're done
viewModel.resetIsAllSuccessFull();
});
}
//function in viewmodel class
public void resetIsAllSuccessFull(){
purchaseRepository.reSetIsAllSuccessFull();
}
//function in repository class
public void resetIsAllSuccessFull(){
successLiveData = new MutableLiveData<>();
}
In this way if you need to recall purchaseAllResultFun() function it won't give the stored value.

Categories

Resources