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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Repository Pattern with Jetpack Compose
40 mins
- Getting Started
- The Repository Pattern
- Understanding Datasources
- Using a Repository
- Creating a UI for Words
- Creating the Main ViewModel
- Building the WordRepository
- Working With State in Compose
- Upgrading State to Flow
- Using StateFlow to Deliver Results to the UI
- Showing a Loading State
- Storing Words With Room
- Adding Pagination
- Searching the Dictionary
- Reacting to Searches
- Showing an Empty Search Result
- Where to Go From Here?
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.
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:
Sync the project. Then, build and run. The app will look like this:
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:
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:
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:
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 = {
}
)
}
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:
- Define a Row that displays elements horizontally.
- Add a modifier to capture clicks and forward them to the
onClick
callback. - Include padding in the layout so the content has breathing room.
- 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:
- Creates a
LazyColumn
. - Tells
LazyColumn
to render a list of words. - 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:
- Converting the list of strings into a list of
words
. - 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:
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
.