Book Image

Kickstart Modern Android Development with Jetpack and Kotlin

By : Catalin Ghita
5 (1)
Book Image

Kickstart Modern Android Development with Jetpack and Kotlin

5 (1)
By: Catalin Ghita

Overview of this book

With Jetpack libraries, you can build and design high-quality, robust Android apps that have an improved architecture and work consistently across different versions and devices. This book will help you understand how Jetpack allows developers to follow best practices and architectural patterns when building Android apps while also eliminating boilerplate code. Developers working with Android and Kotlin will be able to put their knowledge to work with this condensed practical guide to building apps with the most popular Jetpack libraries, including Jetpack Compose, ViewModel, Hilt, Room, Paging, Lifecycle, and Navigation. You'll get to grips with relevant libraries and architectural patterns, including popular libraries in the Android ecosystem such as Retrofit, Coroutines, and Flow while building modern applications with real-world data. By the end of this Android app development book, you'll have learned how to leverage Jetpack libraries and your knowledge of architectural concepts for building, designing, and testing robust Android applications for various use cases.
Table of Contents (17 chapters)
1
Part 1: Exploring the Core Jetpack Suite and Other Libraries
7
Part 2: A Guide to Clean Application Architecture with Jetpack Libraries
13
Part 3: Diving into Other Jetpack Libraries

Building a Compose-based screen

Let's say we want to build an application that showcases some restaurants. We will build the UI with Compose and go through the steps of creating a new Compose project. We will then build a list item for such a restaurant and finally display a dummy list of such items.

To summarize, in this section, we will build our first Compose-based application: a restaurant explorer app! To achieve that, we must display some restaurants, which we will do by covering the following topics:

  • Creating your first Compose project
  • Building a restaurant element layout
  • Displaying a list of restaurants with Compose

Now that we have a clear path, let's get started.

Creating your first Compose project

To build a restaurant app, we have to create a new Compose-based project:

  1. Open Android Studio and select the New Project option:
Figure 1.20 – Starting a new project with Android Studio

Figure 1.20 – Starting a new project with Android Studio

If you already have Android Studio open, go to File, then New, and finally New Project.

Note

Make sure that you have Android Studio version Arctic Fox 2020.3.1 or newer. If you're using a newer version though, some files might have differences in the generated code.

  1. In the Phone and tablet template section, select Empty Compose Activity and then choose Next:

Figure 1.21 – Starting a new project with Android Studio

  1. Next, enter some details about your application. In the Name field, enter Restaurants app. Leave Kotlin as-is for Language and set Minimum SDK to API 21. Then, click Finish.

    Important note

    The upcoming step is an essential configuration step. It makes sure that the project Android Studio has configured for you the same versions of dependencies (from Compose, to Kotlin and other dependencies) that we use throughout the book. By doing so, you will be able to follow the code snippets and inspect the code source without any API differences.

  2. Inside the newly generated project, before inspecting the code, make sure that the generated project uses the versions of dependencies that are used throughout the book.

To do so, first go to the project-level build.gradle file and inside the dependencies block, make sure that the Kotlin version is set to 1.6.10:

buildscript {
    […]
    dependencies {
        classpath "com.android.tools.build:gradle:7.0.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle- 
            plugin:1.6.10"
      […]
    }
}

Alternatively, if you're using a newer version of Android Studio, you might find the Kotlin version used in this project inside the plugins block, like so:

plugins {
    […]
    id 'org.jetbrains.kotlin.android' version '1.6.10' 
        apply false
}

If you haven't already, you might need to install the 1.6.10 plugin version of Kotlin in Android Studio. To do that, click on the Tools option of Android Studio on the Kotlin and on the Configure Kotlin Plugin Updates options. In the newly opened window, you can update your Kotlin version to 1.6.10.

Still in the project-level build.gradle file, because Compose is tied to the Kotlin version used in our project, make sure that the Compose version is set to 1.1.1 inside the ext { } block:

buildscript {
    ext {
        compose_version = '1.1.1'
    }
    repositories {…}
    dependencies {…}
}

Then, move into the app-level build.gradle file. First check that the composeOptions { } block looks like this:

plugins { ... }
android {
    [...]
    buildFeatures { compose true }
    composeOptions {
        kotlinCompilerExtensionVersion compose_version
    }
    packagingOptions { ... }
}

In some versions of Android Studio, the composeOptions { } block would add an outdated kotlinCompilerVersion '1.x.xx' line that should be removed.

Finally, make sure that the dependencies block of the app-level build.gradle file includes the following versions for its dependencies:

dependencies {
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:
        material:1.5.0'
    implementation "androidx.compose.ui:ui:
        $compose_version"
    implementation "androidx.compose.material:
        material:$compose_version"
    implementation "androidx.compose.ui:ui-tooling-
        preview:$compose_version"
    implementation 'androidx.lifecycle:lifecycle-
        runtime-ktx:2.4.1'
    implementation 'androidx.activity:activity-
        compose:1.4.0'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation
        'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 
        'androidx.test.espresso:espresso-core:3.4.0'
    androidTestImplementation "androidx.compose.ui:ui-
        test-junit4:$compose_version"
    debugImplementation "androidx.compose.ui:ui-
        tooling:$compose_version"
}

If you had to make any changes, synchronize your project with its Gradle files by clicking on the Sync your project with Gradle files button in Android Studio or by pressing on the File menu option and then by selecting Sync Project with Gradle Files.

Now we're set. Let's return to the source code generated by Android Studio.

And here we are – our first Compose project has been set up! Let's check out the source code by navigating to the MainActivity.kt file. We can conclude that it consists of three main parts:

  • The MainActivity class
  • The Greeting composable function
  • The DefaultPreview composable function

The MainActivity class is where content is passed to the setContent method in the onCreate callback. As we know by now, we need to call setContent to set up a Compose UI and pass composable functions as our UI:

setContent {
   RestaurantsAppTheme {
       Surface(color = MaterialTheme.colors.background) {
           Greeting("Android")
       }
   }
}

The IDE template has already implemented a Greeting composable that is wrapped into a Surface that uses the theme's background color. But what is that RestaurantsAppTheme function that was passed as the parent composable to the setContent method?

If you press Ctrl + B or Command + B on the function name, you will be taken to the Theme.kt file, which is where our theme is generated. RestaurantsAppTheme is a composable function that was auto-generated by the IDE as it holds the app's name:

@Composable
fun RestaurantsAppTheme(
   darkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() -> Unit
) {
   ...
   MaterialTheme(
       colors = colors,
       typography = Typography,
       shapes = Shapes,
       content = content)
}

The app's theme is a wrapper over MaterialTheme and if we pass it to the setContent call, it allows us to reuse custom styles and color schemes defined within the app's theme. For it to take effect and reuse custom styles, we must pass our composables functions to the content parameter of our theme composable – in our case, in MainActivity, the Greeting composable wrapped in the Surface composable is passed to the RestaurantsAppTheme composable.

Let's go back inside the MainActivity.kt file to have a look at the other parts generated by Android studio. We can see that the Greeting composable displays text through Text, similar to our composable functions from the previous examples.

To preview the Greeting composable, the IDE also generated a preview composable for us called DefaultPreview, which allows us to preview the content that MainActivity displays; that is, Greeting. It also makes use of the theme composable to get the consistently themed UI.

Now that we've achieved a big milestone in that we've created a Compose-based application, it's time to start working on our Restaurants App!

Building a restaurant element layout

It's time to get our hands dirty and start building the layout for a restaurant within the app:

  1. Create a new file by left-clicking the application package and selecting New and then Kotlin Class/File. Enter RestaurantsScreen for the name and select the type as File.
  2. Inside this file, let's create a RestaurantsScreen composable function for our first Compose screen:
    @Composable
    fun RestaurantsScreen() {
       RestaurantItem()
    }
  3. Next, inside the RestaurantsScreen.kt file, let's define the RestaurantItem composable, which features a Card composable with elevation and padding:
    @Composable
    fun RestaurantItem() {
        Card(elevation = 4.dp,
             modifier = Modifier.padding(8.dp)
        ) {
            Row(verticalAlignment =
                    Alignment.CenterVertically,
                modifier = Modifier.padding(8.dp)) {
                RestaurantIcon(
                    Icons.Filled.Place,
                    Modifier.weight(0.15f))
                RestaurantDetails(Modifier.weight(0.85f))
            }
        }
    }

Make sure that every import you include is part of the androidx.compose.* package. If you're unsure what imports to include, check out the source code for the RestaurantsScreen.kt file at the following URL:

https://github.com/PacktPublishing/Kickstart-Modern-Android-Development-with-Jetpack-and-Kotlin/blob/main/Chapter_01/chapter_1_restaurants_app/app/src/main/java/com/codingtroops/restaurantsapp/RestaurantsScreen.kt

Getting back to the previous code snippet, we could say that the Card composable is similar to Cardview from the old View System as it allows us to beautify the UI piece that represents a restaurant with border or elevation.

In our case, Card contains a Row composable whose children composables are centered vertically and are surrounded by some padding. We used Row since we will show some details about the restaurant in a horizontal fashion: an icon and some text details.

We passed the RestaurantIcon and RestaurantDetails composables as children of the Row composable but these functions are not defined so we have compilation errors. For now, don't worry about the weight modifiers. Let's define the RestaurantIcon composable first!

  1. Still inside the RestaurantsScreen.kt file, create another composable function entitled RestaurantIcon with the following code:
    @Composable
    private fun RestaurantIcon(icon: ImageVector, modifier: Modifier) {
       Image(imageVector = icon,
             contentDescription = "Restaurant icon",
             modifier = modifier.padding(8.dp))
    }

The RestaurantIcon composable sets an ImageVector icon to an Image composable – in our case, a predefined Material Theme icon called Icons.Filled.Place. It also sets a contentDescription value and adds padding on top of the modifier it receives.

However, the most interesting part is the fact that RestaurantIcon receives a Modifier as an argument from its parent Row. The argument it receives is Modifier.weight(0.15f), which means that our Row assigns weights to each of its horizontally positioned children. The value – in this case, 0.15f – means that this child RestaurantIcon will take 15% of the horizontal space from its parent Row.

  1. Now, still inside the RestaurantsScreen.kt file, create a RestaurantDetails function that displays the restaurant's details:
    @Composable
    private fun RestaurantDetails(modifier: Modifier) {
       Column(modifier = modifier) {
           Text(text = "Alfredo's dishes",
                style = MaterialTheme.typography.h6)
           CompositionLocalProvider(
               LocalContentAlpha provides 
                   ContentAlpha.medium) {
               Text(text = "At Alfredo's … seafood dishes.",
                    style = MaterialTheme.typography.body2)
           }
       }
    }

Similarly, RestaurantDetails receives a Modifier.weight(0.85f) modifier as an argument from Row, which will make it occupy the remaining 85% of the horizontal space.

The RestaurantDetails composable is a simple Column that arranges two Text composables vertically, with one being the title of the restaurant, and the other being its description.

But what's up with CompositionLocalProvider? To display the description that's faded out in contrast to the title, we applied a LocalContentAlpha of ContentAlpha.medium. This way, the child Text with the restaurant description will be faded or grayed out.

CompositionLocalProvider allows us to pass data down to the composable hierarchy. In this case, we want the child Text to be grayed out, so we passed a LocalContentAlpha object with a ContentAlpha.medium value using the infix provides method.

  1. For a moment, go to MainActivity.kt and remove the DefaultPreview composable function as we will define our own a @Preview composable up next.
  2. Go back inside the RestaurantsScreen.kt file, define a @Preview composable:
    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
       RestaurantsAppTheme {
           RestaurantsScreen()
       }
    }

If you have chosen a different name for your app, you might need to update the previous snippet with the theme composable defined in the Theme.kt file.

  1. Rebuild the project and let's inspect the RestaurantsScreen() composable by previewing the newly created DefaultPreview composable, which should display a restaurant item:

Figure 1.22 – Previewing a restaurant item

  1. Finally, go back to MainActivity.kt and remove the Greeting composable. Also, remove the Surface and Greeting function calls in the setContent method and replace them with RestaurantScreen:
    setContent {
       RestaurantsAppTheme {
           RestaurantsScreen()
       }
    }

By passing RestaurantScreen to our MainActivity's setContent method, we ensure that the application will render the desired UI when built and run.

  1. Optionally, you can now Run the app to see the restaurant directly on your device or emulator.

Now that we have built a layout for a restaurant, it's time to learn how to display more of them!

Displaying a list of restaurants with Compose

So far, we've displayed a restaurant item, so it's time to display an entire list of them:

  1. First, create a new class in the root package, next to MainActivity.kt, called Restaurant.kt. Here, we will add a data class called Restaurant and add the fields that we expect a restaurant to have:
    data class Restaurant(val id: Int,
                          val title: String,
                          val description: String)
  2. In the same Restaurant.kt file, create a dummy list of Restaurant items, preferably at least 10 to fill up the entire screen:
    data class Restaurant(val id: Int,
                          val title: String,
                          val description: String)
    val dummyRestaurants = listOf(
        Restaurant(0, "Alfredo foods", "At Alfredo's …"),
        [...],
        Restaurant(13, "Mike and Ben's food pub", "")
    )

You can find the pre-populated list in this book's GitHub repository, inside the Restaurant.kt file:

https://github.com/PacktPublishing/Kickstart-Modern-Android-Development-with-Jetpack-and-Kotlin/blob/main/Chapter_01/chapter_1_restaurants_app/app/src/main/java/com/codingtroops/restaurantsapp/Restaurant.kt.

  1. Go back inside the RestaurantsScreen.kt file and update your RestaurantItem so that it receives a Restaurant object as an argument, while also passing the restaurant's title and description to the RestaurantDetails composable as parameters:
    @Composable
    fun RestaurantItem(item: Restaurant) {
        Card(...) {
            Row(...) {
                RestaurantIcon(...)
                RestaurantDetails(
                    item.title,
                    item.description,
                    Modifier.weight(0.85f)
                )
            }
        }
    }
  2. We have passed the restaurant's title and description to the RestaurantDetails composable as parameters. Propagate these changes in the RestaurantDetails composable and pass the title into the first Text composable and the description into the second Text composable:
    @Composable
    fun RestaurantDetails(title: String, description: String, modifier: Modifier){
       Column(modifier = modifier) {
           Text(text = title, ...)
           CompositionLocalProvider( … ) {
               Text(text = description, ...)
           }
       }
    }
  3. Go back to the RestaurantsScreen composable and update it to display a vertical list of Restaurant objects. We already know that we can use a Column to achieve this. Then, iterate over each restaurant in dummyRestaurants and bind it to a RestaurantItem:
    @Composable
    fun RestaurantsScreen() {
       Column {
           dummyRestaurants.forEach { restaurant ->
               RestaurantItem(restaurant)
           }
       }
    }

This will create a beautiful vertical list that we can preview through our DefaultPreview composable.

  1. Rebuild the project to see the updated preview generated by the DefaultPreview composable:
Figure 1.23 – Previewing RestaurantsScreen with the Column composable

Figure 1.23 – Previewing RestaurantsScreen with the Column composable

Alternatively, you can Run the app to see the restaurants directly on your device or emulator.

We've finally created our first list with Compose! It looks very nice and beautiful, yet it has one huge issue – it doesn't scroll! We'll address this together in the next section.