Search code examples
androidkotlinandroid-jetpack-composedagger-hilt

How to communicate the progress of a running foreground service to a composable?


I am looking for a way to return real time progress to my UI when my service is running, currently I'm using a shared hilt viewModel both for progress % and for the result but injecting into Service gives:

Injection of an @HiltViewModel class is prohibited since it does not create a ViewModel instance correctly. Access the ViewModel via the Android APIs (e.g. ViewModelProvider) instead.

my composables:

Button(onClick = {
                val intent = Intent(context, TestService::class.java).also {
                    it.action = TestService.Actions.START.toString()
                    it.putExtra("imageUri", imageUri) // Pass the image URI to the service
                }

                ContextCompat.startForegroundService(context, intent)
            }) {
            Text(text = "Start Service")
            }

            ProgressBar()


@Composable
fun ProgressBar() {
    val serviceViewModel: ServiceViewModel = viewModel()

    val progress by serviceViewModel.progress.collectAsState()
    val result by serviceViewModel.result.collectAsState()

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        LinearProgressIndicator(
            progress = { progress },
        ) // Use state
        Text("Progress: ${progress}%")
    }

}

my service :

@AndroidEntryPoint
class TestService : Service(){


    @Inject
    lateinit var serviceViewModel: ServiceViewModel

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when(intent?.action) {
            Actions.STOP.toString() -> stopSelf()
            Actions.START.toString() -> start(intent)
        }
        return START_NOT_STICKY 
    }
//.... other stuff

        val progressCallback: (Float) -> Unit = { progress ->
            serviceViewModel.updateProgress(progress)
        }

//....calling progressCallback

and my viewmodel:

@HiltViewModel
class ServiceViewModel @Inject constructor() : ViewModel(){
    private val _progress = MutableStateFlow(0f)
    val progress: StateFlow<Float> = _progress.asStateFlow()

    private val _result = MutableStateFlow<Extracted?>(null)
    val result: StateFlow<Extracted?> = _result.asStateFlow()

    fun updateProgress(newProgress: Float) {
        viewModelScope.launch { 
            _progress.value = newProgress
        }
    }

    fun updateResult(newResult: Extracted) {
        viewModelScope.launch {
            _result.value = newResult
        }
    }
}

I tried ViewModelProvider(this)[ServiceViewModel::class.java], solution to the error in stack overflow but seems deprecated and the only way I got it to work created multiple instances.

I also tried broadcast receiver but the composable does not see the broadcast.


Solution

  • Move the flows into a singleton class separate from the ViewModel then inject the new singleton class into the ViewModel and Service. The service then updates the progress and the ViewModel just provides the flow to the composables