Android Activity Core Concepts: Layout, Lifecycle, Navigation, and Data Passing
An Android project typically involves three key files working together: a Java/Kotlin controller, an XML layout definition, and the app manifest. Understanding their roles forms the foundation of Activity development.
Structuring the User Interface
The XML layout file dictates the visual arrangement of UI components. While Android Studio defaults to ConstraintLayout, switching to simpler containers like LinearLayout during initial learning clarifies the basic structure.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HomeScreen">
<TextView
android:id="@+id/display_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Initial Content" />
</LinearLayout>
Connecting Logic to Views
The activity class bridges the static layout and dynamic behavior. Inside onCreate(), the setContentView method inflates the XML. Components defined in the layout become accessible via their unique identifiers.
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_home);
TextView output = (TextView) findViewById(R.id.display_text);
output.setText("Updated programmatically");
}
The manifest declares every Activity to the operating system and defines launch properties. An entry with MAIN action and LAUNCHER category marks the app's primary screen.
<activity android:name=".HomeScreen"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Navigating Between Screens
Switching between activities relies on Intent objects. A basic navigation call targets the destination class directly.
Intent move = new Intent(this, DetailView.class);
startActivity(move);
Forwarding Primitive and String Values
Data can be attached to the intent using key-value pairs. The receiving activity retrieves them using typed accessor methods.
// Sender
Intent sendData = new Intent(getApplicationContext(), DataReceiver.class);
sendData.putExtra(DataReceiver.KEY_COUNT, 42);
sendData.putExtra(DataReceiver.KEY_FLAG, true);
sendData.putExtra(DataReceiver.KEY_LABEL, "Sample");
startActivity(sendData);
public class DataReceiver extends BaseActivity {
public static final String KEY_COUNT = "count_val";
public static final String KEY_FLAG = "flag_val";
public static final String KEY_LABEL = "label_val";
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
Bundle extras = getIntent().getExtras();
if (extras != null) {
int num = extras.getInt(KEY_COUNT, 0);
boolean status = extras.getBoolean(KEY_FLAG, false);
String text = extras.getString(KEY_LABEL);
}
}
}
Transferring Complex Objects with Parcelable
Android's Parcelable offers efficient in-memory serialization. Unlike Serializable, it requires explicitly defining how data is packed and unpacked.
public class SongRecord implements Parcelable {
private int id;
private String title;
private String artist;
public SongRecord(int id, String title, String artist) {
this.id = id;
this.title = title;
this.artist = artist;
}
protected SongRecord(Parcel source) {
id = source.readInt();
title = source.readString();
artist = source.readString();
}
@Override
public void writeToParcel(Parcel output, int flags) {
output.writeInt(id);
output.writeString(title);
output.writeString(artist);
}
public static final Creator<SongRecord> CREATOR = new Creator<SongRecord>() {
@Override
public SongRecord createFromParcel(Parcel in) {
return new SongRecord(in);
}
@Override
public SongRecord[] newArray(int size) {
return new SongRecord[size];
}
};
@Override
public int describeContents() {
return 0;
}
}
Receiving Results from a Target Activity
To get data back after a sub-activity completes, use startActivityForResult and override onActivityResult.
private static final int EDIT_REQUEST = 100;
// Launching
Intent edit = new Intent(this, EditorScreen.class);
startActivityForResult(edit, EDIT_REQUEST);
// Handling return
@Override
protected void onActivityResult(int request, int result, Intent payload) {
super.onActivityResult(request, result, payload);
if (request == EDIT_REQUEST && result == RESULT_OK && payload != null) {
String updatedText = payload.getStringExtra(EditorScreen.RETURN_VALUE);
refreshDisplay(updatedText);
}
}
The target activity sets the result payload before calling finish().
Intent output = new Intent();
output.putExtra(RETURN_VALUE, editField.getText().toString());
setResult(RESULT_OK, output);
finish();
Understanding the Activity Lifecycle
Activity transitions through distinct states. The visible lifetime spans onStart() through onStop(), while the foreground lifetime is bounded by onResume() and onPause().
Key distinctions:
onCreate()performs one-time initialization only when the system instantiates the activity.onStart()makes the UI visible, but the activity may not yet have input focus.onPause()should be brief; avoid performing heavy data base writes or network calls here because the method must complete before the next activity resumes. UseonStop()for CPU-intensive teardown tasks and releasing resources that are not needed while the activity is completely hidden.
Retrieving View Dimensions Correctly
Requesting a view's width or height inside onCreate() or onResume() typically returns 0 because layout measurement hasn't finalized. Reliable alternatives include:
Using a Window Focus Listener
@Override
public void onWindowFocusChanged(boolean focused) {
super.onWindowFocusChanged(focused);
if (focused) {
int w = targetView.getWidth();
int h = targetView.getHeight();
}
}
Registering a Global Layout Observer
ViewTreeObserver observer = targetView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int measuredWidth = targetView.getWidth();
int measuredHeight = targetView.getHeight();
targetView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
Posting a Runnable to the View's Message Queue
targetView.post(() -> {
int w = targetView.getWidth();
int h = targetView.getHeight();
});
Once dimensions are known, you can dynamically adjust layouts, such as scaling an ImageView proportionally.
Launch Modes Overview
Activities are organized into tasks (back stacks). The launch mode dictates how instances are created:
standard: Default. A new instance is always created in the target task.singleTop: If an instance already sits at the top of the target stack, it receives the intent viaonNewIntent()instead of creating a duplicate.singleTask: The system retains a single instance. If an existing instance is elsewhere in the stack, all activities above it are destroyed, and the intent is routed toonNewIntent().singleInstance: The activity operates in its own isolated task and its task cannot contain other activities.
Defining Custom Transition Animations
Smooth activity transitions can be crafted using XML animation resources placed in the res/anim directory.
<!-- slide_in_right.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:fromXDelta="100%p"
android:toXDelta="0%p" />
<!-- slide_out_left.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:fromXDelta="0%p"
android:toXDelta="-100%p" />
Apply these through a style definition.
<style name="BaseAppTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowAnimationStyle">@style/CustomTransition</item>
</style>
<style name="CustomTransition">
<item name="android:activityOpenEnterAnimation">@anim/slide_in_right</item>
<item name="android:activityOpenExitAnimation">@anim/slide_out_left</item>
<item name="android:activityCloseEnterAnimation">@anim/slide_in_left</item>
<item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
</style>