├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── flatcircle │ │ │ └── coroutinehelperexample │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── content_main.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── io │ └── flatcircle │ └── coroutinehelperexample │ └── ExampleUnitTest.kt ├── build.gradle ├── coroutinehelper ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── flatcircle │ │ │ └── coroutinehelper │ │ │ ├── ApiResult.kt │ │ │ ├── CallbackFlattening.kt │ │ │ └── CoroutineScopeDelegates.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── io │ └── flatcircle │ └── coroutinehelper │ └── ExampleUnitTest.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ktlint.gradle └── settings.gradle /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | prepare: 4 | working_directory: ~/code 5 | docker: 6 | - image: circleci/android:api-28-alpha 7 | environment: 8 | JVM_OPTS: -Xmx3g 9 | steps: 10 | - checkout 11 | - restore_cache: 12 | key: gradle-{{ checksum "build.gradle" }} 13 | - run: 14 | name: Download dependencies 15 | command: ./gradlew androidDependencies 16 | - save_cache: 17 | paths: 18 | - ~/.gradle 19 | key: gradle-{{ checksum "build.gradle" }} 20 | destination: reports 21 | 22 | ktlint: 23 | working_directory: ~/code 24 | docker: 25 | - image: circleci/android:api-28-alpha 26 | environment: 27 | JVM_OPTS: -Xmx3g 28 | steps: 29 | - checkout 30 | - restore_cache: 31 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 32 | - run: 33 | name: Run ktlint 34 | command: ./gradlew ktlint 35 | 36 | tests: 37 | working_directory: ~/code 38 | docker: 39 | - image: circleci/android:api-28-alpha 40 | environment: 41 | JVM_OPTS: -Xmx3g 42 | steps: 43 | - checkout 44 | - restore_cache: 45 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 46 | - run: 47 | name: Run Tests 48 | command: ./gradlew testDebugUnitTest -x lint 49 | - store_artifacts: 50 | path: app/build/reports 51 | destination: reports 52 | - store_test_results: 53 | path: app/build/test-results 54 | 55 | deploy: 56 | working_directory: ~/code 57 | docker: 58 | - image: circleci/android:api-28-alpha 59 | environment: 60 | JVM_OPTS: -Xmx3g 61 | steps: 62 | - checkout 63 | - restore_cache: 64 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 65 | - run: 66 | name: Publish to Bintray 67 | command: ./gradlew build bintrayUpload -PdryRun=false 68 | 69 | workflows: 70 | version: 2 71 | build-and-deploy: 72 | jobs: 73 | - prepare 74 | - ktlint: 75 | requires: 76 | - prepare 77 | - tests: 78 | requires: 79 | - prepare 80 | - hold: 81 | type: approval 82 | requires: 83 | - ktlint 84 | - tests 85 | filters: 86 | branches: 87 | only: 88 | - master 89 | - /release\/.*/ # all branches starting with release/ 90 | - deploy: 91 | requires: 92 | - hold 93 | filters: 94 | branches: 95 | only: 96 | - master 97 | - /release\/.*/ # all branches starting with release/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/navEditor.xml 5 | /.idea/assetWizardSettings.xml 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | /.idea/ 11 | /gradle.properties 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 flatcircle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoroutineHelper 2 | Extension functions, utility functions and delegates for Coroutines 3 | 4 | [![CircleCI](https://circleci.com/gh/flatcircle/CoroutineHelper.svg?style=svg)](https://circleci.com/gh/flatcircle/CoroutineHelper) [ ![Download](https://api.bintray.com/packages/flatcircle/CoroutineHelper/coroutinehelper/images/download.svg) ](https://bintray.com/flatcircle/CoroutineHelper/coroutinehelper/_latestVersion) 5 | 6 | Installation 7 | -------- 8 | 9 | ```groovy 10 | implementation 'io.flatcircle:coroutinehelper:{version}' 11 | ``` 12 | 13 | Delegates 14 | ----- 15 | 16 | The library comes with a few delegate functions to make declaring your CoroutineScope easier 17 | 18 | | Delegate | Description | Example | 19 | | ------------- | ------------- | ------------- | 20 | | : CoroutineScope by MainCoroutineScope | Implements a coroutine scope in the class on the Main/UI Dispatcher | [Example](https://github.com/flatcircle/LiveDataHelper/blob/master/app/src/main/java/io/flatcircle/livedatahelperexample/MainActivity.kt#L34) | 21 | | : CoroutineScope by DefaultCoroutineScope | Implements a coroutine scope in the class on the Default Dispatcher | [Example](https://github.com/flatcircle/LiveDataHelper/blob/master/app/src/main/java/io/flatcircle/livedatahelperexample/MainActivity.kt#L34) | 22 | | : CoroutineScope by IoCoroutineScope | Implements a coroutine scope in the class on the IO Dispatcher | [Example](https://github.com/flatcircle/LiveDataHelper/blob/master/app/src/main/java/io/flatcircle/livedatahelperexample/MainActivity.kt#L34) | 23 | 24 | Railway Oriented Programming 25 | ----- 26 | 27 | This library provides an ApiResult<> class with infix functions, to be used in place of the Kotlin Result<> class, much as described in the Kotlin [Railway Oriented Programming Post on ProAndroidDev](https://proandroiddev.com/railway-oriented-programming-in-kotlin-f1bceed399e5). 28 | 29 | 30 | ```kotlin 31 | // longRunningOperation is a function which returns an ApiResult<> 32 | longRunningOperation(100) then { apiResult -> 33 | if (apiResult.value > 80) { 34 | Failure(IllegalStateException("`then` can throw an exception and pass it down the line")) 35 | } else { 36 | apiResult 37 | } 38 | } onSuccess { value -> 39 | textView.text = "Result = $value" 40 | } onFail { throwable -> 41 | textView.text = "A failure happened, with details $throwable" 42 | } 43 | ``` 44 | 45 | Callback Reduction 46 | ----- 47 | 48 | The library also has some convenience functions for reducing callback hell. 49 | 50 | ```kotlin 51 | // The original callback function. This could be anything 52 | functionWithCallback1("hello") { result -> 53 | println(result) 54 | } 55 | 56 | // Using suspendAsync function 57 | val result = suspendAsync(::functionWithCallback1, "hello") 58 | println(result) 59 | 60 | // You can also call a function of an instance of a class with instance::method reference, like so 61 | val userInstance = User.getInstance() 62 | val result = suspendAsync(userInstance::functionWithCallback1, "hello") 63 | println(result) 64 | 65 | // Using infix notation 66 | val result = ::functionWithCallback1 suspendAndInvokeWith "hello" 67 | println(result) 68 | 69 | // You can have up to three inputs, but you have to pass in a Pair()/Triple when using infix 70 | val result = ::functionWithCallback2 suspendAndInvokeWith Pair("hello", 2) 71 | val result = ::functionWithCallback3 suspendAndInvokeWith Triple("Hello", 2, "goodbye") 72 | println(result) 73 | 74 | // You can have up to three outputs using suspendAndInvokeWith2/3, but you have to receive it as a pair/triple 75 | val (result1, result2) = ::functionWithCallback22 suspendAndInvokeWith2 Pair("hello", 2) 76 | val (result1, result2, result3) = ::functionWithCallback33 suspendAndInvokeWith3 Triple("hello", 2, "goodbye") 77 | println(result1 + result2 + result3) 78 | 79 | ``` 80 | 81 | You can find some additional reading on the callback hell functions [in this blog post](https://jacquessmuts.github.io/post/callback_hell/) 82 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 28 7 | defaultConfig { 8 | applicationId "io.flatcircle.coroutinehelperexample" 9 | minSdkVersion 19 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | 26 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1" 27 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1" 28 | 29 | implementation project(':coroutinehelper') 30 | 31 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 32 | implementation 'androidx.appcompat:appcompat:1.0.2' 33 | implementation 'androidx.core:core-ktx:1.0.2' 34 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 35 | implementation 'com.google.android.material:material:1.0.0' 36 | testImplementation 'junit:junit:4.12' 37 | androidTestImplementation 'androidx.test:runner:1.1.1' 38 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 39 | } 40 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/io/flatcircle/coroutinehelperexample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.flatcircle.coroutinehelperexample 2 | 3 | import android.os.Bundle 4 | import android.view.Menu 5 | import android.view.MenuItem 6 | import androidx.appcompat.app.AppCompatActivity 7 | import io.flatcircle.coroutinehelper.ApiResult 8 | import io.flatcircle.coroutinehelper.Failure 9 | import io.flatcircle.coroutinehelper.Success 10 | import io.flatcircle.coroutinehelper.onFail 11 | import io.flatcircle.coroutinehelper.onSuccess 12 | import io.flatcircle.coroutinehelper.then 13 | import kotlinx.android.synthetic.main.activity_main.* 14 | import kotlinx.android.synthetic.main.content_main.* 15 | import kotlinx.coroutines.Dispatchers 16 | import kotlinx.coroutines.GlobalScope 17 | import kotlinx.coroutines.delay 18 | import kotlinx.coroutines.launch 19 | import java.net.SocketTimeoutException 20 | import kotlin.random.Random 21 | 22 | class MainActivity : AppCompatActivity() { 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | setContentView(R.layout.activity_main) 27 | setSupportActionBar(toolbar) 28 | 29 | fab.setOnClickListener { view -> 30 | launchLongRunningOperation() 31 | } 32 | 33 | launchLongRunningOperation() 34 | } 35 | 36 | fun launchLongRunningOperation() { 37 | // Not the right way to launch a coroutine, but useful for demonstrative purposes 38 | GlobalScope.launch(Dispatchers.Main) { 39 | 40 | longRunningOperation(100) then { apiResult -> 41 | if (apiResult.value > 80) { 42 | Failure(IllegalStateException("`then` can throw an exception and pass it down the line")) 43 | } else { 44 | apiResult 45 | } 46 | } onSuccess { value -> 47 | textView.text = "Result = $value" 48 | } onFail { throwable -> 49 | textView.text = "A failure happened, with details $throwable" 50 | } 51 | } 52 | } 53 | 54 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 55 | // Inflate the menu; this adds items to the action bar if it is present. 56 | menuInflater.inflate(R.menu.menu_main, menu) 57 | return true 58 | } 59 | 60 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 61 | // Handle action bar item clicks here. The action bar will 62 | // automatically handle clicks on the Home/Up button, so long 63 | // as you specify a parent activity in AndroidManifest.xml. 64 | return when (item.itemId) { 65 | R.id.action_settings -> true 66 | else -> super.onOptionsItemSelected(item) 67 | } 68 | } 69 | 70 | suspend fun longRunningOperation(input: Int): ApiResult { 71 | delay(5000) 72 | val value = Random.nextInt(0, input*2) 73 | 74 | return if (value > input) { 75 | Success(value) 76 | } else { 77 | Failure(SocketTimeoutException("This could be any exception. Faking an api timeout here.")) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatcircle/CoroutineHelper/b2fb007ba3d26efb689ec33f313312fad7581855/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatcircle/CoroutineHelper/b2fb007ba3d26efb689ec33f313312fad7581855/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatcircle/CoroutineHelper/b2fb007ba3d26efb689ec33f313312fad7581855/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatcircle/CoroutineHelper/b2fb007ba3d26efb689ec33f313312fad7581855/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatcircle/CoroutineHelper/b2fb007ba3d26efb689ec33f313312fad7581855/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatcircle/CoroutineHelper/b2fb007ba3d26efb689ec33f313312fad7581855/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatcircle/CoroutineHelper/b2fb007ba3d26efb689ec33f313312fad7581855/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatcircle/CoroutineHelper/b2fb007ba3d26efb689ec33f313312fad7581855/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatcircle/CoroutineHelper/b2fb007ba3d26efb689ec33f313312fad7581855/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flatcircle/CoroutineHelper/b2fb007ba3d26efb689ec33f313312fad7581855/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CoroutineHelperExample 3 | Settings 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |