Android equivalent to NSNotificationCenter

Viewed 27341

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.

9 Answers

Kotlin: Here's a @Shiki's version in Kotlin with a little bit refactor in a fragment.

  1. 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))
    }

}
  1. Post notification anywhere. Only you need the context.

    LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(Constant.NOTIFICATION_SOMETHING_HAPPEN))```
    

PS:

  1. 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"
}
  1. 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 */
                }
            }
        }
    }

}
Related