In 2018, Google popularized Single Activity Architecture as the modern way to build Android apps. Like many other teams, we adopted it across our entire codebase.
Eight years later, after running large-scale production apps, we partially reversed that decision.
This article explains why single-activity architecture broke down for us at scale, where multiple activities still make sense, and how a hybrid approach helped us regain stability, modularity, and development velocity.
This isn’t about contradicting Google’s recommendations—it’s about adapting them to the realities of production at scale. Over 12 years of shipping Android apps, I’ve learned that architectural choices age. Patterns that simplify a greenfield app can quietly turn into constraints as the product and organization grow.
Who this article is for: Senior Android engineers, tech leads, and teams maintaining large production apps — not greenfield tutorials or MVPs.
Part 1: Why Single Activity Felt Like the Right Choice
Before we critique it, let's acknowledge why single activity architecture deserved its moment.
Issues We Inherited from the Legacy App
Before adopting a single activity, our app looked something like this:
LoginActivity → RegisterActivity → VerificationActivity → ProfileActivity
HomeActivity → ProductListActivity → ProductDetailActivity
SettingsActivity with Multiple sub-screens
Each screen transition meant:
- A new activity instantiation
- Dozens of intent filters clutter the manifest
- Navigation logic scattered across the codebase
- Fragile back-stack behavior
- Data passed via Bundles, Intents, or (shudder) Singletons
What Single Activity Gave Us
Moving to a single activity immediately delivered:
- Centralized Navigation - One Navigation graph, one source of truth
- Type-Safe Arguments - Safe Args Gradle plugin eliminated Bundle boilerplate
- Simplified Back Stack - Fragment lifecycle became predictable
- Consistent Lifecycle - No more Activity.onCreate() vs Fragment.onViewCreated() confusion
At the time, this felt like cleaning up years of accumulated architectural debt.
For many use cases, this was absolutely the right call:
- Startups apps
- Simple utilities
- Client MVPs
- Small teams with tightly coupled features
But there were trade-offs we didn’t anticipate.
Part 2: Where It Started to Break for Us
1. Deep Linking Becomes Confusing
The Problem
Single-activity architecture centralizes entry points. With multiple activities, any activity can be launched independently. With a single activity, everything funnels through MainActivity.
In a fintech app with:
- Emergency support chat feature
- Real-time notifications
- Payment confirmation screens
- Background authentication flows
This assumption started to fail.
**What we saw in production \ When users tapped a deep link from a notification while the app was notin memory, the navigation stack wasn’t fully initialized.
kotlin
// App process is dead
// MainActivity is created
// Navigation graph builds
// Fragment back stack depends on assumptions that haven't been met
The Result:
- Users landed on payment screens without an authentication state
- Fragment initialization assumptions were violated
- Support tickets spiked
- Debugging took weeks
With multiple activities, each activity was self-contained. A DeepLinkHandlerActivity could exist independently and route users appropriately.
2. Memory Pressure and Task Affinity Lose Flexibility
The Problem
In a multiple activity architecture, you could separate heavyweight features into distinct activities with different task affinity and launch modes:
<activity
android:name=".features.videocall.VideoCallActivity"
android:taskAffinity=".videocall"
android:launchMode="singleTask" />
This allowed:
- The video call persists across app navigation
- Users to toggle between the main app and video call without destroying the call state
- System memory management to treat the video call as a separate concern
What broke
In a Single activity world:
- All features shared the same task and activity lifecycle
- High-memory operations (video encoding, streaming) increased pressure across the entire app
- Unrelated screens became less stable
The solution?
We reintroduced a separate VideoCallActivity.
Outcome: Suddenly, memory profiling improved, and the feature became more stable.
3. Launch Modes and Back Stack Control Become Difficult
The Problem:Some features need non-standard back-stack behavior:
Authentication flow: Should create a new back stack, not integrate into the existing one
onboarding wizard: Should be clearable entirely, not stored in history
Feature modules: Should be launchable independently without requiring going through the main app flow
A single activity makes this hard. You end up either:
- Fighting the framework with hacky fragment back stack manipulation
- Creating pseudo-transactions that don't work with standard Android patterns
- Maintaining parallel navigation graphs that are hard to debug
With multiple activities, intent flags, and launch modes made intent explicit:
<activity
android:name=".auth.LoginActivity"
android:launchMode="singleTask"
android:taskAffinity=".auth" />
This is explicit, maintainable, and respects Android's design principles.
4. Modular Architecture and Feature Isolation Become Awkward
The Problem: Modern Android development embraces feature modules.
With single-activity, every feature had to plug into the main navigation graph:
Feature Module (Payment) → Knows about Main Navigation Graph
Feature Module (Chat) → Knows about Main Navigation Graph
Main App → Coordinates everything
The navigation graph becomes a dependency bottleneck.
With multiple activities:
Feature Module (Payment) → Exports PaymentActivity
Feature Module (Chat) → Exports
ChatActivity Main App → Launches via Intent
launches them via IntentFeature modules that remain loosely coupled, testable, and autonomous.
5. Rotation, Configuration Changes, and Screen-Specific Concerns
The Problem
In a single activity:
- Rotation recreated everything
- One activity coordinated state for 10+ fragments
- Screen-specific behavior leaked everywhere
Some screens needed custom handling:
- Video players
- Drawing canvases
- Chat lists with preserved scroll state
In single-activity, your one activity becomes a god object managing the rotation behavior for dozens of fragments. In multiple activities, VideoPlayerActivity can handle its rotation, and DrawingActivity can handle its rotation independently.
6. Process Death and Restoration Becomes Complicated
The Problem: Android can kill your app process when memory is low. When the user returns, the OS should restore your app's state.
With multiple activities, it's straightforward:
- Each activity's savedInstanceState restores independently.
With single-activity:
- The activity restores
- The navigation graph restores
- Any deserialization failure crashes the app
Isolated activities reduced the blast radius significantly.
Part 3: What We Ended Up Doing Instead
After 3+ years of single activity production experience and 2+ years of refactoring back to a hybrid approach, here's what we now implement:
The Rule: Multiple Activities for Major Feature Boundaries
We use separate activities when a feature:
- Feature has distinct task affinity (video calling, payment processing, authentication)
- Feature has memory-intensive operations (video, camera, document processing)
- Feature needs an independent lifecycle (onboarding, login, dynamic modules)
- Feature is a separate entry point (deep links, notifications, widgets)
Otherwise, we use fragments within a main activity.
Our Architecture Today:
MainActivity
├── HomeFragment
├── ProductListFragment
├── ProductDetailFragment
└── SettingsFragment
AuthActivity
├── LoginFragment
├── RegisterFragment
└── ForgotPasswordFragment
VideoCallActivity
└── Full-screen video UI
PaymentActivity
├── PaymentMethodFragment
├── ConfirmationFragment
└── ReceiptFragment
Benefits We've Measured:
- Memory profiling: showed a clear reduction in memory usage during video calls after isolating the feature in a dedicated activity.
- Crash rates: measurable drop in restoration-related crashes (isolated navigation graphs)
- Deep linking: near-perfect deep link success
- Team velocity: Feature work speed up noticeably, especially for auth and payments, because teams stopped touching the main navigation graph.
- Code maintainability: fewer navigation-related PR discussions
Part 4: Modern Android Frameworks Change the Calculus
Jetpack Compose Is Different
Single activity architecture was optimized for XML layout-based Android development. With Jetpack Compose, the rules change.
@Composable
fun MainApp() {
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen() }
composable("products") { ProductListScreen() }
composable("payment") { PaymentScreen() }
}
}
Compose:
- Centralizes state
- Reduces fragment overhead
- Makes single-activity more viable for new projects
Predictive Back Gesture and Gesture Navigation
Modern Android (15+) emphasizes gesture-based navigation. This actually favors single-activity because gestures operate at the activity level. Multiple activities create gesture inconsistencies across features.
We're watching this space closely.
Part 5: Choosing the Right Architecture for Your Project
Use this framework to decide:
Use Single Activity When:
- App is relatively simple (5-8 max screens)
- Using Jetpack Compose (new projects)
- Deep links are minimal and straightforward
- Features are tightly integrated
- Memory constraints aren't a concern
- Navigation is a simple DAG (directed acyclic graph)
Use Hybrid (Multiple Activities) When:
- App has distinct feature boundaries
- Features have heavyweight operations (video, camera, ML)
- Deep links are complex or numerous
- Separate teams own separate features
- You have dynamic feature modules
- Features need independent deployment or A/B testing
- You need fine-grained back-stack control
My Recommendation for Enterprise:
Use a hybrid architecture with Jetpack Compose:
- Main UI flows: Single activity + Compose Navigation
- Feature modules: Separate activities, each with Compose Navigation
Modular, testable, scalable, and maintain Google's best approaches/practices
Part 6: Real-World Lessons from Production
**Lesson 1: Architectural Decisions Are Reversible (But Expensive):**Refactoring a production app from single to multiple activities took us 4 months and 150+ pull requests. If we had to do a hybrid decision upfront, we'd have saved 6 months of engineering time across the project lifecycle.
Takeaway: Don't blindly follow industry consensus. Understand the trade-offs for YOUR project.
Lesson 2: Fragments Aren't Going Away: You’ll often see claims that single-activity with fragments is outdated. That framing misses the point. Fragments remain a first-class tool for building reusable UI on Android. The debate isn’t about fragments themselves, but about whether every fragment should be hosted by one activity.
**Lesson 3: Profile Before Optimizing:**We spent weeks speculating about memory issues. Profiling showed the video calling feature was the actual culprit. Move one feature to a separate activity, memory restored.
Takeaway: Use Android Profiler early and often. Let data guide architecture.
Lesson 4: Testing Improves With Multiple Activities: Single-activity created test complexity because navigation graph configuration became test boilerplate. Multiple activities simplified testing because each activity tests independently.
Conclusion: The Right Tool for the Job
I've spent 12 years watching Android architecture evolve. Here's what I'm confident about:
Google’s recommendations were well-suited in 2018, when apps were less complex, and Jetpack Compose didn’t yet exist.
Single activity is still correct for many projects (startups, simple utilities, Compose-first apps)
Hybrid is more practical for enterprise apps that have grown organically with various feature needs
There is no one correct answer - only trade-offs
Architecture should serve your product and your team, not ideology.
Single-activity architecture isn’t wrong — it’s just incomplete as a universal rule.
The best Android architecture is the one that helps your team ship reliably, recover gracefully, and evolve without friction.
References & Further Reading
Android Developers Guide to Architecture - https://developer.android.com/topic/architecture
Jetpack Navigation Component - https://developer.android.com/guide/navigation
Modern Activity Lifecycle - https://developer.android.com/guide/components/activities
Jetpack Compose Navigation - https://developer.android.com/jetpack/compose/navigation
Deep Linking Best Practices - https://developer.android.com/training/app-links
Modular Architecture with Feature Modules - https://developer.android.com/training/modular-architecture