Initial Movies UI

Refactor Scaffold Use

Before we flesh out the screens, we'll make a couple of helper functions and refactor our use of the Scaffold.

First, we create a couple of helper components for consistency. Display() and Label() are just Text()s with some parameters set for styling and padding. Using this approach of creating wrapper functions gives us "extended" components.

show in full file app/src/main/java/com/androidbyexample/compose/movies/components/Display.kt
// ...
import androidx.compose.ui.unit.dp

@Composable
fun Display(
    text: String,
    modifier: Modifier = Modifier,
) {
    Text(
        text = text,
        style = MaterialTheme.typography.titleLarge,
        modifier = modifier
            .padding(8.dp)
            .padding(start = 16.dp)
            .fillMaxWidth(),
    )
}
show in full file app/src/main/java/com/androidbyexample/compose/movies/components/Label.kt
// ...
import androidx.compose.ui.unit.dp

@Composable
fun Label(
    @StringRes textId: Int,
    modifier: Modifier = Modifier,
) {
    Text(
        text = stringResource(id = textId),
        style = MaterialTheme.typography.titleMedium,
        modifier = modifier
            .padding(8.dp)
            .fillMaxWidth(),
    )
}

Note

Note how Display has two padding modifiers. These are applied in the order they're defined. In this case, we apply an 8dp padding all around, and then an extra 16dp padding only at the start of the text. This causes the text to appear indented.

If we did something like

Modifier
    .padding(8.dp)
    .border(2.dp, Color.Blue, RoundedCornerShape(4.dp))
    .padding(8.dp)

we'd see 8dp padding, a blue border inside it, and 8dp more padding inside the border. This is a much simpler system than the margin/padding combinations that existed with the old "views" UI approach.

We're going to want more control of the Scaffolding, per-screen, so we'll remove it from MainActivity

show in full file app/src/main/java/com/androidbyexample/compose/movies/MainActivity.kt
// ...
class MainActivity : ComponentActivity() {
    // ...
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        setContent {
            MoviesTheme {
//              Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
//                  Ui (
                Ui(
                        viewModel = viewModel,
//                      modifier = Modifier.padding(innerPadding),
                    ) {
                        finish()
                }
//          }
        }
    }
    }
}

and add it in our MovieDisplay()

show in full file app/src/main/java/com/androidbyexample/compose/movies/screens/MovieDisplay.kt
// ...

@OptIn(ExperimentalMaterial3Api::class) // for TopAppBar
@Composable
fun MovieDisplayUi(
    // ...
    modifier: Modifier = Modifier,
) {
    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text(text = movie.title)
                }
            )
        },
        modifier = modifier,
    ) { innerPadding ->
    Text(
        text = "Movie Display: ${movie.title}",
//      modifier = modifier,
            modifier = Modifier.padding(innerPadding),
    )
    }
}

and our MovieList()

show in full file app/src/main/java/com/androidbyexample/compose/movies/screens/MovieList.kt
// ...

@OptIn(ExperimentalMaterial3Api::class) // for TopAppBar
@Composable
fun MovieListUi(
    // ...
    onMovieClicked: (Movie) -> Unit,
) {
    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text(text = stringResource(R.string.movies))
                }
            )
        },
        modifier = modifier,
    ) { innerPadding ->
    Text(
        text = "Movie List",
//      modifier = modifier.clickable {
            modifier = Modifier
                .padding(innerPadding)
                .clickable {
            onMovieClicked(movies[0])
        }
    )
    }
}

Note that this also required moving the Modifier.padding() setup.

The Scaffold is a "Slot API" composable function. This is an implementation of the Template Method Pattern, an algorithm with replaceable steps. The Scaffold defines an algorithm for managing several "slots" on the screen, areas like "stuff at the top", "stuff at the bottom", "stuff in between", etc. Scaffold defines the layout algorithm for measuring and placing elements in the specified position; you define what goes in those positions.

In this example, we define topBar, passing a lambda for which elements should appear in that section of the Scaffold. When the scaffold is performing its layout work, it calls that lambda to obtain the components, measures them, and places them. (Note that some slotted apis may conditionally execute lambdas such as these; for example, if you define a navigation drawer, it might not appear unless the user has swiped in from the side.)

Our topBar declares that a TopAppBar should appear. This is a common tool bar element, which can contain a title, navigation icon, and action icons. We'll do much more with it in later modules. Here we're just setting a title.

Note

Pay close attention to how the passed-in Modifier is being used. The caller may pass one in to control how the UI for MovieDisplay() or MovieList() appears. We pass the modifier parameter to the Scaffold, as that's the top-level Composable inside these functions.

In the Text(), we start a new Modifier by using upper-case Modifier. This is a nested Composable that we want to fully control.

For the MovieList, we need to define a String resource for the word "Movies". All String literals that appear for the user should be externalized into the strings.xml file:

show in full file app/src/main/res/values/strings.xml
<resources>
    <string name="app_name">Movies</string>
    <string name="movies">Movies</string>
</resources>

This allows easy overriding for alternative device configurations, in particular, localization to other languages. We add a string entry to strings.xml with an id, and the Android Gradle Plugin generates a helper class for typesafe resource references. In our code, we use this generated R class using

stringResource(R.id.movies)

which will fetch the value from the closest-matching resource file.

See [App Resources Overview] for more details on how resource files work. Note that some of the resource types, such as layouts, aren't used when writing Compose code.


All code changes

CHANGED: app/src/main/java/com/androidbyexample/compose/movies/MainActivity.kt
package com.androidbyexample.compose.movies

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
//import androidx.compose.foundation.layout.fillMaxSize
//import androidx.compose.foundation.layout.padding
//import androidx.compose.material3.Scaffold
//import androidx.compose.ui.Modifier
import com.androidbyexample.compose.movies.screens.Ui
import com.androidbyexample.compose.movies.ui.theme.MoviesTheme

class MainActivity : ComponentActivity() {
private val viewModel by viewModels<MovieViewModel>()
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { MoviesTheme {
// Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> // Ui ( Ui( viewModel = viewModel, // modifier = Modifier.padding(innerPadding), ) { finish() } // }
} } } }
ADDED: app/src/main/java/com/androidbyexample/compose/movies/components/Display.kt
package com.androidbyexample.compose.movies.components

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable fun Display( text: String, modifier: Modifier = Modifier, ) { Text( text = text, style = MaterialTheme.typography.titleLarge, modifier = modifier .padding(8.dp) .padding(start = 16.dp) .fillMaxWidth(), ) }
ADDED: app/src/main/java/com/androidbyexample/compose/movies/components/Label.kt
package com.androidbyexample.compose.movies.components

import androidx.annotation.StringRes
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp

@Composable fun Label( @StringRes textId: Int, modifier: Modifier = Modifier, ) { Text( text = stringResource(id = textId), style = MaterialTheme.typography.titleMedium, modifier = modifier .padding(8.dp) .fillMaxWidth(), ) }
CHANGED: app/src/main/java/com/androidbyexample/compose/movies/screens/MovieDisplay.kt
package com.androidbyexample.compose.movies.screens

import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.androidbyexample.compose.movies.Movie

@OptIn(ExperimentalMaterial3Api::class) // for TopAppBar @Composable fun MovieDisplayUi( movie: Movie, modifier: Modifier = Modifier, ) {
Scaffold( topBar = { TopAppBar( title = { Text(text = movie.title) } ) }, modifier = modifier, ) { innerPadding -> Text( text = "Movie Display: ${movie.title}", // modifier = modifier, modifier = Modifier.padding(innerPadding), ) }
}
CHANGED: app/src/main/java/com/androidbyexample/compose/movies/screens/MovieList.kt
package com.androidbyexample.compose.movies.screens

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.androidbyexample.compose.movies.Movie
import com.androidbyexample.compose.movies.R

@OptIn(ExperimentalMaterial3Api::class) // for TopAppBar @Composable fun MovieListUi( movies: List<Movie>, modifier: Modifier = Modifier, onMovieClicked: (Movie) -> Unit, ) {
Scaffold( topBar = { TopAppBar( title = { Text(text = stringResource(R.string.movies)) } ) }, modifier = modifier, ) { innerPadding -> Text( text = "Movie List",
// modifier = modifier.clickable { modifier = Modifier .padding(innerPadding) .clickable { onMovieClicked(movies[0]) }
) }
}
CHANGED: app/src/main/res/values/strings.xml
<resources>
    <string name="app_name">Movies</string>
<string name="movies">Movies</string>
</resources>