Skip to content

Commit

Permalink
Building your first Store
Browse files Browse the repository at this point in the history
Signed-off-by: mramotar <mramotar@dropbox.com>
  • Loading branch information
matt-ramotar committed Jan 16, 2024
1 parent 5677f62 commit 580512c
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 85 deletions.
2 changes: 1 addition & 1 deletion website/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ sidebar_class_name: tutorial_index

import {StoreNewsAppImage} from "../src/components/StoreNewsAppImage";

# Store Fundamentals in 30 Minutes
# Store Fundamentals in 15 Minutes

## Introduction

Expand Down
11 changes: 6 additions & 5 deletions website/docs/step_1.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ sidebar_class_name: tutorial_1
title: Setting Up Our KMP Project
---

### Creating a new repository
- Use this template: https://github.com/matt-ramotar/news

import YouTubeEmbed from "../src/components/YouTubeEmbed";

Create a new Kotlin Multiplatform project. You can find the official Kotlin tutorial [here](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-getting-started.html).

<YouTubeEmbed embedId={"suc5KoopKCw"}/>
### Generating a Scoop API token
- TODO(mramotar)

### Using your Scoop API token
- Add it to shared/lib/env/env.properties

79 changes: 44 additions & 35 deletions website/docs/step_2.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,55 @@
slug: /tutorial/2
sidebar_position: 2
sidebar_class_name: tutorial_2
title: Adding Dependencies
title: Onboarding To Our KMP Project
---

Open `libs.versions.toml` and add:
### Directory Structure

```yaml
# gradle/libs.versions.toml

[versions]
kotlin = "1.9.21" # https://github.com/JetBrains/kotlin/releases
serialization = "1.5.1" # https://github.com/Kotlin/kotlinx.serialization/releases
store = "5.0.0"
- android/
- app/

- ios/
- app/

- shared/
- core/
- di/
- api/ # Core shared component interfaces
- impl/ # Core shared component implementations
- navigation/ # Shared navigation
- api/
- impl/
- feat/ # Shared feature modules
- homeTab/
- api/
- impl/
- lib/
- composableModel/ # Shared view model
- env/ # Shared environment, including secrets
- httpClient/ # Shared HTTP client
- res/ # Shared resources
- scoop/ # KMP SDK for Scoop API
- theme/ # Shared theming

- tooling/
```

[libraries]
serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" }
serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
store = { module = "org.mobilenativefoundation.store:store5", version.ref = "store" }
### Overview
- Take a look at the `shared` module. This is where we will be writing our shared code. A few notes:
- Take a look at the `android` module. This is where we will be writing our Android code.
- Take a look at the `ios` module. This is where we will be writing our iOS code.
- Take a look at the `tooling` module. This is where our tooling code is located.

[plugins]
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
```
### Dependencies
- Take a look at `gradle/libs.versions.toml`. These are our dependencies. A few of the core ones:

Then update our `shared` module:

```kts
// shared/build.gradle.kts

plugins {
alias(libs.plugins.serialization)
}

kotlin {
sourceSets {
commonMain {
dependencies {
implementation(libs.serialization.core)
implementation(libs.serialization.json)
implementation(libs.store)
}
}
}
}
```yaml
- store # KMP data loading and caching
- buildkonfig # KMP build config
- kotlin-inject # KMP dependency injection
- ktor # KMP Networking
- voyager # KMP Navigation
- scoop # KMP SDK for Scoop API
```
147 changes: 104 additions & 43 deletions website/docs/step_3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,141 @@
slug: /tutorial/3
sidebar_position: 3
sidebar_class_name: tutorial_3
title: Creating Data Models
title: Building Your First Store
---

import {IssuesImage} from "../src/components/IssuesImage";



import {PublicationsImage} from "../src/components/PublicationsImage";
import {TopStoriesImage} from "../src/components/TopStoriesImage";
import {StoryImage} from "../src/components/StoryImage";
import {BusinessStoriesImage} from "../src/components/BusinessStoriesImage";

## Publications View
### Publications View

<PublicationsImage/>

<div style={{height: 40, width: "100%"}}/>

- Open `PublicationsStore.kt` under `:shared:feat:exploreTab:impl`.

- We will leverage the Scoop SDK.

- Here's the `Publication` data model.

```kotlin
@Serializable
// From the Scoop SDK

package ai.wandering.scoop.v1.models

data class Publication(
val id: String,
val name: String,
val description: String,
val isFollowing: Boolean,
val issues: List<Issue>
val title: String,
val logoUrl: String,
)
```

## Detailed Publication View
- Here's the `GetPublications` use case.

<div style={{display: "flex", flexDirection: "row", justifyContent: "space-around"}}>
<TopStoriesImage/>

<IssuesImage/>
```kotlin
// From the Scoop SDK

<BusinessStoriesImage/>
package ai.wandering.scoop.v1.models

</div>
interface GetPublications {
suspend operator fun invoke(args: GetPublicationsArgs): GetPublicationsResponse
}
```

<div style={{height: 40, width: "100%"}}/>
- Here's the `GetPublicationsArgs` data model.

```kotlin
@Serializable
data class Issue(
val id: String,
val title: String,
val coverImageUrl: String,
val datePublished: Long,
val topStories: List<Story>
// From the Scoop SDK

package ai.wandering.scoop.v1.models

data class GetPublicationsArgs(
val count: Int,
val direction: CursorDirection,
val cursor: Cursor?,
)
```

- To fetch publications with the Scoop SDK, all we need to do is:

```kotlin
@Serializable
data class Tag(
val id: String,
val name: String,
)
// PublicationsStore.kt

package org.mobilenativefoundation.store.news.shared.feat.exploreTab.impl

val args = GetPublicationsArgs(count = 100, direction = CursorDirection.NEWER, cursor = null)

when (val response = scoopClient.getPublications(args)) {
is Error -> { /** TODO(): Handle error */ }
is Data -> { /** TODO(): Handle publications */ }
}
```

## Detailed Story View
- Let's introduce the concept of a `Fetcher`. A `Fetcher` is a class that fetches data from a remote source. In our case, we will be fetching data using the Scoop SDK, as above.

<StoryImage/>
```kotlin
// PublicationsStore.kt

<div style={{height: 40, width: "100%"}}/>
package org.mobilenativefoundation.store.news.shared.feat.exploreTab.impl

val fetcher = Fetcher.of<GetPublicationsArgs, Data> { args ->
flowOf(when (val response = scoopClient.getPublications(args)) {
is Error -> {
FetcherResult.Error.Message(response.value.message)
}

is Data -> {
response
}
})
}

```

- Our next concept is a `SourceOfTruth`. A `SourceOfTruth` is a class that persists items. It's usually backed by a database or a disk cache, but it doesn't need to be. Here's a simple example:

```kotlin
@Serializable
data class Story(
val id: String,
val title: String,
val summary: String,
val content: String,
val imageUrl: String,
val datePublished: String,
val tags: List<Tag>
// PublicationsStore.kt

package org.mobilenativefoundation.store.news.shared.feat.exploreTab.impl

val db = mutableMapOf<GetPublicationsArgs, Data>()

// Don't worry too much about the generics right now.
// We'll cover more advanced use cases with different network and local representations later.
val sourceOfTruth = SourceOfTruth.of<GetPublicationsArgs, Data, Data>(
reader = {args: GetPublicationsArgs ->
flowOf(db[args])
},
writer = { (args: GetPublicationsArgs, data: Data) ->
db[args] = data
}
)
```

- The last concept we need for getting started with Store is `StoreBuilder`. It builds a Store from a `Fetcher` and `SourceOfTruth`. It can also take in other classes and configurations (e.g., memory cache, validator, converter). We will cover these later.


```kotlin
// PublicationsStore.kt

package org.mobilenativefoundation.store.news.shared.feat.exploreTab.impl

val storeBuilder = StoreBuilder.from(
fetcher = fetcher,
sourceOfTruth = sourceOfTruth
)
```

- Finally, to build the Store:

```kotlin
// PublicationsStore.kt

package org.mobilenativefoundation.store.news.shared.feat.exploreTab.impl

val store = storeBuilder.build()
```
8 changes: 8 additions & 0 deletions website/docs/step_4.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
slug: /tutorial/4
sidebar_position: 4
sidebar_class_name: tutorial_4
title: Using Your Store
---


2 changes: 1 addition & 1 deletion website/src/components/StoreNewsAppImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import storeNewsAppImg from "@site/static/img/store_news_app.png"

export function StoreNewsAppImage() {
return (
<img src={storeNewsAppImg} alt="Store news app" style={{height: 750}}/>
<img src={storeNewsAppImg} alt="Store news app" style={{height: 500}}/>
)
}

0 comments on commit 580512c

Please sign in to comment.