Search code examples
androidkotlinmvvmandroid-jetpack-composeviewmodel

Viewmodels in android using mvvm and jetpackcompose


I'm new to MVVM and trying to create an application.

The app has 4 different parts: Profile (login - edit Profile and etc) Categories (shows cats and subcats) Items (show all items, single item page, search, etc) Favorites (fav lists, add to fav, delete fav)

My questions are, how do i navigate the UI using jetpack compose ? Do i need to have one main activity and a navigator that handles the whole app pages or Do i need to create 4 activities with 4 different view models to switch between ?

Is that ideal to have 4 different repositories and gather them all in one big view model ?

Also how do i manage to view model's life cycle, should they live inside the activity ?


Solution

  • In Compose apps you usually only have one activity, derived from ComponentActivity. The main purpose of the activity will be to set up the Compose tree with setContent. Your entire UI will be rooted here with nested calls of composable functions. The different parts of your app will all be independent composable functions that must be called here. They are called Screens, although that is just a naming convention.They are just simple composable functions you create.

    To provide a means of navigation between those parts, you will usually use a NavHost. That is a built-in composable that will have no content of its own, instead it will host your screens. You can use a controller to switch which screen should be currently displayed. Screens are supposed to be displayed using the full screen size minus what is needed for the navigation bars.

    Your screen composables will display each of your parts. They can even contain another NavHost if you need a nested navigation when a screen consist of several sub screens. Usually each screen has its own view model associated with it, but it does not need to be only one. The view model should provide all the data that a sceen should display. If you'll only use one view model or multiple ones, or if you even share a view model with different screens is up to you to decide, but each view model should have a meaningful scope of what data it provides. You should probably start out with one view model per screen and go from there.

    Your screens should request an instance of the view models they need. You shouldn't create those yourself, there are compose functions available to do so (namely viewModel()). The easiest way to handle view models is to use a dependency injection framework like Hilt, especially if you need to provide parameters to the view model (then you will use hiltViewModel()). Although created for the first time when a screen needs it, view models have their own lifecycle. View models are not automatically destroyed when your composable leaves the compositon, for example when the user navigates to another screen. This is useful because navigating back to the screen will have the view model provide the previous state. You should let the Android SDK handle the lifcycle of your view models.

    The responsibility of the view model is just to transform data so it can be easily displayed by your screen composables (the business logic, which you might even delegate to a separate domain layer if too complex). The data itself is retrieved from repositories. It is common that a view model uses multiple repositories (again, easiest way to obtain an instance of a repository is to use Hilt). Each repository should provide easy access to retrieve and manipulate the data it holds. Where view models are more UI oriented in what they do, repositories are data oriented. Their main purpose is to provide an agnostic access to the data sources: In an ideal case you could switch out the data source with anonther implementation and only have to adapt the repository; the view model and your compose UI will see no difference. You will want your repositories to provide data as a Flow (that your view model will transform and only in the UI will get collected). In the best case your data sources already provide flows so there won't be much to do for the repository, but maybe the data source is callback based, then your repository would need to encapsulate that with a flow. Repositories may also implement their own caching behavior. Repositories should usually be singletons, meaning there should only be a single instance of each repository class, all view models that need that repository should share the same instance. This also can be easily enforced by using Hilt.

    Google provides various sample projects that you can use to see how they have solved all of this. A good one to start with would be the Sunflower project.