Module 04: Activity & Fragment
Objective: Understand Android's screen model, the lifecycle of Activity and Fragment, and how to navigate and communicate between screens.
Frontend Comparison: Screen Model Differences
| Frontend (SPA) | Android | Description |
|---|---|---|
Route page /home | Activity / Fragment | A single screen |
history.pushState() | startActivity(Intent) | Navigate to a new screen |
history.back() | finish() / Back button | Go back to previous screen |
URL params ?id=123 | Intent Extras | Pass data between screens |
| Component nesting | Fragment nested in Activity | Modular screens |
useEffect(() => cleanup, []) | onDestroy() | Cleanup on component destruction |
React Router <Outlet /> | Fragment container | Dynamic content area |
1. Activity Basics
1.1 Creating an Activity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Bind the layout file (similar to returning JSX from React's render())
setContentView(R.layout.activity_main);
// Initialize views
TextView titleText = findViewById(R.id.tv_title);
Button submitBtn = findViewById(R.id.btn_submit);
// Set click listener
submitBtn.setOnClickListener(v -> {
// Handle click
titleText.setText("Button was clicked!");
});
}
}
AppCompatActivityvsActivity: Always extendAppCompatActivity-- it provides backward-compatible Material Design support.
1.2 Activity Lifecycle
This is one of the most fundamental concepts in Android development. Compare it with frontend component lifecycles:
Frontend Component Lifecycle Android Activity Lifecycle
───────────────────────── ─────────────────────────
constructor() onCreate() ← Create, initialize, bind layout
componentDidMount() onStart() ← Visible
Subscription in useEffect onResume() ← Has focus, interactive
onPause() ← Lost focus (dialog shown / app switched)
onStop() ← Not visible (navigated to another Activity)
componentWillUnmount() onDestroy() ← Destroyed, clean up resourcesFull lifecycle diagram:
┌──────────┐
│ onCreate │ Initialize: setContentView, findViewById
└────┬─────┘
│
┌────▼─────┐
│ onStart │ Visible but may not have focus
└────┬─────┘
│
┌──────────▼──────────┐
│ onResume │ Has focus, user can interact ← Running state
└──────────┬──────────┘
│
┌─────▼─────┐
│ onPause │ Lost focus (e.g., dialog appeared)
└─────┬─────┘
│
┌─────▼─────┐
│ onStop │ Completely invisible
└─────┬─────┘
│
┌──────▼──────┐
│ onDestroy │ About to be destroyed
└─────────────┘public class MyActivity extends AppCompatActivity {
private static final String TAG = "MyActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_my);
// savedInstanceState: When restoring from a destroyed state, previously saved data is passed in
if (savedInstanceState != null) {
String savedText = savedInstanceState.getString("key_text");
}
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
// Good for: registering broadcast receivers, starting animations
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
// Good for: resuming camera, restarting polling
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
// Good for: pausing animations, saving temporary data
// ⚠️ Don't do expensive operations here!
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
// Good for: releasing heavy resources, cancelling network requests
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
// Good for: releasing all references, unregistering listeners
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save UI state (e.g., when the screen rotates)
outState.putString("key_text", "Saved text");
}
}1.3 Key Lifecycle Scenarios
| Scenario | Callback Order |
|---|---|
| Launch Activity | onCreate → onStart → onResume |
| Press Home button | onPause → onStop |
| Return from background | onRestart → onStart → onResume |
| Press Back button | onPause → onStop → onDestroy |
| Rotate screen | onPause → onStop → onDestroy → onCreate → onStart → onResume |
| Navigate from A to B | A:onPause → B:onCreate → onStart → onResume → A:onStop |
| Navigate back from B to A | B:onPause → A:onRestart → onStart → onResume → B:onStop → onDestroy |
⚠️ Screen rotation destroys and recreates the Activity: This is the source of many bugs. Solutions: use ViewModel to persist data, configure
android:configChanges="orientation|screenSize"inAndroidManifest.xml(not recommended), or save state inonSaveInstanceState.
2. Intent: Navigation Between Screens
Intent is the "message" object in Android, used for communication between components.
2.1 Explicit Intent (Target Specified)
// Navigate from MainActivity to DetailActivity
// Frontend equivalent: router.push('/detail?id=123&name=John')
Intent intent = new Intent(MainActivity.this, DetailActivity.class);
// Pass parameters (similar to URL query params)
intent.putExtra("id", 123);
intent.putExtra("name", "John");
intent.putExtra("is_vip", true);
// Launch the Activity
startActivity(intent);
// If you need a result back (similar to await router.push())
// Modern approach: ActivityResultLauncher (recommended)
ActivityResultLauncher<Intent> launcher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK) {
String data = result.getData().getStringExtra("result_key");
}
}
);
launcher.launch(intent);Legacy Approach: startActivityForResult (deprecated but common in old projects)
// ⚠️ Old projects heavily use this pattern; new code should use ActivityResultLauncher above
// Send request (requestCode = 0)
Intent intent = new Intent(this, ActResponseActivity.class);
Bundle bundle = new Bundle(); // create a parcel
bundle.putString("request_time", DateUtil.getNowTime()); // put data into parcel
bundle.putString("request_content", "Have you eaten?");
intent.putExtras(bundle); // attach parcel to intent
startActivityForResult(intent, 0); // second param is request code
// Receive returned data (similar to await router.push() return value)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (intent != null && requestCode == 0 && resultCode == Activity.RESULT_OK) {
Bundle bundle = intent.getExtras(); // get parcel from returned intent
String responseTime = bundle.getString("response_time");
String responseContent = bundle.getString("response_content");
}
}Bundle is like a serializable object in frontend. It supports
putString/putInt/putBoolean/putSerializablemethods. Frequently used for passing data between Activities, Fragments, and Services.
2.2 Receiving Parameters in the Target Activity
public class DetailActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
// Get the passed parameters
Intent intent = getIntent();
int id = intent.getIntExtra("id", 0); // Second param is default value
String name = intent.getStringExtra("name");
boolean isVip = intent.getBooleanExtra("is_vip", false);
// Use the parameters
TextView tv = findViewById(R.id.tv_info);
tv.setText("ID: " + id + ", Name: " + name);
}
}2.3 Implicit Intent (Matched by Action)
// Open a web page (similar to window.open)
Intent webIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse("https://www.example.com"));
startActivity(webIntent);
// Make a phone call
Intent callIntent = new Intent(Intent.ACTION_DIAL,
Uri.parse("tel:10086"));
startActivity(callIntent);
// Share content
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, "Check out this app!");
startActivity(Intent.createChooser(shareIntent, "Share to"));
// Send an email
Intent emailIntent = new Intent(Intent.ACTION_SENDTO,
Uri.parse("mailto:someone@example.com"));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
emailIntent.putExtra(Intent.EXTRA_TEXT, "Email body content");
startActivity(emailIntent);2.4 Common Intent Operations
// Go back to the previous screen
finish();
// Go back and pass a result
Intent result = new Intent();
result.putExtra("result_key", "Operation successful");
setResult(RESULT_OK, result);
finish();
// Clear all Activities in the stack and navigate to a new screen
// Similar to: router.replace('/login') then clear history
Intent loginIntent = new Intent(this, LoginActivity.class);
loginIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(loginIntent);
// Navigate to a screen while keeping the current one in the stack
// Similar to: router.push('/settings')
startActivity(new Intent(this, SettingsActivity.class));2.5 Activity Launch Modes (launchMode)
Configure in AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:launchMode="standard" /> <!-- Default: creates a new instance every time -->
<activity
android:name=".HomeActivity"
android:launchMode="singleTop" /> <!-- Don't create new instance if already on top -->
<activity
android:name=".LoginActivity"
android:launchMode="singleTask" /> <!-- Single instance in the entire stack -->
<activity
android:name=".SplashActivity"
android:launchMode="singleInstance" /> <!-- Gets its own dedicated task stack -->In legacy projects:
singleTaskis commonly used for the home screen and login screen to avoid duplicate instances.
3. Fragment
A Fragment is a "sub-screen" within an Activity that can be reused and composed. Similar to route nesting or component nesting in frontend.
3.1 Creating a Fragment
public class HomeFragment extends Fragment {
// Bind the layout
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Similar to Activity's setContentView
return inflater.inflate(R.layout.fragment_home, container, false);
}
// View has been created, safe to use findViewById
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView title = view.findViewById(R.id.tv_title);
Button btn = view.findViewById(R.id.btn_action);
btn.setOnClickListener(v -> {
title.setText("Fragment button was clicked");
});
}
}3.2 Fragment Lifecycle
onAttach() ← Fragment is bound to Activity
onCreate() ← Initialization (no UI yet)
onCreateView() ← Create the UI (inflate layout)
onViewCreated() ← UI is created, safe to interact with views ★ Recommended for initialization
onStart() ← Visible
onResume() ← Interactive
onPause() ← Lost focus
onStop() ← Not visible
onDestroyView() ← UI is destroyed (Fragment may still be in the back stack)
onDestroy() ← Fragment is destroyed
onDetach() ← Unbound from ActivityKey difference: Fragment has
onDestroyView()but Activity does not. When a Fragment goes onto the back stack, its View is destroyed but the Fragment instance still exists.onCreateView()will be called again when it comes back.
3.3 Fragment Management
// Add a Fragment to an Activity
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, new HomeFragment())
.commit();
// Add to back stack (similar to router.push)
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, new DetailFragment())
.addToBackStack("detail") // Add to back stack
.commit();
// Pass arguments to a Fragment
DetailFragment fragment = new DetailFragment();
Bundle args = new Bundle();
args.putInt("id", 123);
args.putString("name", "John");
fragment.setArguments(args);
// Retrieve arguments in the Fragment
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
Bundle args = getArguments();
if (args != null) {
int id = args.getInt("id");
String name = args.getString("name");
}
}
// Pop back to the previous Fragment (similar to router.back())
getSupportFragmentManager().popBackStack();
// Clear the entire back stack
getSupportFragmentManager().popBackStack(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE);3.4 Communication Between Fragment and Activity
// Approach 1: Interface callbacks (common in legacy projects)
public class HomeFragment extends Fragment {
public interface OnItemSelectedListener {
void onItemSelected(String item);
}
private OnItemSelectedListener listener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnItemSelectedListener) {
listener = (OnItemSelectedListener) context;
}
}
// Trigger the callback
private void onItemClick(String item) {
if (listener != null) {
listener.onItemSelected(item);
}
}
}
// Activity implements the interface
public class MainActivity extends AppCompatActivity
implements HomeFragment.OnItemSelectedListener {
@Override
public void onItemSelected(String item) {
// Handle the selection event
}
}// Approach 2: Shared ViewModel (recommended, Jetpack approach)
// Activity and Fragment share the same ViewModel instance
public class SharedViewModel extends ViewModel {
private final MutableLiveData<String> selectedItem = new MutableLiveData<>();
public void select(String item) {
selectedItem.setValue(item);
}
public LiveData<String> getSelectedItem() {
return selectedItem;
}
}
// In the Fragment
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelectedItem().observe(getViewLifecycleOwner(), item -> {
// React to selection
});
// In the Activity
SharedViewModel model = new ViewModelProvider(this).get(SharedViewModel.class);
model.select("Some option");4. Overview of the Four Core Components
Besides Activity, Android has three other important components:
4.1 Service (Background Service)
// Similar to Web Worker / Service Worker in frontend
public class DownloadService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String url = intent.getStringExtra("download_url");
// Execute a long-running task in the background
new Thread(() -> {
// Download logic
downloadFile(url);
stopSelf(); // Stop the service when done
}).start();
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
// Start the service
Intent serviceIntent = new Intent(this, DownloadService.class);
serviceIntent.putExtra("download_url", "https://example.com/file.zip");
startService(serviceIntent);4.2 BroadcastReceiver (Broadcast Receiver)
// Similar to window.addEventListener('online', handler)
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
boolean isConnected = checkNetwork(context);
// Handle network change
}
}
}
// Register dynamically (in Activity/Fragment)
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
NetworkReceiver receiver = new NetworkReceiver();
registerReceiver(receiver, filter);
// Remember to unregister in onDestroy
unregisterReceiver(receiver);4.3 ContentProvider (Content Provider)
// Used for sharing data across apps, less common in legacy projects
// Common scenarios: reading contacts, photo gallery, and other system data
Cursor cursor = getContentResolver().query(
ContactsContract.Contacts.CONTENT_URI,
null, null, null, null
);5. The Context Concept
Context is one of the most important yet most confusing concepts in Android. Think of it as a reference to the application environment -- almost every operation requires it.
// The Activity itself is a Context
Activity activity = this; // 'this' is the Context
// Get Context in a Fragment
Context context = requireContext(); // Generic Context
Activity activity = requireActivity(); // Host Activity
// Application Context (global lifecycle)
Context appContext = getApplicationContext();
// Common scenarios that require Context
Toast.makeText(this, "Toast message", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(this, OtherActivity.class);
SharedPreferences prefs = this.getSharedPreferences("prefs", MODE_PRIVATE);
Resources res = this.getResources();When to Use Which Context:
| Scenario | Use |
|---|---|
| UI operations (Toast, Dialog) | Activity Context (this) |
| Loading resources | Activity Context |
| Long-lived references (singletons, caches) | Application Context |
| Inside a Service | Service Context (this) |
| Inside a Fragment | requireContext() |
Common bug: Using Activity Context to create a Dialog from a non-UI thread or a non-Activity context will cause a crash.
6. Handler: Delayed Tasks and Page Navigation
Handler is the core mechanism for delayed execution and inter-thread communication in Android, heavily used in legacy projects.
// Scenario: splash screen auto-navigates to main page after 3 seconds
// Frontend analogy: setTimeout(() => router.push('/home'), 3000)
public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
TextView tv = findViewById(R.id.tv_hint);
tv.setText("Entering main page in 3 seconds...");
// Delay 3 seconds then start navigation task
new Handler(Looper.myLooper()).postDelayed(mGoMain, 3000);
}
private Runnable mGoMain = new Runnable() {
@Override
public void run() {
// Navigate from SplashActivity to MainActivity
startActivity(new Intent(SplashActivity.this, MainActivity.class));
finish(); // close splash
}
};
}// Scenario: lifecycle logging (common for debugging)
// From actual tutorial project ActLifeActivity.java
public class LifeCycleActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "LifeCycleActivity";
private TextView tvLife; // declare text view
private String mStr = ""; // log accumulator string
private void refreshLife(String desc) {
Log.d(TAG, desc); // print to Logcat
mStr = String.format("%s%s %s\n", mStr, DateUtil.getNowTimeDetail(), desc);
tvLife.setText(mStr); // display on screen
}
@Override protected void onCreate(Bundle s) { super.onCreate(s); refreshLife("onCreate"); }
@Override protected void onStart() { super.onStart(); refreshLife("onStart"); }
@Override protected void onResume() { super.onResume(); refreshLife("onResume"); }
@Override protected void onPause() { super.onPause(); refreshLife("onPause"); }
@Override protected void onStop() { super.onStop(); refreshLife("onStop"); }
@Override protected void onRestart() { super.onRestart(); refreshLife("onRestart"); }
@Override protected void onDestroy() { super.onDestroy(); refreshLife("onDestroy"); }
}
implements View.OnClickListeneris a very common pattern in legacy projects: the Activity itself implements the click interface, usingv.getId()to distinguish different buttons. Frontend analogy:class MyPage extends React.Component implements ClickHandler.
7. Threading Model
Android has strict threading rules:
// ⚠️ Main thread (UI thread) rules:
// 1. All UI operations must run on the main thread
// 2. Expensive operations (network, database, file I/O) must NOT run on the main thread
// 3. Violating these rules causes ANR (Application Not Responding) or crashes
// Run expensive operations on a background thread
new Thread(() -> {
// Network requests, database operations, etc.
String result = fetchDataFromNetwork();
// ⚠️ Can't update UI from a background thread!
// textView.setText(result); // ❌ Crash!
// Switch back to the main thread to update UI
runOnUiThread(() -> {
textView.setText(result); // ✅ Correct
});
}).start();
// Using Handler (common in legacy projects)
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> {
textView.setText(result);
});
// Using AsyncTask (deprecated, but still widely used in legacy projects)
// ⚠️ Just understand it -- don't use it in new code
class MyAsyncTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... params) {
return fetchData(params[0]); // Background thread
}
@Override
protected void onPostExecute(String result) {
textView.setText(result); // Main thread
}
}8. State Saving and Restoration
public class FormActivity extends AppCompatActivity {
private EditText editText;
private boolean isChecked;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_form);
editText = findViewById(R.id.et_input);
// Restore from saved state
if (savedInstanceState != null) {
editText.setText(savedInstanceState.getString("input_text"));
isChecked = savedInstanceState.getBoolean("checkbox_state");
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("input_text", editText.getText().toString());
outState.putBoolean("checkbox_state", isChecked);
}
}ViewModel can replace onSaveInstanceState: ViewModel is not destroyed during configuration changes (e.g., screen rotation), making it a more modern approach to state management (see Module 08).
Frontend Developer Cheat Sheet
| Frontend Concept | Android Equivalent | Notes |
|---|---|---|
| React component | Activity / Fragment | Screen / module |
useEffect(() => {}, []) | onCreate() | Initialization |
useEffect(cleanup, []) | onDestroy() | Cleanup |
| Run when component becomes visible | onResume() | Every time it becomes visible |
| Pause when component becomes invisible | onPause() | Every time it becomes invisible |
router.push(path, params) | startActivity(intent) | Navigation |
router.back() | finish() / popBackStack() | Go back |
| URL params / query | Intent.putExtra() | Pass data between screens |
useParams() | getIntent().getXxxExtra() | Receive data |
| Context (React) | Context (Android) | Different concept, same name |
| Web Worker | Service | Background tasks |
addEventListener | BroadcastReceiver | Event listening |
window.location.hash | Activity back stack | Navigation history |
Next Steps
Now that you understand Activity and Fragment, move on to Module 05: UI Layouts & Widgets to learn how to build interfaces with XML.