Quickstart
Let’s build your first Store.
In this example, we’ll build a simple Store to fetch and cache posts for the Trails app.
The code for this example is available in the Trails repository.
Prerequisites
Quick Links to Prerequisites:
- Kotlin Multiplatform Project Setup: Basic understanding of KMP and a project set up with shared
commonMain
code. - Coroutines and Flow: Basic understanding of Kotlin Coroutines and Flow for asynchronous programming.
- Gradle: Familiarity with Gradle for dependency management.
Installation
Add the Dependency
Add the Store library to your project’s dependencies. Since we’re working with a KMP project, we’ll add the dependency to the commonMain
source set.
Understanding Version Catalogs:
The libs.versions.toml
file is part of Gradle’s Version Catalogs feature, which simplifies dependency management.
If you’re not using Version Catalogs, you can add the dependency directly to your build.gradle.kts
:
For more information on Version Catalogs, visit the Gradle Documentation.
Sync the Project
After adding the dependency, sync your project with Gradle to download the Store library.
Building a Store
Now, let’s build a simple Store to fetch and cache posts from our API and cache it for offline access.
Define the Data Models
Define the models for a post.
Using SqlDelight for Local Database Models:
The SQL schema defined using SqlDelight will generate Kotlin models for you. Here’s how the PostEntity
class might look after generation:
For more details on how SqlDelight generates Kotlin classes from SQL schemas, check out the SqlDelight Docs.
Create the API Interface
Define and implement an interface for your network calls. In this example, we’ll use Ktor for HTTP requests.
Dependency Injection Context:
Trails uses kotlin-inject, a compile-time dependency injection library for Kotlin. The @Inject
annotation indicates a class can be injected.
Implement Converters
We need to convert between our network model, domain model, and local database model.
Set Up the Store Factory
We’ll use a factory for creating a PostStore
instance.
About the TODO()
Placeholders:
The TODO()
placeholders indicate where implementations will be provided in the subsequent steps.
Implement the Fetcher
Our Fetcher will interact with the network data source using the PostOperations
interface.
Implement the Source of Truth
Our Source of Truth will delegate to a local SqlDelight database.
Observing Database Changes Over Time:
To ensure your SourceOfTruth
observes changes in the database, use asFlow()
and appropriate mapping functions.
Implement the Converter
Our Converter will convert between our network model, local database model, and domain model using the PostExtensions
object.
Understanding the Converter’s Role:
The Converter bridges the gap between the network model, local database model, and domain model within the Store.
While you have extension functions for conversions, the Converter integrates these into the Store’s pipeline, ensuring data flows correctly through each layer.
Implement the Updater
Our Updater will make a network call to update the post.
Implement the Bookkeeper
Our Bookkeeper keeps track of failed syncs to enable eagerly resolving conflicts after local mutations.
Build the Store
Provide the implementations to the Store Builder.
How Components Work Together:
Each component of the Store has a specific role:
Fetcher
: Retrieves data from the network.Source of Truth
: Manages local data storage.Converter
: Handles data transformations between models.Updater
: Syncs local changes back to the network.Bookkeeper
: Keeps track of failed updates for retry mechanisms.
By integrating these components, the Store efficiently manages data flow between the network, local storage, and your app’s UI.
Using the Store
Now, let’s use the Store to fetch and cache post data for the Trails post detail screen.
Create a Post Repository
We’ll create a PostRepository
that uses the PostStore
to fetch and cache post data. The primary reason for this extra layer is it enables us to extract Store
from the domain layer as an implementation detail of the PostRepository
. It also enables us to add additional methods and strategies to the PostRepository
in the future.
PostRepository
is feature agnostic. We define the PostRepository
under the
common lib/market/post/api
namespace and implement it in the
lib/market/post/impl
module to facilitate its reuse across features.
Consumers can depend on the lib/market/post/api
module without being exposed
to the implementation details, such as the PostStore
.
A market is a composition of stores and systems enabling exchange between consumers and providers.
Implement the Post Detail Presenter
A Circuit Presenter is intended to be the business logic for a screen’s UI and a translation layer in front of the data layer. Our PostDetailScreenPresenter
will use the PostRepository
to load the post data and update the UI in response to user actions.
Display the Post Detail Screen
Next Steps
Now that you have built your first Store, it’s time to explore what else is possible: