Repository Pattern with Jetpack Compose

In this tutorial, you’ll learn how to combine Jetpack Compose and the repository pattern, making your Android code easier to read and more maintainable. By Pablo Gonzalez Alonso.

4.7 (14) · 6 Reviews

Download materials
Save for later
Share

On Android, you used to always have to create user interface layouts using XML. But in 2019, Google introduced a fresh, new approach to building user interfaces: Jetpack Compose. Compose uses a declarative API to build UI with the power of Kotlin.

Note: This Jetpack Compose tutorial requires intermediate knowledge of Kotlin as well as some basic knowledge of Jetpack Compose. You can freshen up on the basics of Compose with the Jetpack Compose Tutorial for Android.

In this tutorial, you’ll combine the power of Jetpack Compose with the repository pattern to build an English dictionary app.

You’ll need to install Android Studio Arctic Fox to work with Jetpack Compose. Note that this is the first stable release of Android Studio supporting Jetpack Compose.

While building your dictionary app, you’ll learn to:

  • Read and display remote data.
  • Persist and restore local data with Room.
  • Use pagination with LazyColumn.
  • Manage and update UI States with Compose.

You’ll see how Jetpack Compose really shines by eliminating the need for RecyclerView and simplifying the state management. OK. It’s time to start!

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

Open the project with Android Studio. You’ll see the following file structure:


Project structure

Sync the project. Then, build and run. The app will look like this:


Initial app state

As you can see, it’s nothing too flashy, yet. :] Before diving into the code to make your app flashier (err, I mean, more useful), you’ll learn a few things about the repository pattern.

The Repository Pattern

A repository defines data operations. The most common operations are creating, reading, updating, and deleting data, (also known as CRUD). These operations sometimes need parameters that define how to run them. For instance, a parameter could be a search term to filter results.

The repository pattern is a structural design pattern. It’s instrumental for organizing how you access data. It also helps divide concerns into smaller parts. For more information and terminology, check out Clean Architecture for Android: Getting Started.

The repository pattern was first introduced in 2004 by Eric Evans in his book, Domain-Driven Design: Tackling Complexity in the Heart of Software.

You’ll be implementing the repository pattern with Jetpack Compose. The first step is add the datasource. You’ll learn about this next.

Understanding Datasources

Repository operations delegate to a relevant datasource. Datasources can be remote or local. The repository operation has logic that determines the relevance of a given datasource. For instance, the repository can provide a value from a local datasource or ask a remote datasource to load from the network.

Stores and Sources are two of the most important types of datasources. Stores get their data from local sources and Sources get their data from remote sources. The following illustration shows what a simple repository implementation looks like:


Simple repository implementation

Using a Repository

When would you need to use a repository? Well, imagine that your app’s user wants to see their profile. The app has a repository that checks the Store for a local copy of the user’s profile. If the local copy isn’t present, then the repository checks with the remote Source. Implementing this kind of repository looks like this:


User Repository with Remote and Local sources

By the end of this tutorial, you’ll use the repository pattern with both Store and Source datasources. In other words, your app will use both remote and local data to populate and store the words.

Other datasources may rely on different types of Sources like Location Services, Permission Results or Sensor inputs.

For instance, the user repository can include two additional data sources: One to verify the user’s authorization and another for an in-memory cache. The first one is useful if you need to make sure the user can see the profile, while the second one is helpful when accessing an entity often since you may not want the app to read from the database every time. Here’s a simple illustration of a repository with authorization and in-memory cache:


User Repository with authorization and in memory cache

One benefit of the repository pattern is that it’s straightforward to add new layers of functionality. And, at the same time, repositories keep concerns separated and organize logic into components. These logical components also need less responsibility. This keeps the code concise and decoupled.

Ok, that’s enough theory for now. Time for some coding fun. :]

Creating a UI for Words

Now it’s time to create the UI for your app, Words.

Create a file called WordListUi.kt in the UI package. Inside the file, define WordListUi with a basic Scaffold:

@Composable
fun WordListUi() {
  Scaffold(
    topBar = { MainTopBar() },
    content = {

    }
  )
}
Note: When copying the code in this tutorial, you can use alt+Enter to auto import the components.

Now, open MainActivity.kt and replace the Scaffold in onCreate with WordListUi():

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContent {
    WordsTheme {
      WordListUi()
    }
  }
}

Now the Scaffold defined in WordListUi is displayed when the app is launched inside the main activity.

Before building more UI elements, you’ll create the model that defines each word. In the package data.words, add a new data class, Word.kt with the following code:

data class Word(val value: String)

Then, in WordsListUi.kt, define a Composable below WordListUi to show a word as a list item:

@Composable
private fun WordColumnItem(
  word: Word,
  onClick: () -> Unit,
) {
  Row(                                              // 1
    modifier = Modifier.clickable { onClick() },    // 2
  ) {
    Text(
      modifier = Modifier.padding(16.dp),           // 3
      text = word.value,                            // 4
    )
  }
}

By doing this, you’re setting the WordColumnItem Composable to:

  1. Define a Row that displays elements horizontally.
  2. Add a modifier to capture clicks and forward them to the onClick callback.
  3. Include padding in the layout so the content has breathing room.
  4. Use the value of the word as the text.

Next, you’ll create a Composable to display a list of words.

To do this in Compose, add the following composable to the bottom of WordListUi.kt:

@Composable
private fun WordsContent(
  words: List<Word>,
  onSelected: (Word) -> Unit,
) {
  LazyColumn {              // 1
    items(words) { word ->  // 2
        WordColumnItem(     // 3
          word = word
        ) { onSelected(word) }
    }
  }
}

The above code:

  1. Creates a LazyColumn.
  2. Tells LazyColumn to render a list of words.
  3. Creates a WordColumnItem for each of the items.

LazyColumn renders the items as the user scrolls.

This is so much simpler than RecyclerView and ListView! Where have you been all our lives, LazyColumns? :]

To test the layout, use RandomWords. Add the following inside of content inWordListUi:

WordsContent(
  words = RandomWords.map { Word(it) },       // 1                          
  onSelected = { word -> Log.e("WordsContent", 
                         "Selected: $word") } // 2
)

The two main things you’re doing here are:

  1. Converting the list of strings into a list of words.
  2. Printing a message to Logcat to verify button taps.

Now, build and run. Since you used RandomWords to test the layout, you'll see a list of random words:


List of gibberish words

It's gibberish, but it gives you a rough idea of how your app will look.

Next, you'll create a ViewModel for the main screen and a repository for Words.