If I create viewModel inside a composable then I c...
# compose
z
If I create viewModel inside a composable then I can’t preview the composable. Also, it is recommended to create a composable which is independent of the Navigation Library. And to test UI easily it is recommended to create stateless composable. To solve all of these I am thinking about the following pattern: 1. Every screen composable will have three parameters. (a) state: a data class. (b) viewModelCallback: an interface so that the screen can interact with the viewModel. (c) screenCallback: an interface to interact with navigation and activity results 2. There would be an overloaded version of the screen which will be stateful and will know about navController and viewModel. 3. I will use a stateless screen composable to preview and test. What do you think about this approach? for every screen, I have to create a data class, two interfaces, two dummy implementation of those for preview, and test implementation for those. will these extra works be beneficial or I am over-engineering
j
I did the following: - Top screen composable which has the ViewModel. This ViewModel emit a sealed class with each state, and I have a composable for each state. - These composables can be used with Preview passing its respective fakes. So you can see all states that the screen can have. - I inject a navigation interface directly to the ViewModel so you can navigate directly without callbacks/observables
z
@Javier are you handling navigation manually?
j
Navigation component, I had to do a tricky thing until Koin (or other DI framework) supports the injection of composables so I can directly inject rememberNavController in the navigation implementation.
z
then how do you manage callbacks?
j
I have no callbacks
i
Note that there's a third option - create the ViewModel in your
composable
lambda, then pass it into your separate screen Composable:
Copy code
composable("route") {
  val viewModel: YourViewModel = viewModel()
  YourScreen(viewModel)
}
👍 1
YourScreen
is then easy to preview with a fake ViewModel, easy to test with a fake ViewModel, etc
Your ViewModel can't hold onto a reference to the NavController, so you'd still want separate lambdas for click listeners that trigger Navigation / have that
composable
collect a flow of events from your ViewModel
a
@Ian Lake we don't need
by
keyword for the
viewModel
composable function
i
Good call, updated my code
z
can I use
Hilt
to inject
ViewModel
while testing?
I think the
Crane
app already using
Hilt
to inject ViewModel.
Normally a
ViewModel
will have
repository,
database
as construction parameter. Would it be good practice to make constructor parameters nullable in order to fake the
ViewModel
i
I would suspect you'd also wanting to be testing your ViewModel as well, which would entail that you already have a fake repository, etc.
j
@Ian Lake which Hilt was easy to get the fragment in a hilt module so you can call findnavcontroller and abstracting the navigation. Do you think this can have problems with Compose (without fragments) when the DI frameworks give more support to composables?
d
@Andrey Kulikov I don't understand why we don't need the
by
keyword in that case, could you elaborate for me please ?
a
d
@Andrey Kulikov alright, thank you
a
@zoha131 Curious what you ended up doing. Did you start to write fake viewmodel and its fake dependencies?
I thought fakes are supposed to stay in the Test source sets
z
I took @Ian Lake’s advice and mixed my earlier plan. So for every screen I create two overload of composable. One takes states and callbacks. Then I can preview it. And other takes ViewModel and callback for navigation. And I creates ViewModel inside
composable
of
NavHost
.
a
like below?
Copy code
fun Screen(state)
fun Screen(viewModel) {
  Screen(viewModel.state)
}
I was thinking the same, but I would have to pass all events from all descendants up to the Screen in order to delegate the logic into the viewModel. Otherwise, I would have to have overloads of all descendants as well. Have you thought about this case? Do you just Preview the top level screen?
Realized I don’t need to create anything else than the ViewModel if we just extract an interface to implement. Then we can just leave a blank implementation for the sake of Preview
z
I went with interface before but I think, it will require more boilerplate code. then I stick with the function type.
I don’t preview the top level screen. I don’t think that is needed.
a
it will require more boilerplate code
can you elaborate
I don’t preview the top level screen. I don’t think that is needed.
What do you mean? Your initial post was about making Two Screen composable overloads so you can preview it…”
I will use a stateless screen composable to preview and test.
z
What do you mean? Your initial post was about making Two Screen composable overloads so you can preview it…”
You said top level screen. I have some screens which has multiple nested screens. I thought You are talking about this kind of situation.
it will require more boilerplate code
if you go with interface approach then you have to create interface for every single screen and a stub implementation of that interface to preview. this is why I think interface approach would require more boilerplate code. and to get rid of this situation I have started using Function as parameter.