Skip to content

Commit 156e653

Browse files
committed
basic android sample
1 parent 9157c65 commit 156e653

File tree

17 files changed

+831
-2
lines changed

17 files changed

+831
-2
lines changed

.idea/kotlinc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

channel-event-bus/src/commonMain/kotlin/com/hoc081098/channeleventbus/ChannelEventBusValidationBeforeClosing.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ public enum class ChannelEventBusValidationBeforeClosing {
2929
@JvmField
3030
public val ALL: Set<ChannelEventBusValidationBeforeClosing> = entries.toSet()
3131

32-
public val NONE: Set<ChannelEventBusValidationBeforeClosing> get() = emptySet()
32+
public inline val NONE: Set<ChannelEventBusValidationBeforeClosing> get() = emptySet()
3333
}
3434
}

sample/app/build.gradle.kts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
@Suppress("DSL_SCOPE_VIOLATION")
2+
plugins {
3+
alias(libs.plugins.android.app)
4+
alias(libs.plugins.kotlin.android)
5+
}
6+
7+
android {
8+
namespace = "com.hoc081098.channeleventbus.sample.android"
9+
compileSdk = libs.versions.sample.android.compile.get().toInt()
10+
defaultConfig {
11+
applicationId = "com.hoc081098.channeleventbus.sample.android"
12+
minSdk = libs.versions.android.min.get().toInt()
13+
targetSdk = libs.versions.sample.android.target.get().toInt()
14+
versionCode = 1
15+
versionName = "1.0"
16+
}
17+
buildFeatures {
18+
compose = true
19+
buildConfig = true
20+
}
21+
composeOptions {
22+
kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get()
23+
}
24+
packaging {
25+
resources {
26+
excludes += "/META-INF/{AL2.0,LGPL2.1}"
27+
}
28+
}
29+
buildTypes {
30+
getByName("release") {
31+
isMinifyEnabled = false
32+
}
33+
}
34+
compileOptions {
35+
sourceCompatibility = JavaVersion.toVersion(libs.versions.java.target.get())
36+
targetCompatibility = JavaVersion.toVersion(libs.versions.java.target.get())
37+
}
38+
kotlinOptions {
39+
jvmTarget = JavaVersion.toVersion(libs.versions.java.target.get()).toString()
40+
}
41+
}
42+
43+
dependencies {
44+
implementation(project(":channel-event-bus"))
45+
46+
implementation(platform(libs.androidx.compose.bom))
47+
implementation(libs.androidx.lifecycle.runtime.compose)
48+
49+
implementation(libs.androidx.compose.ui.ui)
50+
debugImplementation(libs.androidx.compose.ui.tooling)
51+
implementation(libs.androidx.compose.ui.tooling.preview)
52+
implementation(libs.androidx.compose.foundation)
53+
implementation(libs.androidx.compose.material3)
54+
implementation(libs.androidx.compose.material)
55+
implementation(libs.androidx.compose.runtime)
56+
implementation(libs.androidx.activity.compose)
57+
implementation(libs.androidx.navigation.compose)
58+
59+
implementation(libs.koin.androidx.compose)
60+
implementation(libs.coil.compose)
61+
62+
implementation(libs.kotlinx.collections.immutable)
63+
}
64+
65+
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
66+
kotlinOptions {
67+
if (project.findProperty("composeCompilerReports") == "true") {
68+
freeCompilerArgs = freeCompilerArgs + listOf(
69+
"-P",
70+
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.buildDir.absolutePath}/compose_compiler",
71+
)
72+
}
73+
if (project.findProperty("composeCompilerMetrics") == "true") {
74+
freeCompilerArgs = freeCompilerArgs + listOf(
75+
"-P",
76+
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=${project.buildDir.absolutePath}/compose_compiler",
77+
)
78+
}
79+
}
80+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<uses-permission android:name="android.permission.INTERNET" />
5+
6+
<application
7+
android:name=".MyApp"
8+
android:allowBackup="false"
9+
android:supportsRtl="true"
10+
android:theme="@style/AppTheme">
11+
<activity
12+
android:name=".MainActivity"
13+
android:exported="true">
14+
<intent-filter>
15+
<action android:name="android.intent.action.MAIN" />
16+
<category android:name="android.intent.category.LAUNCHER" />
17+
</intent-filter>
18+
</activity>
19+
</application>
20+
</manifest>
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
2+
3+
package com.hoc081098.channeleventbus.sample.android
4+
5+
import android.os.Bundle
6+
import androidx.activity.ComponentActivity
7+
import androidx.activity.compose.setContent
8+
import androidx.compose.foundation.layout.ExperimentalLayoutApi
9+
import androidx.compose.foundation.layout.consumeWindowInsets
10+
import androidx.compose.foundation.layout.fillMaxSize
11+
import androidx.compose.foundation.layout.padding
12+
import androidx.compose.material.Icon
13+
import androidx.compose.material.icons.Icons
14+
import androidx.compose.material.icons.filled.ArrowBack
15+
import androidx.compose.material3.ExperimentalMaterial3Api
16+
import androidx.compose.material3.IconButton
17+
import androidx.compose.material3.MaterialTheme
18+
import androidx.compose.material3.Scaffold
19+
import androidx.compose.material3.Surface
20+
import androidx.compose.material3.Text
21+
import androidx.compose.material3.TopAppBar
22+
import androidx.compose.runtime.Composable
23+
import androidx.compose.runtime.derivedStateOf
24+
import androidx.compose.runtime.getValue
25+
import androidx.compose.runtime.remember
26+
import androidx.compose.ui.Modifier
27+
import androidx.navigation.NavHostController
28+
import androidx.navigation.compose.NavHost
29+
import androidx.navigation.compose.composable
30+
import androidx.navigation.compose.currentBackStackEntryAsState
31+
import androidx.navigation.compose.rememberNavController
32+
import androidx.navigation.navigation
33+
import com.hoc081098.channeleventbus.sample.android.common.MyApplicationTheme
34+
import com.hoc081098.channeleventbus.sample.android.ui.register.stepone.RegisterStepOneScreen
35+
36+
@OptIn(ExperimentalLayoutApi::class)
37+
class MainActivity : ComponentActivity() {
38+
override fun onCreate(savedInstanceState: Bundle?) {
39+
super.onCreate(savedInstanceState)
40+
41+
setContent {
42+
MyApplicationTheme {
43+
Surface(
44+
modifier = Modifier.fillMaxSize(),
45+
color = MaterialTheme.colorScheme.background,
46+
) {
47+
val navController = rememberNavController()
48+
val currentBackStackEntryAsState = navController.currentBackStackEntryAsState()
49+
50+
val route by rememberCurrentRouteAsState(currentBackStackEntryAsState)
51+
val previousBackStackEntry by remember(currentBackStackEntryAsState) {
52+
derivedStateOf {
53+
currentBackStackEntryAsState.value
54+
navController.previousBackStackEntry
55+
}
56+
}
57+
58+
Scaffold(
59+
topBar = {
60+
TopAppBar(
61+
title = {
62+
Text(
63+
text = when (route) {
64+
Route.RegisterStepOne -> "Register step 1"
65+
Route.RegisterStepThree -> "Register step 2"
66+
Route.RegisterStepTwo -> "Register step 3"
67+
null -> ""
68+
},
69+
)
70+
},
71+
navigationIcon = {
72+
if (previousBackStackEntry != null) {
73+
IconButton(onClick = navController::popBackStack) {
74+
Icon(
75+
imageVector = Icons.Default.ArrowBack,
76+
contentDescription = "Back",
77+
)
78+
}
79+
}
80+
},
81+
)
82+
},
83+
) { innerPadding ->
84+
AppNavHost(
85+
modifier = Modifier
86+
.padding(innerPadding)
87+
.consumeWindowInsets(innerPadding)
88+
.fillMaxSize(),
89+
navController = navController,
90+
)
91+
}
92+
}
93+
}
94+
}
95+
}
96+
}
97+
98+
@Composable
99+
private fun AppNavHost(
100+
modifier: Modifier = Modifier,
101+
navController: NavHostController = rememberNavController(),
102+
) {
103+
NavHost(
104+
modifier = modifier,
105+
navController = navController,
106+
startDestination = "register_graph",
107+
) {
108+
navigation(startDestination = "register_graph", route = Route.RegisterStepOne.routePattern) {
109+
composable(route = Route.RegisterStepOne.routePattern) {
110+
RegisterStepOneScreen()
111+
}
112+
113+
composable(route = Route.RegisterStepTwo.routePattern) {
114+
}
115+
116+
composable(route = Route.RegisterStepThree.routePattern) {
117+
}
118+
}
119+
}
120+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.hoc081098.channeleventbus.sample.android
2+
3+
import android.app.Application
4+
import org.koin.android.ext.koin.androidContext
5+
import org.koin.android.ext.koin.androidLogger
6+
import org.koin.core.context.startKoin
7+
import org.koin.core.logger.Level
8+
9+
class MyApp : Application() {
10+
override fun onCreate() {
11+
super.onCreate()
12+
13+
startKoin {
14+
androidContext(this@MyApp)
15+
androidLogger(
16+
if (BuildConfig.DEBUG) {
17+
Level.DEBUG
18+
} else {
19+
Level.ERROR
20+
},
21+
)
22+
}
23+
}
24+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.hoc081098.channeleventbus.sample.android
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.State
5+
import androidx.compose.runtime.derivedStateOf
6+
import androidx.compose.runtime.remember
7+
import androidx.navigation.NavBackStackEntry
8+
import kotlinx.collections.immutable.ImmutableList
9+
import kotlinx.collections.immutable.persistentListOf
10+
11+
@Composable
12+
internal fun rememberCurrentRouteAsState(currentBackStackEntryAsState: State<NavBackStackEntry?>): State<Route?> =
13+
remember(currentBackStackEntryAsState) {
14+
derivedStateOf {
15+
currentBackStackEntryAsState.value
16+
?.destination
17+
?.route
18+
?.let(Route.Companion::ofOrNull)
19+
}
20+
}
21+
22+
internal sealed class Route {
23+
abstract val routePattern: String
24+
25+
fun matches(route: String): Boolean = route == routePattern
26+
27+
data object RegisterStepOne : Route() {
28+
override val routePattern = "register_step_one"
29+
val route = routePattern
30+
}
31+
32+
data object RegisterStepTwo : Route() {
33+
override val routePattern = "register_step_two"
34+
val route = routePattern
35+
}
36+
37+
data object RegisterStepThree : Route() {
38+
override val routePattern = "register_step_three"
39+
val route = routePattern
40+
}
41+
42+
companion object {
43+
private val VALUES: ImmutableList<Route> by lazy {
44+
persistentListOf(
45+
RegisterStepOne,
46+
RegisterStepTwo,
47+
RegisterStepThree,
48+
)
49+
}
50+
51+
fun ofOrNull(route: String): Route? = VALUES.singleOrNull { it.matches(route) }
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.hoc081098.channeleventbus.sample.android.common
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.NonRestartableComposable
5+
import androidx.compose.runtime.RememberObserver
6+
import androidx.compose.runtime.getValue
7+
import androidx.compose.runtime.remember
8+
import androidx.compose.runtime.rememberUpdatedState
9+
import androidx.compose.ui.platform.LocalLifecycleOwner
10+
import androidx.lifecycle.Lifecycle
11+
import androidx.lifecycle.LifecycleOwner
12+
import androidx.lifecycle.repeatOnLifecycle
13+
import kotlinx.coroutines.CoroutineScope
14+
import kotlinx.coroutines.Dispatchers
15+
import kotlinx.coroutines.Job
16+
import kotlinx.coroutines.cancel
17+
import kotlinx.coroutines.flow.Flow
18+
import kotlinx.coroutines.launch
19+
20+
/**
21+
* Collect the given [Flow] in a Effect that runs in the [Dispatchers.Main.immediate] coroutine,
22+
* when [LifecycleOwner.lifecycle] is at least at [minActiveState].
23+
*/
24+
@Composable
25+
fun <T> Flow<T>.CollectWithLifecycleEffect(
26+
vararg keys: Any?,
27+
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
28+
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
29+
collector: (T) -> Unit,
30+
) {
31+
val flow = this
32+
val currentCollector by rememberUpdatedState(collector)
33+
34+
LaunchedEffectInImmediateMain(flow, lifecycleOwner, minActiveState, *keys) {
35+
lifecycleOwner.repeatOnLifecycle(minActiveState) {
36+
flow.collect { currentCollector(it) }
37+
}
38+
}
39+
}
40+
41+
@Composable
42+
@NonRestartableComposable
43+
@Suppress("ArrayReturn")
44+
private fun LaunchedEffectInImmediateMain(
45+
vararg keys: Any?,
46+
block: suspend CoroutineScope.() -> Unit,
47+
) {
48+
remember(*keys) { LaunchedEffectImpl(block) }
49+
}
50+
51+
private class LaunchedEffectImpl(
52+
private val task: suspend CoroutineScope.() -> Unit,
53+
) : RememberObserver {
54+
private val scope = CoroutineScope(Dispatchers.Main.immediate)
55+
private var job: Job? = null
56+
57+
override fun onRemembered() {
58+
job?.cancel("Old job was still running!")
59+
job = scope.launch(block = task)
60+
}
61+
62+
override fun onForgotten() {
63+
job?.cancel()
64+
job = null
65+
}
66+
67+
override fun onAbandoned() {
68+
job?.cancel()
69+
job = null
70+
}
71+
}

0 commit comments

Comments
 (0)