I use Hilt as DI in an Android Studio project, and viewModel()
will create an instance of SoundViewModel
automatically.
Code A works well.
I think viewModel()
will create an Singleton of SoundViewModel
.
I think mViewMode_A
will be assigned to mViewMode_B
automatically without creating a new instance in Code B.
I think both mViewMode_A
and mViewMode_B
will point the same instance in Code B.
But I don't know why I get Result B when I run Code B, could you tell me?
Result B
java.lang.RuntimeException: Cannot create an instance of class info.dodata.soundmeter.presentation.viewmodel.SoundViewModel
Code A
@Composable
fun NavGraph(
mViewModel_A: SoundViewModel = viewModel()
) {
ScreenHome(mViewMode_B = mViewMode1_A)
}
@Composable
fun ScreenHome(
mViewModel_B: SoundViewModel
) {
...
}
@HiltViewModel
class SoundViewModel @Inject constructor(
@ApplicationContext private val appContext: Context,
...
): ViewModel() {
...
}
Code B
@Composable
fun NavGraph(
mViewMode_A: SoundViewModel = viewModel()
) {
ScreenHome()
}
@Composable
fun ScreenHome(
mViewMode_B: SoundViewModel = viewModel() // I think mViewMode_A will be assigned to mViewMode_B automatically without creating a new instnace.
) {
...
}
//The same
You need to pass the key in ViewModel initialization
I'm not good at Composable but this will resolve your problem
For creating a new instance of ViewModel we need to set Key property
ViewModelProvider(requireActivity()).get(<UniqueKey>, SoundViewModel::class.java)
key – The key to use to identify the ViewModel.
val mViewMode_A = viewModel<SoundViewModel>(key = "NavGraph")
val mViewMode_B = viewModel<SoundViewModel>(key = "ScreenHome")
For Composable this link may help you separateViewmodel
If you want to have a different instance of the same ViewModel type for your composable screens, you need to go like this:
MainActivity :
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
AppTheme {
NavHost(
navController = navController,
startDestination = "screen_a"
) {
composable(route = "screen_a") {
ScreenA {
navController.navigate(route = "screen_b") {
launchSingleTop = true
}
}
}
composable(route = "screen_b") {
ScreenB()
}
}
}
}
}
}
ScreenA :
@Composable
fun ScreenA(
viewModel: MainViewModel = hiltViewModel(),
navToScreenB: () -> Unit
) {
val state by viewModel.state
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = state)
Button(onClick = navToScreenB) {
Text(text = "Nav to Screen B")
}
}
}
ScreenB :
@Composable
fun ScreenB(viewModel: MainViewModel = hiltViewModel()) {
val state by viewModel.state
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = state)
}
}
MainViewModel :
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
private var _state = mutableStateOf(
value = "random value is: ${Random.nextInt(from = 0, until = 99999999)}"
)
val state: State<String> get() = _state
}
Following this logic, every time you navigate to ScreenB a new instance of a MainViewModel will be generated. This happens because we instantiate a MainViewModel with the hiltViewModel() in the constructor ScreenB (and also on ScreenA).
But if you want to share the same instance of the ViewModel, you need to create it at a level above the composable screens (for example, in MainActivity) and pass the instance on to whoever will use it, like this:
MainActivity :
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
// this will instantiate in normal way, to use in any scope on MainActivity
// private val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
// this will generate the instance for this setContent composable scope only
val mainViewModel: MainViewModel = hiltViewModel()
AppTheme {
NavHost(
navController = navController,
startDestination = "screen_a"
) {
composable(route = "screen_a") {
ScreenA(viewModel = mainViewModel) {
navController.navigate(route = "screen_b") {
launchSingleTop = true
}
}
}
composable(route = "screen_b") {
ScreenB(viewModel = mainViewModel)
}
}
}
}
}
}
Note: you must choose only one of the MainViewModel initialization examples that I put in MainActivity.
ScreenA :
@Composable
fun ScreenA(
viewModel: MainViewModel,
navToScreenB: () -> Unit
) {
val state by viewModel.state
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = state)
Button(onClick = navToScreenB) {
Text(text = "Nav to Screen B")
}
}
}
ScreenB :
@Composable
fun ScreenB(viewModel: MainViewModel) {
val state by viewModel.state
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = state)
}
}
And that's it, ScreenA and ScreenB have the same instance of a MainViewModel, because this time we didn't create the instance in their constructor, but instead passed on the instance created in MainActivity.
Edit :
I forgot to mention that you will need the following dependencies :
// hilt standard
implementation 'com.google.dagger:hilt-android:2.42'
kapt 'com.google.dagger:hilt-android-compiler:2.42'
// hilt support for compose (to use hiltViewModel())
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
// navigation for compose
implementation 'androidx.navigation:navigation-compose:2.4.2'
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.