├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── andreabresolin │ │ └── androidcoroutinesplayground │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── andreabresolin │ │ │ └── androidcoroutinesplayground │ │ │ ├── MainActivity.kt │ │ │ ├── app │ │ │ ├── App.kt │ │ │ ├── coroutines │ │ │ │ ├── AppCoroutineScope.kt │ │ │ │ ├── AppCoroutinesConfiguration.kt │ │ │ │ ├── AppCoroutinesExtensions.kt │ │ │ │ ├── AppCoroutinesHelpers.kt │ │ │ │ └── testing │ │ │ │ │ └── TestAppCoroutineScope.kt │ │ │ ├── di │ │ │ │ ├── ActivityBindingsModule.kt │ │ │ │ ├── AppComponent.kt │ │ │ │ ├── AppModule.kt │ │ │ │ ├── FragmentBindingsModule.kt │ │ │ │ └── scope │ │ │ │ │ ├── PerActivity.kt │ │ │ │ │ └── PerFragment.kt │ │ │ ├── domain │ │ │ │ ├── BaseUseCase.kt │ │ │ │ └── task │ │ │ │ │ ├── CallbackTaskUseCase.kt │ │ │ │ │ ├── ChannelTaskUseCase.kt │ │ │ │ │ ├── ExceptionsTaskUseCase.kt │ │ │ │ │ ├── LongComputationTaskUseCase.kt │ │ │ │ │ ├── MultipleTasksUseCase.kt │ │ │ │ │ ├── ParallelErrorTaskUseCase.kt │ │ │ │ │ ├── ParallelTaskUseCase.kt │ │ │ │ │ ├── SequentialErrorTaskUseCase.kt │ │ │ │ │ └── SequentialTaskUseCase.kt │ │ │ ├── exception │ │ │ │ └── CustomTaskException.kt │ │ │ ├── model │ │ │ │ ├── TaskExecutionResult.kt │ │ │ │ └── TaskExecutionState.kt │ │ │ ├── presentation │ │ │ │ ├── BasePresenter.kt │ │ │ │ └── BaseViewModel.kt │ │ │ ├── repository │ │ │ │ └── RemoteRepository.kt │ │ │ ├── util │ │ │ │ ├── DateTimeProvider.kt │ │ │ │ └── LogUtil.kt │ │ │ └── view │ │ │ │ ├── BaseActivity.kt │ │ │ │ └── BaseFragment.kt │ │ │ ├── home │ │ │ └── view │ │ │ │ └── HomeFragment.kt │ │ │ ├── mvp │ │ │ ├── di │ │ │ │ └── MVPModule.kt │ │ │ ├── presenter │ │ │ │ ├── MVPPresenter.kt │ │ │ │ └── MVPPresenterImpl.kt │ │ │ └── view │ │ │ │ ├── MVPFragment.kt │ │ │ │ └── MVPView.kt │ │ │ └── mvvm │ │ │ ├── di │ │ │ └── MVVMModule.kt │ │ │ ├── view │ │ │ └── MVVMFragment.kt │ │ │ └── viewmodel │ │ │ ├── MVVMViewModel.kt │ │ │ ├── MVVMViewModelFactory.kt │ │ │ └── MVVMViewModelImpl.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_home.xml │ │ ├── fragment_mvp.xml │ │ ├── fragment_mvvm.xml │ │ └── fragment_tasks_common.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 │ │ ├── navigation │ │ └── activity_main_navigation.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── andreabresolin │ └── androidcoroutinesplayground │ ├── app │ ├── coroutines │ │ └── AppCoroutinesHelpersTest.kt │ └── domain │ │ └── task │ │ ├── CallbackTaskUseCaseTest.kt │ │ ├── ChannelTaskUseCaseTest.kt │ │ ├── ExceptionsTaskUseCaseTest.kt │ │ ├── LongComputationTaskUseCaseTest.kt │ │ ├── MultipleTasksUseCaseTest.kt │ │ ├── ParallelTaskUseCaseTest.kt │ │ └── SequentialTaskUseCaseTest.kt │ ├── mvp │ └── presenter │ │ └── MVPPresenterImplTest.kt │ ├── mvvm │ └── viewmodel │ │ └── MVVMViewModelImplTest.kt │ └── testing │ ├── BaseMockitoTest.kt │ ├── BasePresenterTest.kt │ ├── BaseViewModelTest.kt │ ├── KotlinTestUtils.kt │ └── MockableDeferred.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | /projectFilesBackup/.idea/ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Andrea Bresolin 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Coroutines Playground 2 | 3 | Example that shows a possible way of structuring the code in an Android app by making use of [Kotlin coroutines](https://kotlinlang.org/docs/reference/coroutines.html). 4 | 5 | This is based on the stable version of coroutines. 6 | 7 | The example is provided with both MVP and MVVM architectures (making use of Clean Architecture as well). 8 | 9 | For more information on the ideas used in this source code, check the [Kotlin Coroutines in Android](https://medium.com/p/3937ae46c43b) series: 10 | 11 | * [Part 1: Introduction](https://medium.com/p/3937ae46c43b) 12 | * [Part 2: The basics](https://medium.com/p/20af01151627) 13 | * [Part 3: Coroutines in Android Studio](https://medium.com/p/32364831d8ac) 14 | * [Part 4: Running coroutines sequentially or in parallel](https://medium.com/p/183272a25644) 15 | * [Part 5: Coroutine cancellation](https://medium.com/p/64dcb60570f6) 16 | * [Part 6: Exception propagation](https://medium.com/p/483ed521374) 17 | * [Part 7: A small DSL for Android apps development](https://medium.com/p/65f65f85824d) 18 | * [Part 8: MVP and MVVM with Clean Architecture](https://medium.com/p/231aab849e44) 19 | * [Part 9: Combining multiple tasks with the operators on collections](https://medium.com/p/2e8621c6e46b) 20 | * [Part 10: Handling callbacks](https://medium.com/p/4aaa0e4132c9) 21 | * [Part 11: Channels](https://medium.com/p/1b53f8b5f61c) 22 | * [Part 12: Testing](https://medium.com/p/17e77d5df5f3) 23 | 24 | ## License 25 | 26 | ``` 27 | Copyright 2019 Andrea Bresolin 28 | 29 | Licensed under the Apache License, Version 2.0 (the "License"); 30 | you may not use this file except in compliance with the License. 31 | You may obtain a copy of the License at 32 | 33 | http://www.apache.org/licenses/LICENSE-2.0 34 | 35 | Unless required by applicable law or agreed to in writing, software 36 | distributed under the License is distributed on an "AS IS" BASIS, 37 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 38 | See the License for the specific language governing permissions and 39 | limitations under the License. 40 | ``` -------------------------------------------------------------------------------- /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 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion 28 8 | defaultConfig { 9 | applicationId "andreabresolin.androidcoroutinesplayground" 10 | minSdkVersion 28 11 | targetSdkVersion 28 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_8 24 | targetCompatibility JavaVersion.VERSION_1_8 25 | } 26 | } 27 | 28 | ext { 29 | appcompat_version = '1.0.2' 30 | core_ktx_version = '1.0.1' 31 | dagger_version = '2.20' 32 | kotlin_coroutines_version = '1.2.1' 33 | lifecycle_version = '2.0.0' 34 | constraintlayout_version = '1.1.3' 35 | navigation_version = '1.0.0' 36 | paris_version = '1.2.1' 37 | timber_version = '4.7.1' 38 | junit_version = '4.12' 39 | mockito_version = '2.24.0' 40 | assertj_version = '3.11.1' 41 | architecture_core_testing_version = '1.1.0' 42 | test_runner_version = '1.1.1' 43 | espresso_core_version = '3.1.1' 44 | } 45 | 46 | dependencies { 47 | implementation fileTree(dir: 'libs', include: ['*.jar']) 48 | 49 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 50 | implementation "androidx.appcompat:appcompat:$appcompat_version" 51 | implementation "androidx.core:core-ktx:$core_ktx_version" 52 | 53 | // Dagger 54 | implementation "com.google.dagger:dagger:$dagger_version" 55 | implementation "com.google.dagger:dagger-android:$dagger_version" 56 | implementation "com.google.dagger:dagger-android-support:$dagger_version" 57 | kapt "com.google.dagger:dagger-compiler:$dagger_version" 58 | kapt "com.google.dagger:dagger-android-processor:$dagger_version" 59 | 60 | // Kotlin coroutines 61 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" 62 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" 63 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version" 64 | 65 | // Architecture Components: Lifecycle, LiveData, ViewModel 66 | implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" 67 | kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" 68 | 69 | // ConstraintLayout 70 | implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version" 71 | 72 | // Architecture Components: Navigation 73 | implementation "android.arch.navigation:navigation-fragment-ktx:$navigation_version" 74 | implementation "android.arch.navigation:navigation-ui-ktx:$navigation_version" 75 | 76 | // Paris 77 | implementation "com.airbnb.android:paris:$paris_version" 78 | kapt "com.airbnb.android:paris-processor:$paris_version" 79 | 80 | // Timber 81 | implementation "com.jakewharton.timber:timber:$timber_version" 82 | 83 | // JUnit 84 | testImplementation "junit:junit:$junit_version" 85 | 86 | // Mockito 87 | testImplementation "org.mockito:mockito-core:$mockito_version" 88 | testImplementation "org.mockito:mockito-inline:$mockito_version" 89 | androidTestImplementation "org.mockito:mockito-android:$mockito_version" 90 | 91 | // AssertJ 92 | testImplementation "org.assertj:assertj-core:$assertj_version" 93 | 94 | // Architecture Components: testing 95 | testImplementation "android.arch.core:core-testing:$architecture_core_testing_version" 96 | 97 | // Espresso 98 | androidTestImplementation "androidx.test:runner:$test_runner_version" 99 | androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_core_version" 100 | } 101 | -------------------------------------------------------------------------------- /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/androidTest/java/andreabresolin/androidcoroutinesplayground/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("andreabresolin.androidcoroutinesplayground", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.view.BaseActivity 4 | import android.os.Bundle 5 | 6 | class MainActivity : BaseActivity() { 7 | 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_main) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/App.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app 2 | 3 | import andreabresolin.androidcoroutinesplayground.BuildConfig 4 | import andreabresolin.androidcoroutinesplayground.app.di.DaggerAppComponent 5 | import android.app.Activity 6 | import android.app.Application 7 | import dagger.android.AndroidInjector 8 | import dagger.android.DispatchingAndroidInjector 9 | import dagger.android.HasActivityInjector 10 | import javax.inject.Inject 11 | import timber.log.Timber.DebugTree 12 | import timber.log.Timber 13 | 14 | class App : Application(), HasActivityInjector { 15 | 16 | @Inject 17 | internal lateinit var activityInjector: DispatchingAndroidInjector 18 | 19 | init { 20 | System.setProperty("kotlinx.coroutines.debug", if (BuildConfig.DEBUG) "on" else "off") 21 | } 22 | 23 | override fun onCreate() { 24 | super.onCreate() 25 | 26 | if (BuildConfig.DEBUG) { 27 | Timber.plant(DebugTree()) 28 | } 29 | 30 | DaggerAppComponent.builder() 31 | .application(this) 32 | .build() 33 | .inject(this) 34 | } 35 | 36 | override fun activityInjector(): AndroidInjector = activityInjector 37 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/coroutines/AppCoroutineScope.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.coroutines 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Job 5 | import javax.inject.Inject 6 | import kotlin.coroutines.CoroutineContext 7 | 8 | open class AppCoroutineScope 9 | @Inject constructor() : CoroutineScope { 10 | 11 | private val job = Job() 12 | 13 | override val coroutineContext: CoroutineContext 14 | get() = AppCoroutinesConfiguration.uiDispatcher + job 15 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/coroutines/AppCoroutinesConfiguration.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.coroutines 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | class AppCoroutinesConfiguration { 7 | 8 | companion object { 9 | const val TEST_TIMEOUT: Long = 500L 10 | 11 | var uiDispatcher: CoroutineDispatcher = Dispatchers.Main 12 | var backgroundDispatcher: CoroutineDispatcher = Dispatchers.Default 13 | var ioDispatcher: CoroutineDispatcher = Dispatchers.IO 14 | var isDelayEnabled: Boolean = true 15 | var useTestTimeout: Boolean = false 16 | var isLoggingEnabled: Boolean = true 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/coroutines/AppCoroutinesExtensions.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.coroutines 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.AppCoroutinesHelpers.Companion.startJob 4 | import andreabresolin.androidcoroutinesplayground.app.coroutines.AppCoroutinesHelpers.Companion.startTask 5 | import andreabresolin.androidcoroutinesplayground.app.coroutines.AppCoroutinesHelpers.Companion.startTaskAsync 6 | import kotlinx.coroutines.* 7 | 8 | fun CoroutineScope.uiJob(timeout: Long = 0L, block: suspend CoroutineScope.() -> Unit) { 9 | startJob(this, AppCoroutinesConfiguration.uiDispatcher, timeout, block) 10 | } 11 | 12 | fun CoroutineScope.backgroundJob(timeout: Long = 0L, block: suspend CoroutineScope.() -> Unit) { 13 | startJob(this, AppCoroutinesConfiguration.backgroundDispatcher, timeout, block) 14 | } 15 | 16 | fun CoroutineScope.ioJob(timeout: Long = 0L, block: suspend CoroutineScope.() -> Unit) { 17 | startJob(this, AppCoroutinesConfiguration.ioDispatcher, timeout, block) 18 | } 19 | 20 | suspend fun uiTask(timeout: Long = 0L, block: suspend CoroutineScope.() -> T): T { 21 | return startTask(AppCoroutinesConfiguration.uiDispatcher, timeout, block) 22 | } 23 | 24 | suspend fun backgroundTask(timeout: Long = 0L, block: suspend CoroutineScope.() -> T): T { 25 | return startTask(AppCoroutinesConfiguration.backgroundDispatcher, timeout, block) 26 | } 27 | 28 | suspend fun ioTask(timeout: Long = 0L, block: suspend CoroutineScope.() -> T): T { 29 | return startTask(AppCoroutinesConfiguration.ioDispatcher, timeout, block) 30 | } 31 | 32 | fun CoroutineScope.uiTaskAsync(timeout: Long = 0L, block: suspend CoroutineScope.() -> T): Deferred { 33 | return startTaskAsync(this, AppCoroutinesConfiguration.uiDispatcher, timeout, block) 34 | } 35 | 36 | fun CoroutineScope.backgroundTaskAsync(timeout: Long = 0L, block: suspend CoroutineScope.() -> T): Deferred { 37 | return startTaskAsync(this, AppCoroutinesConfiguration.backgroundDispatcher, timeout, block) 38 | } 39 | 40 | fun CoroutineScope.ioTaskAsync(timeout: Long = 0L, block: suspend CoroutineScope.() -> T): Deferred { 41 | return startTaskAsync(this, AppCoroutinesConfiguration.ioDispatcher, timeout, block) 42 | } 43 | 44 | suspend fun delayTask(milliseconds: Long) { 45 | if (AppCoroutinesConfiguration.isDelayEnabled) { 46 | delay(milliseconds) 47 | } 48 | } 49 | 50 | suspend fun Deferred.awaitOrReturn(returnIfCancelled: T): T { 51 | return try { 52 | await() 53 | } catch (e: CancellationException) { 54 | returnIfCancelled 55 | } 56 | } 57 | 58 | suspend fun awaitAllOrCancel(vararg deferreds: Deferred): List { 59 | try { 60 | return awaitAll(*deferreds) 61 | } catch (e: Exception) { 62 | if (e !is CancellationException) { 63 | deferreds.forEach { if (it.isActive) it.cancel() } 64 | } 65 | 66 | throw e 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/coroutines/AppCoroutinesHelpers.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.coroutines 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.util.logCompleted 4 | import andreabresolin.androidcoroutinesplayground.app.util.logStarted 5 | import kotlinx.coroutines.* 6 | import java.util.concurrent.atomic.AtomicLong 7 | import kotlin.coroutines.CoroutineContext 8 | 9 | internal class AppCoroutinesHelpers { 10 | 11 | companion object { 12 | private enum class HelperType { 13 | JOB, TASK, TASK_ASYNC 14 | } 15 | 16 | private val loggingTaskId = AtomicLong(1L) 17 | 18 | private suspend fun executeBlock( 19 | parentScope: CoroutineScope, 20 | callerType: HelperType, 21 | coroutineContext: CoroutineContext, 22 | block: suspend CoroutineScope.() -> T 23 | ): T { 24 | if (AppCoroutinesConfiguration.isLoggingEnabled) { 25 | val taskId = loggingTaskId.getAndIncrement() 26 | 27 | val methodName = when (coroutineContext) { 28 | AppCoroutinesConfiguration.uiDispatcher -> "ui" 29 | AppCoroutinesConfiguration.backgroundDispatcher -> "background" 30 | AppCoroutinesConfiguration.ioDispatcher -> "io" 31 | else -> "" 32 | } + when (callerType) { 33 | HelperType.JOB -> "Job" 34 | HelperType.TASK -> "Task" 35 | HelperType.TASK_ASYNC -> "TaskAsync" 36 | } + "#$taskId" 37 | 38 | logStarted(methodName) 39 | val result = parentScope.block() 40 | logCompleted(methodName) 41 | return result 42 | } else { 43 | return parentScope.block() 44 | } 45 | } 46 | 47 | private fun computeTimeout(timeout: Long): Long { 48 | return if (AppCoroutinesConfiguration.useTestTimeout) { 49 | AppCoroutinesConfiguration.TEST_TIMEOUT 50 | } else { 51 | timeout 52 | } 53 | } 54 | 55 | fun startJob( 56 | parentScope: CoroutineScope, 57 | coroutineContext: CoroutineContext, 58 | timeout: Long = 0L, 59 | block: suspend CoroutineScope.() -> Unit 60 | ) { 61 | parentScope.launch(coroutineContext) { 62 | supervisorScope { 63 | if (timeout > 0L) { 64 | withTimeout(computeTimeout(timeout)) { 65 | executeBlock(this, HelperType.JOB, coroutineContext, block) 66 | } 67 | } else { 68 | executeBlock(this, HelperType.JOB, coroutineContext, block) 69 | } 70 | } 71 | } 72 | } 73 | 74 | suspend fun startTask( 75 | coroutineContext: CoroutineContext, 76 | timeout: Long = 0L, 77 | block: suspend CoroutineScope.() -> T 78 | ): T { 79 | return withContext(coroutineContext) { 80 | return@withContext if (timeout > 0L) { 81 | withTimeout(computeTimeout(timeout)) { 82 | executeBlock(this, HelperType.TASK, coroutineContext, block) 83 | } 84 | } else { 85 | executeBlock(this, HelperType.TASK, coroutineContext, block) 86 | } 87 | } 88 | } 89 | 90 | fun startTaskAsync( 91 | parentScope: CoroutineScope, 92 | coroutineContext: CoroutineContext, 93 | timeout: Long = 0L, 94 | block: suspend CoroutineScope.() -> T 95 | ): Deferred { 96 | return parentScope.async(coroutineContext) { 97 | return@async supervisorScope { 98 | return@supervisorScope if (timeout > 0L) { 99 | withTimeout(computeTimeout(timeout)) { 100 | executeBlock(this, HelperType.TASK_ASYNC, coroutineContext, block) 101 | } 102 | } else { 103 | executeBlock(this, HelperType.TASK_ASYNC, coroutineContext, block) 104 | } 105 | } 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/coroutines/testing/TestAppCoroutineScope.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.coroutines.testing 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.AppCoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.Job 6 | import kotlinx.coroutines.cancelChildren 7 | import javax.inject.Inject 8 | import kotlin.coroutines.CoroutineContext 9 | 10 | class TestAppCoroutineScope 11 | @Inject constructor() : AppCoroutineScope() { 12 | 13 | private val job = Job() 14 | 15 | override val coroutineContext: CoroutineContext 16 | get() = Dispatchers.Unconfined + job 17 | 18 | fun cancelJobs() { 19 | coroutineContext.cancelChildren() 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/di/ActivityBindingsModule.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.di 2 | 3 | import andreabresolin.androidcoroutinesplayground.MainActivity 4 | import andreabresolin.androidcoroutinesplayground.app.di.scope.PerActivity 5 | import dagger.Module 6 | import dagger.android.AndroidInjectionModule 7 | import dagger.android.ContributesAndroidInjector 8 | 9 | @Module(includes = [AndroidInjectionModule::class]) 10 | abstract class ActivityBindingsModule { 11 | 12 | @PerActivity 13 | @ContributesAndroidInjector 14 | abstract fun bindMainActivity(): MainActivity 15 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/di/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.di 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.App 4 | import android.app.Application 5 | import dagger.BindsInstance 6 | import dagger.Component 7 | import dagger.android.AndroidInjector 8 | import dagger.android.DaggerApplication 9 | import dagger.android.support.AndroidSupportInjectionModule 10 | import javax.inject.Singleton 11 | 12 | @Singleton 13 | @Component( 14 | modules = [ 15 | AndroidSupportInjectionModule::class, 16 | AppModule::class, 17 | ActivityBindingsModule::class, 18 | FragmentBindingsModule::class 19 | ] 20 | ) 21 | interface AppComponent : AndroidInjector { 22 | 23 | @Component.Builder 24 | interface Builder { 25 | 26 | @BindsInstance 27 | fun application(application: Application): Builder 28 | 29 | fun build(): AppComponent 30 | } 31 | 32 | fun inject(app: App) 33 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.di 2 | 3 | import dagger.Module 4 | 5 | @Module 6 | abstract class AppModule -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/di/FragmentBindingsModule.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.di 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.di.scope.PerFragment 4 | import andreabresolin.androidcoroutinesplayground.mvp.di.MVPModule 5 | import andreabresolin.androidcoroutinesplayground.mvp.view.MVPFragment 6 | import andreabresolin.androidcoroutinesplayground.mvvm.di.MVVMModule 7 | import andreabresolin.androidcoroutinesplayground.mvvm.view.MVVMFragment 8 | import dagger.Module 9 | import dagger.android.AndroidInjectionModule 10 | import dagger.android.ContributesAndroidInjector 11 | 12 | @Module(includes = [AndroidInjectionModule::class]) 13 | abstract class FragmentBindingsModule { 14 | 15 | @PerFragment 16 | @ContributesAndroidInjector(modules = [MVPModule::class]) 17 | abstract fun bindMVPFragment(): MVPFragment 18 | 19 | @PerFragment 20 | @ContributesAndroidInjector(modules = [MVVMModule::class]) 21 | abstract fun bindMVVMFragment(): MVVMFragment 22 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/di/scope/PerActivity.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.di.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class PerActivity -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/di/scope/PerFragment.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.di.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class PerFragment -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/domain/BaseUseCase.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.domain 2 | 3 | abstract class BaseUseCase -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/domain/task/CallbackTaskUseCase.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.domain.task 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.backgroundTask 4 | import andreabresolin.androidcoroutinesplayground.app.coroutines.delayTask 5 | import andreabresolin.androidcoroutinesplayground.app.domain.BaseUseCase 6 | import andreabresolin.androidcoroutinesplayground.app.exception.CustomTaskException 7 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionResult 8 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionSuccess 9 | import kotlinx.coroutines.CancellableContinuation 10 | import kotlinx.coroutines.suspendCancellableCoroutine 11 | import javax.inject.Inject 12 | import kotlin.coroutines.resume 13 | import kotlin.coroutines.resumeWithException 14 | import kotlin.random.Random 15 | 16 | class CallbackTaskUseCase 17 | @Inject constructor() : BaseUseCase() { 18 | 19 | private class ExecutorWithCallback { 20 | 21 | fun executeAction( 22 | input: String, 23 | successCallback: (Long) -> Unit, 24 | cancelCallback: () -> Unit, 25 | errorCallback: () -> Unit 26 | ) { 27 | when (input) { 28 | "SUCCESS" -> successCallback(10L) 29 | "CANCEL" -> cancelCallback() 30 | else -> errorCallback() 31 | } 32 | } 33 | } 34 | 35 | suspend fun execute(param: String): TaskExecutionResult = backgroundTask { 36 | val taskDuration = Random.nextLong(1000, 2000) 37 | delayTask(taskDuration) 38 | 39 | return@backgroundTask suspendCancellableCoroutine { continuation -> 40 | ExecutorWithCallback().executeAction(param, 41 | { result -> successCallback(result, continuation) }, 42 | { cancelCallback(continuation) }, 43 | { errorCallback(continuation) }) 44 | } 45 | } 46 | 47 | private fun successCallback(result: Long, continuation: CancellableContinuation) { 48 | continuation.resume(TaskExecutionSuccess(result)) 49 | } 50 | 51 | private fun cancelCallback(continuation: CancellableContinuation) { 52 | continuation.cancel() 53 | } 54 | 55 | private fun errorCallback(continuation: CancellableContinuation) { 56 | continuation.resumeWithException(CustomTaskException()) 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/domain/task/ChannelTaskUseCase.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.domain.task 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.backgroundTaskAsync 4 | import andreabresolin.androidcoroutinesplayground.app.coroutines.delayTask 5 | import andreabresolin.androidcoroutinesplayground.app.domain.BaseUseCase 6 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionResult 7 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionSuccess 8 | import andreabresolin.androidcoroutinesplayground.app.util.logIteration 9 | import kotlinx.coroutines.CoroutineScope 10 | import kotlinx.coroutines.Deferred 11 | import kotlinx.coroutines.channels.SendChannel 12 | import kotlinx.coroutines.selects.select 13 | import javax.inject.Inject 14 | 15 | class ChannelTaskUseCase 16 | @Inject constructor() : BaseUseCase() { 17 | 18 | fun executeAsync( 19 | parentScope: CoroutineScope, 20 | sendInterval: Long, 21 | sentItemsCount: Long, 22 | primaryChannel: SendChannel, 23 | backupChannel: SendChannel? = null 24 | ): Deferred = parentScope.backgroundTaskAsync { 25 | var iterationNumber = 1L 26 | 27 | while (iterationNumber <= sentItemsCount) { 28 | logIteration("ChannelTaskUseCase.executeAsync@$parentScope", iterationNumber) 29 | delayTask(sendInterval) 30 | 31 | if (backupChannel != null) { 32 | select { 33 | primaryChannel.onSend(iterationNumber) { } 34 | backupChannel.onSend(iterationNumber) { } 35 | } 36 | } else { 37 | primaryChannel.send(iterationNumber) 38 | } 39 | 40 | iterationNumber++ 41 | } 42 | 43 | primaryChannel.close() 44 | backupChannel?.close() 45 | 46 | return@backgroundTaskAsync TaskExecutionSuccess(sentItemsCount) 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/domain/task/ExceptionsTaskUseCase.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.domain.task 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.* 4 | import andreabresolin.androidcoroutinesplayground.app.domain.BaseUseCase 5 | import andreabresolin.androidcoroutinesplayground.app.exception.CustomTaskException 6 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionResult 7 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionSuccess 8 | import andreabresolin.androidcoroutinesplayground.app.repository.RemoteRepository 9 | import kotlinx.coroutines.CoroutineScope 10 | import kotlinx.coroutines.Deferred 11 | import javax.inject.Inject 12 | import kotlin.random.Random 13 | 14 | class ExceptionsTaskUseCase 15 | @Inject constructor( 16 | private val remoteRepository: RemoteRepository 17 | ) : BaseUseCase() { 18 | 19 | suspend fun execute( 20 | startDelay: Long, 21 | minDuration: Long, 22 | maxDuration: Long 23 | ): TaskExecutionResult = backgroundTask { 24 | delayTask(startDelay) 25 | 26 | val taskDuration = Random.nextLong(minDuration, maxDuration + 1) 27 | 28 | delayTask(taskDuration) 29 | 30 | throw CustomTaskException("Error in ExceptionsTaskUseCase.execute()") 31 | } 32 | 33 | fun executeAsync( 34 | parentScope: CoroutineScope, 35 | startDelay: Long, 36 | minDuration: Long, 37 | maxDuration: Long 38 | ): Deferred = parentScope.backgroundTaskAsync { 39 | delayTask(startDelay) 40 | 41 | val taskDuration = Random.nextLong(minDuration, maxDuration + 1) 42 | 43 | delayTask(taskDuration) 44 | 45 | throw CustomTaskException("Error in ExceptionsTaskUseCase.executeAsync()") 46 | } 47 | 48 | fun executeWithRepositoryAsync( 49 | parentScope: CoroutineScope, 50 | startDelay: Long, 51 | minDuration: Long, 52 | maxDuration: Long 53 | ): Deferred = parentScope.backgroundTaskAsync { 54 | delayTask(startDelay) 55 | 56 | val taskDuration = Random.nextLong(minDuration, maxDuration + 1) 57 | 58 | val fetchedData1 = ioTaskAsync { 59 | delayTask(2000) 60 | remoteRepository.fetchDataWithException() 61 | } 62 | 63 | val fetchedData2 = ioTaskAsync { 64 | delayTask(5000) 65 | remoteRepository.fetchData(taskDuration) 66 | } 67 | 68 | delayTask(taskDuration) 69 | 70 | return@backgroundTaskAsync TaskExecutionSuccess(awaitAllOrCancel(fetchedData1, fetchedData2).sum()) 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/domain/task/LongComputationTaskUseCase.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.domain.task 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.backgroundTaskAsync 4 | import andreabresolin.androidcoroutinesplayground.app.domain.BaseUseCase 5 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionResult 6 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionSuccess 7 | import andreabresolin.androidcoroutinesplayground.app.util.DateTimeProvider 8 | import andreabresolin.androidcoroutinesplayground.app.util.logCancelled 9 | import andreabresolin.androidcoroutinesplayground.app.util.logIteration 10 | import kotlinx.coroutines.CancellationException 11 | import kotlinx.coroutines.CoroutineScope 12 | import kotlinx.coroutines.Deferred 13 | import kotlinx.coroutines.isActive 14 | import javax.inject.Inject 15 | 16 | class LongComputationTaskUseCase 17 | @Inject constructor( 18 | private val dateTimeProvider: DateTimeProvider 19 | ) : BaseUseCase() { 20 | 21 | fun executeAsync( 22 | parentScope: CoroutineScope, 23 | iterationDuration: Long, 24 | iterationsCount: Long, 25 | timeout: Long = 0L 26 | ): Deferred = parentScope.backgroundTaskAsync(timeout = timeout) { 27 | var iterationNumber = 1L 28 | var nextIterationTime = dateTimeProvider.currentTimeMillis() 29 | 30 | while (isActive && iterationNumber <= iterationsCount) { 31 | if (dateTimeProvider.currentTimeMillis() >= nextIterationTime) { 32 | logIteration("LongComputationTaskUseCase.executeAsync@$parentScope", iterationNumber) 33 | nextIterationTime += iterationDuration 34 | iterationNumber++ 35 | } 36 | } 37 | 38 | if (!isActive) { 39 | logCancelled("LongComputationTaskUseCase.executeAsync@$parentScope") 40 | throw CancellationException() 41 | } 42 | 43 | return@backgroundTaskAsync TaskExecutionSuccess(iterationNumber - 1) 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/domain/task/MultipleTasksUseCase.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.domain.task 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.backgroundTask 4 | import andreabresolin.androidcoroutinesplayground.app.coroutines.delayTask 5 | import andreabresolin.androidcoroutinesplayground.app.coroutines.ioTask 6 | import andreabresolin.androidcoroutinesplayground.app.coroutines.ioTaskAsync 7 | import andreabresolin.androidcoroutinesplayground.app.domain.BaseUseCase 8 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionResult 9 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionSuccess 10 | import andreabresolin.androidcoroutinesplayground.app.repository.RemoteRepository 11 | import javax.inject.Inject 12 | import kotlin.random.Random 13 | 14 | class MultipleTasksUseCase 15 | @Inject constructor( 16 | private val remoteRepository: RemoteRepository 17 | ) : BaseUseCase() { 18 | 19 | suspend fun execute(param1: Long, param2: Long, param3: Long): TaskExecutionResult = backgroundTask { 20 | val taskDuration = Random.nextLong(1000, 2000) 21 | delayTask(taskDuration) 22 | 23 | val fetchedData: Long = arrayOf(param1, param2, param3) 24 | .map { ioTaskAsync { remoteRepository.fetchData(it) } } 25 | .map { it.await() } 26 | .map { ioTask { remoteRepository.fetchData(it) } } 27 | .sum() 28 | 29 | return@backgroundTask TaskExecutionSuccess(fetchedData) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/domain/task/ParallelErrorTaskUseCase.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.domain.task 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.backgroundTaskAsync 4 | import andreabresolin.androidcoroutinesplayground.app.coroutines.delayTask 5 | import andreabresolin.androidcoroutinesplayground.app.domain.BaseUseCase 6 | import andreabresolin.androidcoroutinesplayground.app.exception.CustomTaskException 7 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionError 8 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionResult 9 | import kotlinx.coroutines.CoroutineScope 10 | import kotlinx.coroutines.Deferred 11 | import javax.inject.Inject 12 | import kotlin.random.Random 13 | 14 | class ParallelErrorTaskUseCase 15 | @Inject constructor() : BaseUseCase() { 16 | 17 | fun executeAsync( 18 | parentScope: CoroutineScope, 19 | startDelay: Long, 20 | minDuration: Long, 21 | maxDuration: Long 22 | ): Deferred = parentScope.backgroundTaskAsync { 23 | delayTask(startDelay) 24 | 25 | val taskDuration = Random.nextLong(minDuration, maxDuration + 1) 26 | delayTask(taskDuration) 27 | 28 | return@backgroundTaskAsync TaskExecutionError(CustomTaskException()) 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/domain/task/ParallelTaskUseCase.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.domain.task 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.backgroundTaskAsync 4 | import andreabresolin.androidcoroutinesplayground.app.coroutines.delayTask 5 | import andreabresolin.androidcoroutinesplayground.app.coroutines.ioTask 6 | import andreabresolin.androidcoroutinesplayground.app.domain.BaseUseCase 7 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionResult 8 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionSuccess 9 | import andreabresolin.androidcoroutinesplayground.app.repository.RemoteRepository 10 | import kotlinx.coroutines.CoroutineScope 11 | import kotlinx.coroutines.Deferred 12 | import javax.inject.Inject 13 | import kotlin.random.Random 14 | 15 | class ParallelTaskUseCase 16 | @Inject constructor( 17 | private val remoteRepository: RemoteRepository 18 | ) : BaseUseCase() { 19 | 20 | fun executeAsync( 21 | parentScope: CoroutineScope, 22 | startDelay: Long, 23 | minDuration: Long, 24 | maxDuration: Long 25 | ): Deferred = parentScope.backgroundTaskAsync { 26 | delayTask(startDelay) 27 | 28 | val taskDuration = Random.nextLong(minDuration, maxDuration + 1) 29 | 30 | val fetchedData = ioTask { remoteRepository.fetchData(taskDuration) } 31 | 32 | delayTask(taskDuration) 33 | 34 | return@backgroundTaskAsync TaskExecutionSuccess(fetchedData) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/domain/task/SequentialErrorTaskUseCase.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.domain.task 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.backgroundTask 4 | import andreabresolin.androidcoroutinesplayground.app.coroutines.delayTask 5 | import andreabresolin.androidcoroutinesplayground.app.domain.BaseUseCase 6 | import andreabresolin.androidcoroutinesplayground.app.exception.CustomTaskException 7 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionError 8 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionResult 9 | import javax.inject.Inject 10 | import kotlin.random.Random 11 | 12 | class SequentialErrorTaskUseCase 13 | @Inject constructor() : BaseUseCase() { 14 | 15 | suspend fun execute( 16 | startDelay: Long, 17 | minDuration: Long, 18 | maxDuration: Long 19 | ): TaskExecutionResult = backgroundTask { 20 | delayTask(startDelay) 21 | 22 | val taskDuration = Random.nextLong(minDuration, maxDuration + 1) 23 | delayTask(taskDuration) 24 | 25 | return@backgroundTask TaskExecutionError(CustomTaskException()) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/domain/task/SequentialTaskUseCase.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.domain.task 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.backgroundTask 4 | import andreabresolin.androidcoroutinesplayground.app.coroutines.delayTask 5 | import andreabresolin.androidcoroutinesplayground.app.coroutines.ioTask 6 | import andreabresolin.androidcoroutinesplayground.app.domain.BaseUseCase 7 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionResult 8 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionSuccess 9 | import andreabresolin.androidcoroutinesplayground.app.repository.RemoteRepository 10 | import javax.inject.Inject 11 | import kotlin.random.Random 12 | 13 | class SequentialTaskUseCase 14 | @Inject constructor( 15 | private val remoteRepository: RemoteRepository 16 | ) : BaseUseCase() { 17 | 18 | suspend fun execute( 19 | startDelay: Long, 20 | minDuration: Long, 21 | maxDuration: Long 22 | ): TaskExecutionResult = backgroundTask { 23 | delayTask(startDelay) 24 | 25 | val taskDuration = Random.nextLong(minDuration, maxDuration + 1) 26 | 27 | val fetchedData = ioTask { remoteRepository.fetchData(taskDuration) } 28 | 29 | delayTask(taskDuration) 30 | 31 | return@backgroundTask TaskExecutionSuccess(fetchedData) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/exception/CustomTaskException.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.exception 2 | 3 | class CustomTaskException : Exception { 4 | constructor() : super() 5 | constructor(message: String) : super(message) 6 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/model/TaskExecutionResult.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.model 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.exception.CustomTaskException 4 | 5 | sealed class TaskExecutionResult 6 | data class TaskExecutionSuccess(val result: Long) : TaskExecutionResult() 7 | data class TaskExecutionError(val exception: CustomTaskException) : TaskExecutionResult() 8 | object TaskExecutionCancelled : TaskExecutionResult() -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/model/TaskExecutionState.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.model 2 | 3 | enum class TaskExecutionState { 4 | INITIAL, 5 | RUNNING, 6 | COMPLETED, 7 | CANCELLED, 8 | ERROR 9 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/presentation/BasePresenter.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.presentation 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.cancelChildren 5 | 6 | abstract class BasePresenter 7 | constructor(private val coroutineScope: CoroutineScope) : CoroutineScope by coroutineScope { 8 | 9 | fun cancelJobs() { 10 | coroutineScope.coroutineContext.cancelChildren() 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/presentation/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.presentation 2 | 3 | import androidx.lifecycle.ViewModel 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.cancelChildren 6 | 7 | abstract class BaseViewModel 8 | constructor(private val coroutineScope: CoroutineScope) : ViewModel(), CoroutineScope by coroutineScope { 9 | 10 | override fun onCleared() { 11 | coroutineScope.coroutineContext.cancelChildren() 12 | super.onCleared() 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/repository/RemoteRepository.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.repository 2 | 3 | import java.io.IOException 4 | import javax.inject.Inject 5 | import javax.inject.Singleton 6 | 7 | @Singleton 8 | class RemoteRepository 9 | @Inject constructor() { 10 | 11 | fun fetchData(input: Long): Long { 12 | return input * 10 13 | } 14 | 15 | fun fetchDataWithException(): Long { 16 | throw IOException("Error while reading repository data") 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/util/DateTimeProvider.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.util 2 | 3 | import javax.inject.Inject 4 | 5 | class DateTimeProvider 6 | @Inject constructor() { 7 | 8 | fun currentTimeMillis() = System.currentTimeMillis() 9 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/util/LogUtil.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.util 2 | 3 | import timber.log.Timber 4 | 5 | fun logStarted(methodName: String) { 6 | Timber.d("[%s][%s] Started", methodName, Thread.currentThread().name) 7 | } 8 | 9 | fun logCompleted(methodName: String) { 10 | Timber.d("[%s][%s] Completed", methodName, Thread.currentThread().name) 11 | } 12 | 13 | fun logCancelled(methodName: String) { 14 | Timber.d("[%s][%s] Cancelled", methodName, Thread.currentThread().name) 15 | } 16 | 17 | fun logIteration(methodName: String, iterationNumber: Long) { 18 | Timber.d("[%s][%s] Iteration %s", methodName, Thread.currentThread().name, iterationNumber.toString()) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/view/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.view 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.fragment.app.Fragment 6 | import dagger.android.AndroidInjection 7 | import dagger.android.AndroidInjector 8 | import dagger.android.DispatchingAndroidInjector 9 | import dagger.android.support.HasSupportFragmentInjector 10 | import javax.inject.Inject 11 | 12 | abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector { 13 | 14 | @Inject 15 | internal lateinit var fragmentInjector: DispatchingAndroidInjector 16 | 17 | override fun supportFragmentInjector(): AndroidInjector = fragmentInjector 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | AndroidInjection.inject(this) 21 | super.onCreate(savedInstanceState) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/app/view/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.app.view 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import dagger.android.AndroidInjector 9 | import dagger.android.DispatchingAndroidInjector 10 | import dagger.android.support.AndroidSupportInjection 11 | import dagger.android.support.HasSupportFragmentInjector 12 | import javax.inject.Inject 13 | 14 | abstract class BaseFragment : Fragment(), HasSupportFragmentInjector { 15 | 16 | @Inject 17 | internal lateinit var fragmentInjector: DispatchingAndroidInjector 18 | 19 | override fun supportFragmentInjector(): AndroidInjector = fragmentInjector 20 | 21 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 22 | AndroidSupportInjection.inject(this) 23 | return null 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/home/view/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.home.view 2 | 3 | import andreabresolin.androidcoroutinesplayground.R 4 | import andreabresolin.androidcoroutinesplayground.app.view.BaseFragment 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.navigation.findNavController 10 | import kotlinx.android.synthetic.main.fragment_home.* 11 | 12 | class HomeFragment : BaseFragment() { 13 | 14 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 15 | return inflater.inflate(R.layout.fragment_home, container, false) 16 | } 17 | 18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 19 | super.onViewCreated(view, savedInstanceState) 20 | setUpListeners() 21 | } 22 | 23 | private fun setUpListeners() { 24 | mvpFragmentBtn.setOnClickListener { view -> view.findNavController().navigate(R.id.homeToMVPAction) } 25 | mvvmFragmentBtn.setOnClickListener { view -> view.findNavController().navigate(R.id.homeToMVVMAction) } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/mvp/di/MVPModule.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.mvp.di 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.di.scope.PerFragment 4 | import andreabresolin.androidcoroutinesplayground.mvp.presenter.MVPPresenter 5 | import andreabresolin.androidcoroutinesplayground.mvp.presenter.MVPPresenterImpl 6 | import andreabresolin.androidcoroutinesplayground.mvp.view.MVPFragment 7 | import andreabresolin.androidcoroutinesplayground.mvp.view.MVPView 8 | import dagger.Binds 9 | import dagger.Module 10 | 11 | @Module 12 | abstract class MVPModule { 13 | 14 | @PerFragment 15 | @Binds 16 | abstract fun bindMVPPresenter(mvpPresenter: MVPPresenterImpl): MVPPresenter 17 | 18 | @PerFragment 19 | @Binds 20 | abstract fun bindMVPView(mvpFragment: MVPFragment): MVPView 21 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/mvp/presenter/MVPPresenter.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.mvp.presenter 2 | 3 | interface MVPPresenter { 4 | 5 | fun runSequentialTasks() 6 | 7 | fun runParallelTasks() 8 | 9 | fun runSequentialTasksWithError() 10 | 11 | fun runParallelTasksWithError() 12 | 13 | fun runMultipleTasks() 14 | 15 | fun runCallbackTasksWithError() 16 | 17 | fun runLongComputationTasks() 18 | 19 | fun cancelLongComputationTask1() 20 | 21 | fun cancelLongComputationTask2() 22 | 23 | fun cancelLongComputationTask3() 24 | 25 | fun runLongComputationTasksWithTimeout() 26 | 27 | fun runChannelsTasks() 28 | 29 | fun runExceptionsTasks() 30 | 31 | fun cancelJobs() 32 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/mvp/presenter/MVPPresenterImpl.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.mvp.presenter 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.* 4 | import andreabresolin.androidcoroutinesplayground.app.domain.task.* 5 | import andreabresolin.androidcoroutinesplayground.app.exception.CustomTaskException 6 | import andreabresolin.androidcoroutinesplayground.app.model.* 7 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionState.* 8 | import andreabresolin.androidcoroutinesplayground.app.presentation.BasePresenter 9 | import andreabresolin.androidcoroutinesplayground.mvp.view.MVPView 10 | import kotlinx.coroutines.CancellationException 11 | import kotlinx.coroutines.Deferred 12 | import kotlinx.coroutines.TimeoutCancellationException 13 | import kotlinx.coroutines.channels.Channel 14 | import java.io.IOException 15 | import javax.inject.Inject 16 | 17 | class MVPPresenterImpl 18 | @Inject constructor( 19 | appCoroutineScope: AppCoroutineScope, 20 | private val view: MVPView, 21 | private val sequentialTask1: SequentialTaskUseCase, 22 | private val sequentialTask2: SequentialTaskUseCase, 23 | private val sequentialTask3: SequentialTaskUseCase, 24 | private val parallelTask1: ParallelTaskUseCase, 25 | private val parallelTask2: ParallelTaskUseCase, 26 | private val parallelTask3: ParallelTaskUseCase, 27 | private val sequentialErrorTask: SequentialErrorTaskUseCase, 28 | private val parallelErrorTask: ParallelErrorTaskUseCase, 29 | private val multipleTasks1: MultipleTasksUseCase, 30 | private val multipleTasks2: MultipleTasksUseCase, 31 | private val multipleTasks3: MultipleTasksUseCase, 32 | private val callbackTask1: CallbackTaskUseCase, 33 | private val callbackTask2: CallbackTaskUseCase, 34 | private val callbackTask3: CallbackTaskUseCase, 35 | private val longComputationTask1: LongComputationTaskUseCase, 36 | private val longComputationTask2: LongComputationTaskUseCase, 37 | private val longComputationTask3: LongComputationTaskUseCase, 38 | private val channelTask1: ChannelTaskUseCase, 39 | private val channelTask2: ChannelTaskUseCase, 40 | private val channelTask3: ChannelTaskUseCase, 41 | private val exceptionsTask: ExceptionsTaskUseCase 42 | ) : BasePresenter(appCoroutineScope), MVPPresenter { 43 | 44 | private var longComputationTask1Deferred: Deferred? = null 45 | private var longComputationTask2Deferred: Deferred? = null 46 | private var longComputationTask3Deferred: Deferred? = null 47 | 48 | private fun processTaskResult(taskExecutionResult: TaskExecutionResult): TaskExecutionState { 49 | return when (taskExecutionResult) { 50 | is TaskExecutionSuccess -> COMPLETED 51 | is TaskExecutionCancelled -> CANCELLED 52 | is TaskExecutionError -> ERROR 53 | } 54 | } 55 | 56 | override fun runSequentialTasks() = uiJob { 57 | view.updateTaskExecutionState(1, INITIAL) 58 | view.updateTaskExecutionState(2, INITIAL) 59 | view.updateTaskExecutionState(3, INITIAL) 60 | 61 | delayTask(1000) 62 | 63 | view.updateTaskExecutionState(1, RUNNING) 64 | val task1Result: TaskExecutionResult = sequentialTask1.execute(100, 500, 1500) 65 | view.updateTaskExecutionState(1, processTaskResult(task1Result)) 66 | 67 | view.updateTaskExecutionState(2, RUNNING) 68 | val task2Result: TaskExecutionResult = sequentialTask2.execute(300, 200, 2000) 69 | view.updateTaskExecutionState(2, processTaskResult(task2Result)) 70 | 71 | view.updateTaskExecutionState(3, RUNNING) 72 | val task3Result: TaskExecutionResult = sequentialTask3.execute(200, 600, 1800) 73 | view.updateTaskExecutionState(3, processTaskResult(task3Result)) 74 | } 75 | 76 | override fun runParallelTasks() = uiJob { 77 | view.updateTaskExecutionState(1, INITIAL) 78 | view.updateTaskExecutionState(2, INITIAL) 79 | view.updateTaskExecutionState(3, INITIAL) 80 | 81 | delayTask(1000) 82 | 83 | view.updateTaskExecutionState(1, RUNNING) 84 | val task1Result: Deferred = parallelTask1.executeAsync(this, 100, 500, 1500) 85 | 86 | view.updateTaskExecutionState(2, RUNNING) 87 | val task2Result: Deferred = parallelTask2.executeAsync(this, 300, 200, 2000) 88 | 89 | view.updateTaskExecutionState(3, RUNNING) 90 | val task3Result: Deferred = parallelTask3.executeAsync(this, 200, 600, 1800) 91 | 92 | view.updateTaskExecutionState(1, processTaskResult(task1Result.await())) 93 | view.updateTaskExecutionState(2, processTaskResult(task2Result.await())) 94 | view.updateTaskExecutionState(3, processTaskResult(task3Result.await())) 95 | } 96 | 97 | override fun runSequentialTasksWithError() = uiJob { 98 | view.updateTaskExecutionState(1, INITIAL) 99 | view.updateTaskExecutionState(2, INITIAL) 100 | view.updateTaskExecutionState(3, INITIAL) 101 | 102 | delayTask(1000) 103 | 104 | view.updateTaskExecutionState(1, RUNNING) 105 | val task1Result: TaskExecutionResult = sequentialTask1.execute(100, 500, 1500) 106 | view.updateTaskExecutionState(1, processTaskResult(task1Result)) 107 | 108 | view.updateTaskExecutionState(2, RUNNING) 109 | val task2Result: TaskExecutionResult = sequentialErrorTask.execute(300, 200, 2000) 110 | view.updateTaskExecutionState(2, processTaskResult(task2Result)) 111 | 112 | view.updateTaskExecutionState(3, RUNNING) 113 | val task3Result: TaskExecutionResult = sequentialTask3.execute(200, 600, 1800) 114 | view.updateTaskExecutionState(3, processTaskResult(task3Result)) 115 | } 116 | 117 | override fun runParallelTasksWithError() = uiJob { 118 | view.updateTaskExecutionState(1, INITIAL) 119 | view.updateTaskExecutionState(2, INITIAL) 120 | view.updateTaskExecutionState(3, INITIAL) 121 | 122 | delayTask(1000) 123 | 124 | view.updateTaskExecutionState(1, RUNNING) 125 | val task1Result: Deferred = parallelTask1.executeAsync(this, 100, 500, 1500) 126 | 127 | view.updateTaskExecutionState(2, RUNNING) 128 | val task2Result: Deferred = parallelErrorTask.executeAsync(this, 300, 200, 2000) 129 | 130 | view.updateTaskExecutionState(3, RUNNING) 131 | val task3Result: Deferred = parallelTask3.executeAsync(this, 200, 600, 1800) 132 | 133 | view.updateTaskExecutionState(1, processTaskResult(task1Result.await())) 134 | view.updateTaskExecutionState(2, processTaskResult(task2Result.await())) 135 | view.updateTaskExecutionState(3, processTaskResult(task3Result.await())) 136 | } 137 | 138 | override fun runMultipleTasks() = uiJob { 139 | view.updateTaskExecutionState(1, INITIAL) 140 | view.updateTaskExecutionState(2, INITIAL) 141 | view.updateTaskExecutionState(3, INITIAL) 142 | 143 | delayTask(1000) 144 | 145 | view.updateTaskExecutionState(1, RUNNING) 146 | val task1Result: TaskExecutionResult = multipleTasks1.execute(1, 10, 100) 147 | view.updateTaskExecutionState(1, processTaskResult(task1Result)) 148 | 149 | view.updateTaskExecutionState(2, RUNNING) 150 | val task2Result: TaskExecutionResult = multipleTasks2.execute(2, 20, 200) 151 | view.updateTaskExecutionState(2, processTaskResult(task2Result)) 152 | 153 | view.updateTaskExecutionState(3, RUNNING) 154 | val task3Result: TaskExecutionResult = multipleTasks3.execute(3, 30, 300) 155 | view.updateTaskExecutionState(3, processTaskResult(task3Result)) 156 | } 157 | 158 | override fun runCallbackTasksWithError() = uiJob { 159 | view.updateTaskExecutionState(1, INITIAL) 160 | view.updateTaskExecutionState(2, INITIAL) 161 | view.updateTaskExecutionState(3, INITIAL) 162 | 163 | delayTask(1000) 164 | 165 | view.updateTaskExecutionState(1, RUNNING) 166 | try { 167 | val task1Result: TaskExecutionResult = callbackTask1.execute("RANDOM STRING") 168 | view.updateTaskExecutionState(1, processTaskResult(task1Result)) 169 | } catch (e: CustomTaskException) { 170 | view.updateTaskExecutionState(1, processTaskResult(TaskExecutionError(e))) 171 | } 172 | 173 | view.updateTaskExecutionState(2, RUNNING) 174 | val task2Result: TaskExecutionResult = callbackTask2.execute("SUCCESS") 175 | view.updateTaskExecutionState(2, processTaskResult(task2Result)) 176 | 177 | view.updateTaskExecutionState(3, RUNNING) 178 | try { 179 | val task3Result: TaskExecutionResult = callbackTask3.execute("CANCEL") 180 | view.updateTaskExecutionState(3, processTaskResult(task3Result)) 181 | } catch (e: CancellationException) { 182 | view.updateTaskExecutionState(3, processTaskResult(TaskExecutionCancelled)) 183 | } 184 | } 185 | 186 | override fun runLongComputationTasks() { 187 | uiJob { 188 | view.updateTaskExecutionState(1, INITIAL) 189 | delayTask(1000) 190 | view.updateTaskExecutionState(1, RUNNING) 191 | 192 | longComputationTask1Deferred = longComputationTask1.executeAsync(this, 500, 10) 193 | longComputationTask1Deferred?.let { 194 | view.updateTaskExecutionState(1, processTaskResult(it.awaitOrReturn(TaskExecutionCancelled))) 195 | } 196 | } 197 | 198 | uiJob { 199 | view.updateTaskExecutionState(2, INITIAL) 200 | delayTask(1000) 201 | view.updateTaskExecutionState(2, RUNNING) 202 | 203 | longComputationTask2Deferred = longComputationTask2.executeAsync(this, 1000, 5) 204 | longComputationTask2Deferred?.let { 205 | view.updateTaskExecutionState(2, processTaskResult(it.awaitOrReturn(TaskExecutionCancelled))) 206 | } 207 | } 208 | 209 | uiJob { 210 | view.updateTaskExecutionState(3, INITIAL) 211 | delayTask(1000) 212 | view.updateTaskExecutionState(3, RUNNING) 213 | 214 | longComputationTask3Deferred = longComputationTask3.executeAsync(this, 300, 20) 215 | longComputationTask3Deferred?.let { 216 | view.updateTaskExecutionState(3, processTaskResult(it.awaitOrReturn(TaskExecutionCancelled))) 217 | } 218 | } 219 | } 220 | 221 | override fun cancelLongComputationTask1() { 222 | longComputationTask1Deferred?.cancel() 223 | } 224 | 225 | override fun cancelLongComputationTask2() { 226 | longComputationTask2Deferred?.cancel() 227 | } 228 | 229 | override fun cancelLongComputationTask3() { 230 | longComputationTask3Deferred?.cancel() 231 | } 232 | 233 | override fun runLongComputationTasksWithTimeout() { 234 | uiJob { 235 | view.updateTaskExecutionState(1, INITIAL) 236 | delayTask(1000) 237 | view.updateTaskExecutionState(1, RUNNING) 238 | 239 | val taskResult: Deferred = longComputationTask1.executeAsync(this, 500, 10, 4000) 240 | view.updateTaskExecutionState(1, processTaskResult(taskResult.awaitOrReturn(TaskExecutionCancelled))) 241 | } 242 | 243 | uiJob { 244 | view.updateTaskExecutionState(2, INITIAL) 245 | delayTask(1000) 246 | view.updateTaskExecutionState(2, RUNNING) 247 | 248 | try { 249 | uiTask(timeout = 3000) { 250 | val taskResult: Deferred = longComputationTask2.executeAsync(this, 1000, 5) 251 | view.updateTaskExecutionState(2, processTaskResult(taskResult.await())) 252 | } 253 | } catch (e: TimeoutCancellationException) { 254 | view.updateTaskExecutionState(2, processTaskResult(TaskExecutionCancelled)) 255 | } 256 | } 257 | 258 | uiJob(timeout = 2000) { 259 | view.updateTaskExecutionState(3, INITIAL) 260 | delayTask(1000) 261 | view.updateTaskExecutionState(3, RUNNING) 262 | 263 | val taskResult: Deferred = longComputationTask3.executeAsync(this, 300, 20) 264 | view.updateTaskExecutionState(3, processTaskResult(taskResult.awaitOrReturn(TaskExecutionCancelled))) 265 | } 266 | } 267 | 268 | override fun runChannelsTasks() { 269 | uiJob { 270 | view.updateTaskExecutionState(1, INITIAL) 271 | 272 | val channel = Channel() 273 | val itemProcessingTime = 400L 274 | 275 | val taskResult: Deferred = channelTask1.executeAsync(this, 800, 10, channel) 276 | 277 | for (receivedItem in channel) { 278 | view.updateTaskExecutionState(1, RUNNING) 279 | backgroundTask { delayTask(itemProcessingTime) } 280 | view.updateTaskExecutionState(1, INITIAL) 281 | } 282 | 283 | view.updateTaskExecutionState(1, processTaskResult(taskResult.await())) 284 | } 285 | 286 | uiJob { 287 | try { 288 | view.updateTaskExecutionState(2, INITIAL) 289 | 290 | val channel = Channel() 291 | val itemProcessingTime = 1000L 292 | 293 | val taskResult: Deferred = channelTask2.executeAsync(this, 800, 10, channel) 294 | 295 | for (receivedItem in channel) { 296 | view.updateTaskExecutionState(2, RUNNING) 297 | backgroundTask { delayTask(itemProcessingTime) } 298 | view.updateTaskExecutionState(2, INITIAL) 299 | } 300 | 301 | view.updateTaskExecutionState(2, processTaskResult(taskResult.await())) 302 | } catch (e: CancellationException) { 303 | view.updateTaskExecutionState(2, processTaskResult(TaskExecutionCancelled)) 304 | } 305 | } 306 | 307 | uiJob { 308 | view.updateTaskExecutionState(3, INITIAL) 309 | 310 | val primaryChannel = Channel() 311 | val backpressureChannel = Channel() 312 | val itemProcessingTime = 1500L 313 | 314 | val taskResult: Deferred = channelTask3.executeAsync(this, 500, 20, primaryChannel, backpressureChannel) 315 | 316 | val primaryHandler = backgroundTaskAsync { 317 | for (receivedItem in primaryChannel) { 318 | uiTask { view.updateTaskExecutionState(3, RUNNING) } 319 | delayTask(itemProcessingTime) 320 | uiTask { view.updateTaskExecutionState(3, INITIAL) } 321 | } 322 | } 323 | 324 | val backpressureHandler = backgroundTaskAsync { 325 | for (receivedItem in backpressureChannel) { 326 | uiTask { view.updateTaskExecutionState(3, ERROR) } 327 | } 328 | } 329 | 330 | primaryHandler.await() 331 | backpressureHandler.await() 332 | view.updateTaskExecutionState(3, processTaskResult(taskResult.await())) 333 | } 334 | } 335 | 336 | override fun runExceptionsTasks() = uiJob { 337 | view.updateTaskExecutionState(1, INITIAL) 338 | view.updateTaskExecutionState(2, INITIAL) 339 | view.updateTaskExecutionState(3, INITIAL) 340 | 341 | delayTask(1000) 342 | 343 | view.updateTaskExecutionState(1, RUNNING) 344 | try { 345 | val task1Result: TaskExecutionResult = exceptionsTask.execute(100, 500, 1500) 346 | view.updateTaskExecutionState(1, processTaskResult(task1Result)) 347 | } catch (e: CustomTaskException) { 348 | view.updateTaskExecutionState(1, ERROR) 349 | } 350 | 351 | view.updateTaskExecutionState(2, RUNNING) 352 | val task2Result: Deferred = exceptionsTask.executeAsync(this, 300, 200, 2000) 353 | 354 | view.updateTaskExecutionState(3, RUNNING) 355 | val task3Result: Deferred = exceptionsTask.executeWithRepositoryAsync(this, 200, 600, 1800) 356 | 357 | try { 358 | view.updateTaskExecutionState(2, processTaskResult(task2Result.await())) 359 | } catch (e: CustomTaskException) { 360 | view.updateTaskExecutionState(2, ERROR) 361 | } 362 | 363 | try { 364 | view.updateTaskExecutionState(3, processTaskResult(task3Result.await())) 365 | } catch (e: IOException) { 366 | view.updateTaskExecutionState(3, ERROR) 367 | } 368 | } 369 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/mvp/view/MVPFragment.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.mvp.view 2 | 3 | import andreabresolin.androidcoroutinesplayground.R 4 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionState 5 | import andreabresolin.androidcoroutinesplayground.app.view.BaseFragment 6 | import andreabresolin.androidcoroutinesplayground.mvp.presenter.MVPPresenter 7 | import android.os.Bundle 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import com.airbnb.paris.extensions.style 12 | import kotlinx.android.synthetic.main.fragment_tasks_common.* 13 | import javax.inject.Inject 14 | 15 | class MVPFragment : BaseFragment(), MVPView { 16 | 17 | @Inject 18 | internal lateinit var presenter: MVPPresenter 19 | 20 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 21 | super.onCreateView(inflater, container, savedInstanceState) 22 | return inflater.inflate(R.layout.fragment_mvp, container, false) 23 | } 24 | 25 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 26 | super.onViewCreated(view, savedInstanceState) 27 | setUpListeners() 28 | } 29 | 30 | override fun onStop() { 31 | presenter.cancelJobs() 32 | super.onStop() 33 | } 34 | 35 | private fun setUpListeners() { 36 | runSequentialBtn.setOnClickListener { onRunSequentialBtnClicked() } 37 | runParallelBtn.setOnClickListener { onRunParallelBtnClicked() } 38 | runSequentialWithErrorBtn.setOnClickListener { onRunSequentialWithErrorBtnClicked() } 39 | runParallelWithErrorBtn.setOnClickListener { onRunParallelWithErrorBtnClicked() } 40 | runMultipleBtn.setOnClickListener { onRunMultipleBtnClicked() } 41 | runCallbackWithErrorBtn.setOnClickListener { onRunCallbackWithErrorBtnClicked() } 42 | runLongComputationBtn.setOnClickListener { onRunLongComputationBtnClicked() } 43 | cancelLongComputation1Btn.setOnClickListener { onCancelLongComputation1BtnClicked() } 44 | cancelLongComputation2Btn.setOnClickListener { onCancelLongComputation2BtnClicked() } 45 | cancelLongComputation3Btn.setOnClickListener { onCancelLongComputation3BtnClicked() } 46 | runLongComputationWithTimeoutBtn.setOnClickListener { onRunLongComputationWithTimeoutBtnClicked() } 47 | runChannelsBtn.setOnClickListener { onRunChannelsBtnClicked() } 48 | runExceptionsBtn.setOnClickListener { onRunExceptionsBtnClicked() } 49 | } 50 | 51 | private fun onRunSequentialBtnClicked() { 52 | presenter.runSequentialTasks() 53 | } 54 | 55 | private fun onRunParallelBtnClicked() { 56 | presenter.runParallelTasks() 57 | } 58 | 59 | private fun onRunSequentialWithErrorBtnClicked() { 60 | presenter.runSequentialTasksWithError() 61 | } 62 | 63 | private fun onRunParallelWithErrorBtnClicked() { 64 | presenter.runParallelTasksWithError() 65 | } 66 | 67 | private fun onRunMultipleBtnClicked() { 68 | presenter.runMultipleTasks() 69 | } 70 | 71 | private fun onRunCallbackWithErrorBtnClicked() { 72 | presenter.runCallbackTasksWithError() 73 | } 74 | 75 | private fun onRunLongComputationBtnClicked() { 76 | presenter.runLongComputationTasks() 77 | } 78 | 79 | private fun onCancelLongComputation1BtnClicked() { 80 | presenter.cancelLongComputationTask1() 81 | } 82 | 83 | private fun onCancelLongComputation2BtnClicked() { 84 | presenter.cancelLongComputationTask2() 85 | } 86 | 87 | private fun onCancelLongComputation3BtnClicked() { 88 | presenter.cancelLongComputationTask3() 89 | } 90 | 91 | private fun onRunLongComputationWithTimeoutBtnClicked() { 92 | presenter.runLongComputationTasksWithTimeout() 93 | } 94 | 95 | private fun onRunChannelsBtnClicked() { 96 | presenter.runChannelsTasks() 97 | } 98 | 99 | private fun onRunExceptionsBtnClicked() { 100 | presenter.runExceptionsTasks() 101 | } 102 | 103 | private fun applyTaskStyleForState(taskView: View, taskExecutionState: TaskExecutionState) { 104 | when (taskExecutionState) { 105 | TaskExecutionState.INITIAL -> taskView.style(R.style.TaskBoxInitialState) 106 | TaskExecutionState.RUNNING -> taskView.style(R.style.TaskBoxRunningState) 107 | TaskExecutionState.COMPLETED -> taskView.style(R.style.TaskBoxCompletedState) 108 | TaskExecutionState.CANCELLED -> taskView.style(R.style.TaskBoxCancelledState) 109 | TaskExecutionState.ERROR -> taskView.style(R.style.TaskBoxErrorState) 110 | } 111 | } 112 | 113 | override fun updateTaskExecutionState(taskNumber: Int, taskExecutionState: TaskExecutionState) { 114 | val taskView: View? = when (taskNumber) { 115 | 1 -> task1Box 116 | 2 -> task2Box 117 | 3 -> task3Box 118 | else -> null 119 | } 120 | 121 | taskView?.let { applyTaskStyleForState(it, taskExecutionState) } 122 | } 123 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/mvp/view/MVPView.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.mvp.view 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionState 4 | 5 | interface MVPView { 6 | 7 | fun updateTaskExecutionState(taskNumber: Int, taskExecutionState: TaskExecutionState) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/mvvm/di/MVVMModule.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.mvvm.di 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.di.scope.PerFragment 4 | import andreabresolin.androidcoroutinesplayground.mvvm.viewmodel.MVVMViewModelFactory 5 | import androidx.lifecycle.ViewModelProvider 6 | import dagger.Binds 7 | import dagger.Module 8 | 9 | @Module 10 | abstract class MVVMModule { 11 | 12 | @PerFragment 13 | @Binds 14 | abstract fun bindMVVMViewModelFactory(mvvmViewModelFactory: MVVMViewModelFactory): ViewModelProvider.Factory 15 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/mvvm/view/MVVMFragment.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.mvvm.view 2 | 3 | import andreabresolin.androidcoroutinesplayground.R 4 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionState 5 | import andreabresolin.androidcoroutinesplayground.app.view.BaseFragment 6 | import andreabresolin.androidcoroutinesplayground.mvvm.viewmodel.MVVMViewModel 7 | import android.os.Bundle 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import androidx.lifecycle.Observer 12 | import androidx.lifecycle.ViewModelProvider 13 | import androidx.lifecycle.ViewModelProviders 14 | import com.airbnb.paris.extensions.style 15 | import kotlinx.android.synthetic.main.fragment_tasks_common.* 16 | import javax.inject.Inject 17 | 18 | class MVVMFragment : BaseFragment() { 19 | 20 | @Inject 21 | internal lateinit var viewModelFactory: ViewModelProvider.Factory 22 | 23 | private lateinit var viewModel: MVVMViewModel 24 | 25 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 26 | super.onCreateView(inflater, container, savedInstanceState) 27 | return inflater.inflate(R.layout.fragment_mvvm, container, false) 28 | } 29 | 30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 31 | super.onViewCreated(view, savedInstanceState) 32 | viewModel = ViewModelProviders.of(this, viewModelFactory).get(MVVMViewModel::class.java) 33 | observeViewState() 34 | setUpListeners() 35 | } 36 | 37 | private fun observeViewState() { 38 | viewModel.task1State.observe(this, task1StateObserver) 39 | viewModel.task2State.observe(this, task2StateObserver) 40 | viewModel.task3State.observe(this, task3StateObserver) 41 | } 42 | 43 | private fun setUpListeners() { 44 | runSequentialBtn.setOnClickListener { onRunSequentialBtnClicked() } 45 | runParallelBtn.setOnClickListener { onRunParallelBtnClicked() } 46 | runSequentialWithErrorBtn.setOnClickListener { onRunSequentialWithErrorBtnClicked() } 47 | runParallelWithErrorBtn.setOnClickListener { onRunParallelWithErrorBtnClicked() } 48 | runMultipleBtn.setOnClickListener { onRunMultipleBtnClicked() } 49 | runCallbackWithErrorBtn.setOnClickListener { onRunCallbackWithErrorBtnClicked() } 50 | runLongComputationBtn.setOnClickListener { onRunLongComputationBtnClicked() } 51 | cancelLongComputation1Btn.setOnClickListener { onCancelLongComputation1BtnClicked() } 52 | cancelLongComputation2Btn.setOnClickListener { onCancelLongComputation2BtnClicked() } 53 | cancelLongComputation3Btn.setOnClickListener { onCancelLongComputation3BtnClicked() } 54 | runLongComputationWithTimeoutBtn.setOnClickListener { onRunLongComputationWithTimeoutBtnClicked() } 55 | runChannelsBtn.setOnClickListener { onRunChannelsBtnClicked() } 56 | runExceptionsBtn.setOnClickListener { onRunExceptionsBtnClicked() } 57 | } 58 | 59 | private val task1StateObserver = Observer { newState -> applyTaskStyleForState(task1Box, newState) } 60 | private val task2StateObserver = Observer { newState -> applyTaskStyleForState(task2Box, newState) } 61 | private val task3StateObserver = Observer { newState -> applyTaskStyleForState(task3Box, newState) } 62 | 63 | private fun applyTaskStyleForState(taskView: View, taskExecutionState: TaskExecutionState) { 64 | when (taskExecutionState) { 65 | TaskExecutionState.INITIAL -> taskView.style(R.style.TaskBoxInitialState) 66 | TaskExecutionState.RUNNING -> taskView.style(R.style.TaskBoxRunningState) 67 | TaskExecutionState.COMPLETED -> taskView.style(R.style.TaskBoxCompletedState) 68 | TaskExecutionState.CANCELLED -> taskView.style(R.style.TaskBoxCancelledState) 69 | TaskExecutionState.ERROR -> taskView.style(R.style.TaskBoxErrorState) 70 | } 71 | } 72 | 73 | private fun onRunSequentialBtnClicked() { 74 | viewModel.runSequentialTasks() 75 | } 76 | 77 | private fun onRunParallelBtnClicked() { 78 | viewModel.runParallelTasks() 79 | } 80 | 81 | private fun onRunSequentialWithErrorBtnClicked() { 82 | viewModel.runSequentialTasksWithError() 83 | } 84 | 85 | private fun onRunParallelWithErrorBtnClicked() { 86 | viewModel.runParallelTasksWithError() 87 | } 88 | 89 | private fun onRunMultipleBtnClicked() { 90 | viewModel.runMultipleTasks() 91 | } 92 | 93 | private fun onRunCallbackWithErrorBtnClicked() { 94 | viewModel.runCallbackTasksWithError() 95 | } 96 | 97 | private fun onRunLongComputationBtnClicked() { 98 | viewModel.runLongComputationTasks() 99 | } 100 | 101 | private fun onCancelLongComputation1BtnClicked() { 102 | viewModel.cancelLongComputationTask1() 103 | } 104 | 105 | private fun onCancelLongComputation2BtnClicked() { 106 | viewModel.cancelLongComputationTask2() 107 | } 108 | 109 | private fun onCancelLongComputation3BtnClicked() { 110 | viewModel.cancelLongComputationTask3() 111 | } 112 | 113 | private fun onRunLongComputationWithTimeoutBtnClicked() { 114 | viewModel.runLongComputationTasksWithTimeout() 115 | } 116 | 117 | private fun onRunChannelsBtnClicked() { 118 | viewModel.runChannelsTasks() 119 | } 120 | 121 | private fun onRunExceptionsBtnClicked() { 122 | viewModel.runExceptionsTasks() 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/mvvm/viewmodel/MVVMViewModel.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.mvvm.viewmodel 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.AppCoroutineScope 4 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionState 5 | import andreabresolin.androidcoroutinesplayground.app.presentation.BaseViewModel 6 | import androidx.lifecycle.LiveData 7 | 8 | abstract class MVVMViewModel 9 | constructor(appCoroutineScope: AppCoroutineScope) : BaseViewModel(appCoroutineScope) { 10 | 11 | abstract val task1State: LiveData 12 | abstract val task2State: LiveData 13 | abstract val task3State: LiveData 14 | 15 | abstract fun runSequentialTasks() 16 | 17 | abstract fun runParallelTasks() 18 | 19 | abstract fun runSequentialTasksWithError() 20 | 21 | abstract fun runParallelTasksWithError() 22 | 23 | abstract fun runMultipleTasks() 24 | 25 | abstract fun runCallbackTasksWithError() 26 | 27 | abstract fun runLongComputationTasks() 28 | 29 | abstract fun cancelLongComputationTask1() 30 | 31 | abstract fun cancelLongComputationTask2() 32 | 33 | abstract fun cancelLongComputationTask3() 34 | 35 | abstract fun runLongComputationTasksWithTimeout() 36 | 37 | abstract fun runChannelsTasks() 38 | 39 | abstract fun runExceptionsTasks() 40 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/mvvm/viewmodel/MVVMViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.mvvm.viewmodel 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.AppCoroutineScope 4 | import andreabresolin.androidcoroutinesplayground.app.domain.task.* 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.ViewModelProvider 7 | import javax.inject.Inject 8 | 9 | class MVVMViewModelFactory 10 | @Inject constructor( 11 | private val appCoroutineScope: AppCoroutineScope, 12 | private val sequentialTask1: SequentialTaskUseCase, 13 | private val sequentialTask2: SequentialTaskUseCase, 14 | private val sequentialTask3: SequentialTaskUseCase, 15 | private val parallelTask1: ParallelTaskUseCase, 16 | private val parallelTask2: ParallelTaskUseCase, 17 | private val parallelTask3: ParallelTaskUseCase, 18 | private val sequentialErrorTask: SequentialErrorTaskUseCase, 19 | private val parallelErrorTask: ParallelErrorTaskUseCase, 20 | private val multipleTasks1: MultipleTasksUseCase, 21 | private val multipleTasks2: MultipleTasksUseCase, 22 | private val multipleTasks3: MultipleTasksUseCase, 23 | private val callbackTask1: CallbackTaskUseCase, 24 | private val callbackTask2: CallbackTaskUseCase, 25 | private val callbackTask3: CallbackTaskUseCase, 26 | private val longComputationTask1: LongComputationTaskUseCase, 27 | private val longComputationTask2: LongComputationTaskUseCase, 28 | private val longComputationTask3: LongComputationTaskUseCase, 29 | private val channelTask1: ChannelTaskUseCase, 30 | private val channelTask2: ChannelTaskUseCase, 31 | private val channelTask3: ChannelTaskUseCase, 32 | private val exceptionsTask: ExceptionsTaskUseCase 33 | ) : ViewModelProvider.Factory { 34 | 35 | @Suppress("UNCHECKED_CAST") 36 | override fun create(modelClass: Class): T { 37 | return MVVMViewModelImpl( 38 | appCoroutineScope, 39 | sequentialTask1, 40 | sequentialTask2, 41 | sequentialTask3, 42 | parallelTask1, 43 | parallelTask2, 44 | parallelTask3, 45 | sequentialErrorTask, 46 | parallelErrorTask, 47 | multipleTasks1, 48 | multipleTasks2, 49 | multipleTasks3, 50 | callbackTask1, 51 | callbackTask2, 52 | callbackTask3, 53 | longComputationTask1, 54 | longComputationTask2, 55 | longComputationTask3, 56 | channelTask1, 57 | channelTask2, 58 | channelTask3, 59 | exceptionsTask 60 | ) as T 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/java/andreabresolin/androidcoroutinesplayground/mvvm/viewmodel/MVVMViewModelImpl.kt: -------------------------------------------------------------------------------- 1 | package andreabresolin.androidcoroutinesplayground.mvvm.viewmodel 2 | 3 | import andreabresolin.androidcoroutinesplayground.app.coroutines.* 4 | import andreabresolin.androidcoroutinesplayground.app.domain.task.* 5 | import andreabresolin.androidcoroutinesplayground.app.exception.CustomTaskException 6 | import andreabresolin.androidcoroutinesplayground.app.model.* 7 | import andreabresolin.androidcoroutinesplayground.app.model.TaskExecutionState.* 8 | import androidx.lifecycle.MutableLiveData 9 | import kotlinx.coroutines.* 10 | import kotlinx.coroutines.channels.Channel 11 | import java.io.IOException 12 | 13 | class MVVMViewModelImpl 14 | constructor( 15 | appCoroutineScope: AppCoroutineScope, 16 | private val sequentialTask1: SequentialTaskUseCase, 17 | private val sequentialTask2: SequentialTaskUseCase, 18 | private val sequentialTask3: SequentialTaskUseCase, 19 | private val parallelTask1: ParallelTaskUseCase, 20 | private val parallelTask2: ParallelTaskUseCase, 21 | private val parallelTask3: ParallelTaskUseCase, 22 | private val sequentialErrorTask: SequentialErrorTaskUseCase, 23 | private val parallelErrorTask: ParallelErrorTaskUseCase, 24 | private val multipleTasks1: MultipleTasksUseCase, 25 | private val multipleTasks2: MultipleTasksUseCase, 26 | private val multipleTasks3: MultipleTasksUseCase, 27 | private val callbackTask1: CallbackTaskUseCase, 28 | private val callbackTask2: CallbackTaskUseCase, 29 | private val callbackTask3: CallbackTaskUseCase, 30 | private val longComputationTask1: LongComputationTaskUseCase, 31 | private val longComputationTask2: LongComputationTaskUseCase, 32 | private val longComputationTask3: LongComputationTaskUseCase, 33 | private val channelTask1: ChannelTaskUseCase, 34 | private val channelTask2: ChannelTaskUseCase, 35 | private val channelTask3: ChannelTaskUseCase, 36 | private val exceptionsTask: ExceptionsTaskUseCase 37 | ) : MVVMViewModel(appCoroutineScope) { 38 | 39 | override val task1State = MutableLiveData() 40 | override val task2State = MutableLiveData() 41 | override val task3State = MutableLiveData() 42 | 43 | private var longComputationTask1Deferred: Deferred? = null 44 | private var longComputationTask2Deferred: Deferred? = null 45 | private var longComputationTask3Deferred: Deferred? = null 46 | 47 | private fun processTaskResult(taskExecutionResult: TaskExecutionResult): TaskExecutionState { 48 | return when (taskExecutionResult) { 49 | is TaskExecutionSuccess -> COMPLETED 50 | is TaskExecutionCancelled -> CANCELLED 51 | is TaskExecutionError -> ERROR 52 | } 53 | } 54 | 55 | override fun runSequentialTasks() = uiJob { 56 | task1State.value = INITIAL 57 | task2State.value = INITIAL 58 | task3State.value = INITIAL 59 | 60 | delayTask(1000) 61 | 62 | task1State.value = RUNNING 63 | val task1Result: TaskExecutionResult = sequentialTask1.execute(100, 500, 1500) 64 | task1State.value = processTaskResult(task1Result) 65 | 66 | task2State.value = RUNNING 67 | val task2Result: TaskExecutionResult = sequentialTask2.execute(300, 200, 2000) 68 | task2State.value = processTaskResult(task2Result) 69 | 70 | task3State.value = RUNNING 71 | val task3Result: TaskExecutionResult = sequentialTask3.execute(200, 600, 1800) 72 | task3State.value = processTaskResult(task3Result) 73 | } 74 | 75 | override fun runParallelTasks() = uiJob { 76 | task1State.value = INITIAL 77 | task2State.value = INITIAL 78 | task3State.value = INITIAL 79 | 80 | delayTask(1000) 81 | 82 | task1State.value = RUNNING 83 | val task1Result: Deferred = parallelTask1.executeAsync(this, 100, 500, 1500) 84 | 85 | task2State.value = RUNNING 86 | val task2Result: Deferred = parallelTask2.executeAsync(this, 300, 200, 2000) 87 | 88 | task3State.value = RUNNING 89 | val task3Result: Deferred = parallelTask3.executeAsync(this, 200, 600, 1800) 90 | 91 | task1State.value = processTaskResult(task1Result.await()) 92 | task2State.value = processTaskResult(task2Result.await()) 93 | task3State.value = processTaskResult(task3Result.await()) 94 | } 95 | 96 | override fun runSequentialTasksWithError() = uiJob { 97 | task1State.value = INITIAL 98 | task2State.value = INITIAL 99 | task3State.value = INITIAL 100 | 101 | delayTask(1000) 102 | 103 | task1State.value = RUNNING 104 | val task1Result: TaskExecutionResult = sequentialTask1.execute(100, 500, 1500) 105 | task1State.value = processTaskResult(task1Result) 106 | 107 | task2State.value = RUNNING 108 | val task2Result: TaskExecutionResult = sequentialErrorTask.execute(300, 200, 2000) 109 | task2State.value = processTaskResult(task2Result) 110 | 111 | task3State.value = RUNNING 112 | val task3Result: TaskExecutionResult = sequentialTask3.execute(200, 600, 1800) 113 | task3State.value = processTaskResult(task3Result) 114 | } 115 | 116 | override fun runParallelTasksWithError() = uiJob { 117 | task1State.value = INITIAL 118 | task2State.value = INITIAL 119 | task3State.value = INITIAL 120 | 121 | delayTask(1000) 122 | 123 | task1State.value = RUNNING 124 | val task1Result: Deferred = parallelTask1.executeAsync(this, 100, 500, 1500) 125 | 126 | task2State.value = RUNNING 127 | val task2Result: Deferred = parallelErrorTask.executeAsync(this, 300, 200, 2000) 128 | 129 | task3State.value = RUNNING 130 | val task3Result: Deferred = parallelTask3.executeAsync(this, 200, 600, 1800) 131 | 132 | task1State.value = processTaskResult(task1Result.await()) 133 | task2State.value = processTaskResult(task2Result.await()) 134 | task3State.value = processTaskResult(task3Result.await()) 135 | } 136 | 137 | override fun runMultipleTasks() = uiJob { 138 | task1State.value = INITIAL 139 | task2State.value = INITIAL 140 | task3State.value = INITIAL 141 | 142 | delayTask(1000) 143 | 144 | task1State.value = RUNNING 145 | val task1Result: TaskExecutionResult = multipleTasks1.execute(1, 10, 100) 146 | task1State.value = processTaskResult(task1Result) 147 | 148 | task2State.value = RUNNING 149 | val task2Result: TaskExecutionResult = multipleTasks2.execute(2, 20, 200) 150 | task2State.value = processTaskResult(task2Result) 151 | 152 | task3State.value = RUNNING 153 | val task3Result: TaskExecutionResult = multipleTasks3.execute(3, 30, 300) 154 | task3State.value = processTaskResult(task3Result) 155 | } 156 | 157 | override fun runCallbackTasksWithError() = uiJob { 158 | task1State.value = INITIAL 159 | task2State.value = INITIAL 160 | task3State.value = INITIAL 161 | 162 | delayTask(1000) 163 | 164 | task1State.value = RUNNING 165 | try { 166 | val task1Result: TaskExecutionResult = callbackTask1.execute("RANDOM STRING") 167 | task1State.value = processTaskResult(task1Result) 168 | } catch (e: CustomTaskException) { 169 | task1State.value = processTaskResult(TaskExecutionError(e)) 170 | } 171 | 172 | task2State.value = RUNNING 173 | val task2Result: TaskExecutionResult = callbackTask2.execute("SUCCESS") 174 | task2State.value = processTaskResult(task2Result) 175 | 176 | task3State.value = RUNNING 177 | try { 178 | val task3Result: TaskExecutionResult = callbackTask3.execute("CANCEL") 179 | task3State.value = processTaskResult(task3Result) 180 | } catch (e: CancellationException) { 181 | task3State.value = processTaskResult(TaskExecutionCancelled) 182 | } 183 | } 184 | 185 | override fun runLongComputationTasks() { 186 | uiJob { 187 | task1State.value = INITIAL 188 | delayTask(1000) 189 | task1State.value = RUNNING 190 | 191 | longComputationTask1Deferred = longComputationTask1.executeAsync(this, 500, 10) 192 | longComputationTask1Deferred?.let { 193 | task1State.value = processTaskResult(it.awaitOrReturn(TaskExecutionCancelled)) 194 | } 195 | } 196 | 197 | uiJob { 198 | task2State.value = INITIAL 199 | delayTask(1000) 200 | task2State.value = RUNNING 201 | 202 | longComputationTask2Deferred = longComputationTask2.executeAsync(this, 1000, 5) 203 | longComputationTask2Deferred?.let { 204 | task2State.value = processTaskResult(it.awaitOrReturn(TaskExecutionCancelled)) 205 | } 206 | } 207 | 208 | uiJob { 209 | task3State.value = INITIAL 210 | delayTask(1000) 211 | task3State.value = RUNNING 212 | 213 | longComputationTask3Deferred = longComputationTask3.executeAsync(this, 300, 20) 214 | longComputationTask3Deferred?.let { 215 | task3State.value = processTaskResult(it.awaitOrReturn(TaskExecutionCancelled)) 216 | } 217 | } 218 | } 219 | 220 | override fun cancelLongComputationTask1() { 221 | longComputationTask1Deferred?.cancel() 222 | } 223 | 224 | override fun cancelLongComputationTask2() { 225 | longComputationTask2Deferred?.cancel() 226 | } 227 | 228 | override fun cancelLongComputationTask3() { 229 | longComputationTask3Deferred?.cancel() 230 | } 231 | 232 | override fun runLongComputationTasksWithTimeout() { 233 | uiJob { 234 | task1State.value = INITIAL 235 | delayTask(1000) 236 | task1State.value = RUNNING 237 | 238 | val taskResult: Deferred = longComputationTask1.executeAsync(this, 500, 10, 4000) 239 | task1State.value = processTaskResult(taskResult.awaitOrReturn(TaskExecutionCancelled)) 240 | } 241 | 242 | uiJob { 243 | task2State.value = INITIAL 244 | delayTask(1000) 245 | task2State.value = RUNNING 246 | 247 | try { 248 | uiTask(timeout = 3000) { 249 | val taskResult: Deferred = longComputationTask2.executeAsync(this, 1000, 5) 250 | task2State.value = processTaskResult(taskResult.await()) 251 | } 252 | } catch (e: TimeoutCancellationException) { 253 | task2State.value = processTaskResult(TaskExecutionCancelled) 254 | } 255 | } 256 | 257 | uiJob(timeout = 2000) { 258 | task3State.value = INITIAL 259 | delayTask(1000) 260 | task3State.value = RUNNING 261 | 262 | val taskResult: Deferred = longComputationTask3.executeAsync(this, 300, 20) 263 | task3State.value = processTaskResult(taskResult.awaitOrReturn(TaskExecutionCancelled)) 264 | } 265 | } 266 | 267 | override fun runChannelsTasks() { 268 | uiJob { 269 | task1State.value = INITIAL 270 | 271 | val channel = Channel() 272 | val itemProcessingTime = 400L 273 | 274 | val taskResult: Deferred = channelTask1.executeAsync(this, 800, 10, channel) 275 | 276 | for (receivedItem in channel) { 277 | task1State.value = RUNNING 278 | backgroundTask { delayTask(itemProcessingTime) } 279 | task1State.value = INITIAL 280 | } 281 | 282 | task1State.value = processTaskResult(taskResult.await()) 283 | } 284 | 285 | uiJob { 286 | try { 287 | task2State.value = INITIAL 288 | 289 | val channel = Channel() 290 | val itemProcessingTime = 1000L 291 | 292 | val taskResult: Deferred = channelTask2.executeAsync(this, 800, 10, channel) 293 | 294 | for (receivedItem in channel) { 295 | task2State.value = RUNNING 296 | backgroundTask { delayTask(itemProcessingTime) } 297 | task2State.value = INITIAL 298 | } 299 | 300 | task2State.value = processTaskResult(taskResult.await()) 301 | } catch (e: CancellationException) { 302 | task2State.value = CANCELLED 303 | } 304 | } 305 | 306 | uiJob { 307 | task3State.value = INITIAL 308 | 309 | val primaryChannel = Channel() 310 | val backpressureChannel = Channel() 311 | val itemProcessingTime = 1500L 312 | 313 | val taskResult: Deferred = channelTask3.executeAsync(this, 500, 20, primaryChannel, backpressureChannel) 314 | 315 | val primaryHandler = backgroundTaskAsync { 316 | for (receivedItem in primaryChannel) { 317 | task3State.postValue(RUNNING) 318 | delayTask(itemProcessingTime) 319 | task3State.postValue(INITIAL) 320 | } 321 | } 322 | 323 | val backpressureHandler = backgroundTaskAsync { 324 | for (receivedItem in backpressureChannel) { 325 | task3State.postValue(ERROR) 326 | } 327 | } 328 | 329 | primaryHandler.await() 330 | backpressureHandler.await() 331 | task3State.value = processTaskResult(taskResult.await()) 332 | } 333 | } 334 | 335 | override fun runExceptionsTasks() = uiJob { 336 | task1State.value = INITIAL 337 | task2State.value = INITIAL 338 | task3State.value = INITIAL 339 | 340 | delayTask(1000) 341 | 342 | task1State.value = RUNNING 343 | try { 344 | val task1Result: TaskExecutionResult = exceptionsTask.execute(100, 500, 1500) 345 | task1State.value = processTaskResult(task1Result) 346 | } catch (e: CustomTaskException) { 347 | task1State.value = ERROR 348 | } 349 | 350 | task2State.value = RUNNING 351 | val task2Result: Deferred = exceptionsTask.executeAsync(this, 300, 200, 2000) 352 | 353 | task3State.value = RUNNING 354 | val task3Result: Deferred = exceptionsTask.executeWithRepositoryAsync(this, 200, 600, 1800) 355 | 356 | try { 357 | task2State.value = processTaskResult(task2Result.await()) 358 | } catch (e: CustomTaskException) { 359 | task2State.value = ERROR 360 | } 361 | 362 | try { 363 | task3State.value = processTaskResult(task3Result.await()) 364 | } catch (e: IOException) { 365 | task3State.value = ERROR 366 | } 367 | } 368 | } -------------------------------------------------------------------------------- /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 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 20 | 21 |