Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace CFlows with StateFlows using SKIE #57

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@ dependencies {
implementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.compose.material)

implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.startup.runtime)
}
24 changes: 0 additions & 24 deletions androidApp/src/main/java/com/mirego/kmp/boilerplate/Greeting.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@ package com.mirego.kmp.boilerplate
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity

import com.mirego.kmp.boilerplate.viewmodels.ViewModelFactory
import com.mirego.kmp.boilerplate.viewmodels.lifecycleViewModel
import com.mirego.kmp.boilerplate.views.ExampleView

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val viewModelFactory = ViewModelFactory()

setContent {
Greeting(textFlow = Greeting().greeting())
ExampleView(
viewModel = lifecycleViewModel {
viewModelFactory.example()
}
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.startup.AppInitializer
import com.mirego.kmp.boilerplate.platform.AppContextInitializer
import com.mirego.kmp.boilerplate.viewmodels.ViewModelFactory

@Composable
fun PreviewContext(content: @Composable () -> Unit) {
fun PreviewContext(content: @Composable (ViewModelFactory) -> Unit) {
// @Composable previews do not call AppInitializer. We must initialize our components manually.
AppInitializer.getInstance(LocalContext.current)
.initializeComponent(AppContextInitializer::class.java)

content()
val viewModelFactory = ViewModelFactory()

content(viewModelFactory)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.mirego.kmp.boilerplate.viewmodels

import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory


/**
* Convenience viewModel builder which creates the common ViewModel using the provided initializer
* and wraps it into an androidx.lifecycle.ViewModel() for proper cancellation.
*/
@Composable
inline fun <reified VM : ViewModel> lifecycleViewModel(crossinline initializer: () -> VM): VM {
val factory = viewModelFactory {
initializer {
LifecycleViewModel(vm = initializer())
}
}
return viewModel<LifecycleViewModel<VM>>(factory = factory).vm
}

/**
* Wraps our common ViewModel into an androidx.lifecycle.ViewModel() to cancel work when cleared.
*/
class LifecycleViewModel<VM : ViewModel>(val vm: VM) : androidx.lifecycle.ViewModel() {
override fun onCleared() = vm.cancel()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.mirego.kmp.boilerplate.views

import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.mirego.kmp.boilerplate.previews.PreviewContext
import com.mirego.kmp.boilerplate.viewmodels.example.ExampleViewModel

@Composable
fun ExampleView(viewModel: ExampleViewModel) {
val state: ExampleViewModel.State by viewModel.state.collectAsStateWithLifecycle()

Text(text = state.greeting)
}

@Preview(showSystemUi = true)
@Composable
fun PreviewExampleView() {
PreviewContext { viewModelFactory ->
ExampleView(
viewModel = viewModelFactory.example()
)
}
}
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ plugins {
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.kotlin.native.cocoapods) apply false
alias(libs.plugins.serialization) apply false
alias(libs.plugins.ktlint) apply false
alias(libs.plugins.serialization) apply false
alias(libs.plugins.skie) apply false

alias(libs.plugins.owasp.dependencycheck)
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Gradle
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
org.gradle.configuration-cache=true
org.gradle.configuration-cache=false
#Kotlin
kotlin.code.style=official
#Android
Expand Down
17 changes: 12 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
[versions]
androidComposeCompiler = "1.5.6"
androidGradlePlugin = "8.2.0"
androidxStartup = "1.1.1"
androidxActivityCompose = "1.8.1"
androidGradlePlugin = "8.2.1"
androidxActivityCompose = "1.8.2"
androidxAppcompat = "1.6.1"
androidxComposeBom = "2023.10.01"
androidxLifecycle = "2.6.2"
androidxStartup = "1.1.1"
konnectivity = "0.3.0"
kotlin = "1.9.21"
kotlinxCoroutines = "1.7.3"
kotlinxSerialization = "1.6.0"
ktlint = "11.6.1"
kotlinxSerialization = "1.6.2"
ktlint = "12.0.2"
skie = "0.6.1"

[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidxActivityCompose" }
Expand All @@ -18,11 +20,15 @@ androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", versi
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-material = { group = "androidx.compose.material", name = "material" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycle" }
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "androidxStartup" }
konnectivity = { module = "com.mirego:konnectivity", version.ref = "konnectivity" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
skie-configuration-annotations = { module = "co.touchlab.skie:configuration-annotations", version.ref = "skie" }

[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
Expand All @@ -33,6 +39,7 @@ kotlin-native-cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", versio
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
owasp-dependencycheck = { id = "org.owasp.dependencycheck", version = "8.4.2" }
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
skie = { id = "co.touchlab.skie", version.ref = "skie" }

[bundles]

18 changes: 7 additions & 11 deletions ios/iosApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
7555FF83242A565900829871 /* GreetingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* GreetingView.swift */; };
7555FF83242A565900829871 /* ExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ExampleView.swift */; };
9B8ACFDB4E332DFCA8B97CBB /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E4E1328B104D05A50A097EE /* Pods_iosApp.framework */; };
BC5700EB2B1A94D200525C22 /* PreviewContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5700EA2B1A94D200525C22 /* PreviewContext.swift */; };
BC83B466276E4F080053E064 /* FlowUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC83B465276E4F080053E064 /* FlowUtils.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -23,10 +22,9 @@
308A8A1989CCC0B3DD133EE0 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = "<group>"; };
4E4E1328B104D05A50A097EE /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
7555FF82242A565900829871 /* GreetingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GreetingView.swift; sourceTree = "<group>"; };
7555FF82242A565900829871 /* ExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleView.swift; sourceTree = "<group>"; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
BC5700EA2B1A94D200525C22 /* PreviewContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewContext.swift; sourceTree = "<group>"; };
BC83B465276E4F080053E064 /* FlowUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowUtils.swift; sourceTree = "<group>"; };
E232C917135C2C1E3BC8748A /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -75,19 +73,18 @@
7555FF8C242A565B00829871 /* Info.plist */,
058557BA273AAA24004C7B11 /* Assets.xcassets */,
2152FB032600AC8F00CF470E /* iOSApp.swift */,
7555FF82242A565900829871 /* GreetingView.swift */,
BC83B464276E4EF80053E064 /* Utils */,
BCB66E1E2B2177530025AC5F /* Views */,
058557D7273AAEEB004C7B11 /* Preview Content */,
);
path = iosApp;
sourceTree = "<group>";
};
BC83B464276E4EF80053E064 /* Utils */ = {
BCB66E1E2B2177530025AC5F /* Views */ = {
isa = PBXGroup;
children = (
BC83B465276E4F080053E064 /* FlowUtils.swift */,
7555FF82242A565900829871 /* ExampleView.swift */,
);
path = Utils;
path = Views;
sourceTree = "<group>";
};
C8C629BFDC2144230B71E3BC /* Frameworks */ = {
Expand Down Expand Up @@ -242,9 +239,8 @@
buildActionMask = 2147483647;
files = (
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
7555FF83242A565900829871 /* GreetingView.swift in Sources */,
7555FF83242A565900829871 /* ExampleView.swift in Sources */,
BC5700EB2B1A94D200525C22 /* PreviewContext.swift in Sources */,
BC83B466276E4F080053E064 /* FlowUtils.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
17 changes: 0 additions & 17 deletions ios/iosApp/GreetingView.swift

This file was deleted.

6 changes: 4 additions & 2 deletions ios/iosApp/Preview Content/PreviewContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import Shared
import SwiftUI

struct PreviewContext<Content>: View where Content: View {
let content: @MainActor () -> Content
let content: @MainActor (ViewModelFactory) -> Content

let viewModelFactory = ViewModelFactory()

var body: some View {
content()
content(viewModelFactory)
}
}
21 changes: 0 additions & 21 deletions ios/iosApp/Utils/FlowUtils.swift

This file was deleted.

37 changes: 37 additions & 0 deletions ios/iosApp/Views/ExampleView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Shared
import SwiftUI

struct ExampleView: View {

private let viewModel: ExampleViewModel

@State private var state: ExampleViewModelState

init(viewModel: ExampleViewModel) {
self.viewModel = viewModel
self.state = viewModel.state.value
}

var body: some View {
VStack {
Text(state.greeting)
}
.task {
await withTaskCancellationHandler {
for await state in viewModel.state {
self.state = state
}
} onCancel: {
viewModel.cancel()
}
}
}
}

#Preview {
PreviewContext { viewModelFactory in
ExampleView(
viewModel: viewModelFactory.example()
)
}
}
15 changes: 10 additions & 5 deletions ios/iosApp/iOSApp.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import Shared
import SwiftUI

@main
struct IOSApp: App {
var body: some Scene {
WindowGroup {
GreetingView()
}
}
let viewModelFactory = ViewModelFactory()

var body: some Scene {
WindowGroup {
ExampleView(
viewModel: viewModelFactory.example()
)
}
}
}
Loading
Loading