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
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)
*/
}