androidandroid-intentandroid-activityviewmodelandroid-permissions

Start Intent from ViewModel


I would like to start a Foreground Service and call a LauncherForActivityResult.

As far as I know, i neither can start a Service from a ViewModel, nor call a LauncherForActivityResult.

I am working on an Android app, and in the beginning, I was just coding without real architectural decisions. But as my app grew, I want to separate things into their own module. I have a BottomBar, where the user can enter some info, and start a Foreground Service, which just tracks their location. On pressing a button, I want to validate the inputs in the ViewModel, and if it's correct, launch the Service. But as far as I know, the ViewModel shouldn't know about the Android Context.

I would like to make a service for requesting permission, which I could use from any screen, maybe passing the context. Or my other though was making a callback function in the viewModel and passing LocalContext.current, since you can access that in Composables.

My current approach is the following:

Button(
     modifier = Modifier.align(Alignment.CenterHorizontally),
     onClick = {
          permissionsResultLauncher.launch(
               arrayOf(
                   Manifest.permission.ACCESS_COARSE_LOCATION,
                   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
                         Manifest.permission.POST_NOTIFICATIONS
                   else ""
               )
          )
     // TODO: Only start service if permissions are granted
     // TODO: Handle starting service in the ViewModel
     Intent(context, PatrolService::class.java).also {
          it.action = PatrolService.Actions.START.toString()
          context.startService(it)
     }
}) {
     Text(text = "Járőrözés kezdése")
}

My other concern was that I can request permission, but how can I check whether they were accepted, since calling the Intent to launch the service crashes the app, if the notification and location permissions are not accepted.


Solution

  • But as far as I know, the ViewModel shouldn't know about the Android Context.

    There specifically is AndroidViewModel for supplying the Application singleton as a Context. Or, you could use your preferred dependency inversion framework (Dagger/Hilt, Koin, etc.) to inject the Application into your viewmodel.

    I would like to make a service for requesting permission

    First, that is impractical. A service cannot launch UI; requesting permission involves launching UI.

    Second, the point behind an ordinary foreground service is to do things when you do not have UI in the foreground and need to do those things continuously. Requesting permission only makes sense when you have UI in the foreground, as the user needs to respond to the UI for the permission.

    There certainly are scenarios where one needs a service. They are far fewer than newcomers to Android might think.

    which I could use from any screen

    A service is not a replacement for standard reuse patterns, such as inheritance and composition.

    My current approach is the following

    Launching the permission request from a composable is fine. If you have a legitimate need for a service, and you want to manage that service from a viewmodel, you could do so.

    since calling the Intent to launch the service crashes the app, if the notification and location permissions are not accepted

    Your service should check to see if the permissions were accepted before doing work that requires the permissions. If the notification permission is granted but others are not, the service could then raise a Notification to lead the user back to your UI where the user could grant those permissions.

    Your service or viewmodel (or perhaps your composable) can check to see if a specific permission was granted (using checkSelfPermission() on Context IIRC).