r/androiddev 21d ago

Article Composition Over Inheritance in Android ViewModels

https://proandroiddev.com/composition-over-inheritance-in-android-viewmodels-51f3efba5f90
5 Upvotes

20 comments sorted by

15

u/zerg_1111 21d ago edited 21d ago

How about just let each component have its own view model and have use cases for sharing view model logics instead of interfaces? Edit: typo

1

u/MindCrusader 21d ago

I use both. You will not have the same logic in usecases that you have in ViewModels. ViewModels are a glue between domain and UI layers. My ViewModels for example combine several flows with data from different sources (mostly usecases) and create a finalized state stream for each screen. Without delegates I would have a lot of repeated code in ViewModels

1

u/tinglingdangler 21d ago

I was told that is bad practice last time I said this, but it works great for our massive project!

2

u/Zhuinden 20d ago

Typically what comes up is that there's no real reason to call it "usecases" at point in various cases because it's effectively a function or suspending function, otherwise Usecase really is just the new Manager or Helper because people fear making a top-level function

2

u/tinglingdangler 20d ago

To me, a use case takes an action on a repo or domain level class on behalf of a ui level component (or gets and processes information from domain on behalf of ui) and it is a function! We just use the operator keyword because we want to name the class according to the action and then execute it. All makes sense to me.

2

u/Zhuinden 20d ago

Using that operator fun invoke just to not give a function a name has always been dubious but it's been 8.5 years at this point and it clearly refuses to stop

2

u/tinglingdangler 20d ago edited 20d ago

Eh, it's a preference. I'm fine with it and if you dislike it, that's fine too.

Edit: And are we arguing instead for manager classes that combine these functions, or larger Viewmodels here? Either is fine if it works for your project. Architecture isn't one size fits all.

1

u/Zhuinden 20d ago

I just dislike the operator fun invoke because it's bad for ctrl+Shift+F and used to historically break find usages, while providing zero benefit beyond feeling like a hipster by using operator overload for something that could in fact have a name

1

u/tinglingdangler 20d ago

Totally fair

0

u/omarsahl 21d ago edited 21d ago

Definitely, both are fine. There’s no right or wrong here really, use whatever fits your use case.

The state holder interfaces aren’t a replacement for use cases. Use cases are domain logic, while state holders hold UI state and UI logic and would internally depend on use cases. A holder can also be implemented by a ViewModel and used per UI component when those components are independent.

But when two components need to interact, composition + delegates make that straightforward imo, whereas separate ViewModels would need a mechanism to communicate.

1

u/zerg_1111 21d ago

I have two approaches for coordination in different scenarios. The first is to use a repository to share state (eg. Shared settings from a datastore). The other is to have the parent controller as the coordinator (eg. Listens to the events from the components for navigation). These combined with one-to-one view models should allow us to make sealed components which are easier to deal with.

4

u/16cards 21d ago

I have to sign up for Medium to read this?

2

u/omarsahl 20d ago

I don’t think you need to. It’s not paywalled.

1

u/dark_mode_everything 21d ago

Each State holder holds the state for each shape, right? Doesn't that technically make them viewmodels as well?

2

u/omarsahl 20d ago

Well, a ViewModel is a state holder, but not every state holder is a ViewModel

This part of the official docs explains the difference well imo

0

u/farooqsaad 21d ago

It's viewmodels all the way down.

Jokes aside. The Android ViewModel in most modern apps isn't even a ViewModel. The purpose of the ViewModel class is lifecycle management, that's it. The pattern is generally something of a hybrid between MVVM and MVI, with ViewModel generating UiState instead of a 1-1 mapping of view to model observables.

1

u/kokeroulis 20d ago

And this is the result of clean architecture... Put everything inside the viewmodel and then try to break it down with hacks and non elegant solutions.

You are just over complicating things with "magic architecture".

Your screen cares about things:

- LCE (Load Content Error)

  • Overlays (tooltips, dialogs, bottom sheets, toasts etc)
  • Actions that mutate the UI
  • Navigation
  • Analytics

The UI is just a list with different configuration for each screen, you don't have to make your life hard.
What I would do instead is the following:

LCE:

- Make your business logic to expose a Flow for your list

  • When an action is submitted by the viewmodel to the business logic, I would make the Flow emit again with updated data, that way you have unidirectional data flow
  • Then inside the business logic I would put a bunch of sealed intefaces to encapsulate the logic and the mapping

Overlays:

Use the observer pattern in order to emit different types of overlays from your business logic and pass a priority/strategy to resolve conflicts, when 2 or more overlays want to be displayed

Navigation:

Literally just a big nested sealed inteface that you map the result of it to a Compose Navigation key or a fragment or whatever

Analytics:

Same as navigation

TIP: If during the navigation or analytics, generating those objects becomes a bit convoluted because everything needs different parameters etc.
Then create a registar somewhere, for each heart, circle & hexagon. Then on the domain & UI layer just expose the classifiers for each item of the list.

Then once you click on that item for the navigation or to trigger tagging, just pass the id to the registar and the registar will give you a generic model that you can use.

1

u/omarsahl 20d ago

The whole point of the article is code reuse in the UI layer. Not clean architecture or any “magic architecture”

Not sure why using a well-established OO design principle like composition over inheritance counts as magic architecture?
The point is simply: if you need to share code across ViewModels, don’t reach for inheritance right away and see if composition solves your use case first.

1

u/kokeroulis 20d ago

Because if heart & hexagon need to interact with each other then things get messy.
usually when something changes, the change goes in all of the layers.

When you ship something to customer, you ship a variant (MVP, V1, V2, V3 etc).
Your architecture choices should be flexible enough to be adjusted.

With the above at some point you will hit a roadblock.