According to Netguru, consumer spending on mobile applications is projected to reach $288 billion in 2025, growing to approximately $626.4 billion by 2030. In such a highly competitive and fast-paced environment - particularly in regulated industries like fintech, healthcare, and insurance - architectural mistakes are costly. They result not only in bugs and technical debt but also in compliance violations, security risks, and loss of user trust.
Mobile architecture is no longer just an engineering concern - it’s a strategic product decision that directly impacts development speed and the long-term resilience of the business.
To explore why architecture is a product concern rather than merely code-level issue, and to understand how it helps teams maintain control and predictability as they scale, we spoke with Roman Kamyshnikov, a seasoned Senior Android Engineer at Marshmallow, who has led architectural initiatives in fast-growing fintech and insurtech environments, where stability, velocity, and cross-team alignment are essential.
Beyond his hands-on work, Roman is also an active mentor and speaker at Android Academy Global, as well as a published author on ProAndroidDev and Droidcon, where his technical articles have collectively garnered over 100,000 views. He helps mobile teams implement architectural practices that work not only in theory but in the real world - under pressure and regulation, at scale.
Roman: A good minimally viable architecture (MVA) doesn’t try to predict every future scenario or solve all possible problems - it simply provides enough structure to grow safely. This usually means separating the user interface from domain logic and data access. The domain layer can be thin at first, but boundaries are what matter. This aligns with Google’s recommendations for architecture in Android apps, which suggest using a layered architecture that ensures separation of concerns.
Based on my experience, one approach that has proven effective is splitting every feature into API and implementation modules, even early on. This keeps dependencies clean and prevents build times from growing unnecessarily, especially as teams scale.
It’s not necessary to implement a fully-fledged Clean Architecture from day one, but having a clear structure is essential. For example, in one project modularization was introduced as a reaction to growing complexity between seemingly unrelated features. This happened too late and required significant refactoring, which took months. It could have been avoided with some lightweight boundaries in place from the start.
Clean Architecture helps features define clear internal and external boundaries. This makes inter-feature dependencies explicit and easier to manage, allowing developers to work on different parts of the app without stepping on each other’s toes. This also applies to onboarding - a new team member can quickly start working on an isolated feature without needing to learn the entire codebase. It also enables splitting work across layers: once contracts between layers are defined, multiple engineers can work on the same feature or even the same screen in parallel.
Roman: In any regulated industry, compliance and safety are critical, so it’s not only about delivering features but also about knowing exactly where in the system a change was made and what it might affect.
Jetpack Compose fits these principles well. Because it inherently supports unidirectional data flow (UDF), it naturally promotes the kind of state isolation that Clean Architecture encourages. For example, at Marshmallow, the approach is to pair Compose with a ViewModel for each screen, and in some cases even scope the ViewModel to a reusable component.
The latter is an approach that’s being successfully used by companies such as Skyscanner and enabled by popular libraries like Decompose, Slack’s Circuit and CashApp’s Molecule. It encapsulates business logic, improves reusability, and makes it easier to track down issues when something goes wrong - all of which are essential in a regulated environment.
Roman: You usually feel it before you see it in metrics - features take longer to deliver, pull requests touch unrelated parts of the codebase or modify too many files, and developers start avoiding certain areas because “they’re risky to change.” That hesitation is a clear signal that the architecture isn’t holding up.
At the module level, an easy-to-track signal is the number of dependencies a module has and how many other modules depend on it. Duplicated code is another symptom that’s harder to track automatically, but that eventually becomes noticeable.
Fixes can range from introducing clearer module boundaries to rethinking how responsibilities are split. Sometimes, just untangling one overly complicated area can make a big difference in delivery speed and developer confidence. I believe the “Boy Scout” rule applies here: always leave the code cleaner than you found it.
The architecture uses a modular structure - feature and core modules, each split into API and implementation to provide dependency inversion. Features generally don’t depend on each other directly, which allows for safe parallel development. There is no strict module ownership yet, but as the team grows, it will be easy to switch to this model.
With Compose, this setup works well. UDF is used and typically with one ViewModel per screen, but when needed, logic is isolated in reusable components with their own ViewModel. For example, there’s an error screen driven by the backend that can offer the user different actions depending on the error - from retrying the action, to opening live chat, or navigating to another part of the app via a deeplink. The screen has its own ViewModel, so the action-handling logic is shared and doesn’t have to be duplicated in every feature.
Roman: Currently using Detekt, Android Lint, and custom lint rules to catch issues early. Konsist is being explored as a way to enforce architecture rules at the code level.
Gradle scripts and multi-file templates help scaffold new modules and presentation-layer boilerplate. An internally developed Android Studio plugin automates data and domain layer setup for new endpoints - generating domain models, mappers, repositories, and unit tests. This reduces boilerplate and ensures consistency between features. Even a simple README file describing the project’s architecture and the structure of a typical feature is helpful.
By adopting the code generation tools mentioned above, code that used to take hours to write can be generated in just a few seconds. This gets amplified even more by the savings in review times - automated code does not need to be reviewed as thoroughly because it’s less prone to bugs.
Roman: With AI-generated code, strong architecture and guardrails are even more important because they give structure to what the AI produces. The same goes for server-driven UI: wrapping backend models in clean domain layers helps manage complexity and maintain backwards compatibility.
In cross-platform projects like Kotlin Multiplatform (KMP), the layers might shift slightly, but the principles still apply. From my personal experience, KMP almost enforces clean architecture by default - you need common abstractions, and you’d have to actively work against the separation of concerns between platform and shared code to break it.
Clean Architecture isn’t going away - it will evolve. Its core ideas still solve real problems. But it must be applied leanly and pragmatically: if it feels too heavy, teams will avoid it; if it stays practical, it will remain relevant.
Address:
1855 S Ingram Mill Rd
STE# 201
Springfield, Mo 65804
Phone: 1-844-277-3386
Fax:417-429-2935
E-Mail: contact@appdevelopermagazine.com