---
title: 'Tasks and the Back Stack'
source: 'https://academia.sh/en/courses/android-platform-and-tools/tasks-and-back-stack'
course: 'Android Platform and Tools'
language: en
updated: '2026-10-21T05:02:36+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Tells the task, the back stack and the launch mode apart'
    - 'Measures that the standard mode opens a new instance on every request, while singleTop and singleTask can use the existing one'
    - 'Shows that in the singleTask mode the activities above the instance in the stack are cleared'
    - 'Verifies that the same mode is fulfilled in full or by a substitute depending on the state of the stack'
---

# Tasks and the Back Stack

The structure of the task and the back stack, whether the launch modes use a new instance or the existing one, how the same mode gets two different fulfillments depending on the state of the stack, and the edge case of the CLEAR_TOP flag.

When an activity is started with an intent, the platform places it somewhere. In the previous lesson you measured which app an intent reaches; this lesson looks at what happens after it reaches it: whether a new screen is opened, whether the existing screen comes to the front, and where the back button takes the user afterwards.

In the platform, activities are gathered in a **task**. A task is the group of activities the user moves through in order while doing a piece of work: while writing an email, the screen for choosing the sender, the screen for attachments and the writing screen are in the same task. The activities inside a task are ordered in a **back stack**. A newly opened activity is put on the top of the stack; the back button removes the activity on top and brings the one below to the front. When the activity at the very bottom of the stack is removed, the task ends.

The stack works like a **stack** data structure: last in, first out. In this lesson you will show the stack as a list, and its order will be printed. The left end is the activity at the bottom, the right end is the top.

The question of the lesson: when a request to open the same activity again arrives, how does the platform **fulfill** the request? The request is always "open a new instance of this activity". The platform fulfills it according to the launch mode, either with a new instance or by using the existing instance. The first is a full fulfillment, the second a substitute.

```kotlin
// Fulfillment.kt — classifies how the platform fulfills a request
sealed interface Outcome {
    data class Given(val value: String) : Outcome
    data class Waiting(val on: String) : Outcome
    data class Refused(val reason: String) : Outcome
}

class Ask(val label: String, val wanted: String, val outcome: Outcome) {
    val fulfillment: String = when (outcome) {
        is Outcome.Waiting -> "pending"
        is Outcome.Refused -> "unmet"
        is Outcome.Given -> if (outcome.value == wanted) "full" else "substitute"
    }

    fun report() {
        val detail = when (outcome) {
            is Outcome.Given -> "given=${outcome.value}"
            is Outcome.Waiting -> "waiting-on=${outcome.on}"
            is Outcome.Refused -> "reason=${outcome.reason}"
        }
        println("$label: wanted=$wanted | $detail -> $fulfillment")
    }
}

fun tally(asks: List<Ask>) {
    for (name in listOf("full", "substitute", "pending", "unmet")) {
        println("$name: ${asks.count { it.fulfillment == name }}")
    }
}
```

## The Launch Mode

The launch mode (`launchMode`) decides whether a new instance is opened or the existing instance is used when an activity is asked to start. The mode is declared for the activity in the manifest file.

```xml
<activity android:name=".DetailActivity" android:launchMode="singleTop" />
```

There are four modes.

- **standard**: the default mode. Every request puts a new instance of the activity on the top of the task. The same activity can be in the stack more than once.
- **singleTop**: if the target activity is already on the top of the stack, no new instance is opened; the new intent is delivered to the existing instance with the `onNewIntent` callback. If it is not on top, a new instance is opened as in standard.
- **singleTask**: if the target activity exists somewhere in the task, no new instance is opened. The existing instance is brought to the front and all the activities above it are cleared from the stack. If it does not exist, a new instance is opened.
- **singleInstance**: like singleTask, but the activity runs alone in its own task and holds no other activity in that task. This lesson does not measure this mode.

The mode is set not only in the manifest file but also with intent flags. The `FLAG_ACTIVITY_CLEAR_TOP` flag clears the activities above the target if the target is in the stack. `FLAG_ACTIVITY_NEW_TASK` puts the activity in a new task.

```kotlin
// Platform code: starting with a flag
import android.content.Intent

val intent = Intent(this, DetailActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
```

## The Stack Model

The model keeps the stack as a list. The helper file builds the launch mode in a single function: the stack, the target and the mode are given; the new stack and what happened are returned. What happened is one of two values: `new-instance` or `existing-instance`. What is wanted is always `new-instance`, because the calling code wants to open the activity.

```kotlin
// Tasks.kt — builds the launch modes and consecutive requests
class Launch(val stack: List<String>, val how: String)

fun launchActivity(stack: List<String>, target: String, mode: String): Launch {
    val at = stack.lastIndexOf(target)
    return when (mode) {
        "standard" -> Launch(stack + target, "new-instance")
        "singleTop" ->
            if (stack.lastOrNull() == target) Launch(stack, "existing-instance")
            else Launch(stack + target, "new-instance")
        "singleTask" ->
            if (at >= 0) Launch(stack.take(at + 1), "existing-instance")
            else Launch(stack + target, "new-instance")
        "standard-clearTop" ->
            if (at >= 0) Launch(stack.take(at) + target, "new-instance")
            else Launch(stack + target, "new-instance")
        "singleTop-clearTop" ->
            if (at >= 0) Launch(stack.take(at + 1), "existing-instance")
            else Launch(stack + target, "new-instance")
        else -> Launch(stack, "undefined-mode")
    }
}

fun runLaunches(start: List<String>, targets: List<String>, mode: String): Pair<List<Ask>, List<String>> {
    var stack = start
    val asks = mutableListOf<Ask>()
    for ((index, target) in targets.withIndex()) {
        val launch = launchActivity(stack, target, mode)
        asks.add(Ask("${index + 1}-$mode-$target", "new-instance", Outcome.Given(launch.how)))
        stack = launch.stack
    }
    return Pair(asks, stack)
}
```

`runLaunches` applies consecutive requests in order and collects each request as an `Ask`. The request label carries the step number, the mode and the target. The stack is updated after every step.

**AC1.** The model assumes a single stack per task and does not measure the `NEW_TASK` flag, the `taskAffinity` attribute or multi-task cases. The activities in the stack are kept only as names; their state, their lifecycle callbacks and their destruction are not modeled.

## The standard Mode

In the `standard` mode every request opens a new instance. A is open; B, then A, then A again are started. It is valid for A to be in the stack three times.

```kotlin
// Main.kt — standard mode
fun main() {
    val (asks, stack) = runLaunches(listOf("A"), listOf("B", "A", "A"), "standard")
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(stack.joinToString(" > "))
}
```

```text
1-standard-B: wanted=new-instance | given=new-instance -> full
2-standard-A: wanted=new-instance | given=new-instance -> full
3-standard-A: wanted=new-instance | given=new-instance -> full
full: 3
substitute: 0
pending: 0
unmet: 0
A > B > A > A
```

All three requests were fulfilled in full: each opened a new instance. The stack grew to four elements and A is in it three times. The user reaches the first A in this stack by pressing the back button three times, and each of the four elements is a separate instance. This is the cost the standard mode brings: code that opens the same screen again and again grows the stack silently.

## The singleTop Mode

`singleTop` does not open a new instance if the target is already on top. A flow that links from a detail screen to itself, or tapping a user notification twice in a row, is an example of this situation. The stack starts as A, B; two requests arrive for B and two for C.

```kotlin
// Main.kt — singleTop mode
fun main() {
    val (asks, stack) = runLaunches(listOf("A", "B"), listOf("B", "C", "C"), "singleTop")
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(stack.joinToString(" > "))
}
```

```text
1-singleTop-B: wanted=new-instance | given=existing-instance -> substitute
2-singleTop-C: wanted=new-instance | given=new-instance -> full
3-singleTop-C: wanted=new-instance | given=existing-instance -> substitute
full: 1
substitute: 2
pending: 0
unmet: 0
A > B > C
```

B was already on top; the first request used the existing instance and the fulfillment was a substitute. C was not on top, a new instance was opened and it was fulfilled in full. In the third request C is now on top, so this time a substitute. The stack stayed A, B, C; the two substituted requests did not grow the stack. The calling side receives its new intent in the `onNewIntent` callback and has to update its screen. Code that assumes a new instance was opened works wrongly here.

**AC2.** The model does not measure whether `onNewIntent` is called; it measures only what happens to the stack. How the intent is delivered to the existing instance and whether the screen is updated were not measured.

## The singleTask Mode

`singleTask` brings the target to the front if it is anywhere in the stack and clears what is above it. If a request for B arrives in the stack A, B, C, C is removed from the stack; B becomes the top. Then D is opened, then B is requested again: D is cleared too.

```kotlin
// Main.kt — singleTask mode
fun main() {
    val (asks, stack) = runLaunches(listOf("A", "B", "C"), listOf("B", "D", "B"), "singleTask")
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(stack.joinToString(" > "))
}
```

```text
1-singleTask-B: wanted=new-instance | given=existing-instance -> substitute
2-singleTask-D: wanted=new-instance | given=new-instance -> full
3-singleTask-B: wanted=new-instance | given=existing-instance -> substitute
full: 1
substitute: 2
pending: 0
unmet: 0
A > B
```

The first and the third requests used the existing B and were substitutes; the second was fulfilled in full with a new instance because D was not in the stack. The stack ends as A, B. The requests added C and D to the stack and then removed them: `singleTask` does not stop at not opening a new instance, it also discards the screens the user went through before returning to B. This is designed for screens that must stand at the root of the task, such as a home screen or an entry point.

## The Edge Case of the CLEAR_TOP Flag

The `FLAG_ACTIVITY_CLEAR_TOP` flag moves the stack-clearing behavior of singleTask to a flag, but what happens to the existing instance depends on the mode. If the target is in the `standard` mode, the platform destroys the existing instance as well and creates a new one. If the target is in the `singleTop` mode, or if `FLAG_ACTIVITY_SINGLE_TOP` is given together with the flag, the existing instance is kept and `onNewIntent` is called.

Two requests send the same target to the same stack: B in the stack A, B, C.

```kotlin
// Main.kt — CLEAR_TOP and the mode
fun main() {
    val standardClear = runLaunches(listOf("A", "B", "C"), listOf("B"), "standard-clearTop")
    val singleTopClear = runLaunches(listOf("A", "B", "C"), listOf("B"), "singleTop-clearTop")
    val asks = standardClear.first + singleTopClear.first
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(standardClear.second.joinToString(" > "))
    println(singleTopClear.second.joinToString(" > "))
}
```

```text
1-standard-clearTop-B: wanted=new-instance | given=new-instance -> full
1-singleTop-clearTop-B: wanted=new-instance | given=existing-instance -> substitute
full: 1
substitute: 1
pending: 0
unmet: 0
A > B
A > B
```

The two stacks are the same: A, B. The fulfillments differ: with standard a new instance was opened and it is full; with singleTop the existing instance was kept and it is a substitute. The stack as seen from outside is the same in both cases; the difference lies in the identity of the instance and in its state. In the standard mode, the unsaved state inside the screen is lost because the instance is renewed.

The reason for this subtle difference is that what is measured is not the stack but the instance. The fulfillment category looks not at "how does the stack look" but at "how was the request fulfilled", and the two turn out independent of each other.

## The Bounding Measurement

The same mode, the same target; only the state of the stack changes. In the `singleTop` mode B is the target. In one case B is on top, in the other it is left under C. For comparison with `singleTask`, a third case is added: the mode is singleTask, and B is again under C.

```kotlin
// Main.kt — the same mode, two stack states
fun main() {
    val cases = listOf(
        Triple("singleTop-on-top", listOf("A", "B"), "singleTop"),
        Triple("singleTop-not-on-top", listOf("A", "B", "C"), "singleTop"),
        Triple("singleTask-not-on-top", listOf("A", "B", "C"), "singleTask"),
    )
    val asks = mutableListOf<Ask>()
    for ((label, stack, mode) in cases) {
        val launch = launchActivity(stack, "B", mode)
        asks.add(Ask(label, "new-instance", Outcome.Given(launch.how)))
        println("$label stack: ${launch.stack.joinToString(" > ")}")
    }
    for (ask in asks) {
        ask.report()
    }
    tally(asks)
    println(asks.map { it.fulfillment }.distinct().sorted())
}
```

```text
singleTop-on-top stack: A > B
singleTop-not-on-top stack: A > B > C > B
singleTask-not-on-top stack: A > B
singleTop-on-top: wanted=new-instance | given=existing-instance -> substitute
singleTop-not-on-top: wanted=new-instance | given=new-instance -> full
singleTask-not-on-top: wanted=new-instance | given=existing-instance -> substitute
full: 1
substitute: 2
pending: 0
unmet: 0
[full, substitute]
```

The `singleTop` mode got two different fulfillments on two stacks. If B is on top, a substitute: the stack did not change. If B is not on top, full: the stack became A, B, C, B and B is in the stack with two instances. `singleTask`, on the same stack, brought B to the front and discarded C: a substitute.

What this measurement says is this: the mode alone does not determine the fulfillment. The fulfillment comes out of a rule that works over the mode and the current state of the stack together. An app that runs the same code at two different moments sees two different behaviors depending on where the user came from. The fulfillment of this lesson is **substitute**: a request to open the same activity is most often fulfilled by the platform with the existing instance.

**AC3.** The model measures the cases in which the target instance is in the stack only once. Which instance the `singleTop` and `CLEAR_TOP` rules choose when the target has two instances in the stack, and transitions between tasks (for example `singleInstance`), were not measured.

## Summary

- This lesson's criterion: **fulfillment**. Activities are gathered in a task, and the task in a back stack. The launch mode decides whether a request to open the same activity is fulfilled with a new instance or with the existing instance. This lesson's fulfillment: **substitute**; a request to open the same activity is most often carried out with the existing instance.
- `standard` opens a new instance on every request: 3 full. `singleTop` uses the existing instance if the target is on top: substitute.
- `singleTask` brings the target to the front if it is in the stack and clears what is above it: substitute; if it is not in the stack, a new instance, full.
- With `CLEAR_TOP`, the existing instance is renewed in the `standard` mode (full) and kept in the `singleTop` mode (substitute). The stack as seen from outside is the same.
- The same mode gets two fulfillments depending on the state of the stack: `singleTop` is a substitute on top and full when not on top.

## Next Step

You measured which task and which place in the order a screen takes. That screen cannot be started at all unless it has been introduced to the platform beforehand as an app component. The place of this introduction is the manifest file; the next lesson, The Manifest File, measures what is written there and how files that come from more than one source are merged.
