Open menu with table of contents Android Introduction
Logo of Stuttgart Media University for light theme Logo of Stuttgart Media University for dark theme
Android Development

Android Introduction

Stuttgart Media University

Agenda

  • What is Android?
  • Android Architecture (platform challenges, OS layers, Dalvik → ART, DEX files)
  • Developer Toolchain (Android Studio, SDK tools, adb)
  • Android Development Basics
    • Project structure and the Manifest
    • Gradle: SDK versions, applicationId vs. namespace, version catalog
    • Jetpack Compose and Managing State
    • Resources and the R class
    • AndroidX, legacy View-based layouts, the build process
  • Assignment 1

What is Android?

1 What is Android?

  • Operating System for
    • ...
    • ...
    • ...

center 80%

Source: Google Blog

2 What is Android?

  • Operating System for ...
    • Smartphones
    • Tablets and foldables
    • Smart Watches (Wear OS)
    • Smart TVs (Android TV / Google TV)
    • Infotainment Systems (Android Auto)
    • Cars (Android Automotive OS)
    • XR headsets (Android XR)
    • Or more general: Embedded Systems
  • Open source (AOSP, Apache 2.0), developed by Google, the Google apps and the Play Store are proprietary

3 Short Android History (2005 - 2014)

  • July 2005: Google acquires Android Inc.
  • 5. November 2007: Open Handset Alliance established
  • 21. October 2008: Google publishes the Android source code (Apache 2.0 License)
  • 2. February 2009: T-Mobile G1 / HTC Dream is the first available Android hardware in Germany
  • February 2011: Motorola Xoom, first tablet with Android 3.0 (Honeycomb)
  • 19. October 2011: Ice Cream Sandwich (4.0, API 14) on the Samsung Galaxy Nexus, one code base for phones and tablets
  • 9. July 2012: Jelly Bean (4.1 - 4.3) and the Google Nexus 7 tablet
  • 31. October 2013: KitKat (4.4, API 19), ART available as experimental runtime
  • 12. November 2014: Lollipop (5.0 - 5.1, API 21/22) introducing the responsive design language Material Design and ART as default runtime

4 Short Android History (2015 - today)

  • 5. October 2015: Marshmallow (6.0, API 23), runtime permissions, USB-C support
  • 22. August 2016: Nougat (7.0 - 7.1, API 24/25), multi-window, file based encryption, JIT compiler in ART
  • 21. August 2017: Oreo (8.0 - 8.1, API 26/27), Project Treble, background execution limits (our minSdk)
  • 6. August 2018: Pie (9, API 28), gesture navigation
  • 3. September 2019: Android 10 (API 29), dark mode, scoped storage. First version without a dessert name
  • 8. September 2020: Android 11 (API 30), 5G support, conversation notifications
  • 4. October 2021: Android 12 (API 31), Material You as an update of Material Design
  • 15. August 2022: Android 13 (API 33), notification permission, Bluetooth LE Audio
  • 4. October 2023: Android 14 (API 34), customizable lock screen, partial photo and video access
  • 3. September 2024: Android 15 (API 35), private space, edge-to-edge enforced for apps targeting API 35
  • 10. June 2025: Android 16 (API 36), Material 3 Expressive, enhanced Do Not Disturb mode, embedded photo picker
  • 16. June 2026: Android 17 (API 37), App Lock for individual apps, new contacts picker with session-only access, split notification and quick settings panels

5 What comes with Android?

  • Basic system tools, e.g. dialer, address book...
  • Media support: H.264, H.265 HEVC, AV1, MP4, MP3, AAC, WebM, PNG, JPEG, WebP...
  • Messaging: SMS, MMS, RCS
  • Connectivity: Wi-Fi, GSM/EDGE, UMTS, LTE, 5G, Bluetooth, NFC, UWB
  • 2D/3D graphics (OpenGL ES, Vulkan)
  • Multitasking
  • Additional Hardware: Camera, GPS, accelerometer, gyroscope, magnetometer...

center

Source: giphy.com

Android Architecture

1 Android Platform Challenges

  • Small / cheap devices with
    • Low processing power
    • Little memory
    • Battery driven
    • Slow or missing network connectivity
  • Various hardware (Smartphone, Tablet, Watch, Infotainment, TV, car, exotic devices, thousands of brands)
    • Requires platform abstraction
  • Long device lifetimes: an app must run on Android 8 (2017) and Android 17 (2026) at the same time

2 Android Platform Architecture

  • Based on the Linux kernel. Since Android 12, devices shipping with kernel 5.10 or newer must use the Generic Kernel Image (GKI): one kernel per LTS version (android14-6.1, android15-6.6, android16-6.12, android17-6.18), vendor code lives in loadable modules with a stable interface (KMI)
  • Hardware Abstraction Layer (HAL) providing standardized interfaces for the device's hardware
  • Android Runtime (ART), since 5.0 the successor of the Dalvik VM
  • Native libraries written in C/C++ (OpenGL ES, Vulkan, WebView, Media, SQLite)
  • Java API Framework for apps: Activities, Content Providers, Views, Notifications ... (used from Kotlin)
  • System apps use the same APIs as your apps
  • See: Android Developers - Platform Architecture

60%

3 Dalvik → ART

Dalvik VM (Android 1.0 - 4.4) Android Runtime, ART (default since 5.0)
Register based VM (not stack based like the JVM) Same, executes the same DEX bytecode
Interpreter, trace based JIT since 2.2 Ahead-of-time (AOT) compilation to native code at install time (dex2oat)
Install-time optimisation of the DEX file (dexopt) Since 7.0: JIT compiler + profile-guided compilation: hot methods are recorded while the app runs and precompiled when the device is idle and charging
Mark-and-sweep GC with long pauses Concurrent, generational GC with short pauses
No debugging of optimized code Better debugging and profiling support (sampling profiler, heap dumps)
  • Both runtimes read .dex files (Dalvik Executable): all classes of an app in one file, shared constant pool, smaller than the sum of the .class files and mapped read-only into every process
  • Your Kotlin code is compiled to JVM bytecode, then to DEX by D8 (see toolchain). The runtime never sees .class files
  • See: Android Runtime (ART) and Dalvik

4 Dex File Format

80%

Android Developer Toolchain

1 Android Studio and the Android SDK

  • Android Studio (2026.1): the official IDE, based on IntelliJ IDEA
  • Android SDK, installed and updated via the SDK Manager
    • SDK Platforms: one per API level (android-36), the compileSdk of your project
    • Platform Tools: adb (Android Debug Bridge), fastboot
    • Build Tools: aapt2 (compiles and packages resources, generates the R class), D8 (.class → .dex), R8 (shrinks, obfuscates and optimizes release builds), apksigner, zipalign
    • Emulator and Device Manager (Android Virtual Devices, AVD)
  • Android NDK for C/C++ code (not needed in this course)
  • The build itself is driven by Gradle and the Android Gradle Plugin (AGP), not by the IDE
  • Everything can be run from the command line: ./gradlew assembleDebug, ./gradlew connectedAndroidTest

2 Android Debug Bridge (adb)

  • Command line tool to talk to a device or emulator. Android Studio uses it in the background (Run, Logcat)
  • Find it in platform-tools of your SDK and add it to your PATH:
    • macOS: ~/Library/Android/sdk/platform-tools
    • Linux: ~/Android/Sdk/platform-tools
    • Windows: %LOCALAPPDATA%\Android\Sdk\platform-tools
adb devices                                   # list connected devices and running emulators
adb shell                                     # open a shell on the device (-e: emulator, -d: USB device)
adb install app/build/outputs/apk/debug/app-debug.apk    # install an APK
adb uninstall de.hdmstuttgart.movietracker   # remove the app including its data (fresh start, used for grading)
adb push <local> <remote>                     # copy a file or directory to the device
adb pull <remote> [<local>]                   # copy a file or directory from the device
adb logcat                                    # stream the device log (same as the Logcat window)
adb logcat -s MovieTracker                    # only lines with the tag MovieTracker
  • Write to the log from Kotlin with Log.d("MovieTracker", "clicked $counter times")

Android Development Basics

  • Project structure and the Manifest
  • Gradle: SDK versions, applicationId vs. namespace, version catalog
  • Jetpack Compose
  • Managing State
  • Resources (strings, images, etc.) and the R class
  • AndroidX and legacy View-based layouts
  • Android Build Process

1 Project Structure

MovieTracker/
├── settings.gradle.kts                 modules of the project (here only :app) and repositories
├── build.gradle.kts                    root build file: declares the plugins (apply false)
├── gradle/libs.versions.toml           version catalog: all library and plugin versions
├── local.properties                    SDK path (and secrets), NOT committed
└── app/
    ├── build.gradle.kts                module build file: SDK versions, dependencies
    └── src/
        ├── main/
        │   ├── AndroidManifest.xml     app components, permissions
        │   ├── java/de/hdmstuttgart/movietracker/   Kotlin sources (yes, the folder is called java)
        │   └── res/                    resources: values/strings.xml, drawable/, mipmap-*/, xml/
        ├── test/                       local JVM unit tests
        └── androidTest/                instrumented tests running on a device (Assignment1Test.kt)
  • Choose the "Android" view in the project tool window to hide the Gradle noise, "Project" view shows the real files
  • build/ folders and .gradle/ are generated, add them to .gitignore

2 Android Manifest

  • app/src/main/AndroidManifest.xml, one per module, read by the system when the app is installed
  • It describes the components of the application (activities, services, broadcast receivers, content providers) and names the classes that implement them
  • It defines the Intents that these components can handle (intent filters)
  • It declares the permissions the app needs (<uses-permission>)
  • It names the Application class, the launcher icon, the app label and the theme
  • The app id and the SDK versions are not in the manifest any more: applicationId, namespace, minSdk and targetSdk are set in build.gradle.kts and merged into the manifest at build time
  • Libraries ship their own manifests, the build merges them into one (Merged Manifest tab in Android Studio)

2.1 Android Manifest Structure

<?xml version="1.0" encoding="utf-8"?>
<manifest>
    <uses-permission/>
    <application>
        <activity>
            <intent-filter>
                <action/>
                <category/>
                <data/>
            </intent-filter>
            <meta-data/>
        </activity>
        <service>
            <intent-filter>...</intent-filter>
            <meta-data/>
        </service>
    </application>
</manifest>

2.2 Android Manifest Example: MovieTracker

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <!-- Needed to call the OMDb API and to load the poster images -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name=".MovieTrackerApplication"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MovieTracker"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.MovieTracker">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

2.3 Android Manifest Example: What the Elements Mean

  • <uses-permission android:name="android.permission.INTERNET">: without it every network call fails (assignment 5). Dangerous permissions (camera, location) additionally need a runtime request (lecture 6)
  • <application android:name=".MovieTrackerApplication">: your own Application subclass, created before any other component. MovieTracker uses it to hold the dependencies (assignment 4). The leading . is short for the namespace
  • android:icon, android:label, android:theme: resource references @mipmap/..., @string/..., @style/...
  • <activity android:name=".MainActivity">: every Activity must be declared here, otherwise startActivity() crashes with an ActivityNotFoundException (assignment 1: ResultActivity)
  • android:exported="true": the Activity may be started by other apps (the launcher). Required explicitly for every component with an intent filter since Android 12
  • <intent-filter> with action.MAIN + category.LAUNCHER: this Activity gets the icon in the app drawer
  • tools:targetApi="31": only silences a lint warning, the tools namespace is stripped from the build

3 Gradle Build Files: app/build.gradle.kts

  • Build files are written in the Kotlin DSL (KTS), the default for new projects since AGP 8.1. Older projects and tutorials use Groovy (build.gradle)
  • Since AGP 9 the Kotlin compiler is built in: no separate org.jetbrains.kotlin.android plugin is applied any more
// app/build.gradle.kts of the MovieTracker reference solution
plugins {
    alias(libs.plugins.android.application)   // AGP 9.1: builds an app, compiles Kotlin
    alias(libs.plugins.kotlin.compose)        // Compose compiler plugin, version follows Kotlin
    alias(libs.plugins.ksp)                   // Kotlin Symbol Processing, needed by Room (lecture 11)
}
// build.gradle.kts in the project root: declare the plugins once, modules apply them
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.compose) apply false
    alias(libs.plugins.ksp) apply false
}
  • libs.plugins.xyz are aliases from the version catalog (next slides)
  • Find more information here: Configure your build

4 SDK Versions in build.gradle.kts

android {
    namespace = "de.hdmstuttgart.movietracker"      // package of the generated R and BuildConfig classes
    compileSdk = 36                                 // API you compile against: which classes/methods you can see

    defaultConfig {
        applicationId = "de.hdmstuttgart.movietracker"   // unique id of the app on the device and in the store
        minSdk = 26                                 // oldest Android version the app can be installed on
        targetSdk = 36                              // newest version whose behaviour changes you have tested
        versionCode = 1                             // integer, must grow with every release
        versionName = "1.0"                         // string shown to users
    }
}
  • Every Android version has an integer API level: Android 8.0 = 26, Android 16 = 36, Android 17 = 37
  • compileSdk: always the newest stable API, otherwise new APIs and library versions are not available
  • targetSdk: tells the system "I know about the rules up to this version". Google Play requires a recent targetSdk for new apps and updates. Raising it can change behaviour (permissions, edge-to-edge, background limits)
  • minSdk: trade-off between reach and effort. API 26 still covers almost every device in use; calling an API that is newer than minSdk needs an if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.X) check (lint warns you)
  • The starter projects use 36 / 36 / 26: the Compose BOM of 2026 needs a current compileSdk

5 applicationId vs. namespace

  • applicationId (Gradle defaultConfig): the identity of the app
    • Must be unique on the device and in the Play Store, never changes after the first release
    • Used by adb uninstall de.hdmstuttgart.movietracker and as the name of the app's private data folder
    • Variants may change it: applicationIdSuffix = ".debug" lets debug and release build coexist
  • namespace (Gradle android block): the Kotlin package of the generated code (R, BuildConfig)
    • Resolves the relative class names in the manifest (.MainActivity → de.hdmstuttgart.movietracker.MainActivity)
    • The old package attribute of <manifest> was removed with AGP 8, the namespace lives in Gradle now
  • Your own source packages are independent of both, but by convention start with the namespace
  • The UI tests of all assignments expect exactly de.hdmstuttgart.movietracker for both

6 Version Catalog: gradle/libs.versions.toml

[versions]
agp = "9.1.0"
kotlin = "2.4.10"
composeBom = "2026.06.01"
navigationCompose = "2.9.8"

[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
// app/build.gradle.kts: Gradle generates the type-safe accessor `libs`, dashes become dots
dependencies {
    implementation(platform(libs.androidx.compose.bom))    // Bill of Materials: picks matching Compose versions
    implementation(libs.androidx.material3)                // no version: it comes from the BOM
    implementation(libs.androidx.navigation.compose)       // alias androidx-navigation-compose
}
  • One file for all versions, shared by all modules. Android Studio highlights available updates in the catalog
  • Lectures 11 and 12 add libs.androidx.room.*, libs.retrofit and libs.coil.compose the same way
  • See: Migrate your build to version catalogs

7 Jetpack Compose

  • The modern toolkit for building native Android UI, Kotlin only, part of Android Jetpack
  • Declarative: you describe what the UI looks like for a given state, Compose figures out how to update it
  • Less code, no XML layouts, no findViewById, previews directly in Android Studio (@Preview)
  • Material 3 components (Button, TextField, Scaffold, TopAppBar, ...) are included
  • Interoperable with the old View system (AndroidView, ComposeView) for existing apps
  • Versions are managed by the Compose BOM (libs.androidx.compose.bom) and the Compose compiler plugin

8 Comparison Layout XML and Jetpack Compose

8.1 The old Layout XML way

<?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:id="@+id/main"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              tools:context=".MainActivity">

    <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"/>

    <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button"/>

</LinearLayout>

8.2 The new Jetpack Compose way

@Composable
fun HelloWorld() {
    Column(modifier = Modifier.fillMaxSize()) {
        Text(text = "Hello World!")
        Button(onClick = { /*TODO*/ }) {
            Text(text = "Button")
        }
    }
}
  • Composable functions are named in PascalCase like classes (HelloWorld, MovieItem), lint warns otherwise
  • They return Unit and emit UI instead of returning a view
  • The XML needs Java/Kotlin code in addition to wire the button, the Compose version is complete

8.3 Basics of Composable Functions

A composable function always has the annotation @Composable. Within the function the basic building blocks are declared and describe the UI. It might contain several UI elements.

@Composable
fun MovieItem(movie: Movie, onMovieClick: (Movie) -> Unit) {
    Image(
        painter = painterResource(R.drawable.ic_movie_placeholder),
        contentDescription = null
    )
    Text(text = movie.title)
    Text(text = movie.year)
}

center

Without a layout the three elements are drawn on top of each other. In most cases you can just use the standard layout elements Box, Row or Column to place the items on the screen:

@Composable
fun MovieItem(movie: Movie, onMovieClick: (Movie) -> Unit) {
    Row {
        Image(
            painter = painterResource(R.drawable.ic_movie_placeholder),
            contentDescription = null
        )
        Text(text = movie.title)
        Text(text = movie.year)
    }
}

center

Note: the screenshots show the real poster loaded from the web with the Coil library, which is introduced in lecture 12. Until then we use a drawable from res/drawable as placeholder.

8.4 Alignment and Nesting

There are also parameters to align the elements accordingly:

@Composable
fun MovieItem(movie: Movie, onMovieClick: (Movie) -> Unit) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(
            painter = painterResource(R.drawable.ic_movie_placeholder),
            contentDescription = null
        )
        Text(text = movie.title)
        Text(text = movie.year)
    }
}

center

You can also nest layouts like you need them:

@Composable
fun MovieItem(movie: Movie, onMovieClick: (Movie) -> Unit) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(
            painter = painterResource(R.drawable.ic_movie_placeholder),
            contentDescription = null
        )
        Column {
            Text(text = movie.title)
            Text(text = movie.year)
        }
    }
}

center

8.5 The Modifier

A special parameter is the Modifier. It changes size, layout and behaviour of an element and adds information such as accessibility labels or test tags. It also makes elements clickable, which finally uses the onMovieClick parameter:

@Composable
fun MovieItem(movie: Movie, onMovieClick: (Movie) -> Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .testTag("movieItem")                 // the UI test finds the item by this tag
            .clickable { onMovieClick(movie) }    // whole row reacts to taps
            .padding(8.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Image(
            painter = painterResource(R.drawable.ic_movie_placeholder),
            contentDescription = null,
            modifier = Modifier.width(80.dp).padding(8.dp)
        )
        Column {
            Text(text = movie.title, fontSize = 20.sp)
            Text(text = movie.year)
        }
    }
}

center

  • Order matters: modifiers are applied from left to right, each one wraps the element produced so far. .clickable { }.padding(8.dp) makes the padding part of the touch area, .padding(8.dp).clickable { } does not
  • fillMaxWidth(), width(), height(), padding(), background(), clickable(), testTag(), weight() (inside Row/Column) are the ones you will use most
  • Convention: a composable accepts modifier: Modifier = Modifier as first optional parameter and passes it to its root element, so the caller can position it. See Compose modifiers

9 Material Components

  • Each app uses UI components. On Android these are the Material 3 components of androidx.compose.material3
  • The most popular ones are:
    • Scaffold which provides the structure of a screen (app bar, content, floating action button) and the innerPadding you must apply to the content so it does not hide behind system bars
    • TopAppBar to display a title and provide navigation
    • Text to display text
    • Button to make clickable elements, with a composable content lambda for its label
    • TextField / OutlinedTextField for text input
    • LazyColumn / LazyRow for scrollable lists (lecture 7, assignment 2)
    • HorizontalDivider, Card, Checkbox, Switch, Slider, LinearProgressIndicator, ...
  • MaterialTheme provides colors, typography and shapes: MaterialTheme.typography.titleMedium, MaterialTheme.colorScheme.error. The theme of your project lives in ui/theme/Theme.kt

10 The Activity and setContent

The MainActivity is the entry point of the UI. onCreate() belongs to the Activity lifecycle (lecture 6) and is called by the system. In there you call the first composable inside setContent { }:

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()                 // draw behind the system bars (default since Android 15)

        setContent {                       // the Compose tree replaces setContentView(R.layout.xyz)
            MovieTrackerTheme {            // Material theme generated by the project template
                MovieTrackerApp()          // your first composable
            }
        }
    }
}
  • ComponentActivity comes from androidx.activity, setContent from androidx.activity.compose
  • Everything inside setContent is Compose, the Activity itself stays a thin host
  • The manifest entry with the MAIN/LAUNCHER intent filter decides which Activity starts first

11 Managing State: remember and mutableStateOf

  • All apps display state: the counter of assignment 1, the list of saved movies, the text in a search field
  • A composable is a function. A plain var counter = 0 inside it is reset every time the function runs again
  • Compose needs two things: a value that survives re-runs (remember) and one that Compose can observe (mutableStateOf)
@Composable
fun ClickCounter() {
    var counter by remember { mutableIntStateOf(0) }    // State<Int>: observed, kept across recompositions

    Column {
        Text(text = "Clicked $counter times")          // reads the state
        Button(onClick = { counter++ }, modifier = Modifier.testTag("button")) {   // writes the state
            Text(text = "Button")
        }
    }
}
  • Three equivalent syntaxes (see property delegation in the Kotlin lecture):
    • val counterState = remember { mutableStateOf(0) } and use counterState.value
    • var counter by remember { mutableStateOf(0) } (needs import androidx.compose.runtime.getValue/setValue)
    • val (counter, setCounter) = remember { mutableStateOf(0) } (destructuring)
  • mutableIntStateOf, mutableLongStateOf, ... avoid boxing for primitives; mutableStateOf works for every type

12 Recomposition

  • Recomposition = Compose calls your composable functions again to update the UI
  • Trigger: a MutableState that was read during composition is written (counter++)
  • Only the composables that read the changed state are re-executed (Text), not the whole screen
  • Consequences for your code:
    • Composables may run very often and in any order: keep them fast and free of side effects (no network calls, no Log spam, no object creation that must happen once)
    • Local variables are recreated on each run, state must be wrapped in remember { }
    • Mutating a normal object (list.add(...)) is invisible to Compose, only state objects trigger recomposition
  • remember stores the value in the composition, keyed by its position in the UI tree. Leaving the screen (or removing the item from a LazyColumn) forgets it
  • Compose also skips a composable when its parameters have not changed, which is why immutable data classes are a good fit. See Thinking in Compose

13 rememberSaveable: Surviving Rotation

  • remember lives as long as the composable is on screen. When the Activity is recreated (device rotation, dark mode switch, language change, process killed in the background) the whole composition is rebuilt and the state is lost: the counter jumps back to 0
  • rememberSaveable additionally stores the value in the saved instance state Bundle and restores it
  • Works out of the box for Int, String, Boolean, ..., Parcelable and Serializable; other types need a Saver
// assignment 1 (results/assignment1/MainActivity.kt)
var counter by rememberSaveable { mutableIntStateOf(0) }

// SearchScreen.kt: the text in the search field survives a rotation
var searchText by rememberSaveable { mutableStateOf("") }
  • Rule of thumb: UI state that the user would miss after a rotation → rememberSaveable; transient things (animation progress, scroll offset of a list that remembers itself) → remember
  • State that must survive longer or is shared between screens moves into a ViewModel (lecture 11)

14 State Hoisting and Unidirectional Data Flow

  • Stateful composable: owns state with remember. Stateless composable: gets the value and a callback as parameters. Hoisting = moving the state up to the lowest common ancestor that needs it
  • State flows down, events flow up (unidirectional data flow): a child never changes state itself, it reports an event through a lambda and the owner changes the state
@Composable
fun ClickCounter() {                                        // stateful: owns the state
    var counter by rememberSaveable { mutableIntStateOf(0) }
    CounterContent(counter = counter, onIncrement = { counter++ })
}

@Composable
fun CounterContent(counter: Int, onIncrement: () -> Unit) {  // stateless: reusable, testable, previewable
    Column {
        Text(text = "Clicked $counter times")
        Button(onClick = onIncrement, modifier = Modifier.testTag("button")) { Text(text = "Button") }
    }
}
  • Every Compose input works like this. A TextField does not store its text, the caller does:
@Composable
fun HelloContent() {
    var name by rememberSaveable { mutableStateOf("") }                   // owner of the state
    OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("Name") })  // value down, event up
    if (name.isNotEmpty()) Text(text = "Hello, $name!")
}
  • SearchScreen.kt does the same: TextField(value = searchText, onValueChange = { searchText = it })

  • Later the ViewModel becomes the owner (viewModel.searchMovies(searchText)), the composables stay the same

15 State for Lists: mutableStateListOf

// results/assignment2/MainActivity.kt
@Composable
fun MovieList(modifier: Modifier = Modifier) {
    val movieList = remember {
        mutableStateListOf(                              // SnapshotStateList<Movie>: observed by Compose
            Movie("Dr. No", "1962", "Sean Connery"),
            Movie("From Russia with Love", "1963", "Sean Connery")
        )
    }
    LazyColumn(modifier = modifier) {
        items(items = movieList) { movie ->
            MovieItem(movie = movie, onMovieClick = { movieList.remove(it) })   // triggers recomposition
        }
    }
}
  • remember { mutableListOf(...) }: the list survives recomposition, but remove() is not observed, the LazyColumn never updates. A classic bug in assignment 2
  • remember { mutableStateOf(listOf(...)) }: works, but only when you assign a new list (movies = movies - movie), mutating the old one changes nothing
  • mutableStateListOf() / mutableStateMapOf(): observed list and map, add/remove recompose
  • From assignment 4 on the list comes from a database Flow and is collected as state (lectures 10 and 11)

16 Resource Types and Directories

  • Resources are everything that is not code: strings, images, colors, themes, raw files ...
  • Each module keeps them in app/src/main/res/, grouped by type:
    • strings in res/values/strings.xml, colors and themes in res/values/colors.xml, themes.xml
    • vector icons and images in res/drawable/ (ic_movie_placeholder.xml, PNG/WebP/XML vector drawables)
    • launcher icons only in res/mipmap-*/ (the system scales them for the home screen; do not put images here)
    • other XML like backup rules in res/xml/ (backup_rules.xml, data_extraction_rules.xml)
    • res/layout/ for View-based XML layouts, not needed with Jetpack Compose
  • Resources are referenced from XML with @string/app_name, @drawable/ic_movie_placeholder and from Kotlin via the R class:
Image(painter = painterResource(R.drawable.ic_launcher_foreground), contentDescription = null)
Text(text = stringResource(R.string.app_name))
Text(text = stringResource(R.string.search_error, message))     // "Could not load movies: %1$s"
<!-- res/values/strings.xml: never hard-code user visible text, strings.xml allows translations -->
<resources>
    <string name="app_name">MovieTracker</string>
    <string name="search_error">Could not load movies: %1$s</string>
</resources>

17 Resource Qualifiers

  • Problem: many different Android devices with different screen sizes and resolutions exist
  • Thus, there must be a possibility to define different resources depending on the screen size and resolution of a device, the language, the orientation or the dark mode
  • Resource qualifiers allow us to define which resource should be used for which configuration and Android will automatically use the right one at runtime

18 Resource Qualifiers

  • Resources can be configuration specific by using resource qualifiers
  • To specify configuration-specific alternatives for a set of resources:
  1. Create a new directory in res/ named in the form <resources_name>-<config_qualifier>

    • <resources_name> is the directory name of the corresponding default resources
    • <qualifier> is a name that specifies an individual configuration for which these resources are to be used
    • More than one qualifier can be appended, but the order must be correct
  2. Save the respective alternative resources in this new directory. The resource files must be named exactly the same as the default resource files

19 Resource Qualifiers

  • Example for default and alternative resources:
res/
    drawable/
        background.png
    drawable-hdpi/
        background.png
    values/
        strings.xml
    values-de/
        strings.xml
    values-night/
        colors.xml
  • The hdpi qualifier indicates that the resources in that directory are for devices with a high-density screen, de selects the German strings, night the dark mode colors
  • Vector drawables (.xml) scale without density folders, which is why icons are vectors today
  • A list of valid configuration qualifier names can be found here

20 Density Independent Design

center

21 Density Independent Design

  • Sizes of UI components should be defined in Density Independent Pixels (dp) in order to always get the best results.

    More information here.

center

22 Density Independent Design

  • A density independent pixel (dp) describes the size of a UI element in a density independent manner
  • At 160ppi, one dp corresponds to one physical pixel (px)
  • For devices with densities other than 160ppi, the dp values are converted with the help of the densityFactor
  • The densityFactor describes the device's physical density in relation to the default density of 160ppi
  • Text sizes use sp (scale-independent pixels): like dp, but additionally scaled by the user's font size setting
  • In Compose you write Modifier.width(80.dp) and fontSize = 20.sp, the conversion to px is done for you
  • Obtaining the densityFactor at runtime:
val densityFactor = LocalDensity.current.density

23 The R Class

  • The mapping between resource files and code is the generated class R: one nested class per resource type (R.string, R.drawable, R.mipmap, ...) with an Int constant per resource
  • Generated by aapt2 on every build into the namespace package (de.hdmstuttgart.movietracker.R). There is no R.java file in your source tree, it lives in app/build/ and you never edit it
  • Since AGP 8 the R class is non-transitive: it only contains the resources of its own module. Resources of a library are referenced via the library's R class (androidx.compose.material3.R)
  • A resource id is just an Int that the resource framework resolves at runtime for the current configuration
val titleId: Int = R.string.app_name                       // e.g. 0x7f0f0000
Text(text = stringResource(R.string.app_name))             // Compose: picks the right strings.xml
val label = LocalContext.current.getString(R.string.app_name)   // classic framework call
  • If the IDE shows R in red: the build has not run yet or a resource file contains an error (check the Build tab)

24 AndroidX and Jetpack

  • Problem: many older Android versions are still in use. How can we use current APIs and still support them?
  • Solution: Jetpack / AndroidX libraries
    • Shipped with the app instead of the OS, updated independently of the Android version
    • Backward compatible implementations of framework features plus everything new: Compose, Navigation, Room, ViewModel, Lifecycle, WorkManager, ...
    • Package prefix androidx.*, versions in the version catalog
  • Successor of the deprecated Android Support Libraries (android.support.*, com.android.support:appcompat-v7), which you still meet in old code and Stack Overflow answers
  • Allowed in this course: everything from AndroidX. Third-party libraries are limited to Retrofit, Gson, Coil and Google Maps (see course FAQ)

25 ComponentActivity vs. AppCompatActivity

  • androidx.activity.ComponentActivity
    • Base class for Compose apps, used by the Android Studio "Empty Activity" template and in this course
    • Provides the lifecycle, ViewModelStore, saved state, OnBackPressedDispatcher and the Activity Result API
    • setContent { } is an extension function from androidx.activity:activity-compose
  • androidx.appcompat.app.AppCompatActivity (extends FragmentActivity, which extends ComponentActivity)
    • Adds AppCompat theming, the ActionBar, Fragment support and backports of View widgets
    • Needed for View-based (XML) apps and for mixing Fragments with Compose
    • Still correct in Compose apps, but brings a dependency and a theme requirement you do not need
  • Rule for this course: extend ComponentActivity, keep the Activity thin and put the UI into composables

26 Legacy: View-based Layouts

  • Before Compose a screen was an XML file in res/layout/, inflated into a tree of View objects (setContentView(R.layout.activity_main)), accessed with findViewById() or View Binding
  • Layout containers (ViewGroups) you will meet in existing apps and in old tutorials:
    • LinearLayout: children in one row or column, layout_weight distributes space
    • RelativeLayout: children positioned relative to each other or the parent
    • FrameLayout: children stacked on top of each other
    • ConstraintLayout (AndroidX): flat hierarchy, children positioned by constraints, built with the Layout Editor
    • RecyclerView: the list component, replaced by LazyColumn (lecture 7)
  • Compose equivalents: Column/Row (LinearLayout), Box (FrameLayout), ConstraintLayout also exists for Compose
  • Mixing is possible: AndroidView { } embeds a View in Compose, ComposeView embeds Compose in XML (see Interoperability APIs)

27 Android Build Process

  • The Kotlin compiler turns your sources into JVM .class files
  • D8 converts the .class files (and those of all libraries) into .dex files; in release builds R8 additionally shrinks, obfuscates and optimizes the code (isMinifyEnabled = true)
  • aapt2 compiles the resources, generates the R class and the binary AndroidManifest.xml
  • The APK packager combines DEX files, compiled resources, native libraries and the manifest into an APK (or an App Bundle .aab for the Play Store)
  • The APK is signed with the debug keystore (automatically) or your release keystore
  • The APK is zip-aligned so that the system can map it into memory efficiently
  • adb install copies it to the device, ART compiles it on installation and while it runs
  • See: Configure your build

60%

28 Android Build Process: Gradle

  • The whole pipeline is orchestrated by Gradle with the Android Gradle Plugin (AGP)
  • ./gradlew assembleDebug builds app/build/outputs/apk/debug/app-debug.apk, ./gradlew installDebug also installs it, ./gradlew connectedAndroidTest runs the UI tests on a connected device
  • Android Studio runs exactly these Gradle tasks when you press Run; the Build tab shows the output
  • Build files: settings.gradle.kts (modules, repositories), root build.gradle.kts (plugins), app/build.gradle.kts (SDK versions, build types, dependencies), gradle/libs.versions.toml (versions)
  • The Gradle version itself is pinned in gradle/wrapper/gradle-wrapper.properties (Gradle 9.3 for AGP 9.1), so everybody builds with the same Gradle without installing it
  • After changing a build file click Sync Project with Gradle Files, otherwise the IDE does not know about new dependencies
  • Find more information here: Android build overview

29 Android Assignment 1

Assignment 1: create the MovieTracker project, a counter with Text and Button, and open a second Activity after 5 clicks.

What you need from this lecture:

  • Project setup: package name de.hdmstuttgart.movietracker, Kotlin DSL, minSdk 26, an AVD in the Device Manager
  • Column, Text and Button inside setContent { MovieTrackerTheme { ... } }
  • State: var counter by rememberSaveable { mutableIntStateOf(0) } and counter++ in onClick; the Text recomposes by itself
  • Modifier.testTag("button") on the Button, the UI test finds it by this tag
  • Every Activity goes into AndroidManifest.xml: <activity android:name=".ResultActivity" />
  • Starting the second Activity with an explicit Intent and reading the extra is explained in lecture 6 (Android Framework Basics); the assignment text contains the snippet you need
  • Run the test: Assignment1Test.kt in app/src/androidTest/, right-click → Run, or ./gradlew connectedAndroidTest

30 Summary

  • Android is an open-source OS based on the Linux kernel (Generic Kernel Image, LTS kernels) that runs on far more device types than smartphones. Android 17 (API 37) is the current release
  • Apps are written in Kotlin, compiled to DEX bytecode and executed by ART, which combines ahead-of-time, JIT and profile-guided compilation
  • The toolchain is Android Studio + SDK (adb, D8/R8, aapt2) + Gradle with the Android Gradle Plugin; the project's SDK versions (compileSdk/targetSdk/minSdk), applicationId and namespace live in build.gradle.kts, library versions in gradle/libs.versions.toml
  • The manifest declares components (every Activity!), permissions and the launcher intent filter
  • Modern Android apps build their UI with Jetpack Compose: @Composable functions in PascalCase, layouts with Column/Row/Box, Modifiers in a meaningful order, Material 3 components
  • UI state lives in remember { mutableStateOf() } (or rememberSaveable to survive rotation), writes trigger recomposition; state is hoisted and flows down while events flow up; lists use mutableStateListOf
  • Resources live in res/ (strings in values/strings.xml, images in drawable/, launcher icons in mipmap-*/) and are accessed through the generated, non-transitive R class; qualifiers select alternatives per configuration
  • AndroidX/Jetpack ships the modern APIs with the app; Compose apps extend ComponentActivity

31 Recap Questions

  • What does ART do at install time and what does it do while the app is running? Why does an app not ship .class files?
  • What is the difference between compileSdk, targetSdk and minSdk? Which one decides whether a user can install the app?
  • applicationId vs. namespace: which one must never change after the first release and why?
  • What happens when you start an Activity that is not declared in the manifest? What does android:exported="true" mean?
  • Why does var counter = 0 inside a composable not work as a counter? What do remember and mutableStateOf add, and when do you need rememberSaveable?
  • Explain "state flows down, events flow up" using TextField(value = ..., onValueChange = ...).
  • Why does remember { mutableListOf(...) } not update a LazyColumn when an item is removed, and what do you use instead?
  • In which res/ directories do strings, images and launcher icons belong? What are resource qualifiers used for?
  • What is the role of the R class on Android? By whom and when is it created?
  • What does libs.androidx.navigation.compose in build.gradle.kts refer to?

Questions?