aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2019-01-04 23:03:50 -0500
committerGitHub <noreply@github.com>2019-01-04 23:03:50 -0500
commitd61ff7cb4f43d71d2170cdd25ceab2e3edcb81fc (patch)
treeb1fcd17ad4782ef1cb5cd3aba8539669fc2aac3c
parentab349293e52d77f6fc4b44446e19dc80aa8e789f (diff)
downloadkau-d61ff7cb4f43d71d2170cdd25ceab2e3edcb81fc.tar.gz
kau-d61ff7cb4f43d71d2170cdd25ceab2e3edcb81fc.tar.bz2
kau-d61ff7cb4f43d71d2170cdd25ceab2e3edcb81fc.zip
Coroutine tests (#185)
* Add some coroutine tests for implicit cancellation * Create util test and new helper methods * Remove coroutinescope extension from withcontext * Update dependencies
-rw-r--r--buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy8
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt27
-rw-r--r--core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt60
-rw-r--r--core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt58
-rw-r--r--docs/Changelog.md1
-rw-r--r--sample/src/main/res/xml/kau_changelog.xml2
6 files changed, 125 insertions, 31 deletions
diff --git a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy
index e6f3cd7..4afec82 100644
--- a/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy
+++ b/buildSrc/src/main/groovy/ca/allanwang/kau/Versions.groovy
@@ -27,7 +27,7 @@ class Versions {
static def kotlin = '1.3.11'
// https://github.com/Kotlin/kotlinx.coroutines/releases
- static def coroutines = '1.0.1'
+ static def coroutines = '1.1.0'
// https://github.com/mikepenz/AboutLibraries/releases
static def aboutLibraries = '6.2.0'
@@ -72,6 +72,8 @@ class Versions {
static def playPublishPlugin = '1.2.2'
// https://github.com/KeepSafe/dexcount-gradle-plugin/releases
- static def dexCountPlugin = '0.8.3'
- static def gitVersionPlugin = '0.4.4'
+ static def dexCountPlugin = '0.8.5'
+
+ // https://github.com/gladed/gradle-android-git-version/releases
+ static def gitVersionPlugin = '0.4.7'
} \ No newline at end of file
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
index 09ad4ea..fc8049d 100644
--- a/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt
@@ -26,7 +26,6 @@ import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
-import android.os.Handler
import android.os.Looper
import android.util.TypedValue
import android.view.View
@@ -47,36 +46,10 @@ import androidx.core.content.ContextCompat
import ca.allanwang.kau.R
import ca.allanwang.kau.logging.KL
import com.afollestad.materialdialogs.MaterialDialog
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.android.asCoroutineDispatcher
-import kotlin.coroutines.CoroutineContext
/**
* Created by Allan Wang on 2017-06-03.
*/
-object ContextHelper : CoroutineScope {
-
- val looper = Looper.getMainLooper()
-
- val handler = Handler(looper)
-
- /**
- * Creating dispatcher from main handler to avoid IO
- * See https://github.com/Kotlin/kotlinx.coroutines/issues/878
- */
- val dispatcher = handler.asCoroutineDispatcher("kau-main")
-
- override val coroutineContext: CoroutineContext get() = dispatcher
-}
-
-/**
- * Most context items implement [CoroutineScope] by default.
- * We will add a fallback just in case.
- * It is expected that the scope returned always has the Android main dispatcher as part of the context.
- */
-internal inline val Context.ctxCoroutine: CoroutineScope
- get() = this as? CoroutineScope ?: ContextHelper
-
fun Context.runOnUiThread(f: Context.() -> Unit) {
if (ContextHelper.looper === Looper.myLooper()) f() else ContextHelper.handler.post { f() }
}
diff --git a/core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt b/core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt
new file mode 100644
index 0000000..032c407
--- /dev/null
+++ b/core/src/main/kotlin/ca/allanwang/kau/utils/CoroutineUtils.kt
@@ -0,0 +1,60 @@
+package ca.allanwang.kau.utils
+
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+object ContextHelper : CoroutineScope {
+
+ val looper = Looper.getMainLooper()
+
+ val handler = Handler(looper)
+
+ /**
+ * Creating dispatcher from main handler to avoid IO
+ * See https://github.com/Kotlin/kotlinx.coroutines/issues/878
+ */
+ val dispatcher = handler.asCoroutineDispatcher("kau-main")
+
+ override val coroutineContext: CoroutineContext get() = dispatcher
+}
+
+/**
+ * Most context items implement [CoroutineScope] by default.
+ * We will add a fallback just in case.
+ * It is expected that the scope returned always has the Android main dispatcher as part of the context.
+ */
+internal inline val Context.ctxCoroutine: CoroutineScope
+ get() = this as? CoroutineScope ?: ContextHelper
+
+/**
+ * Calls [launch] with an explicit dispatcher for Android's main thread
+ */
+fun CoroutineScope.launchMain(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> Unit
+) = launch(ContextHelper.dispatcher + context, start, block)
+
+/**
+ * Calls [async] with an explicit dispatcher for Android's main thread
+ */
+fun CoroutineScope.asyncMain(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> Unit
+) = async(ContextHelper.dispatcher + context, start, block)
+
+/**
+ * Calls [withContext] with an explicit dispatcher for Android's main thread
+ */
+suspend fun <T> withMainContext(block: suspend CoroutineScope.() -> T) =
+ withContext(ContextHelper.dispatcher, block)
diff --git a/core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt b/core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt
new file mode 100644
index 0000000..91d0174
--- /dev/null
+++ b/core/src/test/kotlin/ca/allanwang/kau/kotlin/CoroutineTest.kt
@@ -0,0 +1,58 @@
+package ca.allanwang.kau.kotlin
+
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.fail
+
+/**
+ * Tests geared towards coroutines
+ */
+class CoroutineTest {
+
+ /**
+ * If a job is cancelled, then a switch to a new context will not run
+ */
+ @Test
+ fun implicitCancellationBefore() {
+ val job = Job()
+ var id = 0
+ try {
+ runBlocking(job) {
+ id++
+ job.cancel()
+ withContext(Dispatchers.IO) {
+ fail("Context switch should not be reached")
+ }
+ }
+ } catch (ignore: CancellationException) {
+ } finally {
+ assertEquals(1, id, "Launcher never executed")
+ }
+ }
+
+ /**
+ * If a job is cancelled, then a switch from a new context will not run
+ */
+ @Test
+ fun implicitCancellationAfter() {
+ val job = Job()
+ var id = 0
+ try {
+ runBlocking(job) {
+ withContext(Dispatchers.IO) {
+ id++
+ job.cancel()
+ }
+ fail("Post context switch should not be reached")
+ }
+ } catch (ignore: CancellationException) {
+ } finally {
+ assertEquals(1, id, "Context switch never executed")
+ }
+ }
+} \ No newline at end of file
diff --git a/docs/Changelog.md b/docs/Changelog.md
index e6d51d2..7291753 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -5,6 +5,7 @@
* :core: Add default CoroutineScope implementation to KauBaseActivity
* :core: Remove zip class. Coroutines and join can be used as an alternative
* :core: Delete flyweight implementation. Kotlin already has getOrPut
+* :core: Introduce ContextHelper, where you can get the default looper, handler, and dispatcher for Android
* :mediapicker: Use video preloading instead of full async loading
## v4.0.0-alpha01
diff --git a/sample/src/main/res/xml/kau_changelog.xml b/sample/src/main/res/xml/kau_changelog.xml
index 0283d7f..570a0b9 100644
--- a/sample/src/main/res/xml/kau_changelog.xml
+++ b/sample/src/main/res/xml/kau_changelog.xml
@@ -11,10 +11,10 @@
<item text=":core: Add default CoroutineScope implementation to KauBaseActivity" />
<item text=":core: Remove zip class. Coroutines and join can be used as an alternative" />
<item text=":core: Delete flyweight implementation. Kotlin already has getOrPut" />
+ <item text=":core: Introduce ContextHelper, where you can get the default looper, handler, and dispatcher for Android" />
<item text=":mediapicker: Use video preloading instead of full async loading" />
<item text="" />
<item text="" />
- <item text="" />
<version title="v4.0.0-alpha01" />
<item text="Migrate to androidx. See migration for external dependency changes." />