In this post I will demonstrate how to use a BroadcastReceiver to update UI in Android through a ViewModel.
Implementing a ViewModel in Android is a way to update UI states. This separates the data layer from the UI layer in clean architecture. The ViewModel will hold states of data objects that will be updated your means of data processing. These data processes could be database retrieval, queries from web, etc.
In this example I will demonstrate how to use a BroadcastReceiver to update the UI through a ViewModel using a singleton repository. Using a repository as a singleton object ensures there is only one instance of it is available in the app as a global point of access. This means the same instance will be used for any calls to the repository.
In a previous post linked below, I have demonstrated implementing a singleton in a JWT project you could use as a reference as well. The approach is slightly different but uses the same concept.
BroadcastReceiver To Update UI Through ViewModel
Code
In this example I will be using the Wifi state to trigger the Android BroadcastReceiver. The BroadcastReceiver will listen for Wifi enabled / disabled to update the StateFlow in the repository which is being watched by the ViewModel to update the UI.
Add Permissions To The AndroidManifest.XML
First we will need to add the necessary permissions for this test example to work.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
....
Create the Singleton Repository
Next we will implement the singleton repository. In Kotlin, you would just need to build the repository as an object.
object DataRepo {
private val _wifiState: MutableStateFlow<Boolean> = MutableStateFlow<Boolean>(true)
val wifiState: StateFlow<Boolean> = _wifiState.asStateFlow()
fun updateWifiState(state : Boolean){
_wifiState.value = state
}
}
ViewModel To Update UI From Observing Repository State Changes
Now we can create the ViewModel observing the StateFlow in the repository.
class ViewModel : ViewModel() {
val wifiState: StateFlow<Boolean> = DataRepo.wifiState
fun updateWifiState(state : Boolean){
DataRepo.updateWifiState(state)
}
}
class ViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if(modelClass.isAssignableFrom(com.itgeek25.samplebroadcastreceiver.viewmodel.ViewModel::class.java)){
com.itgeek25.samplebroadcastreceiver.viewmodel.ViewModel() as T
} else {
throw IllegalArgumentException("ViewModel Not Found!!")
}
}
}
Create The BroadcastReceiver To Listen To Action
Create the BroadcastReceiver and listen for the appropriate action. If the action is found, then perform an update to the StateFlow held in the repository for the ViewModel.
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(intent?.action == WifiManager.WIFI_STATE_CHANGED_ACTION){
val wifistate = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_ENABLED)
when(wifistate){
WifiManager.WIFI_STATE_ENABLED -> {
DataRepo.updateWifiState(true)
}
WifiManager.WIFI_STATE_DISABLED -> {
DataRepo.updateWifiState(false)
}
}
}
}
}
Build the UI Attaching The ViewModel
Finally build the UI using the ViewModel to observe the repository changes from the BroadcastReceiver to update a state.
class MainActivity : ComponentActivity() {
lateinit var viewModel: ViewModel
var mBroadcastReceiver = MyReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val factory = ViewModelFactory()
viewModel = ViewModelProvider(
this,
factory
)[ViewModel::class.java]
registerReceiver(mBroadcastReceiver, IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION))
val wifiMgr = getSystemService(WIFI_SERVICE) as WifiManager
val isEnabled = if(wifiMgr.wifiState == WifiManager.WIFI_STATE_ENABLED) true else false
viewModel.updateWifiState(isEnabled)
setContent {
val wifiCheck = viewModel.wifiState.collectAsStateWithLifecycle().value
SampleBroadcastReceiverTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Column(modifier = Modifier.fillMaxSize().padding(innerPadding).background(if(wifiCheck) Color.Green else Color.Red), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
Text(
modifier = Modifier,
style = MaterialTheme.typography.titleLarge,
text = if(wifiCheck) "Wifi On" else "Wifi Off"
)
}
}
}
}
}
override fun onStop() {
super.onStop()
if(mBroadcastReceiver != null){
unregisterReceiver(mBroadcastReceiver)
}
}
}
Bonus
Below I will show alternative ways to implement various aspects of this implementation.
Create BroadcastReceiver Object in MainActivity
This is another way to create the BroadcastReceiver object in Kotlin instead of creating a separate class. Create it as a variable object in the MainActivity.
class MainActivity : ComponentActivity() {
lateinit var viewModel: ViewModel
var mBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(intent?.action == WifiManager.WIFI_STATE_CHANGED_ACTION){
val wifistate = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_ENABLED)
when(wifistate){
WifiManager.WIFI_STATE_ENABLED -> {
DataRepo.updateWifiState(true)
}
WifiManager.WIFI_STATE_DISABLED -> {
DataRepo.updateWifiState(false)
}
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...