From bc4b61634385c05ff401d52f64fbde6573b3e83b Mon Sep 17 00:00:00 2001 From: Jan Seeger Date: Wed, 8 Nov 2023 21:04:53 +0100 Subject: [PATCH 1/4] Add AndroidHandlerFunc --- .../dachlatten/compose/AndroidHandlerFunc.kt | 45 +++++++++++++++++++ .../src/test/kotlin/AndroidHandlerFuncTest.kt | 22 +++++++++ 2 files changed, 67 insertions(+) create mode 100644 dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/AndroidHandlerFunc.kt create mode 100644 dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt diff --git a/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/AndroidHandlerFunc.kt b/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/AndroidHandlerFunc.kt new file mode 100644 index 0000000..ecdc689 --- /dev/null +++ b/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/AndroidHandlerFunc.kt @@ -0,0 +1,45 @@ +package de.sipgate.dachlatten.compose + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +// This provides a parameterless handlerFunc that can be +// passed around and invoked from anywhere. It will keep a +// reference to the Context given during its creation. +// Warning: +// Depending on the situation this can easily leak the Context! + +typealias AndroidClickHandler = context (ContextProvider) () -> Unit + +@Composable +inline fun withContext(crossinline target: AndroidClickHandler): ClickHandler { + val context = LocalContext.current + return { + with(ContextProviderImpl(context)) { + target.invoke(this) + } + } +} + +@Composable +inline fun withContext( + crossinline target: context (ContextProvider) + (T) -> Unit, +): (T) -> Unit { + val context = LocalContext.current + return { + with(ContextProviderImpl(context)) { + target.invoke(this, it) + } + } +} + +inline fun Context.withContext( + crossinline target: context (ContextProvider) + () -> Unit, +): HandlerFunc = { + with(ContextProviderImpl(this)) { + target(this) + } +} diff --git a/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt b/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt new file mode 100644 index 0000000..3ad7f61 --- /dev/null +++ b/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt @@ -0,0 +1,22 @@ +package de.sipgate.dachlatten.compose + +import android.content.Context +import org.junit.jupiter.api.Test + +class AndroidHandlerFuncTest { + + private lateinit var context: Context + + @Test + fun testAndroidHandlerFuncWillReceiveAContext() { + + val handlerFunc = context.withContext(::someFunctionThatAccessesTheAndroidContext) + + handlerFunc.invoke() + } + + context (ContextProvider) + private fun someFunctionThatAccessesTheAndroidContext() { + context.applicationInfo.name + } +} From 32487706226dd830d94e7caf508a63341a1a97f2 Mon Sep 17 00:00:00 2001 From: Jan Seeger Date: Wed, 8 Nov 2023 21:28:53 +0100 Subject: [PATCH 2/4] Improve AndroidHandlerFunc structure It can now be used with functions that have a return value. Furthermore both funcs (with and without return values) are now available as both Compose and regular variant. --- .../dachlatten/compose/AndroidHandlerFunc.kt | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/AndroidHandlerFunc.kt b/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/AndroidHandlerFunc.kt index ecdc689..8fd9c48 100644 --- a/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/AndroidHandlerFunc.kt +++ b/dachlatten-compose/src/main/kotlin/de/sipgate/dachlatten/compose/AndroidHandlerFunc.kt @@ -14,31 +14,28 @@ typealias AndroidClickHandler = context (ContextProvider) () -> Unit @Composable inline fun withContext(crossinline target: AndroidClickHandler): ClickHandler { - val context = LocalContext.current - return { - with(ContextProviderImpl(context)) { - target.invoke(this) - } - } + return LocalContext.current.withContext(target) } @Composable -inline fun withContext( +inline fun withContext( + crossinline target: context (ContextProvider) + (T) -> R, +): (T) -> R = LocalContext.current.withContext(target) + +inline fun Context.withContext( crossinline target: context (ContextProvider) - (T) -> Unit, -): (T) -> Unit { - val context = LocalContext.current - return { - with(ContextProviderImpl(context)) { - target.invoke(this, it) - } + (T) -> R, +): (T) -> R = { param -> + with(ContextProviderImpl(this)) { + target.invoke(this, param) } } -inline fun Context.withContext( +inline fun Context.withContext( crossinline target: context (ContextProvider) - () -> Unit, -): HandlerFunc = { + () -> R, +): () -> R = { with(ContextProviderImpl(this)) { target(this) } From 5166367214201a21519577ee65cf76c964a3ab78 Mon Sep 17 00:00:00 2001 From: Jan Seeger Date: Wed, 8 Nov 2023 22:36:32 +0100 Subject: [PATCH 3/4] Fix AndroidHandlerFunc tests --- dachlatten-compose/build.gradle.kts | 12 ++++++++++++ .../src/test/kotlin/AndroidHandlerFuncTest.kt | 11 ++++++----- gradle/libs.versions.toml | 3 ++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/dachlatten-compose/build.gradle.kts b/dachlatten-compose/build.gradle.kts index 4d7eff8..cad801b 100644 --- a/dachlatten-compose/build.gradle.kts +++ b/dachlatten-compose/build.gradle.kts @@ -1,9 +1,21 @@ plugins { id("android-library-base") id("android-library-unit-test") + id("android-library-robolectric-test") id("android-library-release") } dependencies { implementation(libs.compose.ui) } + +android { + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() + } + + buildFeatures { + compose = true + } +} + diff --git a/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt b/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt index 3ad7f61..0f8534d 100644 --- a/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt +++ b/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt @@ -1,15 +1,16 @@ package de.sipgate.dachlatten.compose -import android.content.Context -import org.junit.jupiter.api.Test +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +@RunWith(RobolectricTestRunner::class) class AndroidHandlerFuncTest { - private lateinit var context: Context - @Test fun testAndroidHandlerFuncWillReceiveAContext() { - + val context = RuntimeEnvironment.getApplication().applicationContext val handlerFunc = context.withContext(::someFunctionThatAccessesTheAndroidContext) handlerFunc.invoke() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6b9cb3d..2fe9530 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,8 @@ turbine = "1.0.0" annotation-jvm = "1.7.0" androidx-lifecycle = "2.6.2" robolectric = "4.10.3" -compose = "1.5.3" +compose = "1.5.4" +compose-compiler = "1.5.4" [libraries] coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } From 0d0927546d7886e15c4d5c4b00ceeb00d8696363 Mon Sep 17 00:00:00 2001 From: Jan Seeger Date: Wed, 8 Nov 2023 22:41:46 +0100 Subject: [PATCH 4/4] Add test for HandlerFunc with return value --- .../src/test/kotlin/AndroidHandlerFuncTest.kt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt b/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt index 0f8534d..d20a53c 100644 --- a/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt +++ b/dachlatten-compose/src/test/kotlin/AndroidHandlerFuncTest.kt @@ -1,6 +1,7 @@ package de.sipgate.dachlatten.compose import org.junit.Test +import org.junit.jupiter.api.Assertions import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment @@ -16,8 +17,22 @@ class AndroidHandlerFuncTest { handlerFunc.invoke() } + @Test + fun testAndroidHandlerFuncWillReceiveAContextAndReturnValueIsPassedBack() { + val context = RuntimeEnvironment.getApplication().applicationContext + val handlerFunc = context.withContext(::someFunctionThatAccessesTheAndroidContextAndReturnsSomething) + + val result = handlerFunc.invoke() + Assertions.assertTrue(result.isNotEmpty()) + } + context (ContextProvider) private fun someFunctionThatAccessesTheAndroidContext() { - context.applicationInfo.name + context.packageName + } + + context (ContextProvider) + private fun someFunctionThatAccessesTheAndroidContextAndReturnsSomething(): String { + return context.packageName } }