Search code examples
androidkotlinandroid-jetpack-composeandroid-viewmodelkoin

ViewModelScope might get canceled while inserting entries into a Room database


I'm building a score tracking app and I have a screen where you can add new players into a game. It's a simple screen that lets the user specify the player's name and color. This screen has its own ViewModel, which I inject into the screen's composable function by using the Koin framework like this:

fun NewPlayerScreen(
    viewModel: NewPlayerScreenViewModel = getViewModel(),
    navController: NavHostController,
    modifier: Modifier = Modifier
)

This ensures that the ViewModel lives as long as the screen is visible on the screen. When the user clicks the "save" button on the screen, the new player gets inserted into a Room database. My problem is however, that the insertion into the database is handled by the NewPlayerScreenViewModel. As soon as the user submits the new player, the screen exits and the ViewModel gets destroyed, which also means that its CoroutineScope gets canceled, which means that my ongoing database operation which is inserting the player into the database might not finish properly.

I know there is one solution; I could hoist the event out of the function like this:

fun NewPlayerScreen(
    viewModel: NewPlayerScreenViewModel = getViewModel(),
    navController: NavHostController,
    onPlayerSave: (newPlayer: Player) -> Unit,
    modifier: Modifier = Modifier
)

However, this would mean, that I now have to handle the insertion into the database in another ViewModel, in my MainScreenViewModel, since the parent of my NewPlayerScreen() composable is the MainScreen(). I don't like this approach, because I'd like my screens to have their own ViewModels handle the database operations for themselves. Are there any other options or is this the proper way to handle this kind of situation?


Solution

  • You could either:

    • Wait for the insertion to happen before navigating to the next screen

    You can do this by making the insertion function a suspend function, creating a val coroutineScope = rememberCoroutineScope() and then on the save button's onClick callback, do:

    coroutineScope.launch {
        viewModel.insertPlayer()
        navigate()
    }
    

    Doing this, you guarantee that the operations will happend sequentially, with the drawback that the user has to wait until the player is added before navigating to the next screen.

    • Schedule the work using WorkManager's OneTimeWorkRequest