Hey, people. My team and I are working on a green...
# android-architecture
m
Hey, people. My team and I are working on a greenfield project, built from the ground up using compose and MVVM. It was fine and dandy until the VMs started to grow for more complex screens. We got around it by extracting logic away from the VMs, which helped us to keep the classes in check, however, they still need to take a large number of dependencies (use cases, etc.). We would like to improve this part as well as it still feels like the VMs are trying to do too many things. I wonder if anyone has experimented with screens with multiple VMs (e.g. some larger comosables, which are not ‘screens’, having dedicated VMs)? Do you have any experiences to share with this kind of approach or can see why this might not work? How do you deal with the problem of growing VMs in your projects, especially when working with compose?
c
I do have some "screens" which have multiple VMs. For example we're using a non-standard bottom navigation where the bottom nav is not present in all screens. So on the screens where it is present, I have
@Composable fun FooScreen(fooViewModel: FooViewModel, bottomNavViewModel: BottomNavViewModel)
.
However this solution does not go far enough. It only works if the content that the 2 ViewModels render are completely independent. I'm still in search of a way to to make ViewModels composable (not as in Jetpack Compose composable, but to be able to compose together multiple smaller independent ViewModels into one larger one).
One example of this is I have a screen that uses
ModalBottomSheetLayout
. The bottom sheet part itself has quite a lot of logic; and the bottom sheet part is used in from multiple screens. In this situation I did try the the solution of using multiple ViewModels (
@Composable fun FooScreen(fooViewModel: FooViewModel, bottomSheetViewModel: BottomSheetViewModel)
) But this did not work well because the bottom sheet part was not completely independent. For instance, the main content decides whether to show the bottom sheet or not (as opposed to the bottom sheet being a navigation destination). In such cases what I really want is for the
BottomSheetViewModel
to be an independent component; but for a higher level
FooViewModel
to include it as a dependency.
I haven't found a way to achieve this - mainly because of a combination of ViewModel lifetimes and navigation component. I think molecule presenters might help here but I haven't had the time to dig into it.
they still need to take a large number of dependencies (use cases, etc.)
We have this too and I feel it is fine. I don't view it as the VM is doing too many things. Instead, the way I look at is is: • The VM accepts user actions and outputs a state. • For each user action, it delegates to a usecase. • For some presentation-only actions it might update the state itself without going to a UseCase but that's fine In general I view the number of UseCase dependencies for a ViewModel is a function of the number of actions that can occur on that screen
m
Thanks for sharing, good to know we are not the only ones facing those obstacles and toying with those ideas 😄 Reusability of the VMs, for components that are shared between the screens, like the bottom sheet in your example, is something we are after as well. We plan to experiment with creating smaller VMs for some of our composables which we think fit the criteria of being fairly independent and reusable, but complex enough to justify having dedicated VMs. Let’s hope for the best 😄
v
Its possible to create independent controller class or whatever name is more suitable for you, they are jus plain kotlin classes that exposes estate as flow and update these states.
Copy code
//Instead of
viewmodel.state.collectAsState
viewmodel.doSomething
// would be somethinslg like this
viemodel.myIndependentController.state.collectaAsState
viewmodel.myIndependentController.doSomethins
This is a suggestion of use though if you prefer to hide that from the ui you could delegate the calls to the controllers or maybe have some sorte of sealed classes for representing distinct actions that are handled by especific controllers.
y
Thanks for this interesting discussion. Maybe it is my naiv way of thinking, I only pass state and call back to the screen, rather not view model, to avoid recomposition. I also keep the view model as small as possible, and let the activity class or an additional activity state class implementing controller logic to transfer flow state rather than put the controller to the vm.
c
@Yingding Wang do you have one activity per screen? Then your solution might make sense (although I don't see how making your activity a source of truth might scale well). I'm following a single activity, no fragment approach. The activity hardly has any code at all beyond
setContent {MyTopLevelComposable()}
y
@curioustechizen I divided my mobile application to feature groups per activity. And each activity has multiple screens. I either use NavHost or PagerScreen, sometimes also my own controller logic to set the overlay screens on different activities. I use deepLink to move back and forth. I think i put some effort into design the feature groups and also navi structure to have fine control. I moved away from single activity, it also allows me to offload the workload of UI into second process if necessary, sometimes also background/forground process, or coroutine work into second process. I think in most case you don’t need a second process, but sometimes I need some more to compute real time IoT data sequence.
It might be a different design aspect, your application want to follow as I do. The compose declarative screen is a good thing, more composables also means recomposition a lot and it is not cheap. Thus I decided to split the compose tree of my entire app into multiple activities, use the activity rather the screen as a “source of truth” and also to be able to have multiple instances of my app feature groups as distinguished instance of activities to navigate to, should the multitasking with WindowManager proven to be useful in the future.
r
Please forgive the own-horn-tooting, but you might find this talk helpful: https://www.droidcon.com/2022/09/29/navigation-and-dependency-injection-in-compose/