Search code examples
androidkotlinandroid-jetpack-composebroadcastreceiverviewmodel

How to get updated veriable value in Kotlin / Android


I have a app where I have to show current battery level of device and is device charging or not?

and here is my code:-

Battery Info model

data class BatteryInfo(
val batterLevel:Float,
val isCharging:Boolean )

BatteryInfo Module

class BatteryInfoModule (private val context: Context) {


   var batteryInfo  by mutableStateOf<BatteryInfo?>(null)


   fun registerReceiver(){

      context.registerReceiver(BatteryReceiver(), IntentFilter(Intent.ACTION_BATTERY_CHANGED))
   }

   fun unRegisterReceiver(){
       context.unregisterReceiver(BatteryReceiver())
    }

}

My di (dagger hilt)

@Provides
@Singleton
fun provideBatteryInfoModule(context: Context) = BatteryInfoModule(context)

My Broadcast receiver

  class BatteryReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == Intent.ACTION_BATTERY_CHANGED) {
            val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
            val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                    status == BatteryManager.BATTERY_STATUS_FULL
            val chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
//            val usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB
//            val acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC
            val batteryPct = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
            val module = BatteryInfoModule(context!!)
            module.batteryInfo = BatteryInfo(batterLevel =  batteryPct.toFloat() , isCharging = isCharging)
        }
    }
}

My viewmodel

@HiltViewModel
class HomeScreenViewModel @Inject constructor(
    private val batteryInfoModule: BatteryInfoModule,
) : ViewModel() {

private val _uiEvents = Channel<UIEvents>()
val uiEvents = _uiEvents.receiveAsFlow()


var batteryInfo by mutableStateOf<BatteryInfo?>(null)

init {
    viewModelScope.launch {

        batteryInfoModule.registerReceiver()
        batteryInfo = batteryInfoModule.batteryInfo

    }
}

override fun onCleared() {
    super.onCleared()
    batteryInfoModule.unRegisterReceiver()
}
}

My UI (Jetpack compose)

 Text(text = "Charging level :- ${viewModel.batteryInfo?.batteryLevel}",
                                    modifier = Modifier.padding(start = 5.dp, bottom = 5.dp))
Text(text = "is charging:- ${viewModel.batteryInfo?.isCharging}",
                                        modifier = Modifier.padding(start = 5.dp, bottom = 5.dp))

So whenever broadcast got updated value of battery it should be send to BatteryInfoModule and updated value should be receive to viewModel and I am not getting that data it's always null and I don't know how to solve this problem.

NOTE :-

I have tried like normal veriable and like mutablestateflow and observers but it did not solve the problem Please let me know my mistake and tell me how can I solve this.

Thank you


Solution

  • You're just instantiating your own copy of this class instead of using the singleton from Hilt, so it accomplishes nothing to update this instance of it. val module = BatteryInfoModule(context!!)

    I also think you're misusing @Singleton because of the way in which you're using the class. Suppose it is injected into two different ViewModels. Then the two ViewModels will be messing each other up when they register and unregister the callbacks. So I would remove the @Singleton.

    However, if the class would expose a cold flow instead of a MutableState and register/unregister functions, then it wouldn't matter if it's a singleton or not since it wouldn't be holding any mutable state. And I think a Flow would be easier to use because you wouldn't need any register/unregister functions.

    Example:

    class BatteryInfoModule @Inject constructor(
        @ApplicationContext private val context: Context
    ) {
    
        val batteryInfo: Flow<BatteryInfo> = callbackFlow {
            val receiver = object: BroadcastReceiver() {
                override fun onReceive(context: Context, intent: Intent) {
                    if (intent.action == Intent.ACTION_BATTERY_CHANGED) {
                        val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
                        val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                                status == BatteryManager.BATTERY_STATUS_FULL
                        val chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
                        val batteryPct = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
                        trySendBlocking(BatteryInfo(batterLevel = batteryPct.toFloat(), isCharging = isCharging))
                    }
                }
            }
            context.registerReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
            awaitClose {
                context.unregisterReceiver(receiver)
            }
        }
    
    }
    
    @HiltViewModel
    class HomeScreenViewModel @Inject constructor(
        private val batteryInfoModule: BatteryInfoModule
    ) : ViewModel() {
    
        private val _uiEvents = Channel<UIEvents>()
        val uiEvents = _uiEvents.receiveAsFlow()
    
        val batteryInfo: LiveData<BatteryInfo> = batteryInfoModule.batteryInfo.asLiveData()
        /* or:
        val batteryInfo: SharedFlow<BatteryInfo> = batteryInfoModule.batteryInfo.shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), 1)
        */
    }