Android App Development

Chapter 5
Activities, Navigation and Application Lifecycle

Certificate in Web and Android Development
Android App Development   •   Certificate

Activities, Navigation and Application Lifecycle

Introduction

An Android application is not a single continuous screen. It is a collection of components that cooperate with the Android operating system. The most visible component is the activity, which usually represents one focused user interaction, such as reading a message, editing a profile, or selecting a payment method. Activities are connected through navigation, communicate using intents, receive input through arguments, and move through lifecycle states as users and the operating system change the application’s context.

Understanding these mechanisms is essential because Android may create, stop, destroy, or recreate application components without the programmer explicitly requesting it. A robust application therefore does not assume that a screen remains in memory indefinitely. Instead, it separates temporary user-interface state from durable application data and performs work in lifecycle-aware ways.

This chapter uses Kotlin and modern Android concepts, including Jetpack ViewModel, SavedStateHandle, and lifecycle-aware observation. The principles also apply when an application uses XML layouts rather than Jetpack Compose.

Learning Objectives

By the end of this chapter, you should be able to:

  • explain the purpose and lifecycle of an activity;
  • distinguish explicit and implicit intents;
  • pass primitive and structured arguments safely between screens;
  • design predictable navigation and back-stack behaviour;
  • preserve interface state during configuration changes and process recreation;
  • identify suitable lifecycle callbacks for common operations;
  • use ViewModel and lifecycle-aware APIs to avoid data loss, leaks, and duplicated work;
  • evaluate navigation designs in a practical application scenario.

Activities as Android Application Components

An activity is an entry point for a user-facing interaction. It owns a window and provides a context in which views, Compose content, menus, and navigation operations can be displayed. An application may contain one activity or many. Modern applications often use a single activity with a navigation framework, but the activity lifecycle remains important in either design.

Activities are declared in AndroidManifest.xml, either directly or through tools that generate or manage navigation destinations. An activity must be exported only when it should be launched by another application or by the system. For example, a launcher activity is normally exported because the launcher must start it; an internal detail activity generally need not be exported.

An activity should coordinate screen-level concerns rather than become a repository for all application logic. Network access, database operations, and business rules should normally be placed in appropriate layers such as repositories and ViewModels. This separation improves testing and reduces the risk that temporary screen destruction will lose important data.

The Activity Lifecycle

The principal callbacks are onCreate(), onStart(), onResume(), onPause(), onStop(), and onDestroy().

CallbackTypical meaningSuitable responsibilities
onCreate()Activity instance is being createdInflate UI, read arguments, establish one-time screen setup
onStart()Activity becomes visibleRegister visibility-related observers
onResume()Activity is ready for interactionResume foreground interaction, such as a camera preview
onPause()Activity is losing focusPause short-lived foreground work; save lightweight state
onStop()Activity is no longer visibleRelease visibility-dependent resources
onDestroy()Instance is being destroyedFinal instance cleanup; do not rely on it for durable saving

A common forward navigation sequence is:

onCreate → onStart → onResume

When another activity covers the current one, the current activity commonly receives:

onPause → onStop

When the user returns, it commonly receives:

onRestart → onStart → onResume

These sequences are not a universal script. A dialog-like window may cause onPause() without onStop(), and the system may destroy a stopped activity to reclaim memory. The important design rule is to treat callbacks as state transitions, not as guaranteed once-only events.

onCreate() may run again after a configuration change, such as a rotation, locale change, font-scale change, or theme change. The activity instance is replaced because its resources may need to be reconstructed. A configuration change is therefore not necessarily an application failure.

Intents: Requests for Action

An intent is a message describing an operation to be performed. It may identify a component explicitly or describe an action that another component can handle.

Explicit intents

An explicit intent names the target component. It is appropriate for navigation within the application:

val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("ITEM_ID", itemId)
startActivity(intent)

The receiving activity can obtain the argument:

val itemId = intent.getStringExtra("ITEM_ID")

In production code, argument keys should be centralized and validated. A missing or malformed identifier should lead to a safe error state, not an application crash.

Implicit intents

An implicit intent describes an action without naming a particular component. Android resolves it using installed applications and their intent filters:

val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://developer.android.com"))
startActivity(intent)

Because another application may not be installed or may not support the action, robust code checks availability or catches ActivityNotFoundException. Implicit intents are useful for opening web pages, sharing content, selecting files, taking photographs, and sending email.

Intent data may include an action, data URI, MIME type, categories, and extras. These elements should be used consistently. Sensitive information should not be placed casually in extras or URI strings because intents can cross application boundaries.

Screen Arguments and Result Communication

Navigation arguments define the input required by a destination. A good argument is small, stable, and sufficient to identify the required data. Passing an itemId is generally better than passing a large mutable object. The destination can retrieve current data from a repository, avoiding stale or oversized serialized state.

For structured arguments, Android supports types such as Parcelable and Serializable, but serialization has performance and compatibility costs. Navigation Component arguments or typed route systems can provide safer contracts. Regardless of framework, validate arguments at the destination boundary.

A screen may return a result to its caller. Modern Android commonly uses the Activity Result API:

private val selectImage =
    registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
        if (uri != null) viewModel.imageSelected(uri)
    }

fun chooseImage() {
    selectImage.launch("image/*")
}

This approach avoids manually managing integer request codes and integrates more reliably with lifecycle events.

Navigation and the Back Stack

Android maintains a task containing a back stack of activity instances. Starting a new activity normally places it on top of the stack. Pressing Back finishes the top activity and reveals the previous one. If the stack becomes empty, the task ends or the user returns to the preceding task.

For example, navigation from Home to List to Detail produces:

Home → List → Detail

Back from Detail returns to List; Back from List returns to Home.

This default behaviour is usually the most understandable. Altering it requires a clear user-centered reason. Intent flags such as FLAG_ACTIVITY_CLEAR_TOP, FLAG_ACTIVITY_SINGLE_TOP, and FLAG_ACTIVITY_NEW_TASK change stack behaviour and can create surprising results if used without a precise model.

  • SINGLE_TOP reuses the top instance if it already has the requested component; the existing instance receives onNewIntent().
  • CLEAR_TOP removes activities above an existing target in the same task.
  • NEW_TASK affects task selection and is particularly relevant outside ordinary activity-to-activity navigation.

When an existing activity receives a new intent, its original onCreate() is not called again. The developer must handle the new data in onNewIntent() and update the screen appropriately.

With the Navigation Component, destinations are represented in a navigation graph, and a NavController manages movement between destinations. The framework supports destination arguments, deep links, nested graphs, and controlled back-stack operations. The conceptual principles remain the same: define a clear destination contract, avoid duplicate destinations, and make Back return the user to the expected context.

Configuration Changes and State Preservation

Configuration changes can recreate an activity. There are two different preservation problems:

  1. UI state, such as text currently typed into a field, selected tab, or scroll position.
  2. Application data, such as a user profile, order, or downloaded record.

UI state can often be saved in the instance state mechanism. Application data should be stored in a database, repository, or other durable source. A ViewModel survives configuration changes because it belongs to the activity’s logical scope rather than to one activity instance. It does not, however, automatically survive process death.

A simple ViewModel example is:

class DetailViewModel(private val savedState: SavedStateHandle) : ViewModel() {
    val itemId: String = checkNotNull(savedState["ITEM_ID"])

    val item = repository.observeItem(itemId)
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = null
        )
}

The exact repository implementation is omitted, but the design illustrates an important principle: the ViewModel owns screen data and survives recreation, while the activity renders the current state.

For small values that must survive process recreation, use SavedStateHandle or onSaveInstanceState(). For example, a draft identifier or selected filter may be saved, whereas a complete database should not be placed in a bundle. Saved state is limited and should not be treated as a general persistence layer.

Avoid using android:configChanges merely to prevent recreation. It can be appropriate in specialized cases, but it transfers responsibility for resource and state updates to the developer and may conceal design errors.

Lifecycle-Aware Operations

A lifecycle-aware application starts work when the relevant lifecycle is active and stops or suspends it when the lifecycle no longer permits safe interaction.

For example, collecting a Flow should normally be tied to the view or activity lifecycle:

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { state ->
            render(state)
        }
    }
}

repeatOnLifecycle() starts collection when the lifecycle reaches the selected state and cancels the collection when it falls below that state. This prevents updates from being delivered to an invisible screen and reduces leaks.

The lifecycle owner matters. In a Fragment, view binding belongs to the Fragment’s view lifecycle, which can end before the Fragment itself. Observers that update views should therefore use viewLifecycleOwner, not blindly use the Fragment as the owner.

Long-running work should not be tied to an activity if it must continue after the screen disappears. WorkManager is suitable for deferrable, persistent work such as synchronizing records. A foreground service may be appropriate for visible, ongoing user-approved work such as navigation tracking, subject to current Android restrictions. Coroutines in viewModelScope suit screen-related asynchronous work, but they are cancelled when the ViewModel is cleared.

A Practical Navigation Pattern

A maintainable screen commonly follows this sequence:

  1. The caller navigates with a minimal, validated identifier.
  2. The destination reads the identifier once and gives it to a ViewModel.
  3. The ViewModel obtains data from a repository.
  4. The UI observes state while started or resumed.
  5. User actions call ViewModel methods rather than directly embedding business rules in the activity.
  6. Temporary UI state is saved; durable changes are committed to a repository.
  7. Back navigation returns to the prior logical destination unless the task has a documented alternative.

This arrangement prevents a common error: loading data directly in onCreate() and losing it when the activity is recreated. The activity may be recreated many times, but the ViewModel and repository provide continuity.

Illustrative Case: Campus Course Registration

Background

Consider a fictional certificate-learning application called CampusConnect. It contains a course list, a course-detail screen, and a registration confirmation screen.

Problem

The initial implementation passes the entire Course object through an intent. On rotation, the detail activity reloads the course and starts a second network request. When the user presses Back from confirmation, the course list is unexpectedly shown twice. Finally, a registration request sometimes continues to update a destroyed activity.

Analysis

The design confuses screen instances with application data. A mutable object passed through an intent may become stale, while repeated requests arise because data loading is attached directly to onCreate(). The duplicated list results from starting a new list activity rather than returning to the existing one. The update error occurs because asynchronous work is not lifecycle-aware.

Approach and Solution

The revised design passes only courseId. A CourseDetailViewModel receives this identifier through SavedStateHandle and observes the repository. The activity renders the ViewModel state using repeatOnLifecycle(STARTED). Registration is performed in viewModelScope, and the repository records the result durably. After registration, the confirmation screen finishes rather than starting a new course-list activity. The navigation graph defines a single list destination and a detail destination, while the Back stack is allowed to operate normally.

The application also handles a missing course identifier by showing an error screen. It does not expose internal activities unnecessarily, and it uses an Activity Result contract when a document must be selected.

Outcome

Rotation no longer causes duplicated loads, registration results remain available after recreation, Back returns to the expected list, and screen observers are cancelled when the activity is no longer visible. The design is simpler because each component has a clearly defined responsibility.

Learning Points

  • Pass stable identifiers rather than large mutable objects.
  • Treat ViewModel state as the source for screen rendering.
  • Let the back stack represent user history unless there is a documented reason to change it.
  • Tie observation to lifecycle state.
  • Store important data outside the activity instance.

Challenges, Limitations and Best Practices

Lifecycle programming has unavoidable complexity. Process death can occur while an application is in the background; no activity callback should be treated as a guarantee that all data will be saved. Multiple windows, deep links, notifications, and external intents can create entry points that bypass the normal home-screen flow. Testing must therefore include cold starts, rotation, background restoration, deep links, missing arguments, and repeated Back operations.

Security is also relevant. Exported components and intent filters form an application’s externally reachable surface. Validate all incoming extras and URIs, avoid trusting caller-provided paths, and use appropriate permissions. Do not expose private data through implicit sharing without user awareness. For sensitive operations, confirm the destination and consider whether an external application should receive the data.

Recommended practices include:

  • keep activities focused on presentation and coordination;
  • use explicit intents for internal navigation;
  • validate every navigation argument;
  • prefer typed navigation contracts where available;
  • use ViewModel for screen state and repositories for durable data;
  • use repeatOnLifecycle() or equivalent lifecycle-aware APIs;
  • avoid expensive work in lifecycle callbacks on the main thread;
  • test recreation and process restoration rather than testing only the ideal path;
  • document intentional task and back-stack changes;
  • keep exported components and permissions minimal.

Contemporary Android Direction

Android development increasingly favours declarative UI, single-activity architectures, navigation graphs, unidirectional data flow, and lifecycle-aware state collection. These trends reduce some activity-management code, but they do not eliminate lifecycle concerns. A Compose screen can still leave composition, a navigation destination can still be removed, and the process can still be killed. The durable principles remain state ownership, explicit contracts, safe restoration, and correct cancellation.

AI-assisted development tools can help generate navigation boilerplate or test cases, but generated code must be reviewed for incorrect lifecycle scopes, unsafe exported components, missing argument validation, and inappropriate persistence. Responsibility for privacy, security, and correct user experience remains with the developer.

Chapter Summary

Activities represent focused user interactions and move through lifecycle states controlled by Android. Explicit intents support internal navigation, while implicit intents request actions from capable components. Arguments should be minimal, validated, and preferably represented by stable identifiers. The back stack normally models navigation history, and flags should change it only deliberately.

Configuration changes recreate activity instances, so temporary UI state, screen state, and durable application data must be treated differently. ViewModel preserves screen state across configuration changes; repositories and databases preserve application data; SavedStateHandle supports small values during process recreation. Lifecycle-aware collection and scoped asynchronous work prevent leaks, stale updates, and duplicated operations. Good architecture connects these ideas through clear navigation contracts and explicit ownership of state.

Key Terms

Activity: Android component that provides a user-facing interaction surface.

Lifecycle: Sequence of states and callbacks through which a component moves.

Intent: Message describing an action or identifying a component to launch.

Explicit intent: Intent naming a specific target component.

Implicit intent: Intent describing an action without naming a target component.

Extra: Key-value data attached to an intent.

Activity Result API: Lifecycle-aware mechanism for receiving results from launched contracts.

Back stack: Ordered collection of activity or navigation destinations representing user history.

Task: Collection of activities grouped for a user task.

Configuration change: Environmental change that may cause activity recreation.

Instance state: Small temporary state saved for recreation.

ViewModel: Lifecycle-scoped holder of screen-related state and logic.

SavedStateHandle: Key-value mechanism for restoring selected state after process recreation.

Repository: Abstraction responsible for obtaining and storing application data.

Deep link: Link that opens a particular application destination.

Lifecycle owner: Object whose lifecycle controls observation or work.

repeatOnLifecycle: API pattern that starts and cancels collection according to lifecycle state.

Exported component: Component available for launch by other applications or system components.

Review Questions

Short Answer Questions

  1. What is the primary purpose of an activity?
  2. Distinguish an explicit intent from an implicit intent.
  3. What normally happens when the user presses Back on the top activity?
  4. Why should a developer avoid treating onDestroy() as the main place for durable saving?
  5. What problem does repeatOnLifecycle() help prevent?

Long Answer Questions

  1. Explain the main activity lifecycle callbacks and give one suitable responsibility for each.
  2. Describe how configuration changes affect activities and compare the roles of ViewModel, SavedStateHandle, and a repository.
  3. Explain how an Android back stack is formed and discuss the effects of SINGLE_TOP and CLEAR_TOP.
  4. Design a navigation contract for a course-detail screen, including arguments, validation, data loading, and restoration.
  5. Discuss security and privacy risks associated with exported components and implicit intents.

Analytical / Application Questions

  1. An activity starts a network request in onCreate() and updates a view after rotation. Diagnose the likely problems and propose a lifecycle-aware design.
  2. A user navigates Home → List → Detail → Confirmation, but pressing Back from Confirmation opens a second List screen. Explain the cause and propose a correction.
  3. Design a test plan for an application that uses deep links, saved state, ViewModel, and external document selection. Identify at least five lifecycle or navigation cases to test.

Multiple Choice Questions with Answers

Question 1

Which callback is normally reached when an activity becomes ready for user interaction?

A. onCreate()

B. onStart()

C. onResume()

D. onDestroy()

Correct Answer: C. onResume()

Question 2

Which intent is most appropriate for launching a known activity within the same application?

A. An explicit intent

B. An implicit intent

C. A broadcast-only intent

D. A permission intent

Correct Answer: A. An explicit intent

Question 3

What usually happens when the user presses Back while Detail is the top activity in Home → List → Detail?

A. Home is destroyed immediately

B. Detail is finished and List is revealed

C. A new Detail activity is created

D. The application process is always killed

Correct Answer: B. Detail is finished and List is revealed

Question 4

Which component is designed to retain screen-related state across configuration changes?

A. BroadcastReceiver

B. ViewModel

C. Manifest

D. Intent filter

Correct Answer: B. ViewModel

Question 5

Why is passing an item identifier often preferable to passing a complete mutable object?

A. Identifiers disable lifecycle callbacks

B. Objects cannot be used in Kotlin

C. The destination can obtain current data and transfer less state

D. Identifiers automatically encrypt the data

Correct Answer: C. The destination can obtain current data and transfer less state

Question 6

What is the main purpose of repeatOnLifecycle()?

A. To permanently prevent activity recreation

B. To start and cancel collection according to lifecycle state

C. To add an activity to a new task

D. To serialize a database

Correct Answer: B. To start and cancel collection according to lifecycle state

Question 7

Which intent flag can reuse the existing top instance when the requested activity is already on top?

A. FLAG_ACTIVITY_SINGLE_TOP

B. FLAG_ACTIVITY_CLEAR_TASK

C. FLAG_ACTIVITY_NEW_DOCUMENT

D. FLAG_ACTIVITY_NO_HISTORY

Correct Answer: A. FLAG_ACTIVITY_SINGLE_TOP

Question 8

Which storage mechanism is most appropriate for durable application records such as registered courses?

A. A local variable in the activity

B. The activity back stack

C. A repository backed by persistent storage

D. The onPause() call stack

Correct Answer: C. A repository backed by persistent storage

Question 9

What should an application do when an expected navigation argument is missing?

A. Assume a default user identity

B. Crash silently

C. Validate it and show a safe error or recovery state

D. Expose the activity to all applications

Correct Answer: C. Validate it and show a safe error or recovery state

Question 10

Why can an implicit intent fail at runtime?

A. Android never resolves implicit intents

B. No installed component may support the requested action

C. Implicit intents always require a database

D. Implicit intents cannot contain data

Correct Answer: B. No installed component may support the requested action

Android App Development
Chapter 5 of 15