Skip to content

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)AndroidDescription
Route page /homeActivity / FragmentA single screen
history.pushState()startActivity(Intent)Navigate to a new screen
history.back()finish() / Back buttonGo back to previous screen
URL params ?id=123Intent ExtrasPass data between screens
Component nestingFragment nested in ActivityModular screens
useEffect(() => cleanup, [])onDestroy()Cleanup on component destruction
React Router <Outlet />Fragment containerDynamic content area

1. Activity Basics

1.1 Creating an Activity

java
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!");
        });
    }
}

AppCompatActivity vs Activity: Always extend AppCompatActivity -- 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 resources

Full 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
                  └─────────────┘
java
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

ScenarioCallback Order
Launch ActivityonCreate → onStart → onResume
Press Home buttononPause → onStop
Return from backgroundonRestart → onStart → onResume
Press Back buttononPause → onStop → onDestroy
Rotate screenonPause → onStop → onDestroy → onCreate → onStart → onResume
Navigate from A to BA:onPause → B:onCreate → onStart → onResume → A:onStop
Navigate back from B to AB: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" in AndroidManifest.xml (not recommended), or save state in onSaveInstanceState.

2. Intent: Navigation Between Screens

Intent is the "message" object in Android, used for communication between components.

2.1 Explicit Intent (Target Specified)

java
// 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)

java
// ⚠️ 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/putSerializable methods. Frequently used for passing data between Activities, Fragments, and Services.

2.2 Receiving Parameters in the Target Activity

java
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)

java
// 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

java
// 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:

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: singleTask is 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

java
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 Activity

Key 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

java
// 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

java
// 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
    }
}
java
// 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)

java
// 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)

java
// 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)

java
// 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.

java
// 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:

ScenarioUse
UI operations (Toast, Dialog)Activity Context (this)
Loading resourcesActivity Context
Long-lived references (singletons, caches)Application Context
Inside a ServiceService Context (this)
Inside a FragmentrequireContext()

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.

java
// 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
        }
    };
}
java
// 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.OnClickListener is a very common pattern in legacy projects: the Activity itself implements the click interface, using v.getId() to distinguish different buttons. Frontend analogy: class MyPage extends React.Component implements ClickHandler.

7. Threading Model

Android has strict threading rules:

java
// ⚠️ 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

java
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 ConceptAndroid EquivalentNotes
React componentActivity / FragmentScreen / module
useEffect(() => {}, [])onCreate()Initialization
useEffect(cleanup, [])onDestroy()Cleanup
Run when component becomes visibleonResume()Every time it becomes visible
Pause when component becomes invisibleonPause()Every time it becomes invisible
router.push(path, params)startActivity(intent)Navigation
router.back()finish() / popBackStack()Go back
URL params / queryIntent.putExtra()Pass data between screens
useParams()getIntent().getXxxExtra()Receive data
Context (React)Context (Android)Different concept, same name
Web WorkerServiceBackground tasks
addEventListenerBroadcastReceiverEvent listening
window.location.hashActivity back stackNavigation 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.

A Java Android development tutorial for frontend developers