Hello, I have a question about view models. What s...
# compose
c
Hello, I have a question about view models. What should own them? Today, I’m initializing them with my composables, for example:
Copy code
@Composable
fun ParkView(parkId: String, viewModel: ParkViewModel = ParkViewModel(context = LocalContext.current, parkId = parkId)) {}
But when there’s a change inside my composable, I think it gets recomposed and alongside it my view model is recreated. For example, I have a TabRow which updates the view model when its onClick lambda is triggered.
viewModel.tabIndex.value = index
This triggers an infinite loop, where my viewModel is recreated indefinitely. Where the view model should be instantiated or at least, how can I avoid this infinite loop? I’m providing an example of view and its viewModel in the replies. And of course, thank you very much for your answers 😌
The Composable
Copy code
@Composable
fun ParkView(parkId: String, viewModel: ParkViewModel = ParkViewModel(context = LocalContext.current, parkId = parkId)) {
    val park by viewModel.park.collectAsState()
    val scrollState = rememberScrollState()

    val tabIndex by viewModel.tabIndex.collectAsState()
    val tabTitles = listOf("1", "2")

    Column(modifier = Modifier.scrollable(scrollState, Orientation.Vertical)) {
        if (park != null) {
            val park = park!!

            Text(text = park.title ?: "")

            TabRow(selectedTabIndex = tabIndex) { // 3.
                tabTitles.forEachIndexed { index, title ->
                    Tab(selected = tabIndex == index, // 4.
                        onClick = { viewModel.tabIndex.value = index },
                        text = { Text(text = title) }) // 5.
                }
            }

            when (tabIndex) { // 6.
                0 -> Text("1 content")
                1 -> Text("2 content")
            }
        }
    }
}
Its ViewModel
Copy code
class ParkViewModel(
    val context: Context,
    val parkId: String
): ViewModel() {

    private val _park = MutableStateFlow<QLPark?>(null)
    val park: StateFlow<QLPark?> get() = _park

    val tabIndex = MutableStateFlow<Int>(value = 0)

    init {
        viewModelScope.launch {
            fetchPark()
        }
    }

    private suspend fun fetchPark() {
        // This is an asynchronous network call, and ultimately what it does is getting a new park and setting the park property
        _park.value = newPark
    }

}
d
ideally, a viewmodel should be injected into a composable by some dependency injection framework(e.g. Hilt or Koin)
h
Copy code
an example using hilt

@HiltViewModel
class ParkViewModel @Inject constructor(
   
): ViewModel() {
Copy code
@Composable
fun ParkView(parkId: String, viewModel: ParkViewModel = hiltViewModel())
Keep in mind that you also have to inject the parameters to the viewmodel. For example, the context can be injected using
Copy code
@ApplicationContext val context: Context,
On the other hand for the parkId, you can use SaveStateHandle if you need it https://stackoverflow.com/questions/69145407/how-to-pass-id-and-application-to-a-viewmodel-viewmodelfactory-in-jetpack-compos
c
Hey, thank you very much for the nice tips! I’m going to try that 🙂
c
I use hilt, and with that I knkow that they don't get re-initd or anything.
c
I tried Hilt this morning and indeed, it works very well. Thank you 🙂