Compose - NavHost recomposition multiple times
C

2

13

During navigation from Navhost, I found out that the composable screens are getting recomposition multiple times. Because of it, my ViewModel is calling API data source multiple times too.

@Composable
fun MainView() {
    val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
    val scope = rememberCoroutineScope()
    val navController = rememberNavController()
    Scaffold(
        scaffoldState = scaffoldState,
        topBar = { TopBar(
            toolbarTitle = stringResource(id = R.string.app_name),
            scope = scope,
            scaffoldState = scaffoldState
        ) },
        drawerContent = {
           DrawerView(scope = scope, scaffoldState = scaffoldState, navController = navController)
        },
    ) {
        NavGraph(navController = navController)
    }
}

@Composable
fun NavGraph(navController: NavHostController) {
    NavHost(navController, startDestination = NavDrawerItem.Repositories.route) {
        composable(NavDrawerItem.Repositories.route) {
            RepoListView(getViewModel())
        }

        composable(NavDrawerItem.EmojiList.route) {
            EmojiListView(getViewModel())
        }
    }
} 

class RepoListViewModel(
    private val repositoriesUseCase: GetRepositoriesUseCase
): ViewModel() {
    
    init {
        getRepositories()
    }

@Composable
fun RepoListView(viewModel: RepoListViewModel) {
    AppTheme {
        RepoListContent(viewModel)
    }
}

Is there a way to handle it? I mean, I know it's how Android Compose works. But, How can I handle an API call inside a navigation screen?

EDIT

The problem was Koin itself. A new version has come and now its working properly.

Chug answered 1/4, 2022 at 17:54 Comment(5)
How does your DrawerView look likePropraetor
getViewModel shouldn't create a new instance on each recomposition. My only guess is that you can call navigate multiple times, which will create a new route for each call - check out this answer for details. If this won't help, please update your code to minimal reproducible example, as right now it cannot be run.Counterweight
as I said it's hard to help you without minimal reproducible example. first of all show your Koin module setup. the problem occures during initial screen appearance or when you're trying to navigate to an other screen? in second case, show the code you're using to perform navigationCounterweight
What does getViewModel() do? If it is doing anything other than calling the viewModel() method, then that's your problem. It is expected, anytime you are animated between destinations, that they recompose on every frame.Cyclostyle
I have the exact same problem, with the exact same code, with all koin dependencies 3.5.3. Turns out last comment from this issue is the key: github.com/InsertKoinIO/koin/issues/1079 Basically we need the launchSingleTop = true option when using navController.navigate().Hopfinger
G
3

If you're worried about recomposition being the cause of your multiple network calls, you could always make sure that you're only calling into your viewModel once per composable lifecycle with a LaunchedEffect.

So, one approach would be something like:

LaunchedEffect(Unit) {
    viewModel.getRepositories()
}

as long as your key you pass into LaunchedEffect doesn't change it won't recompose. So using Unit will only run this once as long as the composable is alive.

Are you re-creating your viewModel every time you're navigating to that screen? If so, it might be worth looking into putting your network call in another place besides the init block and using the LaunchedEffect approach above OR making it so you're not re-creating your viewModel on every navigation. Whatever fits your use-case best.

Gatian answered 1/4, 2022 at 22:23 Comment(2)
hey, LaunchedEffect(Unit) not working here. Still calling API multiple times when change screensChug
LaunchedEffect(Unit) do not work in this situationRudich
O
0

Get your viewModel outside the composable() lambda.

@Composable
fun NavGraph(navController: NavHostController) {
    val viewModel: RepoListViewModel = ViewModel()
    NavHost(navController, startDestination = NavDrawerItem.Repositories.route) {
        composable(NavDrawerItem.Repositories.route) {
            RepoListView(viewModel)
        }

        composable(NavDrawerItem.EmojiList.route) {
            EmojiListView(viewModel)
        }
    }
} 

+In case your getViewModel() retrieve a ViewModel using ViewModelProvider, then replace getViewModel() with a call to viewModel()

Overstreet answered 14/5, 2022 at 1:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.