Android Activities
In Android development, an Activity represents a single, focused screen with which the user can interact. It is similar to the display of a window in a desktop application or a web page in a browser. Each activity hosts user interface (UI) elements such as buttons, text fields, and lists, and is responsible for displaying content, handling user input, updating the screen, or navigating to other screens. Activities manage their own lifecycle, reacting to system events such as creation, screen rotation, backgrounding, and resumption. They also coordinate with other components of the same app or even with components from other apps to ensure smooth operation and guide the user through different parts of the application.
Activity Lifecycle
Activities have a well-defined lifecycle that consists of multiple stages, managed by lifecycle methods. Proper use of the lifecycle is crucial for managing the app's behavior, especially during transitions between states such as when the app is paused, resumed, or destroyed.
- onCreate(): Called when the activity is first created. Initialize variables, set up the UI, and perform one-time setup tasks. Use the savedInstanceState parameter to restore data if the activity was previously destroyed and recreated (e.g., during a configuration change).
- onStart(): Called when the activity is about to become visible but is not yet interactive. The activity is transitioning from invisible to visible.
- onResume(): Called when the activity is fully visible and interactive, ready for user interaction. Start animations, play music, and acquire resources here, to be released in onPause().
- onPause(): Called when the activity is about to lose focus and go into the background. Stop animations, release resources, and save data as necessary.
- onStop(): Called when the activity is no longer visible. This happens when another activity is launched or when the user navigates away from the current activity.
- onRestart(): Called when the activity is restarting after being stopped. It is followed by onStart().
- onDestroy(): Called when the activity is about to be destroyed. This happens when the user explicitly finishes the activity or the system needs to free up resources.
Lifecycle Sequence Example for Transition Between Activities
A typical lifecycle sequence when transitioning between Activities:
- When Activity A is started, its onCreate() -> onStart() -> onResume() methods are called.
- When Activity B is started (via startActivity()), Activity A goes through onPause() -> onStop().
- When returning from Activity B to Activity A, Activity A goes through onRestart() -> onStart() -> onResume().
Communication Between Activities
Intents: Intents are a core component of Android that allow communication and data exchange between activities, services, broadcast receivers, and other app components.
- Explicit Intents: These specify the exact component (activity) to launch, typically within the same app. Use them to pass data between activities.
- Implicit Intents: These allow the system to choose the appropriate activity to handle the intent based on actions like sending emails or opening URLs.
- Activity Result API: The new API replaces startActivityForResult() and allows handling activity results in a type-safe manner. It also supports non-activity components like Fragments and Composables.
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("key", "Data to Second Activity!")
startActivity(intent)
// In SecondActivity:
val data = intent.getStringExtra("key")
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "Data from app!")
}
startActivity(intent)
// In calling Activity or Fragment
val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val returnedData = result.data?.getStringExtra("responseKey")
// Handle returned data
}
}
// To launch SecondActivity
val intent = Intent(this, SecondActivity::class.java)
launcher.launch(intent)
// In SecondActivity, before finishing:
val resultIntent = Intent().apply {
putExtra("responseKey", "Response from SecondActivity")
}
setResult(Activity.RESULT_OK, resultIntent)
finish()
When sending data between activities and receiving a response, use result codes to indicate success or failure:
- RESULT_OK: Operation was successful.
- RESULT_CANCELED: Operation was canceled or failed.
Communication Between Activities
If sending objects, they must implement either the Serializable or Parcelable interface. Parcelable is strongly preferred on Android for performance reasons. Unlike Serializable, which uses reflection and is slower, Parcelable writes data directly to a Parcel, making it faster and more memory-efficient,
especially important for mobile environments.
In Kotlin, the @Parcelize annotation simplifies implementation and avoids boilerplate code when making a class Parcelable:
@Parcelize
data class User(val id: Int, val name: String) : Parcelable
This object can then be passed via intents:
val intent = Intent(this, ProfileActivity::class.java)
intent.putExtra("user", user)
startActivity(intent)
And retrieved in the target activity:
val user = intent.getParcelableExtra("user")
Data Exchange Between Activities and Regular Classes
From Regular Class to Activity: Directly access an Activity from a regular class by creating an instance or referencing the context, but this approach tightly couples the components. A better approach is to use interfaces or callback functions to send data to the Activity while maintaining modular and testable code.
From Activity to Regular Class: Pass data using constructor parameters, setter methods, or callbacks. Also use singleton objects for global access to data, though this should be done carefully to avoid tight coupling or memory leaks. For reactive communication, tools like LiveData or Kotlin StateFlow/SharedFlow can be used to observe and push data in real time.
Activity Back Stack
Android maintains a back stack of activities in a last-in, first-out (LIFO) order. Pressing the Back button removes (pops) the current activity and returns to the previous one. Developers can influence this behavior using intent flags when launching new activities, allowing control over how activities are created, reused, or cleared from the stack.
Some commonly used intent flags include:
FLAG_ACTIVITY_NEW_TASK: Starts the activity in a new task (a separate back stack) instead of the current one. Useful when launching activities from contexts outside an existing activity, such as from a notification or service.FLAG_ACTIVITY_CLEAR_TASK: When used withFLAG_ACTIVITY_NEW_TASK, clears any existing task that would be associated with the new activity before starting it. This effectively clears the entire back stack of that task, providing a fresh start.FLAG_ACTIVITY_SINGLE_TOP: If the activity being launched is already at the top of the current task's back stack, the system reuses it instead of creating a new instance. This prevents multiple copies of the same activity from stacking up.FLAG_ACTIVITY_CLEAR_TOP: If the activity being launched already exists in the task's back stack, all activities on top of it are cleared (removed), and that existing instance is brought to the front. This helps avoid duplicate activities and resets the UI to a previous state.
Using these flags appropriately allows fine-grained control over navigation flow and back stack behavior, improving user experience and resource management.
Activity Launch Modes
Launch modes, declared in the AndroidManifest.xml via the android:launchMode attribute, define how new instances of an activity behave in relation to the task and back stack. Common modes include:
- standard: A new instance of the activity is always created.
- singleTop: If an instance already exists at the top of the stack, that instance is reused instead of creating a new one.
- singleTask: If an instance exists in the task, it is brought to the front, and other activities above it are removed.
- singleInstance: The activity runs in a separate task and is the only activity in that task.
Activities in Jetpack Compose
In Jetpack Compose, the traditional Android activity model is simplified. Typically, an app has a single main Activity that serves as the entry point and hosts the Compose UI content. Instead of creating multiple activities for different screens, Jetpack Compose encourages the use of composable functions to build and manage UI screens within that main activity.
This approach reduces the need for multiple activity classes. Navigation between screens is handled through Compose-specific navigation libraries (like Navigation
Compose) or state management within composables rather than launching new activities.
While the activity in Compose still manages lifecycle events, it mostly acts as a container or host for composable UI content. This results in simpler app architecture, more modular UI components, and easier state management compared to traditional multi-activity apps.
Multiple activities can still be used if an app requires them (for example, to handle distinct tasks, integrate with system features, or manage different entry points), but the Compose approach primarily focuses on UI composition within fewer activities.
Tweet