Android Activities
Activities classes code in Java or Kotlin displays visual UI, allows user interactions, and manage activity's as well app's lifecycles. They allow users to interact with the app and act as fundamental organizational and structural element in the Android app development.
Activity Lifecycle
Activities have a well-defined lifecycle to manage the behavior at different stages by a series of lifecycle methods that can be overriden. Proper use of the activity lifecycles is used in controlling the flow of the app. They allow to control the behavior of activity and respond to various events and transitions, such as when and where to save and restore data using activity, release resources when activity is not active, manage error handling without crashing the app, transitions to and communication between different screens, initiating network calls and handling responses, identifying and fix various issues related to state management and resource leaks.
Lifecycle Sequence Example for Transition between activities
Typical lifecycle sequence when transitioning between Activities:
- When Activity A is started, its onCreate() -> onStart() -> onResume() methods are called.
- When Activity B is started (e.g., 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: Intent is one of the core class provided by the Android SDK that can be used to perform various operations. Instant of intent class provide mechanism for communication and data exchange between
different activities as well with other components, such as services, broadcast receivers, and even other apps.
- Explicit Intents: These are used when you know the target activity's class name. Explicit intents explicitly specify the target component, which can be an activity within the same app.
- Implicit Intents: These are used to let the system determine the appropriate component to handle the intent based on the specified action, data, or MIME type (specificying right data type). Implicit intents are typically used for actions like sending emails, opening web URLs, or sharing content with other apps. To receive data using an implicit intent, the receiving activity should declare intent filters in its manifest file.
- RESULT_OK: Indicates that the operation was successful.
- RESULT_CANCELED: Indicates that the operation was canceled or not completed as expected.
To pass data between activities using an explicit intent:
1) Create an intent and set the target activity's class using Intent.putExtra() methods to attach data to the intent.
2) Start the target activity using startActivity(intent).Create an intent and set the target activity's class using Intent.putExtra() methods to attach data to the intent.
3) In the target activity, retrieve the data using getIntent().getDATATYPEExtra() methods, where DATATYPE depends on the data type you are retrieving (e.g., getStringExtra(), getIntExtra()).
Intent intent = new Intent(this, SecondActivity.class); intent.putExtra("key", "data to Second Activity!"); startActivity(intent); // In the receiving activity (SecondActivity). Intent intent = getIntent(); String data = intent.getStringExtra("key");
resultCodes:// Sending data using an implicit intent. Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, "data from app!");
Activity Result API: Currently startActivityForResult is deprecated. New Activity Result API has replaced it, which allows to handle results from other activities, fragments, and other components (services, broadcast receivers, or custom UI components). It allows to register a callback for an activity result, launch the activity with an input, and handle the result in a type-safe way. The API also provides predefined contracts for common actions, such as taking a picture, requesting a permission, or picking a contact. It offers to create custom contracts for specific needs. The Activity Result API also offers the flexibility to be used with non-activity components like Fragments, Views, and Composables (UI building blocks in Jetpack compose). Objects: If instead of primtive data, objects have to be sent by intents follow the steps:
- Create an instance of object class, after it implements either the Serializable interface or the Parcelable interface.
- Use the putExtra() method of the Intent to add the object to the intent as an extra. If the object is Serializable, add it directly. If the object is Parcelable, add it using putExtra() with the key and value pair.
- Start the target activity by calling startActivity() and passing the Intent as a parameter.
- In the target activity, retrieve the object from the intent using getSerializableExtra() or getParcelableExtra().
While developing for developing exclusively for Android and for performance reasons, Parcelable is often the recommended choice. However, if compatibility with non-Android platforms or simplicity is more important, Serializable can be a better choice, such as for data sycnchronization with the backend server or cross-platform development etc.
SharedPreferences: SharedPreferences is a simple and lightweight mechanism for storing and sharing small amounts of data between Android activities within the same app. It allows to store key-value pairs persistently, which can be accessed across different parts of the application.Note: In addition to the above mentioned common methods, there are other ways to exchange data between activities, such as using data stored in SQLite database or sending data from Broadcasts. The choice between SQLite databases, broadcasts, and other data-sharing mechanisms depends on your app's specific requirements. Use SharedPreferences for simple, small-scale data sharing, such as user preferences or app settings. Use SQLite databases when there is need of structured, persistent storage for larger datasets. Use broadcasts when there is need notify or share data between loosely coupled components and trigger actions in response to events.
Sending Data from regular class to Activity
1 ) Regular Java or Kotlin classes can be used to perform background tasks such as, data processing and business logic and then result can be passed to activity just by creating instance of regular class inside activity. Methods can be called and properties can be accessed of that instance. This approach is quick and easy to implement providing direct access to regular class's data with an activity while respecting the Android activity's lifecycle and context because the regular class is instantiated within the activity and has access to the activity's context.
2) Alternatively, use callbacks or interfaces to send data from the regular class to the activity without creating an instance of the regular class inside the activity. This is a more decoupled and modular approach and is suitable when activity need to be notified about events or changes in the regular class. Though using callbacks or interfaces can offer more decoupling and flexibility, but it also adds a layer of abstraction.
Sending Data from Activity to regular class
1) Use callbacks and interfaces to send data from an Android activity to regular Java or Kotlin classes. This approach allows for a clear separation of concerns.
2) Pass data to regular classes by including them as parameters in method calls.
3) Provide data to a regular class by passing it through the class constructor when creating an instance of the class. This is more suitable when the regular class will work with the data over an extended period or when the data is required for the class's initialization.4) Create a singleton object in your regular class that can be accessed globally from the activity. This approach is suitable when there is need to maintain a single instance of the regular class throughout the application's lifecycle and share data with it.
5) Utilize Android's LiveData and ViewModel components to share data between the activity and the regular class. This is especially useful to observe and react to data changes in real-time.
6) Use third-party event bus libraries like EventBus or Otto to facilitate communication between the activity and the regular class. These libraries provide a publish-subscribe mechanism where the activity can send events (data) to the regular class, and the regular class can subscribe to those events.
Activities BackStack
A task is a collection of activities related to a specific user workflow. For example, displaying the app's user interface and handling user interactions is a task or fetching data from remote servers. Activities in a task are organized in a stack, forming the back stack, which can contain activities from different apps or from the same app. Android operating system maintains this back stack of Activities that allows users to navigate backward through the app's UI. When a new Activity is started, it's added to the back stack. Users can return to previous Activities by pressing the "Back" button. When the Back button is pressed or an activity is finished, the top activity is removed, revealing the previous one beneath it. This follows the pattern of a stack data structure, i.e. Last-In-First-Out (LIFO) principle.
Back stack can be manipulated to control the app's behavior to create custom navigation flows. The Intent flags like FLAG_ACTIVITY_CLEAR_TOP (clear all activities at top of target activity), FLAG_ACTIVITY_NEW_TASK (starting activity in new task), and FLAG_ACTIVITY_CLEAR_TASK (clear all stack and start new for target activity) can be used to modify the back stack's behavior.
Activity Launch Modes
Activities can have launch modes specified in the manifest, such as "standard," "singleTop," "singleTask," or "singleInstance." These launch modes determine how the Activity is created and managed, including their interaction with the back stack. Launch modes determine whther a new instance of an activity is created or of an existing instance is reused.
- Standard: A new instance of the activity is created each time it is launched, regardless of whether an instance of the activity already exists in the task. This is the default behavior, and it's suitable for most cases. Each time activity is launced, a new instance is added to the task stack, potentially creating a back stack of multiple instances of the same activity.
- SingleTop: Here, if the activity is already at the top of the task stack, a new instance is not created. Instead, the onNewIntent() method is called on the existing instance to handle the new intent. This mode is useful to ensure that there's only one instance of the activity at the top of the stack to handle certain types of incoming intents.
- SingleTask: Here, a new task (activities collection in back stack) is created for the activity if it doesn't exist. If an instance of the activity exists in another task, that existing instance is reused, and the task is brought to the foreground. This mode is useful for activities that represent the entry point to your app or for activities that should have only one instance running at a time.
- SingleInstance: Here, a new task is always created for the activity, and only one instance of the activity can exist in that task. This mode is suitable for activities that should operate independently of the rest of the application, such as phone call screens or maps.
Activities with Jetpack
- Jetpack Compose allows to build UI's declaratively using a more dynamic and responsive UI.
- Jetpack's navigation component is used to handle the navigation between different screens or destinations with the app. It simplifies the navigation with the android app and reduces the complexity of managing the back stack.
- ViewModel, along with other components like LiveData and Room, is a key part of Jetpack. It helps to manage configuration changes, simplify lifecycle management, data persistence and data sharing, with modular code for unit testing.
- Room is one of the key libraries with Jetpack facilitating data storage for activities and other app components and cache network data.
Common Android Activity Bugs
- NullPointer Exception: For view elements in activity without initializing properly or for uninitialized variables.
- Resource Id Mismatches: Using incorrect resource IDs in layout files or code. This can appear as a crash or simply fail to find the desired view or resource.
- Activity Lifecycle Managements: Forgetting to call the superclass method in overridden lifecycle methods.
- Memory leaks: Failing to release resources or references properly. For example, not unregistering broadcast receivers, dismissing dialogs, or releasing resources in the 'onDestroy' method.
- Intent Handling: Mishandling of intents can cause issues such as navigating to the wrong activity, failing to handle deep links, or not receiving data properly.
- Permissions Handling: Inadequate permission handling can lead to security issues or permission-related crashes.