google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.45k stars 2.02k forks source link

jetpack compose. navigation is called twice #2844

Closed vitidev closed 3 years ago

vitidev commented 3 years ago

dependies

implementation "androidx.hilt:hilt-navigation-compose:1.0.0-alpha03"
implementation 'com.google.dagger:hilt-android:2.38.1'
kapt 'com.google.dagger:hilt-android-compiler:2.38.1'
---
classpath "com.google.dagger:hilt-android-gradle-plugin:2.30.1-alpha"

code. just 3 routes

@HiltViewModel
class TestViewModel @Inject constructor(
    val savedStateHandle: SavedStateHandle
) : ViewModel() { }

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()
            Column() {
                NavHost(navController = navController, startDestination = "main") {
                    composable("main") {
                        Main(navController)
                    }
                    composable("first") {
                        Log.d("NAVTEST", "navigation to first")
                        Text("first")
                    }
                    composable("second") {
                        Log.d("NAVTEST", "navigation to second")
                        val viewModel = hiltViewModel<TestViewModel>()
                        Text("second")
                    }
                }
            }
        }
    }
}

@Composable
fun Main(navController: NavController) {
    Column() {
        Button(onClick = { navController.navigate("first") }) {
            Text(text = "nav")
        }
        Button(onClick = { navController.navigate("second") }) {
            Text(text = "nav with viewmodel")
        }
    }
}

navigation to "first" always happens once but when navigating to "second" (with hiltViewModel<>) - it call twice

D/NAVTEST: navigation to second
D/NAVTEST: navigation to second

I understand that this is a recomposition, but can it be fixed?

danysantiago commented 3 years ago

Hey - This does not seem related to Hilt, swapping hiltViewModel with Compose's viewModel function still causes recomposition, so I don't think Hilt's navigation view model function is related here. The viewModel you get should still be the same as it is scoped to the nav graph so this should not be an issue. What sort of trouble is it causing? If you are looking for a more general answer of why is there a recomposition, then it might be best to ask in a Compose forum. Maybe in Stackoverflow or Buganizer.

vitidev commented 3 years ago

What sort of trouble is it causing?

We can get arguments backStackEntry.arguments?.getString("userId") but there is no easy way to pass these parameters into the viewmodel constructor. Where, in general, is an example of how pass parameters to hiltViewModel<>()?

Moreover, the viewmodel may already exist. And the easiest way is to call some method on the viewmodel that ensure that the viewmodel is in valid state before starting the composition of the main content.

For example, "navigate with result" allow get result via navController.previousBackStackEntry?.savedStateHandle? in compose layer only, not by SavedStateHandle injected to ViewModel, so we need configure viewmodell

And composable method is good for putting this code in it. because it usually does not depend on any state variables and therefore never recomposes

Of couse, I can use something like

var flag by remember { mutableStateOf(false) }
if (!flag) {
   ... configure viewmodel state
    flag = true
}

But this is a boilerplate code