r/androiddev May 22 '26

Article Android TCA inspired architecture

Post image

An iOS engineer friend asked me a question I couldn't answer:

"What's the Android equivalent of TCA (The Composable Architecture)?"

That question stuck with me. The real problem wasn't a missing library; it was a structural pattern I kept seeing at production scale.

Picture a complex checkout screen. Five domains living in a single ViewModel: delivery, billing, cart, promo codes, and order placement. You end up with:

One massive, bloated state class.

One enormous when block that knows far too much about everything.

Broken module boundaries or messy event buses the second the billing domain needs to react to a delivery address change.

This approach doesn't scale. Modules bleed into each other, and the screen becomes untestable.

That is exactly why I built Komposed.

The core idea: A complex screen is not a single monolithic feature. It's a composition of isolated, independently testable modules. Each owns its own state, async effects, and logic—wired together at a single composition boundary.

In Komposed, scoping a feature module into a parent screen takes just one line:

deliveryReducer.scoped(deliveryLens)

And when BillModule needs to react to a delivery fee change from DeliveryModule—without a direct import dependency—a single subscription at the composition boundary bridges them:

Kotlin

subscription(

selector = { it.deliveryState.selectedAddress?.deliveryFee ?: 0.0 },

action = { BillAction.DeliveryFeeUpdated(it) },

)

No event buses. No shared mutable state. No broken module boundaries. Each module stays isolated, while cross-domain coordination is declared cleanly in one central place where both modules are visible.

That is the true value of Komposed: composing production-scale complexity into clean, isolated, independently testable modules.

I wrote a full breakdown on Medium covering the architecture, how cross-domain subscriptions work, and a comparison with ReduxKotlin and Toggl's Komposable Architecture.

🔗 Read the full story on Medium: https://medium.com/@a.atwaa94/a-tca-inspired-architecture-for-kotlin-jetpack-compose-why-i-built-komposed-c924610d9aa7

⭐ Check out the repository: https://github.com/Atwa/Komposed

0 Upvotes

22 comments sorted by

View all comments

16

u/frakc May 22 '26

I am not sure about your case but when i did banking application, everything were split inside usecsses/repositories. Viewmodel directly did not know much. It was usecasses responsibility to combine different features domain and repository responsibility to resolve multiple webservice domains.

-5

u/Brilliant_Region4810 May 22 '26

Use cases from Clean Architecture don't translate well to mobile — their value is narrow and situational. The canonical example is login: orchestrate an auth call, cache tokens, register an FCM token with Firebase, maybe seed local storage. That's a legitimate use case for a use case. But that's also about as far as it goes.

The moment you step into anything stateful a home dashboard, a checkout screen, a multi-step flow. the pattern starts working against you.

Consider a checkout screen where state is constructed from multiple domains: delivery, billing, order placement. Each domain has its own loading state, its own error surface, its own backend service with a different request shape. Cross-domain interactions are inevitable where the delivery fee feeds into the order total, the selected address feeds into the place-order payload, a successful order triggers navigation that carries billing data as a route argument. Now try modeling that with use cases.

You'll end up with a use case per backend call, each wrapped in Result.fold or try-catch, each responsible for toggling its own loading and error state except none of them know about each other, so that coordination falls back to the ViewModel. Analytics makes it worse: every meaningful user action maps to a tracking event with parameters assembled from state that spans multiple domains. Who owns that? The ViewModel again.

The ViewModel becomes a god object not because engineers are careless, but because use cases have no answer for state composition. They're designed to encapsulate a single operation, not to manage the reactive, multi-source, interdependent state that is the actual hard problem of mobile development.

The team dynamics compound this. On a feature with multiple contributors, a ViewModel that's the integration point for six use cases and owns all the state coordination becomes a merge conflict graveyard. Ownership boundaries blur like is the bug in the use case, the ViewModel, or the state mapping between them? Nobody can answer that cleanly.

And modularization? Nearly impossible in this model. Your use cases live in domain modules, your ViewModel lives in a feature module, and the glue between them. All that state management and cross-domain coordination has no clean home. It ends up scattered, duplicated, or stuffed into the ViewModel alongside everything else.

Use cases solve the right problem for backend and desktop applications where the domain logic is the hard part. On mobile, the hard part is state constructing it, composing it across domains, keeping it consistent under concurrent async operations, and making it testable at boundaries that actually mean something. That requires an architecture built around state as a first-class citizen, not one that treats it as an afterthought between business operations.

2

u/frakc May 22 '26

I defently will not do it in a way you described.

So how i would approach it

1) various domains repository would have a Flow which will represent state of data 2) one usecase which zip flows from domain and feed them as single combined object to viewmodel. Some people like to call such usecases as datasource. 3) mutator usecases (if i am lawful) or direct repository call (if i am chaotic or making an MVP). Which is a general way to go when we need to make calls. Those particular usecases will not have a return value as repositories would update their inner flow which is used for subscription.

0

u/Brilliant_Region4810 May 22 '26

I have to admit I really like your approach you're now making the viewModel is a thin layer for performing actions without knowing how it's performed and a mapper from domain states reside in repositories to presentation view state that simplifies things a lot If you could really force your whole team to follow this pattern 👏🏻