Skip to content

Commit

Permalink
Merge pull request #92 from hoc081098/renovate/deps.compose.androidxc…
Browse files Browse the repository at this point in the history
…omposecompilerversion

Compose to v1.5.6, kotlin to v1.9.21, remove kswift, use SKIE and more...
  • Loading branch information
hoc081098 committed Dec 12, 2023
2 parents 71773b3 + 4b038b3 commit 1813bfa
Show file tree
Hide file tree
Showing 109 changed files with 1,424 additions and 1,242 deletions.
20 changes: 17 additions & 3 deletions .editorconfig
@@ -1,12 +1,26 @@
root = true
root=true
[*]
indent_size=2
end_of_line=lf
charset=utf-8
trim_trailing_whitespace=true
insert_final_newline=true
[*.{kt, kts}]
[*.{kt,kts}]
ij_kotlin_imports_layout=*
disabled_rules=filename
ij_continuation_indent_size=4
ij_kotlin_allow_trailing_comma_on_call_site=true
ij_kotlin_allow_trailing_comma=true
ktlint_standard_filename=disabled
ktlint_standard_package-name=disabled
ktlint_standard_property-naming=disabled
ktlint_standard_function-naming=disabled
filename=disabled
ktlint_experimental=enabled
ktlint_code_style=android_studio
max_line_length=120
[*.kts]
max_line_length=300
ktlint_standard_multiline-expression-wrapping=disabled
ktlint_standard_function-signature=disabled
[*.xml]
indent_size=4
5 changes: 1 addition & 4 deletions .github/workflows/ios-build.yml
Expand Up @@ -68,10 +68,7 @@ jobs:
${{ runner.os }}-konan-
- name: Gen pod
run: ./gradlew :shared:podGenIOS --parallel

- name: Gen KSwift
run: ./gradlew kSwiftsharedPodspec
run: ./gradlew :shared:podGenIOS :shared:generateDummyFramework --parallel --stacktrace

- name: Install Pod
run: pod install
Expand Down
37 changes: 12 additions & 25 deletions README.md
Expand Up @@ -7,7 +7,7 @@ Github Repos Search - Kotlin Multiplatform Mobile using Jetpack Compose, SwiftUI
[![iOS Build CI](https://github.com/hoc081098/GithubSearchKMM/actions/workflows/ios-build.yml/badge.svg)](https://github.com/hoc081098/GithubSearchKMM/actions/workflows/ios-build.yml)
[![Validate Gradle Wrapper](https://github.com/hoc081098/GithubSearchKMM/actions/workflows/gradle-wrapper-validation.yml/badge.svg)](https://github.com/hoc081098/GithubSearchKMM/actions/workflows/gradle-wrapper-validation.yml)
[![API](https://img.shields.io/badge/API-23%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=23)
[![Kotlin](https://img.shields.io/badge/kotlin-1.9.10-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![Kotlin](https://img.shields.io/badge/kotlin-1.9.21-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fhoc081098%2FGithubSearchKMM&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com)
[![License: MIT](https://img.shields.io/badge/License-MIT-purple.svg)](https://opensource.org/licenses/MIT)
[![codecov](https://codecov.io/gh/hoc081098/GithubSearchKMM/branch/master/graph/badge.svg?token=qzSAFkj09P)](https://codecov.io/gh/hoc081098/GithubSearchKMM)
Expand Down Expand Up @@ -35,7 +35,7 @@ Liked some of my work? Buy me a coffee (or more likely a beer)
## Tech Stacks
- Functional & Reactive programming with **Kotlin Coroutines with Flow**
- **Clean Architecture** with **MVI** (Uni-directional data flow)
- [**Multiplatform ViewModel and SavedStateHandle**](https://github.com/hoc081098/kmp-viewmodel) (save and restore states across process death), by @hoc081098
- [**Multiplatform ViewModel and SavedStateHandle**](https://github.com/hoc081098/kmp-viewmodel) (save and restore states across process death), by [@hoc081098](https://github.com/hoc081098)
- **Multiplatform FlowRedux** State Management
- [**Λrrow** - Functional companion to Kotlin's Standard Library](https://arrow-kt.io/)
- [Either](https://arrow-kt.io/docs/apidocs/arrow-core/arrow.core/-either/)
Expand All @@ -51,8 +51,8 @@ Liked some of my work? Buy me a coffee (or more likely a beer)
- [Ktor client library](https://ktor.io/docs/getting-started-ktor-client-multiplatform-mobile.html) for networking
- [Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization) for JSON serialization/deserialization.
- [Napier](https://github.com/AAkira/Napier) for Multiplatform Logging.
- [FlowExt](https://github.com/hoc081098/FlowExt) provides many kotlinx.coroutines.Flow operators, by @hoc081098.
- [MOKO KSwift](https://github.com/icerockdev/moko-kswift) is a gradle plugin for generation Swift-friendly API for Kotlin/Native framework.
- [FlowExt](https://github.com/hoc081098/FlowExt) provides many kotlinx.coroutines.Flow operators, by [@hoc081098](https://github.com/hoc081098)
- [Touchlab SKIE](https://skie.touchlab.co/) a Swift-friendly API Generator for Kotlin Multiplatform.
- [kotlinx.collections.immutable](https://github.com/Kotlin/kotlinx.collections.immutable): immutable collection interfaces and implementation prototypes for Kotlin..
- Testing
- [Kotlin Test](https://kotlinlang.org/docs/multiplatform-run-tests.html) for running tests with Kotlin Multiplatform.
Expand Down Expand Up @@ -193,7 +193,6 @@ Conform to `ObservableObject` and use `@Published` property wrapper.
import Foundation
import Combine
import shared
import sharedSwift

@MainActor
class IOSGithubSearchViewModel: ObservableObject {
Expand Down Expand Up @@ -243,8 +242,8 @@ class IOSGithubSearchViewModel: ObservableObject {

# Building & Develop

- `Android Studio Giraffe | 2022.3.1` (note: **Java 17 is now the minimum version required**).
- `Xcode 13.2` or later (due to use of new Swift 5.5 concurrency APIs).
- `Android Studio Hedgehog | 2023.1.1` (note: **Java 17 is now the minimum version required**).
- `Xcode 13.2.1` or later (due to use of new Swift 5.5 concurrency APIs).
- Clone project: `git clone https://github.com/hoc081098/GithubSearchKMM.git`
- Android: open project by `Android Studio` and run as usual.
- iOS
Expand All @@ -261,34 +260,22 @@ class IOSGithubSearchViewModel: ObservableObject {
<br>
<kbd>Cmd</kbd> + <kbd>R</kbd> to run.

When you see any error like this:
```
./GithubSearchKMM/iosApp/iosApp/ContentView.swift:4:8: No such module 'sharedSwift'
```
You can run the following commands (must select `Read from disk` inside Xcode):
```shell
# go to iosApp directory
cd iosApp

# install pods
pod install
```
Then, you can build and run inside Xcode as usual.
You can also build and run iOS app from Xcode as usual.

# LOC

```shell
--------------------------------------------------------------------------------
Language Files Lines Blank Comment Code
--------------------------------------------------------------------------------
Kotlin 106 7789 963 417 6409
Kotlin 116 7942 996 453 6493
JSON 7 3938 0 0 3938
Swift 16 903 118 102 683
Markdown 1 294 54 0 240
Bourne Shell 2 250 28 116 106
Swift 16 960 124 102 734
Markdown 1 281 53 0 228
Bourne Shell 2 249 28 116 105
Batch 1 92 21 0 71
XML 6 69 6 0 63
--------------------------------------------------------------------------------
Total 139 13335 1190 635 11510
Total 149 13531 1228 671 11632
--------------------------------------------------------------------------------
```
6 changes: 3 additions & 3 deletions androidApp/build.gradle.kts
Expand Up @@ -61,7 +61,7 @@ android {
"-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlin.Experimental",
// Enable experimental kotlinx serialization APIs
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
}

Expand Down Expand Up @@ -133,7 +133,7 @@ fun Project.buildComposeMetricsParameters(): List<String> {
val metricsFolder = File(project.buildDir, "compose-metrics")
metricParameters.add("-P")
metricParameters.add(
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + metricsFolder.absolutePath
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + metricsFolder.absolutePath,
)
}

Expand All @@ -143,7 +143,7 @@ fun Project.buildComposeMetricsParameters(): List<String> {
val reportsFolder = File(project.buildDir, "compose-reports")
metricParameters.add("-P")
metricParameters.add(
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + reportsFolder.absolutePath
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + reportsFolder.absolutePath,
)
}
return metricParameters.toList()
Expand Down
@@ -0,0 +1,132 @@
package com.hoc081098.github_search_kmm.android.compose_utils

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch

@Immutable
enum class CollectWithLifecycleEffectDispatcher {
/**
* Use [Dispatchers.Main][kotlinx.coroutines.MainCoroutineDispatcher].
*/
Main,

/**
* Use [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
*/
ImmediateMain,

/**
* Use [androidx.compose.runtime.Composer.applyCoroutineContext].
* Under the hood, it uses Compose [androidx.compose.ui.platform.AndroidUiDispatcher].
*/
Composer,
}

/**
* Collect the given [Flow] in an effect that runs when [LifecycleOwner.lifecycle] is at least at [minActiveState].
*
* - If [dispatcher] is [CollectWithLifecycleEffectDispatcher.ImmediateMain], the effect will run in
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
* - If [dispatcher] is [CollectWithLifecycleEffectDispatcher.Main], the effect will run in
* [Dispatchers.Main][kotlinx.coroutines.MainCoroutineDispatcher].
* - If [dispatcher] is [CollectWithLifecycleEffectDispatcher.Composer], the effect will run in
* [androidx.compose.runtime.Composer.applyCoroutineContext].
*
* NOTE: When [dispatcher] or [collector] changes, the effect will **NOT** be restarted.
* The latest [collector] will be used to receive values from the [Flow] ([rememberUpdatedState] is used).
* If you want to restart the effect, you need to change [keys].
*
* @param keys Keys to be used to [remember] the effect.
* @param lifecycleOwner The [LifecycleOwner] to be used to [repeatOnLifecycle].
* @param minActiveState The minimum [Lifecycle.State] to be used to [repeatOnLifecycle].
* @param dispatcher The dispatcher to be used to launch the [Flow].
* @param collector The collector to be used to collect the [Flow].
*
* @see [LaunchedEffect]
* @see [CollectWithLifecycleEffectDispatcher]
*/
@Composable
fun <T> Flow<T>.CollectWithLifecycleEffect(
vararg keys: Any?,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
dispatcher: CollectWithLifecycleEffectDispatcher = CollectWithLifecycleEffectDispatcher.ImmediateMain,
collector: (T) -> Unit,
) {
val flow = this
val collectorState = rememberUpdatedState(collector)

val block: suspend CoroutineScope.() -> Unit = {
lifecycleOwner.repeatOnLifecycle(minActiveState) {
// NOTE: we don't use `flow.collect(collectState.value)` because it can use the old value
flow.collect { collectorState.value(it) }
}
}

when (dispatcher) {
CollectWithLifecycleEffectDispatcher.ImmediateMain -> {
LaunchedEffectInImmediateMain(flow, lifecycleOwner, minActiveState, *keys, block = block)
}

CollectWithLifecycleEffectDispatcher.Main -> {
LaunchedEffectInMain(flow, lifecycleOwner, minActiveState, *keys, block = block)
}

CollectWithLifecycleEffectDispatcher.Composer -> {
LaunchedEffect(flow, lifecycleOwner, minActiveState, *keys, block = block)
}
}
}

@Composable
@NonRestartableComposable
@Suppress("ArrayReturn")
private fun LaunchedEffectInImmediateMain(vararg keys: Any?, block: suspend CoroutineScope.() -> Unit) {
remember(*keys) { LaunchedEffectImpl(block, Dispatchers.Main.immediate) }
}

@Composable
@NonRestartableComposable
@Suppress("ArrayReturn")
private fun LaunchedEffectInMain(vararg keys: Any?, block: suspend CoroutineScope.() -> Unit) {
remember(*keys) { LaunchedEffectImpl(block, Dispatchers.Main) }
}

private class LaunchedEffectImpl(
private val task: suspend CoroutineScope.() -> Unit,
dispatcher: CoroutineDispatcher,
) : RememberObserver {
private val scope = CoroutineScope(dispatcher)
private var job: Job? = null

override fun onRemembered() {
job?.cancel("Old job was still running!")
job = scope.launch(block = task)
}

override fun onForgotten() {
job?.cancel()
job = null
}

override fun onAbandoned() {
job?.cancel()
job = null
}
}
@@ -1,7 +1,18 @@
package com.hoc081098.github_search_kmm.android.compose_utils

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import kotlin.reflect.KProperty

@Stable
@JvmInline
value class StableWrapper<T>(val value: T)

@Stable
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> StableWrapper<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

@Suppress("NOTHING_TO_INLINE")
@Composable
inline fun <T> rememberStableWrapperOf(value: T): StableWrapper<T> = remember(value) { StableWrapper(value) }

This file was deleted.

0 comments on commit 1813bfa

Please sign in to comment.