Warm tip: This article is reproduced from serverfault.com, please click

Update fragment ui from broadcast receiver in MvvM architecture and Kotlin

发布于 2020-04-15 10:33:19

Being new to kotlin and mvvm architecture I wasted the past two days searching for an answer for this problem without any success. So I am searching for a clean solution to change the fragment ui (in my case it is just a textview) when the device is connected/disconnected in the internet. I am using the mvvm architecture and the android architecture component(viewmodels, livedata, data bindings ...). I have a base activity with a bottom navigation and multiple fragments.

I am trying to get the connectivity event from a custom broadcast receiver and I want somehow to pass that event in the viewmodel or my fragment.

My first thought was to use an interface. This interface is triggered by the onreceive of the broadcastreceiver and it is implemented in my fragment or viewmodel, so when the internet event happens, the textview is updated in my fragment. But I am not sure about how to use the interface in the broadcast receiver, or I don't know even if this is possible or a good practice.

what I did till now is create the broadcast receiver

class ConnectionStateObserver: BroadcastReceiver() {
    override fun onReceive(p0: Context?, p1: Intent?) {
       //somehow use the interface in here
    }
}

register/unregister it in my base activity

    override fun onResume() {
        super.onResume()
        val broadcastIntent = IntentFilter()
        broadcastIntent.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
        registerReceiver(ConnectionStateObserver(), broadcastIntent)
    }
    override fun onPause() {
        super.onPause()
        unregisterReceiver(ConnectionStateObserver())
    }

(I know that ConnectivityManager.CONNECTIVITY_ACTION is deprecated but I couldn't find a better solution).

create an interface

interface ConnectionStatusChange{
    fun onChange()
}

and implement the interface in my Fragment

class myFragment : Fragment(), ConnectionStatusChange {
override fun onChange() {
        // do the ui changes here
    }
}

I would like to know if this approach is a good practice and how can I make it work. In the case that it is not possible to be done like that then please give me some suggestions (code always appreciated! =)). Also it would be good to have a modern, rather than a 5 years old solution. Thank you in advance.

Questioner
Konstantinos.T
Viewed
0
Konstantinos.T 2020-04-16 20:06:11

Eventually I found a clean and modern solution like I wanted. The trick is to wrap the BroadcastReceiver in a LiveData. By using this pattern you can observe the connection LiveDada directly from your fragment and update the ui. Of Course it is also possible (like sergiy tikhonov mentioned) to have a companion object in the base activity with a LiveData that is getting updated from the BroadcastReceiver and you can observe it from the fragment, but I didn't like this approach.

So it works like this:

Create an enum with the states of the network.

enum class NetworkAvailability {
    UNKNOWN,
    CONNECTED,
    DISCONNECTED
}

Create the class that extends the LiveData. I am using the singleton pattern because I don't want multiple instances. I create a BroadcastReceiver instance and I register/unregister it in onActive and onInactive methods of the LiveData.

class ConnectionStateLiveData(val context: Context) : LiveData<NetworkAvailability>() {

    companion object {
        private lateinit var instance: ConnectionStateLiveData

        fun get(context: Context): ConnectionStateLiveData {
            instance = if (::instance.isInitialized){
                instance
            }else{
                ConnectionStateLiveData(context)
            }
            return instance
        }
    }

    private val connectivityBroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(p0: Context?, p1: Intent?) {
            val connectivityManager =
                context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            val netInfo = connectivityManager.activeNetworkInfo

            value = if (netInfo != null && netInfo.isConnected) {
                NetworkAvailability.CONNECTED
            } else {
                NetworkAvailability.DISCONNECTED
            }
        }
    }

    override fun onActive() {
        super.onActive()
        value = NetworkAvailability.UNKNOWN
        val broadcastIntent = IntentFilter()
        broadcastIntent.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
        context.registerReceiver(connectivityBroadcastReceiver, broadcastIntent)
    }

    override fun onInactive() {
        super.onInactive()
        context.unregisterReceiver(connectivityBroadcastReceiver)
    }
}

And finally I observe the LiveData in my fragment.

ConnectionStateLiveData.get(context).observe(viewLifecycleOwner, Observer {
                if (it == NetworkAvailability.CONNECTED) {
                    binding.noInternetTextView.visibility = View.GONE
                }
            })