Categories:
Search articles

Search for articles

Sorry, but we couldn't find any matches...

But perhaps we can interest you in one of our more popular articles?

Getting started and developing an application with Jetpack Compose

Jul 30, 2020

How to build native Android apps on Codemagic

How to build native Android apps on Codemagic

Use codemagic.yaml file to keep your build configurations more organized and manage all your workflows
Read more

Written by Muhammed Salih Güler

As time goes by, technologies evolve and companies try to offer a better experience to developers for developing their applications. For example, in the earlier stages of Android, it was pretty common to use EventBus to send data through the app, and Mosby to implement the Model-View-Presenter pattern. Nowadays, they are far from being the most common way to develop applications. It is more common to use the MVVM pattern using AndroidX libraries implemented and recommended by the Android team.

The Android team not only presents and suggests logical patterns, but they are also working on how you implement the UI for the application as well. Right now, Android is heavily leaning towards the imperative UI pattern by using XML (Extensible Markup Language) files. Reaching the UI elements is possible by using findViewById() method. Once the view is retrieved, you can update the properties of it or invoke its methods.

This approach is great and loved by a lot of developers but as Android development shifted towards a more reactive programming approach, the benefits of the imperative approach started to shrink. Therefore the Android team decided to use a different pattern called declarative UI pattern. With declarative UI, developers are declaring what the UI looks like by describing the views and their properties. The UI has a state and each time the state changes, the UI updates and recreates its UI elements.

Jetpack Compose

After they decided to use the declarative UI pattern, the Android team wanted to avoid a mistake they had made before. The UI implementation was finding its place next to the Android platform code. That meant, the releases were less frequent and when an error happened it required a bigger release on the Android OS. That's why the Android team decided to release this new UI approach as a library.

The Android team was working on a set of libraries called Jetpack, which is a suite of libraries to help developers follow best practices, reduce boilerplate code, and write code that works consistently across all Android versions. Including this new UI library as well was the best decision they could make. And this is how Jetpack Compose was born.

Jetpack Compose is Android’s modern toolkit for building native Android applications. It simplifies and accelerates UI development on Android. It does more with less code and avoids entire classes of bugs, so code is simple and easy to maintain. Every time the state changes, the UI automatically gets updated. It is compatible with your existing code so you can adopt it whenever you want.

Getting started with Jetpack Compose

Jetpack Compose is a preview feature, and support for Compose is included only in Canary versions of Android Studio and you need to download it. At this date, the latest version of Android Studio is version 4.2 Canary 5 and Jetpack Compose version is 0.1.0-dev14.

After you have downloaded Android Studio's Canary version, just follow the instruction steps from the installer to be able to install Android Studio. I am not going to describe the Android Studio installation process here but if you are feeling lost, please refer to this document.

Android Studio beginning page
Android Studio beginning page

Today you will be creating a simple Popular Movies application. The application will have some information provided locally by the TMDb API for Popular Movies. The app will show these movies as a list in the application.

Creating the Jetpack Compose project

For creating a Jetpack Compose project, you can start by clicking on Create New Project button in the dialog box. Once you click on it, you will see a window like below.

Application project template
Application project template

As you see from the picture above, you can see multiple project templates. Whatever project type you need for any Android-based OS, you can find a template for it. For our project pick Empty Compose Activity and move to the next window.

Application information
Application information

In the window above, you will be asked to add some information about your application. Let's go over the field one by one:

  • Name: name of the application.
  • Package name: unique identifier for your application. Mainly a domain name for your company and the name of the application.
  • Save location: location of the app directory.
  • Language: programming language to be used in the application. In the classic approach, you can have either Java or Kotlin but with Compose, you need to use Kotlin.
  • Minimum SDK: minimum SDK version supported by the application.

Once you input and select the correct information, click on the Finish button to set up your application. After the setup, you will see the main project window like below.

Main project window
Main project window

Project window in Jetpack Compose

The project window that you see above will be your main workplace. You can see three main project windows. Let's learn about the most important ones in detail.

Project pane

Project pane
Project pane

Your main focus area will be under the app folder. app/build.gradle is your project's Gradle file. This file is used to define information about your application and to declare the libraries you are going to use. The application's source code will be mainly located in the app/src/ folder.

app/src/androidTest and app/src/test folders will be the folders dedicated for testing. Your application will be lying under app/src/main/ folder. Your resources will be under the res folder and your code will be under java/your_app_identifier.

Code pane

Code pane
Code pane

Code pane will be the view that you will be working most of the time. The code you are writing will be shown there. You will learn the code part in detail soon.

Live Preview

Live Preview
Live Preview

With the classical Android approach, you were mostly able to see your layout in the layout view. This gave you a chance to see your changes directly. Jetpack Compose came with a great tool called Live Preview to give you the same feeling.

Once you create your application, you need to run your application at least once. After you run it, the Live Preview will be ready for use.

Whenever you make a change, your changes will be built and drawn into Live Preview. When you make a lot of changes, you might need to restart your Live Preview but it won't take a long time.

Live Preview
Live Preview

After you see the live preview, you will see some buttons. Those buttons will help you to do multiple operations, e.g. deploying changes to device, enabling Live Preview to be clickable, and many more.

Now that you got to know your IDE, it's time to dive deep into code.

Let's see the code: setContent method, Composable functions and Live Preview

When you create the application, it will create a simple project for you.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            PopularMoviesTheme {
                Greeting("Android")
            }
        }
    }
}

Activity creation and its life cycle elements are created as it was before. In the classic Android development approach, you should have called setContentView to get the reference to the view. With Jetpack Compose, you need to use the setContent method to compose your application.

In Jetpack Compose, each function that is defined to create a UI content is a Composable. Composable functions are the fundamental building blocks of an application built with Compose. You can use either existing Composable functions or create your Composable functions by combining multiple Composable functions by using @Composable annotation.

@Composable
fun PopularMoviesTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
            colors = colors,
            typography = typography,
            shapes = shapes,
            content = content
    )
}

PopularMoviesTheme above is the top-level Composable function that keeps the theme information. Just like the other UI elements, the theme information is also a Composable function. It decides on the color palette according to system information. The function also accepts a Composable function as a child element.

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

Greeting is your content to be shown on the screen. It is also a Composable function. It uses one of the many out-of-box Composable functions from the library.

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    PopularMoviesTheme {
        Greeting("Android")
    }
}

You learned about Live Preview, but you haven't seen any sign of it yet. To use Live Preview, you need to add the @Preview annotation to your Composable function. You can modify the attributes of the @Preview annotation. e.g. showBackground will show a background filling.

If you run the app on an emulator, now you can see the view below:

Screenshot
Screenshot

Building the basics

As first step, you will remove the Greeting and DefaultPreview functions and create your Composable function.

@Preview(showBackground = true)
@Composable
fun PopularMoviesMainPage() {
    Column {
        TopAppBar(title = { Text(text = "Popular Movies") })
        AdapterList(data = List(100) { it }) {
            Text(text = it.toString())
        }
    }
}

With the code above, you will create PopularMoviesMainPage the UI elements on the page. As the base UI element, you will use a Composable function called Column. Column is used for placing its children in a vertical sequence. If you want to go for a horizontal sequence, you should use Row. For both of them, you can just put one element after another by writing one Composable function after another.

Column {
    // Multiple Composable functions
}

The next step is to create an app bar on the top of the screen. For that, you need to use a Composable function called TopAppBar. TopAppBar displays information and actions relating to the current screen and is placed at the top of the screen. It has multiple attributes that you can use to change the look of the app bar. You can define backgroundColor, contentColor, navigationIcon and many more other attributes for the TopAppBar.

TopAppBar(title = { Text(text = "Popular Movies") })

The final step for this part is to generate a list of 100 elements to just demonstrate the scrollable view. For this purpose, you will be using a Composable function called AdapterList. It is a vertically scrolling list that only composes and lays out the currently visible items. It will ask you to provide a list of data to represent on the screen.

AdapterList(data = List(100) { it }) {
    Text(text = it.toString())
}

If you put them together, as below, you will see the desired screen as follows.

Column {
    TopAppBar(title = { Text(text = "Popular Movies") })
    AdapterList(data = List(100) { it }) {
        Text(text = it.toString())
    }
}
Basic list
Basic list

Creating a movie list and showing basic information

To create a movie list, you will download the information from TMDb and store them locally. Since this is a bit on the beginner side, network communications will be handled in the other articles.

To store the information about movies, you will create a Kotlin data class called Movie. To do so, right-click on the app/src/main/java/your_package_name/ and create a new folder called data. Under data, create a new Kotlin file, call it Movie.kt and paste the code from below.

data class Movie(
        val title: String,
        val releaseDate: String,
        val overview: String,
        val voteAverage: Double,
        val image: Int
)

The code above is a simple data class keeping information about the movie. All the fields will keep information with the related type of data, only exception is image. image will keep the id reference of the resource in your project. Now that you have the movie data holder, let's feed some information into it.

Under the same folder, create a file called MovieData.kt. In this file, you will be creating your movie information.

Movie files
Movie files

For feeding the movie information, paste the code below.

object MovieData {
    val list = listOf(
            Movie(
                    title = "Palm Springs",
                    releaseDate = "2020-01-26",
                    overview = "When carefree Nyles and reluctant maid of honor Sarah have a chance encounter at a Palm Springs wedding, things get complicated when they find themselves unable to escape the venue, themselves, or each other.",
                    voteAverage = 7.7,
                    image = R.mipmap.palm_springs
            ),
            Movie(
                    title = "The Old Guard",
                    releaseDate = "2020-07-10",
                    overview = "Four undying warriors who've secretly protected humanity for centuries become targeted for their mysterious powers just as they discover a new immortal.",
                    voteAverage = 7.4,
                    image = R.mipmap.the_old_guard
            ),
            Movie(
                    title = "Greyhound",
                    releaseDate = "2020-07-10",
                    overview = "A first-time captain leads a convoy of allied ships carrying thousands of soldiers across the treacherous waters of the “Black Pit” to the front lines of WW2. With no air cover protection for 5 days, the captain and his convoy must battle the surrounding enemy Nazi U-boats in order to give the allies a chance to win the war.",
                    voteAverage = 7.5,
                    image = R.mipmap.greyhound
            ),
            Movie(
                    title = "Ad Astra",
                    releaseDate = "2019-09-17",
                    overview = "Four undying warriors who've secretly protected humanity for centuries become targeted for their mysterious powers just as they discover a new immortal.",
                    voteAverage = 6.1,
                    image = R.mipmap.ad_astra
            ),
            Movie(
                    title = "Fatal Affair",
                    releaseDate = "2020-07-16",
                    overview = "Ellie tries to mend her marriage with her husband Marcus after a brief encounter with an old friend, David, only to find that David is more dangerous and unstable than she'd realized.",
                    voteAverage = 5.0,
                    image = R.mipmap.fatal_affair
            ),
            Movie(
                    title = "Joker",
                    releaseDate = "2019-10-02",
                    overview = "During the 1980s, a failed stand-up comedian is driven insane and turns to a life of crime and chaos in Gotham City while becoming an infamous psychopathic crime figure.",
                    voteAverage = 8.2,
                    image = R.mipmap.joker
            ),
            Movie(
                    title = "Birds of Prey (And the Fantabulous Emancipation of One Harley Quinn)",
                    releaseDate = "2020-02-05",
                    overview = "Harley Quinn joins forces with a singer, an assassin and a police detective to help a young girl who had a hit placed on her after she stole a rare diamond from a crime lord.",
                    voteAverage = 7.2,
                    image = R.mipmap.birds_of_prey
            ),
            Movie(
                    title = "Interstellar",
                    releaseDate = "2014-11-05",
                    overview = "The adventures of a group of explorers who make use of a newly discovered wormhole to surpass the limitations on human space travel and conquer the vast distances involved in an interstellar voyage.",
                    voteAverage = 8.3,
                    image = R.mipmap.interstellar
            ),
            Movie(
                    title = "Avengers: Infinity War",
                    releaseDate = "2018-04-25",
                    overview = "As the Avengers and their allies have continued to protect the world from threats too large for any one hero to handle, a new danger has emerged from the cosmic shadows: Thanos. A despot of intergalactic infamy, his goal is to collect all six Infinity Stones, artifacts of unimaginable power, and use them to inflict his twisted will on all of reality. Everything the Avengers have fought for has led up to this moment - the fate of Earth and existence itself has never been more uncertain.",
                    voteAverage = 8.3,
                    image = R.mipmap.avengers
            ),
            Movie(
                    title = "Parasite",
                    releaseDate = "2019-05-30",
                    overview = "All unemployed, Ki-taek's family takes peculiar interest in the wealthy and glamorous Parks for their livelihood until they get entangled in an unexpected incident.",
                    voteAverage = 8.5,
                    image = R.mipmap.parasite
            )
    )
}

The MovieData is an object that helps us reach out to the movie related data. list variable in the object is keeping the information about the movies. To use this list, you can simply write MovieData.list.

Now let's go back to MainActivity.kt file and update your Composable functions.

First of all, let's change the following part:

AdapterList(data = List(100) { it }) {
    Text(text = it.toString())
}

We will substitute it with the following:

AdapterList(data = MovieData.list) {
    PopularMoviesListItem(it)
}

Once you do that, you will see that the compiler is complaining about PopularMoviesListItem since you have not created it yet. This will be your new Composable function to show movie information.

You can satisfy the compiler by adding the following code.

@Composable
fun PopularMoviesListItem(movie: Movie) {
    Text(text = movie.title, modifier = Modifier.padding(all = 16.dp))
}

With the code above, you are just adding the movie title to a text. You will also use modifier to add some padding. But, what exactly is a modifier? A modifier is an ordered, immutable collection of elements that decorate or add behavior to Compose UI elements like backgrounds, padding, click event listeners, and many more.

If you run the application now, you will see the following in your emulator.

Movie titles
Movie titles

Create beautiful list items

So far, you have implemented the data and the information you can use to create a movie list. But unfortunately, your list items don't look that amazing. Don't worry, we will solve this issue.

For the list item, you will be only working on the PopularMoviesListItem you created. First of all, let's start by wrapping the title with a Card. A Card will give the user a more independent feeling about the item and you can encapsulate all movie information per item.

    Card(
            shape = RoundedCornerShape(8.dp),
            modifier = Modifier
                    .padding(all = 16.dp)
                    .fillMaxWidth()
                    .height(height = 375.dp)
    ) {
           Text(text = movie.title, modifier = Modifier.padding(all = 16.dp)) 
    }

Card has a lot of attributes that you can use. To have a rounded shape (or any other shape), you can use the shape attribute. For the card's general UI specifications you will use the modifier attribute. This way you will handle padding, width, and height for the card.

Card will accept one child under it to show information. You will move your Text inside it as shown above. If you run the app now, you will see the following:

Titled card
Titled card

But we have a problem. You want to show multiple information on the card — but as we learned, Card accepts only one child. Thus you have to use the Column.

Column {
        Text(text = movie.title, modifier = Modifier.padding(all = 16.dp)) 
}

As the next step, you will add the movie image to the list. To add an image, you will use the Composable function called Image. Image is a Composable that lays out a given image asset. The asset property is used to provide an image resource. imageResource function will accept the ID of the image and provide the ImageAsset for the view. Image also has a lot more properties for scaling, aligning, and changing the UI representation of the image. With your example, you will set the height to a specific height. You will also use the contentScale property to make sure the width is equal to the height. To align the image, the alignment property is used. The default alignment is centered, but you will move the image to the top.

To have a nicer UI, you will change the size of the text for the movie title. To do so, you will use the style property and use the typography that has been provided by the MaterialTheme.

If you update your Column as shown in the following code snippet, the image and movie title will look a lot better.

Column {
    Image(
            asset = imageResource(id = movie.image),
            contentScale = ContentScale.FillWidth,
            modifier = Modifier.preferredHeight(height = 200.dp),
            alignment = Alignment.TopCenter
    )
    Text(
            text = movie.title,
            modifier = Modifier.padding(all = 8.dp),
            style = MaterialTheme.typography.h6
    )
}

Your app now looks like this:

Card with both a title and image
Card with both a title and image

Last but not least, let's add the rest of the movie information to the screen. Let's continue with adding the release date and vote for the movies. You will add them in the same horizontal direction using Row.

Row(
        modifier = Modifier.padding(all = 8.dp).fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween
) {
    Text(
            text = "Release Date: ${movie.releaseDate}",
            style = MaterialTheme.typography.subtitle2
    )
    Text(
            text = "Vote: ${movie.voteAverage}/10",
            style = MaterialTheme.typography.subtitle2
    )
}

If you see the code above, the only new and shiny part is the horizontalArrangement property for Row. horizontalArrangment is the horizontal arrangement of the layout's children. You can have the children aligned with equal gaps, put the child elements around the whole horizontal UI component, or simply put them so that the child elements can share the same space in the horizontal UI component.

Finally, let's add the overview information for the movie and wrap it up.

Text(
        text = movie.overview,
        modifier = Modifier.padding(all = 8.dp),
        maxLines = 5,
        overflow = TextOverflow.Ellipsis,
        style = MaterialTheme.typography.caption
)

The Composable function Text has a lot of useful properties. Since the overview can be longer for some movies, it makes sense to limit the maximum lines of texts. For that, you will be using the maxLines property. But when you limit the lines, you don't want to just cut off the text. It makes more sense to show the at the end of the displayed text, so that users know what is going on. To do so, you will be using the overflow property and change it to TextOverflow.Ellipses. Once you are done with it, let's run it and see what your app looks like!

Continuous integration and continuous delivery for your Jetpack Compose project

codemagic.yaml file makes it easy to build native Android apps on Codemagic CI/CD. Since the introduction of the codemagic.yaml file, it has been a lot easier to keep your build configurations more organized and manage all your workflows with just a single file.

Though Codemagic does not come with support for building native Android apps directly from the UI, you can easily configure the codemagic.yaml file for building native apps.

Check out this article to learn how to configure the codemagic.yaml file for building native Android projects with Codemagic CI/CD

Conclusion

Jetpack Compose is still fairly new and actively developed. Therefore, a lot can change along the way. But if you are into declarative UI patterns, it is something you might want to try out.

Jetpack Compose resources

Videos of Jetpack Compose

Project files

Click here to download the whole project as a zipped folder.


Salih is a Senior Mobile Engineer based in Berlin. He is a Google Developer Expert for Flutter and Dart. Salih likes sharing his knowledge with others by mentoring aspiring developers, blogging and talking at conferences.

You can contact him over Twitter (@salihgueler) or via email (muhammedsalihguler@gmail.com).

How did you like this article?

Oops, your feedback wasn't sent

Latest articles

Show more posts