Developing applications for Android is impossible without understanding the life cycle of components and managing the transition between them. Activity (activity) is a single screen with a user interface with which the user can interact. Successful navigation within an application is not just a matter of visual switching, but a complex architecture that requires the correct use of Intent and flags. If you are new to development or want to brush up on navigation, this material will be a comprehensive source of information for you.
At the heart of any transition is an object Intent. This is a special class that describes the action to be performed and contains data for its implementation. You can use it to start a new activity, open a service, or send a broadcast message. Understanding how exactly Android system processes these requests is critical to creating stable applications.
Incorrectly managing the activity stack can lead to memory leaks, duplicate screens, or unpredictable app behavior when you press the back button. In this article, we will examine in detail the switching mechanisms, methods of transferring data between screens, and the subtleties of setting up the task stack. Ready to dive into code?
Navigation Basics: Explicit and Implicit Intents
Before writing the transition code, you need to determine the type Intentthat you will use. There are two main types of intents in Android: explicit and implicit. Explicit intent is used when you know exactly the class of the target activity. This is a standard scenario for internal navigation of your application, for example, moving from the login screen to the main menu.
To create an explicit intent, you need to pass the context of the current screen and the class of the target component. The Android system immediately understands which component needs to be launched. This is the fastest and safest way to navigate within an application.
โ ๏ธ Attention: Never try to use implicit intents to call internal activities in your application unless you have a good reason. This may result in a third-party application intercepting your intent and executing its own activity instead of yours.
Implicit intents work differently. You don't specify a specific class, but rather describe the action that needs to be performed, for example, โopen a web pageโ or โcall a number.โ The system analyzes all installed applications and prompts the user to select the appropriate one. This is useful for interacting with the outside world, but not for internal logic.
Here is what the code for an explicit switch looks like:
Intent intent = new Intent(CurrentActivity.this, TargetActivity.class);
startActivity(intent);
Use Context here is mandatory. Typically, the context is the activity itself (this) or the application context (getApplicationContext()). However, to launch an activity, it is preferable to use the activity context so that the new task is correctly associated with the current one.
Use the Intent(Context packageContext, Class cls) constructor for explicit calls - this ensures that the system launches exactly the class you specified, without additional checks.
Passing data between screens
Often, when moving to a new screen, it is necessary to transfer certain data there: user ID, product name, or calculation result. To do this in the object Intent special methods are provided putExtra(). The data is packaged as key-value pairs and delivered to the target activity.
You can pass primitive data types (int, boolean, float), strings, and even objects that implement the interface Serializable or Parcelable. The latter option is preferable for complex objects, as it is much faster and more efficient in terms of memory usage.
Example of passing a string and an integer:
intent.putExtra("USER_ID", 12345);
intent.putExtra("USER_NAME", "Alex");
In the receiving activity, you should retrieve this data in a method onCreate() or onStart(). It is important to check for the presence of data to avoid application crashes (NullPointerException). Use the methods getIntExtra() or getStringExtra(), specifying the default key and value.
Here is a table of the main methods for retrieving data:
| Data type | Retrieval method | Value default |
|---|---|---|
| String | getStringExtra() |
null |
| int | getIntExtra() |
0 (or your value) |
| boolean | getBooleanExtra() |
false |
| Parcelable | getParcelableExtra() |
null |
If you are transferring large amounts of data or complex object graphs, make sure they do not exceed the Binder transaction limit (usually about 1 MB). Exceeding this limit will throw an exception TransactionTooLargeException.
What to do if you get a TransactionTooLargeException error?
If you encounter this error, it means you are trying to pass too much data through the Intent. Solution: save the data to a database (Room, SQLite) or static storage (Singleton), and through Intent pass only a link or ID to receive it.
Managing the activity stack (Task Affinity and Flags)
Android stores activities in the stack, following the LIFO (Last In, First Out) principle. When you open a new activity, it is placed on top of the current one. When the back button is clicked, the top activity is destroyed and the user returns to the previous one. However, the default behavior is not always suitable for complex navigation scenarios.
Flags are used to control the behavior of the stack. The most popular is FLAG_ACTIVITY_NEW_TASK. It forces the system to create a new task (Task) or find an existing one with the same affinity and place the activity there. This is often used when launching an application from a widget or notification.
Another important flag is FLAG_ACTIVITY_CLEAR_TOP. If the activity you are launching already exists in the current task, then all activities above it on the stack will be destroyed. This activity will become the top one and will receive a new intent in the method onNewIntent().
- ๐ FLAG_ACTIVITY_NEW_TASK: Starts an activity in a new task, taking it out of the current context.
- ๐งน FLAG_ACTIVITY_CLEAR_TOP: Clears the stack to the specified activity, preventing duplicate screens.
- ๐ FLAG_ACTIVITY_SINGLE_TOP: If the activity is already at the top of the stack, it will not be restarted, but will receive call
onNewIntent(). - ๐ FLAG_ACTIVITY_CLEAR_TASK: Clears the entire current task before starting a new activity (often used in conjunction with NEW_TASK).
The combination of flags allows you to implement complex scenarios, for example, logging out of your account, returning to the login screen and clearing the entire navigation history. You must be clear about how each flag affects the stack structure.
โ ๏ธ Attention: Using a flag
CLEAR_TASKwithoutNEW_TASKhas no meaning and can lead to unpredictable behavior. Always check the Android Developer documentation before combining flags.
In addition to software flags, activity behavior can be specified in the file AndroidManifest.xml using the launchModeattribute. The following modes are available: standard, singleTop, singleTask and singleInstance. Choosing the right mode at the manifest stage can save you from having to write extra code with flags.
To implement the โLoginโ screen, where the user should return after exiting any section, the combination of the NEW_TASK and CLEAR_TASK flags is ideal.
Getting results from the target activity
Sometimes you need to not only go to another screen, but also get results from it. A classic example: selecting a contact from a list or editing a profile. Previously, the method used for this was startActivityForResult(), but in modern versions of Android it is considered obsolete (deprecated).
It is now recommended to use Activity Result API. This new approach is more flexible and allows you to register a contract to wait for a result without being tied to the lifecycle of the activity itself. You create a logger that takes the expected data type and a callback to process the result.
The process goes like this: First you log ActivityResultLauncher. Then, when you need to go to another screen, you call the method launch() of this launcher. In the target activity, you set the result via setResult() and call finish().
private ActivityResultLauncherlauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK) {
Intent data = result.getData();
// Data Processing
}
}
);
This approach eliminates problems with method overriding onActivityResult() and makes the code cleaner and more modular. You can declare launchers anywhere in the activity class or even in a fragment.
โ๏ธ Algorithm for getting the result
Don't forget to handle the case when the user clicked the "Back" button or the action was canceled. In this case, the result code will be equal to RESULT_CANCELED. Your logic must be resistant to such scenarios so that the application does not crash if there is no data.
Navigation through fragments and Jetpack Navigation
In modern Android development, transitions between entire activities are used less and less. The Single Activity architecture assumes the presence of one container activity, within which changes Fragment (fragments). This provides smoother animations, saves state, and makes it easier to work with the bottom navigation bar.
Google offers a library Jetpack Navigationto manage transitions between fragments. It allows you to visualize the navigation graph in Android Studio, automatically handle the back button and pass arguments between screens through a safe plugin (Safe Args).
Using a navigation graph makes the code declarative. You describe possible transitions in an XML file, and in code you call methods navigate() by action ID. This reduces the likelihood of errors and simplifies project support.
- ๐ฑ Single Activity: An architectural pattern with one activity and many fragments.
- ๐บ๏ธ NavGraph: An XML resource that describes all screens and the connections between them.
- ๐ก๏ธ Safe Args: A plugin for generating classes that provides type-safe argument passing.
If you are starting a new project today, it is highly recommended to learn Jetpack Navigation instead of manually managing fragment transactions. This is an industry standard that is maintained and developed by the Android team.
โ ๏ธ Attention: Libraries and approaches to navigation are rapidly evolving. Always check the official Android Developers documentation, as the methods for managing fragments may change with the release of new versions of AndroidX.
Switching to fragments also solves the problem of screen flickering when changing activities. Since the container remains the same, only its contents change, which looks much more natural to the user.
What is the difference between replace() and add() in fragments?
The replace() method removes all existing fragments in the container and adds a new one. The add() method adds a new fragment on top of the existing ones, hiding them (if you do not use hide/show). Replace is more often used for simple navigation, and add is used to create a stack with the ability to return.
Frequent errors and debugging navigation
When implementing switching between screens, developers often encounter typical problems. One of the most common is context leakage. If you store a reference to an activity in a static variable or long-lived object, the garbage collector will not be able to free the memory even when the screen is closed.
Another error is trying to start an activity from the background (for example, from a service or BroadcastReceiver) without a flag FLAG_ACTIVITY_NEW_TASK. In such cases, the system will throw an exception AndroidRuntimeExceptionsince the background process does not have its own window.
It is also worth mentioning the problem of duplicating screens in the stack. If a user quickly clicks the go button multiple times, multiple copies of the same activity may be created. Protection against this is implemented through flags or blocking the button while the animation is running.
To debug navigation, use the command adb shell dumpsys activity activities. It displays detailed information about the current task stack, showing which activities are currently running and in what order. This is an indispensable tool for understanding what is happening in your application โunder the hoodโ.
adb shell dumpsys activity activities | grep -A 5 "Run"
Analyze logs (Logcat) for every navigation-related crash. Often the reason lies in the absence of_declared_ activity in the manifest or incorrectly passed arguments.
Add logging to the onStart() and onStop() methods of each activity. This will help you visually track the life cycle and understand at what point an unexpected transition or screen closure occurs.
Conclusion
Proper organization of navigation is the foundation of a quality Android application. Understanding the differences between explicit and implicit intents, the ability to manipulate the task stack using flags, and knowledge of modern approaches through Jetpack Navigation will allow you to create user-friendly and stable interfaces.
Don't be afraid to experiment with different launch modes and architectures. Remember that the choice between activities and fragments depends on the complexity of your application and user experience requirements. Once you master these tools, you can implement any transition scenario, from a simple page opening to complex multi-tasking navigation.
How to pass a complex object between activities?
To pass complex objects, the class must implement the interface Parcelable (recommended) or Serializable. The object is packaged into an Intent via putExtra() and retrieved via getParcelableExtra().
What is Task Affinity?
This is an attribute in the manifest that determines which โtaskโ (activity stack) this activity prefers to belong to. Activities with the same affinity tend to be on the same stack.
Why is the activity not launched from the service?
Because the service does not have a window context. To launch an activity from the service, you need to add a flag Intent.FLAG_ACTIVITY_NEW_TASK to the intent.
How to completely clear the activity stack?
Use a combination of flags: Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK. This will delete all existing activities in the task and start a new one as the root one.
What is the advantage of Jetpack Navigation?
It provides a visual navigation graph editor, automatic back button handling, type-safe argument passing via Safe Args and simplifies working with BottomNavigationView.