Zygote
App architectureintermediate

The archaeology of app architecture

Every recommended Android architecture is a scar from a specific failure: the God Activity, the fragment lifecycle, the lost state on rotation. Reading the sequence explains why the current advice looks the way it does.

11 minAPI 1–37 · 17interactivequizverified 2026-10-27

Nobody designed the recommended Android architecture in one go. It accumulated, one correction at a time, from the failures of the previous recommendation. Reading it as a history is more useful than reading it as a rulebook.

TL;DR#

  • Era 1 (2008): the Activity does everything — UI, data loading, state, navigation.
  • Era 2 (2011): Fragments promise reuse, and add a harder lifecycle.
  • Era 3 (2014–2016): third-party patterns arrive — MVP, , RxJava, Dagger.
  • Era 4 (2017): Architecture Components — , , LiveData — become the official answer to rotation.
  • Era 5 (2018–2021): AndroidX, Navigation, WorkManager, Hilt, and then Compose make unidirectional data flow the natural shape.
  • Era 6 (2025–): Compose-first, with View libraries in maintenance.1

Mental model#

Think of a city that grew without a plan.

The first buildings went up wherever they fit (the God Activity). Then a grid was imposed (Fragments) and it did not match the buildings. Then architects arrived with theories (MVP, MVVM). Eventually the city published a building code (Architecture Components), and the code was written by people who had watched the earlier buildings fall down.

Where the analogy breaks#

Cities keep their old buildings. Android does not: a screen can be rewritten without moving the users. That is why the architecture keeps changing faster than the platform — the cost of migration is low enough that each generation of advice actually gets adopted.

The big picture#

Loading diagram…
Text description of this diagram

A timeline of Android app architecture from 2008 to today, in three lanes: platform, libraries and recommended practice. The platform lane shows the Activity doing everything in 2008 and Fragments arriving in 2011. The libraries lane shows Volley in 2013, Data Binding in 2015, Architecture Components in 2017, AndroidX and the Navigation component in 2018, WorkManager in 2018, Hilt in 2020 and Compose 1.0 in 2021. The practice lane shows Kotlin support in 2017, Compose being announced in 2019, unidirectional data flow becoming the guide default in 2021, and Compose-first in 2025. Each event is a reaction to the previous era's most visible failure.

Read the lanes as a conversation: the platform made a promise, the libraries tried to keep it, and practice changed when the libraries made a new shape possible.

Deep dive#

Era 1: the God Activity (2008–2011)#

// The shape almost every early Android app had.
public class NotesActivity extends Activity {
    private List<Note> notes;
    private DatabaseHelper db;
 
    @Override protected void onCreate(Bundle state) {
        super.onCreate(state);
        setContentView(R.layout.notes);
        db = new DatabaseHelper(this);                 // data access
        notes = db.loadAll();                          // blocking I/O on main
        ListView list = findViewById(R.id.list);       // view wiring
        list.setAdapter(new NotesAdapter(notes));      // presentation
        findViewById(R.id.add).setOnClickListener(v -> {
            notes.add(new Note("Untitled"));
            db.insert(notes.get(notes.size() - 1));    // business logic
            ((BaseAdapter) list.getAdapter()).notifyDataSetChanged();
        });
    }
}

This is not stupidity; it is what the platform encouraged. Activity had a lifecycle, a context, a window and a callback for everything. There was no state holder that survived rotation, so state lived in the Activity by necessity.

The failure modes are the ones the rest of this module addresses: untestable, unmaintainable past a few hundred lines, blocking the main thread, losing state on rotation.

Era 2: Fragments (2011)#

Fragments were introduced in API 11 for tablets: reusable UI pieces that could be composed into different layouts for different screen sizes.2

They solved reuse and introduced a second lifecycle with more states, more edge cases, and a getActivity() that could return null. The fragment lifecycle is the origin of a whole genre of Android bugs, and the community's eventual answer was to use fewer fragments, not more.

Era 3: imported patterns (2014–2016)#

As apps grew, the community imported patterns from other platforms:

  • MVP — a Presenter holds the logic, the View is an interface. Testable, but the interface boilerplate was heavy.
  • MVVM — a ViewModel exposes observable state, the view binds to it. This is the one that survived.
  • Data Binding (2015) — layout XML can bind to observable variables, so the layout becomes a function of state.
<!-- Data Binding: the layout declares its dependencies. -->
<layout>
    <data>
        <variable name="vm" type="com.example.NotesViewModel" />
    </data>
    <TextView android:text="@{vm.title}" />
</layout>

Data Binding was the first official move toward declarative UI on Android, and it was ahead of its time in a way that hurt: expressions in XML were hard to debug and impossible to unit test.

Era 4: Architecture Components (2017)#

Google's answer was a small set of libraries that solved the specific failures:

ComponentFailure it fixed
Lifecyclemanual register/unregister in every activity
ViewModelstate lost on rotation
LiveDatacallbacks to a destroyed view
Roomhand-written SQLite boilerplate
class NotesViewModel(private val repo: NotesRepository) : ViewModel() {
    val notes: LiveData<List<Note>> = repo.notes.asLiveData()
}
 
class NotesActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val vm: NotesViewModel by viewModels()
        vm.notes.observe(this) { notes -> render(notes) }
    }
}

Two lines did what an entire era of manual lifecycle management had failed to do: the observer is removed automatically, and the state survives rotation.

Era 5: AndroidX, Navigation, Compose (2018–2021)#

AndroidX (2018) renamed and reset the support library, letting every library ship independently. That is what made the rest possible: Navigation, WorkManager, Compose and Hilt could all version separately.

  • Navigation component (2018) modelled navigation as destinations and a graph instead of FragmentTransaction calls.
  • WorkManager (2018) gave deferrable work a guarantee.
  • Hilt (2020) made dependency injection practical for Android components.
  • Compose (2021) made the UI a function of state, which made UDF the natural architecture rather than a discipline.
@Composable
fun NotesRoute(viewModel: NotesViewModel = hiltViewModel()) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    NotesScreen(
        state = state,
        onAdd = viewModel::add,
        onOpen = viewModel::open,
    )
}

Note the shape: state down, events up, one state object. That is unidirectional data flow, and Compose made it the path of least resistance instead of a convention.

Era 6: Compose-first (2025–)#

Android 17 states that new platform and Jetpack APIs target Compose only, and that the View libraries enter maintenance mode.1 The architectural consequence is not "rewrite everything". It is that the destination is now unambiguous: new screens are composables in a navigation graph, state lives in a ViewModel, and the data layer is unchanged.

The pattern that did not survive: the God Fragment#

When activities were split up, the same problem reappeared one level down: a fragment that held the data, the network calls, the adapters and the navigation. The "God Fragment" was the God Activity with a harder lifecycle, and it produced the same class of bug — plus a new one, because a fragment can outlive its view.

// The shape that replaced it.
class NotesFragment : Fragment() {
    private val viewModel: NotesViewModel by viewModels()
 
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val binding = FragmentNotesBinding.bind(view)
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.state.collect { state -> binding.render(state) }
            }
        }
    }
}

Two details in that snippet are the era's real lesson. viewLifecycleOwner instead of the fragment's own lifecycle, because the view can be destroyed while the fragment lives. And repeatOnLifecycle, because the collection must stop when the view is not visible.

What "clean architecture" added#

The Uncle Bob-derived "clean architecture" that circulated in Android circles from around 2016 added a layer vocabulary — entities, use cases, interface adapters — and one durable idea: dependencies point inward. The domain layer should not know about Retrofit or Room.

The official guide adopted the useful half and dropped the ceremony. Its version has three layers and makes the domain layer optional, which is a pragmatic correction: a use case that only forwards a call adds a file and no value.

What every era got right#

Reading the sequence, three ideas survived every rewrite:

  1. Separate state from the view. MVP's presenter, MVVM's ViewModel, Compose's state holder — the name changed, the idea did not.
  2. Keep the data layer independent of the UI. A repository that knows about a TextView cannot be tested.
  3. Tie work to a lifetime. Loaders, LiveData, viewModelScope, repeatOnLifecycle — each is an answer to "when should this stop?"

Those three are the actual content of "Android architecture". Everything else is syntax.

What the eras share#

Every era solved the same underlying problem: the platform's components are created and destroyed by the system, and state must survive that. The solutions changed — retained fragments, ViewModel, SavedStateHandle, Compose's rememberSaveable — but the constraint never did.

That is the useful way to read the history. If a new pattern appears, ask which lifecycle it is trying to survive. If it does not answer that question, it is probably a style preference rather than an architectural advance.

Why the cycle keeps turning#

Every era made the previous era's implicit assumptions explicit and then removed them. The Activity assumed it lived forever; ViewModel made lifetime explicit. Fragments assumed a view existed; Compose made existence a function of state. The next era will make some assumption of ours explicit — probably about synchronisation across devices, or about the boundary between app and AI agent.

How it evolved#

Architecture changes29 releases
APIVersionWhat changed
11.0
  • Initial AOSP platform: Linux kernel, Dalvik VM, core libraries
  • The first zygote-based app spawning model
41.6 Donut
  • Support for different screen densities via resource qualifiers
52.0 Eclair
  • First multi-core-friendly scheduling and graphics work
82.2 Froyo
  • Trace-based JIT compiler added to Dalvik
  • Support for x86 and ARMv7 optimisations
92.3 Gingerbread
  • Concurrent garbage collector added to Dalvik (GC pause reduction)
113.0 Honeycomb
  • Hardware acceleration for the View system (opt-in per app)
133.2 Honeycomb
  • New screen-size resource qualifiers for compatibility scaling
144.0 Ice Cream Sandwich
  • One system image for phones and tablets
  • Hardware acceleration on by default
164.1 Jelly Bean
  • Project Butter: vsync, triple buffering, Choreographer
  • RenderThread architecture begins
174.2 Jelly Bean
  • Multi-user support on tablets
  • SELinux enforcement begins in the kernel (behind the scenes)
184.3 Jelly Bean
  • SELinux enforcement rolled out more broadly
  • Binder changes for restricted profiles
194.4 KitKat
  • Android Runtime (ART) appears as an experimental runtime
  • Kernel moved under a tighter vendor control
204.4W KitKat (Wear)
  • First time the platform shipped for a new form factor
215.0 Lollipop
  • ART becomes the only runtime: ahead-of-time compilation at install
  • 64-bit ARM and x86 support
  • RenderThread and OpenGL ES 3.1
  • Project Volta and JobScheduler
  • SELinux fully enforcing
225.1 Lollipop
  • Device protection ties verified boot to Google accounts
236.0 Marshmallow
  • Runtime permission model changes how every app asks for access
  • Doze batches background work
  • Fingerprint HAL
  • Adoptable storage
247.0 Nougat
  • Project Treble groundwork
  • A/B seamless updates
  • Hybrid JIT + AOT compilation in ART
  • MediaServer split into sandboxed processes
268.0 Oreo
  • Project Treble: the vendor interface separates vendor and system code
  • Background execution limits
  • Broadcast and service restrictions
  • HIDL for HALs
278.1 Oreo
  • Android Go: a lighter configuration of the same platform
289 Pie
  • App Standby Buckets
  • Mainline groundwork
  • Non-SDK interface restrictions (greylists) begin
  • BiometricPrompt
2910
  • Project Mainline: APEX and updatable system modules
  • Scoped storage
  • Bubbles
  • Project Mainline starts shipping through Play
3011
  • GKI: the Generic Kernel Image splits the kernel into core + vendor modules
  • App hibernation
  • Package visibility filtering
  • Incremental app installs
3112
  • Bluetooth permissions split into the nearby-devices family
  • Exact alarm permissions
  • Foreground service launch restrictions
3212L
  • Window size classes and activity embedding
3313 Tiramisu
  • Granular media permissions replace READ_EXTERNAL_STORAGE
  • The notification permission becomes a runtime permission
  • Predictive back gesture
3414 Upside Down Cake
  • Foreground service types are mandatory
  • Selected photos access via READ_MEDIA_VISUAL_USER_SELECTED
3515 Vanilla Ice Cream
  • 16 KB page size support for native libraries
  • Edge-to-edge is enforced when targeting 35
3616 Baklava
  • The release moves to Q2 (June) with a Q4 feature drop
  • Two SDK releases per year starting with 36
  • Desktop windowing arrives in developer options then stable
3717 Cinnamon Bun
  • Strict per-device app memory limits are enforced by the system
  • Generational garbage collection is added to ART's Concurrent Mark-Compact collector
  • A lock-free MessageQueue for apps targeting API 37
  • Compose-first: new platform and Jetpack APIs target Compose only; View libraries enter maintenance mode
  • Hybrid APK signing (v3.2) combines classical and ML-DSA post-quantum signatures
  • Native library dynamic code loading must be read-only

Each row in that table corresponds to an era above. The pattern is that the platform makes fewer promises over time about how an app is structured, and the libraries make more.

Under the hood#

Curiosity corner#

  • The God Activity has a name in the docs: "the Activity does everything" is the anti-pattern the architecture guide opens with.
  • Data Binding's decline was caused by its own success. It proved that declarative UI was the right direction; Compose did it in Kotlin, where it could be tested.
  • ViewModel is not an Android class. It is in AndroidX, which is why it can be used from a plain JVM test with a fake lifecycle.

Try it#

See how large an activity has become

find . -name "*Activity.kt" -exec wc -l ; | sort -rn | head

Count ViewModels in a codebase

grep -rn ": ViewModel()" --include=*.kt . | wc -l

Check yourself#

Check yourself

1 / 12

Why model a screen as one immutable `UiState` rather than several independent `StateFlow`s?

0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. blogAndroid 17 is hereAndroid Developers Blogaccessed 2026-10-27↩
  2. docsJetpackdeveloper.android.comaccessed 2026-10-27↩
  3. docsGuide to app architecturedeveloper.android.comaccessed 2026-10-27↩
  4. docsMigrar para o AndroidX | Jetpackdeveloper.android.comaccessed 2026-10-27↩
  5. docsAndroid 10 features and APIsdeveloper.android.comaccessed 2026-10-27↩
  6. docsFeatures and APIs overview | Android 12developer.android.comaccessed 2026-10-27↩
  7. docsBehavior changes: Apps targeting Android 17developer.android.comaccessed 2026-10-27↩