├── .gitattributes
├── .gitignore
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── techyourchance
│ │ │ └── coroutines
│ │ │ ├── MainActivity.kt
│ │ │ ├── MyApplication.kt
│ │ │ ├── common
│ │ │ ├── BaseFragment.kt
│ │ │ ├── ScreensNavigator.kt
│ │ │ ├── ThreadInfoLogger.kt
│ │ │ ├── ToolbarDelegate.kt
│ │ │ └── dependencyinjection
│ │ │ │ ├── ActivityCompositionRoot.kt
│ │ │ │ └── ApplicationCompositionRoot.kt
│ │ │ ├── demonstrations
│ │ │ ├── backgrounddispatcher
│ │ │ │ └── BackgroundDispatcher.kt
│ │ │ ├── backgroundthread
│ │ │ │ └── BackgroundThreadDemoFragment.kt
│ │ │ ├── basiccoroutines
│ │ │ │ └── BasicCoroutinesDemoFragment.kt
│ │ │ ├── concurrentcoroutines
│ │ │ │ └── ConcurrentCoroutinesDemoFragment.kt
│ │ │ ├── coroutinescancellation
│ │ │ │ └── CoroutinesCancellationDemoFragment.kt
│ │ │ ├── coroutinescancellationcooperative
│ │ │ │ ├── CancellableBenchmarkUseCase.kt
│ │ │ │ └── CoroutinesCancellationCooperativeDemoFragment.kt
│ │ │ ├── coroutinescancellationcooperative2
│ │ │ │ ├── BlockingBenchmarkUseCase.kt
│ │ │ │ └── CoroutinesCancellationCooperative2DemoFragment.kt
│ │ │ ├── design
│ │ │ │ ├── BenchmarkUseCase.kt
│ │ │ │ └── DesignDemoFragment.kt
│ │ │ ├── noncancellable
│ │ │ │ ├── Customer.kt
│ │ │ │ ├── CustomersDao.kt
│ │ │ │ ├── MakeCustomerPremiumUseCase.kt
│ │ │ │ ├── NonCancellableDemoFragment.kt
│ │ │ │ └── PremiumCustomersEndpoint.kt
│ │ │ ├── scopecancellation
│ │ │ │ └── ScopeCancellationDemoFragment.kt
│ │ │ ├── scopechildrencancellation
│ │ │ │ └── ScopeChildrenCancellationDemoFragment.kt
│ │ │ ├── structuredconcurrency
│ │ │ │ ├── java
│ │ │ │ │ ├── FibonacciUseCase.java
│ │ │ │ │ ├── FibonacciUseCaseAsync.java
│ │ │ │ │ ├── FibonacciUseCaseAsyncUi.java
│ │ │ │ │ └── FibonacciUseCaseAsyncUiThreadPoster.java
│ │ │ │ └── kotlin
│ │ │ │ │ ├── FibonacciUseCaseAsyncUiCoroutines.kt
│ │ │ │ │ └── FibonacciUseCaseUiCoroutines.kt
│ │ │ ├── uithread
│ │ │ │ └── UiThreadDemoFragment.kt
│ │ │ ├── uncaughtexception
│ │ │ │ ├── LoggedInUser.kt
│ │ │ │ ├── LoginEndpointUncaughtException.kt
│ │ │ │ ├── LoginUseCaseUncaughtException.kt
│ │ │ │ ├── UncaughtExceptionDemoFragment.kt
│ │ │ │ └── UserStateManager.kt
│ │ │ └── viewmodel
│ │ │ │ ├── MyViewModel.kt
│ │ │ │ ├── MyViewModelFactory.kt
│ │ │ │ └── ViewModelDemoFragment.kt
│ │ │ ├── exercises
│ │ │ ├── exercise1
│ │ │ │ ├── Exercise1Fragment.kt
│ │ │ │ └── GetReputationEndpoint.kt
│ │ │ ├── exercise10
│ │ │ │ └── Exercise10Fragment.kt
│ │ │ ├── exercise2
│ │ │ │ └── Exercise2Fragment.kt
│ │ │ ├── exercise3
│ │ │ │ └── Exercise3Fragment.kt
│ │ │ ├── exercise4
│ │ │ │ ├── Exercise4Fragment.kt
│ │ │ │ └── FactorialUseCase.kt
│ │ │ ├── exercise5
│ │ │ │ └── Exercise5Fragment.kt
│ │ │ ├── exercise6
│ │ │ │ ├── Exercise6BenchmarkUseCase.kt
│ │ │ │ ├── Exercise6Fragment.kt
│ │ │ │ └── PostBenchmarkResultsEndpoint.kt
│ │ │ ├── exercise8
│ │ │ │ ├── Exercise8Fragment.kt
│ │ │ │ ├── FetchAndCacheUsersUseCase.kt
│ │ │ │ ├── GetUserEndpoint.kt
│ │ │ │ ├── User.kt
│ │ │ │ └── UsersDao.kt
│ │ │ └── exercise9
│ │ │ │ ├── Exercise9Fragment.kt
│ │ │ │ └── FetchAndCacheUsersUseCaseExercise9.kt
│ │ │ ├── home
│ │ │ ├── HomeArrayAdapter.kt
│ │ │ ├── HomeFragment.kt
│ │ │ └── ScreenReachableFromHome.kt
│ │ │ └── solutions
│ │ │ ├── exercise1
│ │ │ └── Exercise1SolutionFragment.kt
│ │ │ ├── exercise10
│ │ │ └── Exercise10SolutionFragment.kt
│ │ │ ├── exercise2
│ │ │ └── Exercise2SolutionFragment.kt
│ │ │ ├── exercise3
│ │ │ └── Exercise3SolutionFragment.kt
│ │ │ ├── exercise4
│ │ │ └── FactorialUseCaseSolution.kt
│ │ │ ├── exercise5
│ │ │ ├── Exercise5SolutionFragment.kt
│ │ │ └── GetReputationUseCase.kt
│ │ │ ├── exercise6
│ │ │ ├── Exercise6SolutionBenchmarkUseCase.kt
│ │ │ └── Exercise6SolutionFragment.kt
│ │ │ ├── exercise8
│ │ │ ├── Exercise8SolutionFetchAndCacheUsersUseCase.kt
│ │ │ └── Exercise8SolutionFragment.kt
│ │ │ └── exercise9
│ │ │ ├── Exercise9SolutionFragment.kt
│ │ │ └── FetchAndCacheUsersUseCaseSolutionExercise9.kt
│ └── res
│ │ ├── drawable-hdpi
│ │ └── ic_arrow_back.png
│ │ ├── drawable-mdpi
│ │ └── ic_arrow_back.png
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-xhdpi
│ │ └── ic_arrow_back.png
│ │ ├── drawable-xxhdpi
│ │ └── ic_arrow_back.png
│ │ ├── drawable-xxxhdpi
│ │ └── ic_arrow_back.png
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── fragment_exercise_1.xml
│ │ ├── fragment_exercise_2.xml
│ │ ├── fragment_exercise_3.xml
│ │ ├── fragment_exercise_4.xml
│ │ ├── fragment_exercise_5.xml
│ │ ├── fragment_exercise_6.xml
│ │ ├── fragment_exercise_8.xml
│ │ ├── fragment_exercise_9.xml
│ │ ├── fragment_home.xml
│ │ ├── fragment_login.xml
│ │ ├── fragment_loop_iterations_demo.xml
│ │ ├── fragment_premium_customer.xml
│ │ ├── fragment_viewmodel_demo.xml
│ │ └── list_item_screen_reachable_from_home.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── techyourchance
│ └── coroutines
│ ├── common
│ └── TestUtils.kt
│ ├── demonstrations
│ ├── async
│ │ └── AsyncCoroutineBuilderDemoTest.kt
│ ├── cancellationonexception
│ │ └── CancellationOnExceptionDemoTest.kt
│ ├── coroutineexceptionhandler
│ │ └── CoroutineExceptionHandlerDemoTest.kt
│ ├── coroutinesmechanics
│ │ └── CoroutinesMechanicsExplorationsTest.kt
│ ├── exceptionsinasync
│ │ └── ExceptionsInAsyncDemoTest.kt
│ ├── incorrectparalleldecomposition
│ │ └── IncorrectParallelDecompositionDemoTest.kt
│ ├── structuredconcurrency
│ │ ├── java
│ │ │ ├── FibonacciUseCaseAsyncTest.java
│ │ │ ├── FibonacciUseCaseAsyncUiTest.java
│ │ │ ├── FibonacciUseCaseAsyncUiThreadPosterTest.java
│ │ │ └── FibonacciUseCaseTest.java
│ │ └── kotlin
│ │ │ ├── FibonacciUseCaseAsyncUiCoroutinesTest.kt
│ │ │ └── FibonacciUseCaseUiCoroutinesTest.kt
│ └── supervisorjob
│ │ └── SupervisorJobDemoTest.kt
│ ├── exercises
│ ├── exercise4
│ │ └── FactorialUseCaseTest.kt
│ └── exercise7
│ │ └── Exercise7Test.kt
│ └── solutions
│ ├── exercise4
│ └── FactorialUseCaseSolutionTest.kt
│ └── exercise7
│ └── Exercise7SolutionTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── release.keystore
└── settings.gradle
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 |
3 | **/raw/* binary
4 |
5 | *.aar binary
6 | *.jar binary
7 |
8 | *.7z binary
9 | *.avi binary
10 | *.bz binary
11 | *.eot binary
12 | *.flv binary
13 | *.gif binary
14 | *.gz binary
15 | *.jpg binary
16 | *.m4a binary
17 | *.m4v binary
18 | *.mov binary
19 | *.mp3 binary
20 | *.mp4 binary
21 | *.ogg binary
22 | *.ogv binary
23 | *.pdf binary
24 | *.png binary
25 | *.rar binary
26 | *.swf binary
27 | *.tar binary
28 | *.tgz binary
29 | *.ttf binary
30 | *.wav binary
31 | *.webm binary
32 | *.wmv binary
33 | *.woff binary
34 | *.xz binary
35 | *.zip binary
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | #built application files
3 | *.apk
4 | *.ap_
5 |
6 | # files for the dex VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # generated files
13 | bin/
14 | gen/
15 |
16 | # Local configuration file (sdk path, etc)
17 | local.properties
18 |
19 | # Windows thumbnail db
20 | Thumbs.db
21 |
22 | # OSX files
23 | .DS_Store
24 |
25 | # Android Studio
26 | .idea/
27 | .gradle
28 | build/
29 | *.iml
30 | captures/
31 | .externalNativeBuild
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | namespace "com.techyourchance.coroutines"
6 | compileSdk 34
7 | defaultConfig {
8 | minSdkVersion 21
9 | targetSdkVersion 34
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 |
14 | signingConfigs {
15 | release {
16 | storeFile file('../release.keystore')
17 | storePassword 'release'
18 | keyAlias 'release'
19 | keyPassword 'release'
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled true
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | signingConfig signingConfigs.release
28 | }
29 | }
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 | kotlinOptions {
35 | jvmTarget = '1.8'
36 | }
37 | }
38 |
39 | dependencies {
40 | implementation fileTree(dir: 'libs', include: ['*.jar'])
41 | implementation 'androidx.appcompat:appcompat:1.6.1'
42 |
43 | implementation 'com.github.ncapdevi:fragnav:3.3.0'
44 |
45 | implementation 'com.techyourchance:threadposter:1.0.1'
46 |
47 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
48 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
49 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
50 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
51 |
52 |
53 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
54 |
55 | testImplementation "junit:junit:4.13.2"
56 | }
57 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import android.widget.ImageButton
6 | import android.widget.TextView
7 | import androidx.appcompat.app.AppCompatActivity
8 | import com.techyourchance.coroutines.common.ScreensNavigator
9 | import com.techyourchance.coroutines.common.ToolbarDelegate
10 | import com.techyourchance.coroutines.common.dependencyinjection.ActivityCompositionRoot
11 |
12 | class MainActivity : AppCompatActivity(), ToolbarDelegate {
13 |
14 | private lateinit var screensNavigator: ScreensNavigator
15 | private lateinit var btnBack: ImageButton
16 | private lateinit var txtScreenTitle: TextView
17 |
18 | val compositionRoot by lazy {
19 | ActivityCompositionRoot(this,(application as MyApplication).applicationCompositionRoot)
20 | }
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | setContentView(R.layout.activity_main)
25 |
26 | screensNavigator = compositionRoot.screensNavigator
27 | screensNavigator.init(savedInstanceState)
28 |
29 | btnBack = findViewById(R.id.btn_back)
30 | btnBack.setOnClickListener { screensNavigator.navigateUp() }
31 |
32 | txtScreenTitle = findViewById(R.id.txt_screen_title)
33 | }
34 |
35 | override fun onSaveInstanceState(outState: Bundle) {
36 | super.onSaveInstanceState(outState)
37 | screensNavigator.onSaveInstanceState(outState)
38 | }
39 |
40 | override fun onBackPressed() {
41 | if (!screensNavigator.navigateBack()) {
42 | super.onBackPressed()
43 | }
44 | }
45 |
46 | override fun setScreenTitle(screenTitle: String) {
47 | txtScreenTitle.text = screenTitle
48 | }
49 |
50 | override fun showUpButton() {
51 | btnBack.visibility = View.VISIBLE
52 | }
53 |
54 | override fun hideUpButton() {
55 | btnBack.visibility = View.INVISIBLE
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/MyApplication.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines
2 |
3 | import android.app.Application
4 | import com.techyourchance.coroutines.common.dependencyinjection.ApplicationCompositionRoot
5 |
6 | class MyApplication : Application() {
7 |
8 | val applicationCompositionRoot = ApplicationCompositionRoot()
9 |
10 | override fun onCreate() {
11 | super.onCreate()
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/common/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.common
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.Fragment
6 | import com.techyourchance.coroutines.MainActivity
7 |
8 | abstract class BaseFragment : Fragment() {
9 |
10 | protected open val screenTitle = ""
11 |
12 | protected val compositionRoot get() = (requireActivity() as MainActivity).compositionRoot
13 |
14 | protected lateinit var screensNavigator: ScreensNavigator
15 |
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | screensNavigator = compositionRoot.screensNavigator
19 | }
20 |
21 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
22 | val toolbarManipulator = compositionRoot.toolbarManipulator
23 | toolbarManipulator.setScreenTitle(screenTitle)
24 | if (screensNavigator.isRootScreen()) {
25 | toolbarManipulator.hideUpButton()
26 | } else {
27 | toolbarManipulator.showUpButton()
28 | }
29 | }
30 |
31 |
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/common/ThreadInfoLogger.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.common
2 |
3 | import android.util.Log
4 |
5 | object ThreadInfoLogger {
6 |
7 | private const val TAG = "ThreadInfoLogger"
8 |
9 | fun logThreadInfo(message: String) {
10 | Log.i(TAG, "$message; thread name: ${Thread.currentThread().name}; thread ID: ${Thread.currentThread().id}")
11 | }
12 |
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/common/ToolbarDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.common
2 |
3 | interface ToolbarDelegate {
4 | fun setScreenTitle(screenTitle: String)
5 | fun showUpButton()
6 | fun hideUpButton()
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/common/dependencyinjection/ApplicationCompositionRoot.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.common.dependencyinjection
2 |
3 | class ApplicationCompositionRoot
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/backgrounddispatcher/BackgroundDispatcher.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.backgrounddispatcher
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.Runnable
6 | import kotlinx.coroutines.asCoroutineDispatcher
7 | import java.util.concurrent.SynchronousQueue
8 | import java.util.concurrent.ThreadFactory
9 | import java.util.concurrent.ThreadPoolExecutor
10 | import java.util.concurrent.TimeUnit
11 | import java.util.concurrent.atomic.AtomicInteger
12 | import kotlin.coroutines.CoroutineContext
13 |
14 | /**
15 | * Background CoroutineDispatcher for Android applications which replaces both
16 | * [Dispatchers.Default] and [Dispatchers.IO].
17 | */
18 | object BackgroundDispatcher: CoroutineDispatcher() {
19 |
20 | private val threadFactory = object: ThreadFactory {
21 | private val threadCount = AtomicInteger(0)
22 | private val nextThreadName get() = "BackgroundDispatcher-worker-${threadCount.incrementAndGet()}"
23 |
24 | override fun newThread(runnable: java.lang.Runnable): Thread {
25 | return Thread(runnable, nextThreadName)
26 | }
27 | }
28 |
29 | private val threadPool = ThreadPoolExecutor(
30 | 3,
31 | Integer.MAX_VALUE,
32 | 60L,
33 | TimeUnit.SECONDS,
34 | SynchronousQueue(),
35 | threadFactory
36 | );
37 |
38 | private val dispatcher = threadPool.asCoroutineDispatcher()
39 |
40 | override fun dispatch(context: CoroutineContext, block: Runnable) {
41 | dispatcher.dispatch(context, block)
42 | }
43 |
44 | /**
45 | * Background CoroutineDispatcher for Android applications which replaces both
46 | * [Dispatchers.Default] and [Dispatchers.IO].
47 | */
48 | val Dispatchers.Background get() = BackgroundDispatcher
49 |
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/backgroundthread/BackgroundThreadDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.backgroundthread
2 |
3 | import android.os.Bundle
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.Button
10 | import android.widget.TextView
11 | import android.widget.Toast
12 | import androidx.fragment.app.Fragment
13 | import com.techyourchance.coroutines.R
14 | import com.techyourchance.coroutines.common.BaseFragment
15 | import com.techyourchance.coroutines.common.ThreadInfoLogger
16 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
17 |
18 | class BackgroundThreadDemoFragment : BaseFragment() {
19 |
20 | override val screenTitle get() = ScreenReachableFromHome.BACKGROUND_THREAD_DEMO.description
21 |
22 | private lateinit var btnStart: Button
23 | private lateinit var txtRemainingTime: TextView
24 |
25 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
26 | val view = inflater.inflate(R.layout.fragment_loop_iterations_demo, container, false)
27 |
28 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
29 |
30 | btnStart = view.findViewById(R.id.btn_start)
31 | btnStart.setOnClickListener {
32 | logThreadInfo("button callback")
33 | btnStart.isEnabled = false
34 | executeBenchmark()
35 | btnStart.isEnabled = true
36 | }
37 |
38 | return view
39 | }
40 |
41 | private fun executeBenchmark() {
42 | val benchmarkDurationSeconds = 5
43 |
44 | updateRemainingTime(benchmarkDurationSeconds)
45 |
46 | Thread {
47 | logThreadInfo("benchmark started")
48 |
49 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
50 |
51 | var iterationsCount: Long = 0
52 | while (System.nanoTime() < stopTimeNano) {
53 | iterationsCount++
54 | }
55 |
56 | logThreadInfo("benchmark completed")
57 |
58 | Handler(Looper.getMainLooper()).post {
59 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
60 | }
61 |
62 | }.start()
63 |
64 | }
65 |
66 | private fun updateRemainingTime(remainingTimeSeconds: Int) {
67 | logThreadInfo("updateRemainingTime: $remainingTimeSeconds seconds")
68 |
69 | if (remainingTimeSeconds > 0) {
70 | txtRemainingTime.text = "$remainingTimeSeconds seconds remaining"
71 | Handler(Looper.getMainLooper()).postDelayed({
72 | updateRemainingTime(remainingTimeSeconds - 1)
73 | }, 1000)
74 | } else {
75 | txtRemainingTime.text = "done!"
76 | }
77 |
78 | }
79 |
80 | private fun logThreadInfo(message: String) {
81 | ThreadInfoLogger.logThreadInfo(message)
82 | }
83 |
84 | companion object {
85 | fun newInstance(): Fragment {
86 | return BackgroundThreadDemoFragment()
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/basiccoroutines/BasicCoroutinesDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.basiccoroutines
2 |
3 | import android.os.Bundle
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.Button
10 | import android.widget.TextView
11 | import android.widget.Toast
12 | import androidx.fragment.app.Fragment
13 | import com.techyourchance.coroutines.R
14 | import com.techyourchance.coroutines.common.BaseFragment
15 | import com.techyourchance.coroutines.common.ThreadInfoLogger
16 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
17 | import kotlinx.coroutines.CoroutineScope
18 | import kotlinx.coroutines.Dispatchers
19 | import kotlinx.coroutines.launch
20 | import kotlinx.coroutines.withContext
21 |
22 | class BasicCoroutinesDemoFragment : BaseFragment() {
23 |
24 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
25 |
26 | override val screenTitle get() = ScreenReachableFromHome.BASIC_COROUTINES_DEMO.description
27 |
28 | private lateinit var btnStart: Button
29 | private lateinit var txtRemainingTime: TextView
30 |
31 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
32 | val view = inflater.inflate(R.layout.fragment_loop_iterations_demo, container, false)
33 |
34 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
35 |
36 | btnStart = view.findViewById(R.id.btn_start)
37 | btnStart.setOnClickListener {
38 | logThreadInfo("button callback")
39 |
40 | coroutineScope.launch {
41 | btnStart.isEnabled = false
42 | val iterationsCount = executeBenchmark()
43 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
44 | btnStart.isEnabled = true
45 | }
46 |
47 | }
48 |
49 | return view
50 | }
51 |
52 | private suspend fun executeBenchmark(): Long {
53 | val benchmarkDurationSeconds = 5
54 |
55 | updateRemainingTime(benchmarkDurationSeconds)
56 |
57 | return withContext(Dispatchers.Default) {
58 | logThreadInfo("benchmark started")
59 |
60 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
61 |
62 | var iterationsCount: Long = 0
63 | while (System.nanoTime() < stopTimeNano) {
64 | iterationsCount++
65 | }
66 |
67 | logThreadInfo("benchmark completed")
68 |
69 | iterationsCount
70 | }
71 | }
72 |
73 | private fun updateRemainingTime(remainingTimeSeconds: Int) {
74 | logThreadInfo("updateRemainingTime: $remainingTimeSeconds seconds")
75 |
76 | if (remainingTimeSeconds > 0) {
77 | txtRemainingTime.text = "$remainingTimeSeconds seconds remaining"
78 | Handler(Looper.getMainLooper()).postDelayed({
79 | updateRemainingTime(remainingTimeSeconds - 1)
80 | }, 1000)
81 | } else {
82 | txtRemainingTime.text = "done!"
83 | }
84 |
85 | }
86 |
87 | private fun logThreadInfo(message: String) {
88 | ThreadInfoLogger.logThreadInfo(message)
89 | }
90 |
91 | companion object {
92 | fun newInstance(): Fragment {
93 | return BasicCoroutinesDemoFragment()
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/concurrentcoroutines/ConcurrentCoroutinesDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.concurrentcoroutines
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger
14 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
15 | import kotlinx.coroutines.*
16 |
17 | class ConcurrentCoroutinesDemoFragment : BaseFragment() {
18 |
19 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
20 |
21 | override val screenTitle get() = ScreenReachableFromHome.CONCURRENT_COROUTINES_DEMO.description
22 |
23 | private lateinit var btnStart: Button
24 | private lateinit var txtRemainingTime: TextView
25 |
26 | private var jobCounter: Job? = null
27 | private var job: Job? = null
28 |
29 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
30 | val view = inflater.inflate(R.layout.fragment_loop_iterations_demo, container, false)
31 |
32 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
33 |
34 | btnStart = view.findViewById(R.id.btn_start)
35 | btnStart.setOnClickListener {
36 | logThreadInfo("button callback")
37 |
38 | val benchmarkDurationSeconds = 5
39 |
40 | jobCounter = coroutineScope.launch {
41 | updateRemainingTime(benchmarkDurationSeconds)
42 | }
43 |
44 | job = coroutineScope.launch {
45 | btnStart.isEnabled = false
46 | val iterationsCount = executeBenchmark(benchmarkDurationSeconds)
47 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
48 | btnStart.isEnabled = true
49 | }
50 | }
51 |
52 | return view
53 | }
54 |
55 | override fun onStop() {
56 | logThreadInfo("onStop()")
57 | super.onStop()
58 | job?.cancel()
59 | btnStart.isEnabled = true
60 | jobCounter?.apply {
61 | cancel()
62 | txtRemainingTime.text = "done!"
63 | }
64 | }
65 |
66 | private suspend fun executeBenchmark(benchmarkDurationSeconds: Int) = withContext(Dispatchers.Default) {
67 | logThreadInfo("benchmark started")
68 |
69 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
70 |
71 | var iterationsCount: Long = 0
72 | while (System.nanoTime() < stopTimeNano) {
73 | iterationsCount++
74 | }
75 |
76 | logThreadInfo("benchmark completed")
77 |
78 | iterationsCount
79 | }
80 |
81 | private suspend fun updateRemainingTime(remainingTimeSeconds: Int) {
82 | for (time in remainingTimeSeconds downTo 0) {
83 | if (time > 0) {
84 | logThreadInfo("updateRemainingTime: $time seconds")
85 | txtRemainingTime.text = "$time seconds remaining"
86 | delay(1000)
87 | } else {
88 | txtRemainingTime.text = "done!"
89 | }
90 | }
91 | }
92 |
93 | private fun logThreadInfo(message: String) {
94 | ThreadInfoLogger.logThreadInfo(message)
95 | }
96 |
97 | companion object {
98 | fun newInstance(): Fragment {
99 | return ConcurrentCoroutinesDemoFragment()
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/coroutinescancellation/CoroutinesCancellationDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.coroutinescancellation
2 |
3 | import android.os.Bundle
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.Button
10 | import android.widget.TextView
11 | import android.widget.Toast
12 | import androidx.fragment.app.Fragment
13 | import com.techyourchance.coroutines.R
14 | import com.techyourchance.coroutines.common.BaseFragment
15 | import com.techyourchance.coroutines.common.ThreadInfoLogger
16 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
17 | import kotlinx.coroutines.*
18 |
19 | class CoroutinesCancellationDemoFragment : BaseFragment() {
20 |
21 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
22 |
23 | override val screenTitle get() = ScreenReachableFromHome.COROUTINES_CANCELLATION_DEMO.description
24 |
25 | private lateinit var btnStart: Button
26 | private lateinit var txtRemainingTime: TextView
27 |
28 | private var job: Job? = null
29 |
30 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
31 | val view = inflater.inflate(R.layout.fragment_loop_iterations_demo, container, false)
32 |
33 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
34 |
35 | btnStart = view.findViewById(R.id.btn_start)
36 | btnStart.setOnClickListener {
37 | logThreadInfo("button callback")
38 |
39 | job = coroutineScope.launch {
40 | btnStart.isEnabled = false
41 | val iterationsCount = executeBenchmark()
42 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
43 | btnStart.isEnabled = true
44 | }
45 |
46 | }
47 |
48 | return view
49 | }
50 |
51 | override fun onStop() {
52 | logThreadInfo("onStop()")
53 | super.onStop()
54 | job?.cancel()
55 | btnStart.isEnabled = true
56 | }
57 |
58 | private suspend fun executeBenchmark(): Long {
59 | val benchmarkDurationSeconds = 5
60 |
61 | updateRemainingTime(benchmarkDurationSeconds)
62 |
63 | return withContext(Dispatchers.Default) {
64 | logThreadInfo("benchmark started")
65 |
66 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
67 |
68 | var iterationsCount: Long = 0
69 | while (System.nanoTime() < stopTimeNano) {
70 | iterationsCount++
71 | }
72 |
73 | logThreadInfo("benchmark completed")
74 |
75 | iterationsCount
76 | }
77 | }
78 |
79 | private fun updateRemainingTime(remainingTimeSeconds: Int) {
80 | logThreadInfo("updateRemainingTime: $remainingTimeSeconds seconds")
81 |
82 | if (remainingTimeSeconds > 0) {
83 | txtRemainingTime.text = "$remainingTimeSeconds seconds remaining"
84 | Handler(Looper.getMainLooper()).postDelayed({
85 | updateRemainingTime(remainingTimeSeconds - 1)
86 | }, 1000)
87 | } else {
88 | txtRemainingTime.text = "done!"
89 | }
90 |
91 | }
92 |
93 | private fun logThreadInfo(message: String) {
94 | ThreadInfoLogger.logThreadInfo(message)
95 | }
96 |
97 | companion object {
98 | fun newInstance(): Fragment {
99 | return CoroutinesCancellationDemoFragment()
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/coroutinescancellationcooperative/CancellableBenchmarkUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.coroutinescancellationcooperative
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger
4 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.isActive
7 | import kotlinx.coroutines.withContext
8 |
9 | class CancellableBenchmarkUseCase {
10 |
11 | suspend fun executeBenchmark(benchmarkDurationSeconds: Int) = withContext(Dispatchers.Default) {
12 | logThreadInfo("benchmark started")
13 |
14 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
15 |
16 | var iterationsCount: Long = 0
17 | while (System.nanoTime() < stopTimeNano && isActive) {
18 | iterationsCount++
19 | }
20 |
21 | logThreadInfo("benchmark completed")
22 |
23 | iterationsCount
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/coroutinescancellationcooperative/CoroutinesCancellationCooperativeDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.coroutinescancellationcooperative
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
14 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
15 | import kotlinx.coroutines.*
16 |
17 | class CoroutinesCancellationCooperativeDemoFragment : BaseFragment() {
18 |
19 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
20 |
21 | override val screenTitle get() = ScreenReachableFromHome.COROUTINES_CANCELLATION_COOPERATIVE_DEMO.description
22 |
23 | private lateinit var benchmarkUseCase: CancellableBenchmarkUseCase
24 |
25 | private lateinit var btnStart: Button
26 | private lateinit var txtRemainingTime: TextView
27 |
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | super.onCreate(savedInstanceState)
30 | benchmarkUseCase = compositionRoot.cancellableBenchmarkUseCase
31 | }
32 |
33 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
34 | val view = inflater.inflate(R.layout.fragment_loop_iterations_demo, container, false)
35 |
36 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
37 |
38 | btnStart = view.findViewById(R.id.btn_start)
39 | btnStart.setOnClickListener {
40 | logThreadInfo("button callback")
41 |
42 | val benchmarkDurationSeconds = 5
43 |
44 | coroutineScope.launch {
45 | updateRemainingTime(benchmarkDurationSeconds)
46 | }
47 |
48 | coroutineScope.launch {
49 | try {
50 | btnStart.isEnabled = false
51 | val iterationsCount = benchmarkUseCase.executeBenchmark(benchmarkDurationSeconds)
52 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
53 | btnStart.isEnabled = true
54 | } catch (e: CancellationException) {
55 | btnStart.isEnabled = true
56 | txtRemainingTime.text = "done!"
57 | logThreadInfo("coroutine cancelled")
58 | Toast.makeText(requireContext(), "cancelled", Toast.LENGTH_SHORT).show()
59 | }
60 | }
61 | }
62 |
63 | return view
64 | }
65 |
66 | override fun onStop() {
67 | logThreadInfo("onStop()")
68 | super.onStop()
69 | coroutineScope.coroutineContext.cancelChildren()
70 | }
71 |
72 |
73 | private suspend fun updateRemainingTime(remainingTimeSeconds: Int) {
74 | for (time in remainingTimeSeconds downTo 0) {
75 | if (time > 0) {
76 | logThreadInfo("updateRemainingTime: $time seconds")
77 | txtRemainingTime.text = "$time seconds remaining"
78 | delay(1000)
79 | } else {
80 | txtRemainingTime.text = "done!"
81 | }
82 | }
83 | }
84 |
85 | companion object {
86 | fun newInstance(): Fragment {
87 | return CoroutinesCancellationCooperativeDemoFragment()
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/coroutinescancellationcooperative2/BlockingBenchmarkUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.coroutinescancellationcooperative2
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger
4 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
5 | import kotlinx.coroutines.*
6 | import kotlin.coroutines.coroutineContext
7 |
8 | class BlockingBenchmarkUseCase {
9 |
10 | suspend fun executeBenchmark(benchmarkDurationSeconds: Int): Long {
11 | logThreadInfo("benchmark started")
12 |
13 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
14 |
15 | var iterationsCount: Long = 0
16 | while (System.nanoTime() < stopTimeNano) {
17 | coroutineContext.ensureActive()
18 | iterationsCount++
19 | }
20 |
21 | logThreadInfo("benchmark completed")
22 |
23 | return iterationsCount
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/coroutinescancellationcooperative2/CoroutinesCancellationCooperative2DemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.coroutinescancellationcooperative2
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
14 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
15 | import kotlinx.coroutines.*
16 |
17 | class CoroutinesCancellationCooperative2DemoFragment : BaseFragment() {
18 |
19 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
20 |
21 | override val screenTitle get() = ScreenReachableFromHome.COROUTINES_CANCELLATION_COOPERATIVE_2_DEMO.description
22 |
23 | private lateinit var benchmarkUseCase: BlockingBenchmarkUseCase
24 |
25 | private lateinit var btnStart: Button
26 | private lateinit var txtRemainingTime: TextView
27 |
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | super.onCreate(savedInstanceState)
30 | benchmarkUseCase = compositionRoot.blockingBenchmarkUseCase
31 | }
32 |
33 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
34 | val view = inflater.inflate(R.layout.fragment_loop_iterations_demo, container, false)
35 |
36 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
37 |
38 | btnStart = view.findViewById(R.id.btn_start)
39 | btnStart.setOnClickListener {
40 | logThreadInfo("button callback")
41 |
42 | val benchmarkDurationSeconds = 5
43 |
44 | coroutineScope.launch() {
45 | updateRemainingTime(benchmarkDurationSeconds)
46 | }
47 |
48 | coroutineScope.launch(Dispatchers.Default) {
49 | try {
50 | val iterationsCount = benchmarkUseCase.executeBenchmark(benchmarkDurationSeconds)
51 | logThreadInfo("benchmarked iterations: $iterationsCount")
52 | } catch (e: CancellationException) {
53 | logThreadInfo("coroutine cancelled")
54 | }
55 | }
56 | }
57 |
58 | return view
59 | }
60 |
61 | override fun onStop() {
62 | logThreadInfo("onStop()")
63 | super.onStop()
64 | coroutineScope.coroutineContext.cancelChildren()
65 | }
66 |
67 |
68 | private suspend fun updateRemainingTime(remainingTimeSeconds: Int) {
69 | for (time in remainingTimeSeconds downTo 0) {
70 | if (time > 0) {
71 | logThreadInfo("updateRemainingTime: $time seconds")
72 | txtRemainingTime.text = "$time seconds remaining"
73 | delay(1000)
74 | } else {
75 | txtRemainingTime.text = "done!"
76 | }
77 | }
78 | }
79 |
80 | companion object {
81 | fun newInstance(): Fragment {
82 | return CoroutinesCancellationCooperative2DemoFragment()
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/design/BenchmarkUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.design
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.withContext
6 |
7 | class BenchmarkUseCase {
8 |
9 | suspend fun executeBenchmark(benchmarkDurationSeconds: Int) = withContext(Dispatchers.Default) {
10 | logThreadInfo("benchmark started")
11 |
12 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
13 |
14 | var iterationsCount: Long = 0
15 | while (System.nanoTime() < stopTimeNano) {
16 | iterationsCount++
17 | }
18 |
19 | logThreadInfo("benchmark completed")
20 |
21 | iterationsCount
22 | }
23 |
24 |
25 | private fun logThreadInfo(message: String) {
26 | ThreadInfoLogger.logThreadInfo(message)
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/design/DesignDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.design
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger
14 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
15 | import kotlinx.coroutines.*
16 |
17 | class DesignDemoFragment : BaseFragment() {
18 |
19 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
20 |
21 | override val screenTitle get() = ScreenReachableFromHome.DESIGN_DEMO.description
22 |
23 | private lateinit var benchmarkUseCase: BenchmarkUseCase
24 |
25 | private lateinit var btnStart: Button
26 | private lateinit var txtRemainingTime: TextView
27 |
28 | private var hasBenchmarkBeenStartedOnce = false
29 |
30 |
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | benchmarkUseCase = compositionRoot.benchmarkUseCase
34 | }
35 |
36 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
37 | val view = inflater.inflate(R.layout.fragment_loop_iterations_demo, container, false)
38 |
39 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
40 |
41 | btnStart = view.findViewById(R.id.btn_start)
42 | btnStart.setOnClickListener {
43 | logThreadInfo("button callback")
44 |
45 | val benchmarkDurationSeconds = 5
46 |
47 | coroutineScope.launch {
48 | updateRemainingTime(benchmarkDurationSeconds)
49 | }
50 |
51 | coroutineScope.launch {
52 | btnStart.isEnabled = false
53 | val iterationsCount = benchmarkUseCase.executeBenchmark(benchmarkDurationSeconds)
54 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
55 | btnStart.isEnabled = true
56 | }
57 |
58 | hasBenchmarkBeenStartedOnce = true
59 | }
60 |
61 | return view
62 | }
63 |
64 | override fun onStop() {
65 | logThreadInfo("onStop()")
66 | super.onStop()
67 | coroutineScope.coroutineContext.cancelChildren()
68 | if (hasBenchmarkBeenStartedOnce) {
69 | btnStart.isEnabled = true
70 | txtRemainingTime.text = "done!"
71 | }
72 | }
73 |
74 |
75 | private suspend fun updateRemainingTime(remainingTimeSeconds: Int) {
76 | for (time in remainingTimeSeconds downTo 0) {
77 | if (time > 0) {
78 | logThreadInfo("updateRemainingTime: $time seconds")
79 | txtRemainingTime.text = "$time seconds remaining"
80 | delay(1000)
81 | } else {
82 | txtRemainingTime.text = "done!"
83 | }
84 | }
85 | }
86 |
87 | private fun logThreadInfo(message: String) {
88 | ThreadInfoLogger.logThreadInfo(message)
89 | }
90 |
91 | companion object {
92 | fun newInstance(): Fragment {
93 | return DesignDemoFragment()
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/noncancellable/Customer.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.noncancellable
2 |
3 | data class Customer(val id: String, val premium: Boolean)
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/noncancellable/CustomersDao.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.noncancellable
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger
4 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.delay
7 | import kotlinx.coroutines.withContext
8 |
9 | class CustomersDao {
10 |
11 | suspend fun updateCustomer(customer: Customer) = withContext(Dispatchers.IO) {
12 | logThreadInfo("updating local customer's data")
13 | delay(2000)
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/noncancellable/MakeCustomerPremiumUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.noncancellable
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger
4 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
5 | import com.techyourchance.coroutines.exercises.exercise6.PostBenchmarkResultsEndpoint
6 | import kotlinx.coroutines.*
7 |
8 | class MakeCustomerPremiumUseCase(
9 | private val premiumCustomersEndpoint: PremiumCustomersEndpoint,
10 | private val customersDao: CustomersDao
11 | ) {
12 |
13 | /**
14 | * Give the customer premium status
15 | * @return updated information about the customer
16 | */
17 | suspend fun makeCustomerPremium(customerId: String): Customer {
18 | return withContext(Dispatchers.Default) {
19 | withContext(NonCancellable) {
20 | val updatedCustomer = premiumCustomersEndpoint.makeCustomerPremium(customerId)
21 | customersDao.updateCustomer(updatedCustomer)
22 | updatedCustomer
23 | }
24 | }
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/noncancellable/NonCancellableDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.noncancellable
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.Button
10 | import android.widget.EditText
11 | import android.widget.Toast
12 | import androidx.fragment.app.Fragment
13 | import com.techyourchance.coroutines.R
14 | import com.techyourchance.coroutines.common.BaseFragment
15 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
16 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
17 | import kotlinx.coroutines.*
18 |
19 | class NonCancellableDemoFragment : BaseFragment() {
20 |
21 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
22 |
23 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_6.description
24 |
25 | private lateinit var makeCustomerPremiumUseCase: MakeCustomerPremiumUseCase
26 |
27 | private lateinit var edtCustomerId: EditText
28 | private lateinit var btnMakePremium: Button
29 |
30 | override fun onCreate(savedInstanceState: Bundle?) {
31 | super.onCreate(savedInstanceState)
32 | makeCustomerPremiumUseCase = compositionRoot.makeCustomerPremiumUseCase
33 | }
34 |
35 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
36 | val view = inflater.inflate(R.layout.fragment_premium_customer, container, false)
37 |
38 | view.apply {
39 | edtCustomerId = findViewById(R.id.edt_customer_id)
40 | btnMakePremium = findViewById(R.id.btn_make_premium)
41 | }
42 |
43 | edtCustomerId.addTextChangedListener(object : TextWatcher {
44 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
45 |
46 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
47 | btnMakePremium.isEnabled = !s.isNullOrEmpty()
48 | }
49 |
50 | override fun afterTextChanged(s: Editable?) {}
51 | })
52 |
53 | btnMakePremium.setOnClickListener {
54 | logThreadInfo("button callback")
55 |
56 | val benchmarkDurationSeconds = 5
57 |
58 | coroutineScope.launch {
59 | try {
60 | disableUserInput()
61 | makeCustomerPremiumUseCase.makeCustomerPremium(edtCustomerId.text.toString())
62 | enableUserInput()
63 | Toast.makeText(requireContext(), "the user became premium", Toast.LENGTH_SHORT).show()
64 | } catch (e: CancellationException) {
65 | enableUserInput()
66 | logThreadInfo("flow cancelled")
67 | }
68 | }
69 | }
70 |
71 | return view
72 | }
73 |
74 | private fun enableUserInput() {
75 | edtCustomerId.isEnabled = true
76 | btnMakePremium.isEnabled = true
77 | }
78 |
79 | private fun disableUserInput() {
80 | edtCustomerId.isEnabled = false
81 | btnMakePremium.isEnabled = false
82 | }
83 |
84 | override fun onStop() {
85 | logThreadInfo("onStop()")
86 | super.onStop()
87 | coroutineScope.coroutineContext.cancelChildren()
88 | }
89 |
90 | companion object {
91 | fun newInstance(): Fragment {
92 | return NonCancellableDemoFragment()
93 | }
94 | }
95 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/noncancellable/PremiumCustomersEndpoint.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.noncancellable
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger
4 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.delay
7 | import kotlinx.coroutines.withContext
8 |
9 | class PremiumCustomersEndpoint {
10 |
11 | suspend fun makeCustomerPremium(customerId: String): Customer = withContext(Dispatchers.IO) {
12 | logThreadInfo("changing customer's status to premium on the server")
13 | delay(2000)
14 | return@withContext Customer(customerId, true)
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/scopecancellation/ScopeCancellationDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.scopecancellation
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger
14 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
15 | import kotlinx.coroutines.*
16 |
17 | class ScopeCancellationDemoFragment : BaseFragment() {
18 |
19 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
20 |
21 | override val screenTitle get() = ScreenReachableFromHome.SCOPE_CANCELLATION_DEMO.description
22 |
23 | private lateinit var btnStart: Button
24 | private lateinit var txtRemainingTime: TextView
25 |
26 | private var hasBenchmarkBeenStartedOnce = false
27 |
28 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
29 | val view = inflater.inflate(R.layout.fragment_loop_iterations_demo, container, false)
30 |
31 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
32 |
33 | btnStart = view.findViewById(R.id.btn_start)
34 | btnStart.setOnClickListener {
35 | logThreadInfo("button callback")
36 |
37 | val benchmarkDurationSeconds = 5
38 |
39 | coroutineScope.launch {
40 | updateRemainingTime(benchmarkDurationSeconds)
41 | }
42 |
43 | coroutineScope.launch {
44 | btnStart.isEnabled = false
45 | val iterationsCount = executeBenchmark(benchmarkDurationSeconds)
46 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
47 | btnStart.isEnabled = true
48 | }
49 |
50 | hasBenchmarkBeenStartedOnce = true
51 | }
52 |
53 | return view
54 | }
55 |
56 | override fun onStop() {
57 | logThreadInfo("onStop()")
58 | super.onStop()
59 | coroutineScope.cancel()
60 | if (hasBenchmarkBeenStartedOnce) {
61 | btnStart.isEnabled = true
62 | txtRemainingTime.text = "done!"
63 | }
64 | }
65 |
66 | private suspend fun executeBenchmark(benchmarkDurationSeconds: Int) = withContext(Dispatchers.Default) {
67 | logThreadInfo("benchmark started")
68 |
69 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
70 |
71 | var iterationsCount: Long = 0
72 | while (System.nanoTime() < stopTimeNano) {
73 | iterationsCount++
74 | }
75 |
76 | logThreadInfo("benchmark completed")
77 |
78 | iterationsCount
79 | }
80 |
81 | private suspend fun updateRemainingTime(remainingTimeSeconds: Int) {
82 | for (time in remainingTimeSeconds downTo 0) {
83 | if (time > 0) {
84 | logThreadInfo("updateRemainingTime: $time seconds")
85 | txtRemainingTime.text = "$time seconds remaining"
86 | delay(1000)
87 | } else {
88 | txtRemainingTime.text = "done!"
89 | }
90 | }
91 | }
92 |
93 | private fun logThreadInfo(message: String) {
94 | ThreadInfoLogger.logThreadInfo(message)
95 | }
96 |
97 | companion object {
98 | fun newInstance(): Fragment {
99 | return ScopeCancellationDemoFragment()
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/scopechildrencancellation/ScopeChildrenCancellationDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.scopechildrencancellation
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger
14 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
15 | import kotlinx.coroutines.*
16 |
17 | class ScopeChildrenCancellationDemoFragment : BaseFragment() {
18 |
19 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
20 |
21 | override val screenTitle get() = ScreenReachableFromHome.SCOPE_CHILDREN_CANCELLATION_DEMO.description
22 |
23 | private lateinit var btnStart: Button
24 | private lateinit var txtRemainingTime: TextView
25 |
26 | private var hasBenchmarkBeenStartedOnce = false
27 |
28 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
29 | val view = inflater.inflate(R.layout.fragment_loop_iterations_demo, container, false)
30 |
31 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
32 |
33 | btnStart = view.findViewById(R.id.btn_start)
34 | btnStart.setOnClickListener {
35 | logThreadInfo("button callback")
36 |
37 | val benchmarkDurationSeconds = 5
38 |
39 | coroutineScope.launch {
40 | updateRemainingTime(benchmarkDurationSeconds)
41 | }
42 |
43 | coroutineScope.launch {
44 | btnStart.isEnabled = false
45 | val iterationsCount = executeBenchmark(benchmarkDurationSeconds)
46 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
47 | btnStart.isEnabled = true
48 | }
49 |
50 | hasBenchmarkBeenStartedOnce = true
51 | }
52 |
53 | return view
54 | }
55 |
56 | override fun onStop() {
57 | logThreadInfo("onStop()")
58 | super.onStop()
59 | coroutineScope.coroutineContext.cancelChildren()
60 | if (hasBenchmarkBeenStartedOnce) {
61 | btnStart.isEnabled = true
62 | txtRemainingTime.text = "done!"
63 | }
64 | }
65 |
66 | private suspend fun executeBenchmark(benchmarkDurationSeconds: Int) = withContext(Dispatchers.Default) {
67 | logThreadInfo("benchmark started")
68 |
69 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
70 |
71 | var iterationsCount: Long = 0
72 | while (System.nanoTime() < stopTimeNano) {
73 | iterationsCount++
74 | }
75 |
76 | logThreadInfo("benchmark completed")
77 |
78 | iterationsCount
79 | }
80 |
81 | private suspend fun updateRemainingTime(remainingTimeSeconds: Int) {
82 | for (time in remainingTimeSeconds downTo 0) {
83 | if (time > 0) {
84 | logThreadInfo("updateRemainingTime: $time seconds")
85 | txtRemainingTime.text = "$time seconds remaining"
86 | delay(1000)
87 | } else {
88 | txtRemainingTime.text = "done!"
89 | }
90 | }
91 | }
92 |
93 | private fun logThreadInfo(message: String) {
94 | ThreadInfoLogger.logThreadInfo(message)
95 | }
96 |
97 | companion object {
98 | fun newInstance(): Fragment {
99 | return ScopeChildrenCancellationDemoFragment()
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/java/FibonacciUseCase.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.java;
2 |
3 | import java.math.BigInteger;
4 |
5 | class FibonacciUseCase {
6 |
7 | public BigInteger computeFibonacci(int index) {
8 | if (index == 0) {
9 | return new BigInteger("0");
10 | } else if (index == 1) {
11 | return new BigInteger("1");
12 | } else {
13 | return computeFibonacci(index - 1).add(computeFibonacci(index - 2));
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/java/FibonacciUseCaseAsync.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.java;
2 |
3 | import java.math.BigInteger;
4 |
5 | import androidx.annotation.WorkerThread;
6 |
7 | class FibonacciUseCaseAsync {
8 |
9 | public interface Callback {
10 | public void onFibonacciComputed(BigInteger result);
11 | }
12 |
13 | public void computeFibonacci(int index, Callback callback) {
14 | new Thread(new Runnable() {
15 | @Override
16 | public void run() {
17 | BigInteger result = computeFibonacciBg(index);
18 | callback.onFibonacciComputed(result);
19 | }
20 | }).start();
21 | }
22 |
23 | @WorkerThread
24 | private BigInteger computeFibonacciBg(int index) {
25 | if (index == 0) {
26 | return new BigInteger("0");
27 | } else if (index == 1) {
28 | return new BigInteger("1");
29 | } else {
30 | return computeFibonacciBg(index - 1).add(computeFibonacciBg(index - 2));
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/java/FibonacciUseCaseAsyncUi.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.java;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 |
6 | import java.math.BigInteger;
7 |
8 | import androidx.annotation.WorkerThread;
9 |
10 | class FibonacciUseCaseAsyncUi {
11 |
12 | public interface Callback {
13 | public void onFibonacciComputed(BigInteger result);
14 | }
15 |
16 | public void computeFibonacci(int index, Callback callback) {
17 | new Thread(new Runnable() {
18 | @Override
19 | public void run() {
20 | BigInteger result = computeFibonacciBg(index);
21 | notifyResult(result, callback);
22 | }
23 | }).start();
24 | }
25 |
26 |
27 | @WorkerThread
28 | private BigInteger computeFibonacciBg(int index) {
29 | if (index == 0) {
30 | return new BigInteger("0");
31 | } else if (index == 1) {
32 | return new BigInteger("1");
33 | } else {
34 | return computeFibonacciBg(index - 1).add(computeFibonacciBg(index - 2));
35 | }
36 | }
37 |
38 | private void notifyResult(BigInteger result, Callback callback) {
39 | new Handler(Looper.getMainLooper()).post(new Runnable() {
40 | @Override
41 | public void run() {
42 | callback.onFibonacciComputed(result);
43 | }
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/java/FibonacciUseCaseAsyncUiThreadPoster.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.java;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 |
6 | import com.techyourchance.threadposter.BackgroundThreadPoster;
7 | import com.techyourchance.threadposter.UiThreadPoster;
8 |
9 | import java.math.BigInteger;
10 |
11 | import androidx.annotation.WorkerThread;
12 |
13 | class FibonacciUseCaseAsyncUiThreadPoster {
14 |
15 |
16 | public interface Callback {
17 | public void onFibonacciComputed(BigInteger result);
18 | }
19 |
20 | private final BackgroundThreadPoster mBackgroundThreadPoster;
21 | private final UiThreadPoster mUiThreadPoster;
22 |
23 | FibonacciUseCaseAsyncUiThreadPoster(BackgroundThreadPoster backgroundThreadPoster, UiThreadPoster uiThreadPoster) {
24 | mBackgroundThreadPoster = backgroundThreadPoster;
25 | mUiThreadPoster = uiThreadPoster;
26 | }
27 |
28 | public void computeFibonacci(int index, Callback callback) {
29 | mBackgroundThreadPoster.post(new Runnable() {
30 | @Override
31 | public void run() {
32 | BigInteger result = computeFibonacciBg(index);
33 | notifyResult(result, callback);
34 | }
35 | });
36 | }
37 |
38 |
39 | @WorkerThread
40 | private BigInteger computeFibonacciBg(int index) {
41 | if (index == 0) {
42 | return new BigInteger("0");
43 | } else if (index == 1) {
44 | return new BigInteger("1");
45 | } else {
46 | return computeFibonacciBg(index - 1).add(computeFibonacciBg(index - 2));
47 | }
48 | }
49 |
50 | private void notifyResult(BigInteger result, Callback callback) {
51 | mUiThreadPoster.post(new Runnable() {
52 | @Override
53 | public void run() {
54 | callback.onFibonacciComputed(result);
55 | }
56 | });
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/kotlin/FibonacciUseCaseAsyncUiCoroutines.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.kotlin
2 |
3 | import kotlinx.coroutines.*
4 | import java.math.BigInteger
5 |
6 | internal class FibonacciUseCaseAsyncUiCoroutines(private val bgDispatcher: CoroutineDispatcher) {
7 |
8 | interface Callback {
9 | fun onFibonacciComputed(result: BigInteger?)
10 | }
11 |
12 | private val coroutineScope = CoroutineScope(Dispatchers.Main)
13 |
14 | fun computeFibonacci(index: Int, callback: Callback) {
15 | coroutineScope.launch {
16 | val result = computeFibonacciBg(index)
17 | callback.onFibonacciComputed(result)
18 | }
19 | }
20 |
21 | private suspend fun computeFibonacciBg(index: Int): BigInteger = withContext(bgDispatcher) {
22 | if (index == 0) {
23 | BigInteger("0")
24 | } else if (index == 1) {
25 | BigInteger("1")
26 | } else {
27 | computeFibonacciBg(index - 1).add(computeFibonacciBg(index - 2))
28 | }
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/kotlin/FibonacciUseCaseUiCoroutines.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.kotlin
2 |
3 | import kotlinx.coroutines.*
4 | import java.math.BigInteger
5 |
6 | internal class FibonacciUseCaseUiCoroutines() {
7 |
8 | suspend fun computeFibonacci(index: Int): BigInteger = withContext(Dispatchers.Default) {
9 | if (index == 0) {
10 | BigInteger("0")
11 | } else if (index == 1 ) {
12 | BigInteger("1")
13 | } else {
14 | computeFibonacci(index - 1).add(computeFibonacci(index - 2))
15 | }
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/uithread/UiThreadDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.uithread
2 |
3 | import android.os.Bundle
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.Button
10 | import android.widget.TextView
11 | import android.widget.Toast
12 | import androidx.fragment.app.Fragment
13 | import com.techyourchance.coroutines.R
14 | import com.techyourchance.coroutines.common.BaseFragment
15 | import com.techyourchance.coroutines.common.ThreadInfoLogger
16 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
17 |
18 | class UiThreadDemoFragment : BaseFragment() {
19 |
20 | override val screenTitle get() = ScreenReachableFromHome.UI_THREAD_DEMO.description
21 |
22 | private lateinit var btnStart: Button
23 | private lateinit var txtRemainingTime: TextView
24 |
25 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
26 | val view = inflater.inflate(R.layout.fragment_loop_iterations_demo, container, false)
27 |
28 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
29 |
30 | btnStart = view.findViewById(R.id.btn_start)
31 | btnStart.setOnClickListener {
32 | logThreadInfo("button callback")
33 | btnStart.isEnabled = false
34 | executeBenchmark()
35 | btnStart.isEnabled = true
36 | }
37 |
38 | return view
39 | }
40 |
41 | private fun executeBenchmark() {
42 | val benchmarkDurationSeconds = 5
43 |
44 | updateRemainingTime(benchmarkDurationSeconds)
45 |
46 | logThreadInfo("benchmark started")
47 |
48 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
49 |
50 | var iterationsCount: Long = 0
51 | while (System.nanoTime() < stopTimeNano) {
52 | iterationsCount++
53 | }
54 |
55 | logThreadInfo("benchmark completed")
56 |
57 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
58 | }
59 |
60 | private fun updateRemainingTime(remainingTimeSeconds: Int) {
61 | logThreadInfo("updateRemainingTime: $remainingTimeSeconds seconds")
62 |
63 | if (remainingTimeSeconds > 0) {
64 | txtRemainingTime.text = "$remainingTimeSeconds seconds remaining"
65 | Handler(Looper.getMainLooper()).postDelayed({
66 | updateRemainingTime(remainingTimeSeconds - 1)
67 | }, 1000)
68 | } else {
69 | txtRemainingTime.text = "done!"
70 | }
71 |
72 | }
73 |
74 | private fun logThreadInfo(message: String) {
75 | ThreadInfoLogger.logThreadInfo(message)
76 | }
77 |
78 | companion object {
79 | fun newInstance(): Fragment {
80 | return UiThreadDemoFragment()
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/uncaughtexception/LoggedInUser.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.uncaughtexception
2 |
3 | data class LoggedInUser(val id: String, val username: String, val authToken: String)
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/uncaughtexception/LoginEndpointUncaughtException.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.uncaughtexception
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.delay
5 | import kotlinx.coroutines.withContext
6 | import java.lang.RuntimeException
7 | import java.util.*
8 | import java.util.concurrent.atomic.AtomicInteger
9 |
10 | class LoginEndpointUncaughtException {
11 |
12 | class RequestTimeoutException(msg: String) : RuntimeException(msg) {}
13 |
14 | sealed class Response {
15 | data class Success(val user: LoggedInUser): Response()
16 | data class Failure(val statusCode: Int): Response()
17 | }
18 |
19 | private var attemptsCounter = AtomicInteger(0)
20 |
21 | suspend fun logIn(username: String, password: String): Response = withContext(Dispatchers.IO) {
22 | delay(1000)
23 | val attempt = attemptsCounter.incrementAndGet().rem(3)
24 | if (attempt == 0) {
25 | throw RequestTimeoutException("timed out")
26 | }
27 | if (attempt == 1) {
28 | return@withContext Response.Success(
29 | LoggedInUser(
30 | UUID.randomUUID().toString(),
31 | username,
32 | "authToken"
33 | )
34 | )
35 | } else {
36 | return@withContext Response.Failure(401)
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/uncaughtexception/LoginUseCaseUncaughtException.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.uncaughtexception
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.withContext
5 |
6 | class LoginUseCaseUncaughtException(
7 | private val loginEndpointUncaughtException: LoginEndpointUncaughtException,
8 | private val userStateManager: UserStateManager
9 | ) {
10 |
11 | sealed class Result {
12 | data class Success(val user: LoggedInUser): Result()
13 | object InvalidCredentials: Result()
14 | object GeneralError: Result()
15 | }
16 |
17 | suspend fun logIn(username: String, password: String): Result = withContext(Dispatchers.IO) {
18 |
19 | val response = loginEndpointUncaughtException.logIn(username, password)
20 |
21 | return@withContext when(response) {
22 | is LoginEndpointUncaughtException.Response.Success -> {
23 | userStateManager.userLoggedIn(response.user)
24 | Result.Success(response.user)
25 | }
26 | is LoginEndpointUncaughtException.Response.Failure ->
27 | when(response.statusCode) {
28 | 401 -> Result.InvalidCredentials
29 | else -> Result.GeneralError
30 | }
31 | }
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/uncaughtexception/UserStateManager.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.uncaughtexception
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.delay
5 | import kotlinx.coroutines.withContext
6 |
7 | class UserStateManager {
8 |
9 | suspend fun userLoggedIn(user: LoggedInUser) = withContext(Dispatchers.IO) {
10 | delay(1000)
11 | println("new logged in user: $user")
12 | }
13 |
14 | suspend fun userLoggedOut() = withContext(Dispatchers.IO) {
15 | delay(1000)
16 | println("logged out")
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/viewmodel/MyViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.viewmodel
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import com.techyourchance.coroutines.common.ThreadInfoLogger
8 | import kotlinx.coroutines.*
9 |
10 | class MyViewModel: ViewModel() {
11 | private val _elapsedTime = MutableLiveData()
12 | val elapsedTime: LiveData = _elapsedTime
13 |
14 | private val _isTrackingTime = MutableLiveData()
15 | val isTrackingTime: LiveData = _isTrackingTime
16 |
17 | fun toggleTrackElapsedTime() {
18 | val isTrackingTimeNow = isTrackingTime.value
19 | logThreadInfo("trackElapsedTime(); isTrackingTimeNow = $isTrackingTimeNow")
20 | if (isTrackingTimeNow == null || !isTrackingTimeNow) {
21 | startTracking()
22 | } else {
23 | stopTracking()
24 | }
25 | }
26 |
27 | private fun startTracking() = viewModelScope.launch {
28 | logThreadInfo("startTracking()")
29 | _isTrackingTime.postValue(true)
30 |
31 | val startTimeNano = System.nanoTime()
32 |
33 | while (true) {
34 | val elapsedTimeSeconds = (System.nanoTime() - startTimeNano) / 1_000_000_000L
35 | logThreadInfo("elapsed time: $elapsedTimeSeconds seconds")
36 | _elapsedTime.postValue(elapsedTimeSeconds)
37 | delay(1000)
38 | }
39 | }
40 |
41 | private fun stopTracking() {
42 | logThreadInfo("stopTracking()")
43 | _isTrackingTime.postValue(false)
44 | viewModelScope.coroutineContext.cancelChildren()
45 | }
46 |
47 | private fun logThreadInfo(message: String) {
48 | ThreadInfoLogger.logThreadInfo(message)
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/viewmodel/MyViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.viewmodel
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 |
6 | class MyViewModelFactory: ViewModelProvider.Factory {
7 | override fun create(modelClass: Class): T {
8 | return MyViewModel() as T
9 | }
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/demonstrations/viewmodel/ViewModelDemoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.viewmodel
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import androidx.fragment.app.Fragment
10 | import androidx.lifecycle.Observer
11 | import androidx.lifecycle.ViewModelProvider
12 | import com.techyourchance.coroutines.R
13 | import com.techyourchance.coroutines.common.BaseFragment
14 | import com.techyourchance.coroutines.common.ThreadInfoLogger
15 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
16 |
17 | class ViewModelDemoFragment : BaseFragment() {
18 |
19 | override val screenTitle get() = ScreenReachableFromHome.VIEWMODEL_DEMO.description
20 |
21 | private lateinit var btnTrackTime: Button
22 | private lateinit var txtElapsedTime: TextView
23 |
24 | private lateinit var myViewModel: MyViewModel
25 |
26 | override fun onCreate(savedInstanceState: Bundle?) {
27 | super.onCreate(savedInstanceState)
28 | myViewModel = ViewModelProvider(this, MyViewModelFactory()).get(MyViewModel::class.java)
29 | }
30 |
31 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
32 | val view = inflater.inflate(R.layout.fragment_viewmodel_demo, container, false)
33 |
34 | txtElapsedTime = view.findViewById(R.id.txt_elapsed_time)
35 | btnTrackTime = view.findViewById(R.id.btn_track_time)
36 |
37 | btnTrackTime.setOnClickListener {
38 | logThreadInfo("button callback")
39 | myViewModel.toggleTrackElapsedTime()
40 | }
41 |
42 | myViewModel.elapsedTime.observe(viewLifecycleOwner, Observer { elapsedTime ->
43 | txtElapsedTime.text = elapsedTime.toString()
44 | })
45 |
46 | myViewModel.isTrackingTime.observe(viewLifecycleOwner, Observer { isTrackingTime ->
47 | btnTrackTime.text = if (isTrackingTime) { "Stop tracking" } else { "Start tracking" }
48 | })
49 |
50 | return view
51 | }
52 |
53 | override fun onStop() {
54 | logThreadInfo("onStop()")
55 | super.onStop()
56 | }
57 |
58 | private fun logThreadInfo(message: String) {
59 | ThreadInfoLogger.logThreadInfo(message)
60 | }
61 |
62 | companion object {
63 | fun newInstance(): Fragment {
64 | return ViewModelDemoFragment()
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise1/Exercise1Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise1
2 |
3 | import android.os.Bundle
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.text.Editable
7 | import android.text.TextWatcher
8 | import android.view.*
9 | import android.widget.Button
10 | import android.widget.EditText
11 | import android.widget.Toast
12 | import androidx.fragment.app.Fragment
13 | import com.techyourchance.coroutines.R
14 | import com.techyourchance.coroutines.common.BaseFragment
15 | import com.techyourchance.coroutines.common.ThreadInfoLogger
16 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
17 |
18 | class Exercise1Fragment : BaseFragment() {
19 |
20 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_1.description
21 |
22 | private lateinit var edtUserId: EditText
23 | private lateinit var btnGetReputation: Button
24 |
25 | private lateinit var getReputationEndpoint: GetReputationEndpoint
26 |
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 | getReputationEndpoint = compositionRoot.getReputationEndpoint
30 | }
31 |
32 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
33 | val view = inflater.inflate(R.layout.fragment_exercise_1, container, false)
34 |
35 | edtUserId = view.findViewById(R.id.edt_user_id)
36 | edtUserId.addTextChangedListener(object : TextWatcher {
37 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
38 |
39 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
40 | btnGetReputation.isEnabled = !s.isNullOrEmpty()
41 | }
42 |
43 | override fun afterTextChanged(s: Editable?) {}
44 | })
45 |
46 | btnGetReputation = view.findViewById(R.id.btn_get_reputation)
47 | btnGetReputation.setOnClickListener {
48 | logThreadInfo("button callback")
49 | btnGetReputation.isEnabled = false
50 | getReputationForUser(edtUserId.text.toString())
51 | btnGetReputation.isEnabled = true
52 | }
53 |
54 | return view
55 | }
56 |
57 | private fun getReputationForUser(userId: String) {
58 | logThreadInfo("getReputationForUser()")
59 |
60 | val reputation = getReputationEndpoint.getReputation(userId)
61 |
62 | Toast.makeText(requireContext(), "reputation: $reputation", Toast.LENGTH_SHORT).show()
63 | }
64 |
65 | private fun logThreadInfo(message: String) {
66 | ThreadInfoLogger.logThreadInfo(message)
67 | }
68 |
69 | companion object {
70 | fun newInstance(): Fragment {
71 | return Exercise1Fragment()
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise1/GetReputationEndpoint.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise1
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger
4 | import kotlin.random.Random
5 |
6 | class GetReputationEndpoint {
7 | fun getReputation(userId: String): Int {
8 | ThreadInfoLogger.logThreadInfo("GetReputationEndpoint#getReputation(): called")
9 | Thread.sleep(3000)
10 | ThreadInfoLogger.logThreadInfo("GetReputationEndpoint#getReputation(): return data")
11 | return Random.nextInt(0, 100)
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise2/Exercise2Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise2
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.*
7 | import android.widget.Button
8 | import android.widget.EditText
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger
14 | import com.techyourchance.coroutines.exercises.exercise1.GetReputationEndpoint
15 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
16 | import kotlinx.coroutines.CoroutineScope
17 | import kotlinx.coroutines.Dispatchers
18 | import kotlinx.coroutines.launch
19 | import kotlinx.coroutines.withContext
20 |
21 | class Exercise2Fragment : BaseFragment() {
22 |
23 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
24 |
25 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_2.description
26 |
27 | private lateinit var edtUserId: EditText
28 | private lateinit var btnGetReputation: Button
29 |
30 | private lateinit var getReputationEndpoint: GetReputationEndpoint
31 |
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | getReputationEndpoint = compositionRoot.getReputationEndpoint
35 | }
36 |
37 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
38 | val view = inflater.inflate(R.layout.fragment_exercise_2, container, false)
39 |
40 | edtUserId = view.findViewById(R.id.edt_user_id)
41 | edtUserId.addTextChangedListener(object : TextWatcher {
42 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
43 |
44 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
45 | btnGetReputation.isEnabled = !s.isNullOrEmpty()
46 | }
47 |
48 | override fun afterTextChanged(s: Editable?) {}
49 | })
50 |
51 | btnGetReputation = view.findViewById(R.id.btn_get_reputation)
52 | btnGetReputation.setOnClickListener {
53 | logThreadInfo("button callback")
54 | coroutineScope.launch {
55 | btnGetReputation.isEnabled = false
56 | val reputation = getReputationForUser(edtUserId.text.toString())
57 | Toast.makeText(requireContext(), "reputation: $reputation", Toast.LENGTH_SHORT).show()
58 | btnGetReputation.isEnabled = true
59 | }
60 | }
61 |
62 | return view
63 | }
64 |
65 | private suspend fun getReputationForUser(userId: String): Int {
66 | return withContext(Dispatchers.Default) {
67 | logThreadInfo("getReputationForUser()")
68 | getReputationEndpoint.getReputation(userId)
69 | }
70 | }
71 |
72 | private fun logThreadInfo(message: String) {
73 | ThreadInfoLogger.logThreadInfo(message)
74 | }
75 |
76 | companion object {
77 | fun newInstance(): Fragment {
78 | return Exercise2Fragment()
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise3/Exercise3Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise3
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.*
7 | import android.widget.Button
8 | import android.widget.EditText
9 | import android.widget.TextView
10 | import android.widget.Toast
11 | import androidx.fragment.app.Fragment
12 | import com.techyourchance.coroutines.R
13 | import com.techyourchance.coroutines.common.BaseFragment
14 | import com.techyourchance.coroutines.common.ThreadInfoLogger
15 | import com.techyourchance.coroutines.exercises.exercise1.GetReputationEndpoint
16 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
17 | import kotlinx.coroutines.*
18 |
19 | class Exercise3Fragment : BaseFragment() {
20 |
21 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
22 |
23 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_3.description
24 |
25 | private lateinit var edtUserId: EditText
26 | private lateinit var btnGetReputation: Button
27 | private lateinit var txtElapsedTime: TextView
28 |
29 |
30 | private lateinit var getReputationEndpoint: GetReputationEndpoint
31 |
32 | private var job: Job? = null
33 |
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | getReputationEndpoint = compositionRoot.getReputationEndpoint
37 | }
38 |
39 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
40 | val view = inflater.inflate(R.layout.fragment_exercise_3, container, false)
41 |
42 | txtElapsedTime = view.findViewById(R.id.txt_elapsed_time)
43 |
44 | edtUserId = view.findViewById(R.id.edt_user_id)
45 | edtUserId.addTextChangedListener(object : TextWatcher {
46 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
47 |
48 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
49 | btnGetReputation.isEnabled = !s.isNullOrEmpty()
50 | }
51 |
52 | override fun afterTextChanged(s: Editable?) {}
53 | })
54 |
55 | btnGetReputation = view.findViewById(R.id.btn_get_reputation)
56 | btnGetReputation.setOnClickListener {
57 | logThreadInfo("button callback")
58 | job = coroutineScope.launch {
59 | btnGetReputation.isEnabled = false
60 | val reputation = getReputationForUser(edtUserId.text.toString())
61 | Toast.makeText(requireContext(), "reputation: $reputation", Toast.LENGTH_SHORT).show()
62 | btnGetReputation.isEnabled = true
63 | }
64 | }
65 |
66 | return view
67 | }
68 |
69 | override fun onStop() {
70 | super.onStop()
71 | job?.cancel()
72 | btnGetReputation.isEnabled = true
73 | }
74 |
75 | private suspend fun getReputationForUser(userId: String): Int {
76 | return withContext(Dispatchers.Default) {
77 | logThreadInfo("getReputationForUser()")
78 | getReputationEndpoint.getReputation(userId)
79 | }
80 | }
81 |
82 | private fun logThreadInfo(message: String) {
83 | ThreadInfoLogger.logThreadInfo(message)
84 | }
85 |
86 | companion object {
87 | fun newInstance(): Fragment {
88 | return Exercise3Fragment()
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise4/Exercise4Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise4
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import android.text.Editable
6 | import android.text.TextWatcher
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import android.view.inputmethod.InputMethodManager
11 | import android.widget.Button
12 | import android.widget.EditText
13 | import android.widget.TextView
14 |
15 | import java.math.BigInteger
16 | import androidx.fragment.app.Fragment
17 | import com.techyourchance.coroutines.R
18 | import com.techyourchance.coroutines.common.BaseFragment
19 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
20 | import kotlinx.coroutines.CoroutineScope
21 | import kotlinx.coroutines.Dispatchers
22 | import kotlinx.coroutines.cancel
23 | import kotlinx.coroutines.launch
24 |
25 | class Exercise4Fragment : BaseFragment() {
26 |
27 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
28 |
29 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_4.description
30 |
31 | private lateinit var edtArgument: EditText
32 | private lateinit var edtTimeout: EditText
33 | private lateinit var btnStartWork: Button
34 | private lateinit var txtResult: TextView
35 |
36 | private lateinit var factorialUseCase: FactorialUseCase
37 |
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 | super.onCreate(savedInstanceState)
40 | factorialUseCase = compositionRoot.factorialUseCase
41 | }
42 |
43 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
44 | val view = inflater.inflate(R.layout.fragment_exercise_4, container, false)
45 |
46 | view.apply {
47 | edtArgument = findViewById(R.id.edt_argument)
48 | edtTimeout = findViewById(R.id.edt_timeout)
49 | btnStartWork = findViewById(R.id.btn_compute)
50 | txtResult = findViewById(R.id.txt_result)
51 | }
52 |
53 | edtArgument.addTextChangedListener(object : TextWatcher {
54 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
55 |
56 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
57 | btnStartWork.isEnabled = !s.isNullOrEmpty()
58 | }
59 |
60 | override fun afterTextChanged(s: Editable?) {}
61 | })
62 |
63 | btnStartWork.setOnClickListener {
64 | txtResult.text = ""
65 | btnStartWork.isEnabled = false
66 |
67 | val imm = requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
68 | imm.hideSoftInputFromWindow(btnStartWork.windowToken, 0)
69 |
70 | val argument = Integer.valueOf(edtArgument.text.toString())
71 |
72 | coroutineScope.launch {
73 | val result = factorialUseCase.computeFactorial(argument, getTimeout())
74 | when (result) {
75 | is FactorialUseCase.Result.Success -> onFactorialComputed(result.result)
76 | is FactorialUseCase.Result.Timeout -> onFactorialComputationTimedOut()
77 | }
78 | }
79 | }
80 |
81 | return view
82 | }
83 |
84 | override fun onStop() {
85 | super.onStop()
86 | coroutineScope.cancel()
87 | }
88 |
89 | private fun onFactorialComputed(result: BigInteger) {
90 | txtResult.text = result.toString()
91 | btnStartWork.isEnabled = true
92 | }
93 |
94 | private fun onFactorialComputationTimedOut() {
95 | txtResult.text = "Computation timed out"
96 | btnStartWork.isEnabled = true
97 | }
98 |
99 | private fun getTimeout() : Int {
100 | var timeout: Int
101 | if (edtTimeout.text.toString().isEmpty()) {
102 | timeout = MAX_TIMEOUT_MS
103 | } else {
104 | timeout = Integer.valueOf(edtTimeout.text.toString())
105 | if (timeout > MAX_TIMEOUT_MS) {
106 | timeout = MAX_TIMEOUT_MS
107 | }
108 | }
109 | return timeout
110 | }
111 |
112 | companion object {
113 | fun newInstance(): Fragment {
114 | return Exercise4Fragment()
115 | }
116 | private const val MAX_TIMEOUT_MS = 3000
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise4/FactorialUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise4
2 |
3 | import java.math.BigInteger
4 |
5 | import androidx.annotation.WorkerThread
6 | import kotlinx.coroutines.*
7 | import java.util.concurrent.TimeUnit
8 |
9 | class FactorialUseCase {
10 |
11 | public sealed class Result {
12 | class Success(val result: BigInteger) : Result()
13 | object Timeout : Result()
14 | }
15 |
16 | suspend fun computeFactorial(argument: Int, timeout: Int) : Result = withContext(Dispatchers.IO) {
17 | try {
18 | withTimeout(timeMillis = timeout.toLong()) {
19 | val computationRanges = getComputationRanges(argument)
20 |
21 | val partialProductsForRanges = computePartialProducts(computationRanges)
22 |
23 | val result = computeFinalResult(partialProductsForRanges)
24 |
25 | Result.Success(result)
26 | }
27 | } catch (e : TimeoutCancellationException) {
28 | Result.Timeout
29 | }
30 |
31 | }
32 |
33 | private fun getComputationRanges(factorialArgument: Int) : Array {
34 | val numberOfThreads = getNumberOfThreads(factorialArgument)
35 |
36 | val threadsComputationRanges = Array(numberOfThreads) { ComputationRange(0, 0) }
37 |
38 | val computationRangeSize = factorialArgument / numberOfThreads
39 |
40 | var nextComputationRangeEnd = factorialArgument.toLong()
41 |
42 | for (i in numberOfThreads - 1 downTo 0) {
43 | threadsComputationRanges[i] = ComputationRange(
44 | nextComputationRangeEnd - computationRangeSize + 1,
45 | nextComputationRangeEnd
46 | )
47 | nextComputationRangeEnd = threadsComputationRanges[i].start - 1
48 | }
49 |
50 | // add potentially "remaining" values to first thread's range
51 | //threadsComputationRanges[0] = ComputationRange(1, threadsComputationRanges[0].end)
52 |
53 | return threadsComputationRanges
54 | }
55 |
56 | private fun getNumberOfThreads(factorialArgument: Int): Int {
57 | return if (factorialArgument < 20)
58 | 1
59 | else
60 | Runtime.getRuntime().availableProcessors()
61 | }
62 |
63 | private suspend fun computePartialProducts(computationRanges: Array) : List = coroutineScope {
64 | return@coroutineScope withContext(Dispatchers.IO) {
65 | return@withContext computationRanges.map {
66 | computeProductForRangeAsync(it)
67 | }.awaitAll()
68 | }
69 | }
70 |
71 | private fun CoroutineScope.computeProductForRangeAsync(computationRange: ComputationRange) : Deferred = async(Dispatchers.IO) {
72 | val rangeStart = computationRange.start
73 | val rangeEnd = computationRange.end
74 |
75 | var product = BigInteger("1")
76 | for (num in rangeStart..rangeEnd) {
77 | if (!isActive) {
78 | break
79 | }
80 | product = product.multiply(BigInteger(num.toString()))
81 | }
82 |
83 | return@async product
84 | }
85 |
86 | private suspend fun computeFinalResult(partialProducts: List): BigInteger = withContext(Dispatchers.IO) {
87 | var result = BigInteger("1")
88 | for (partialProduct in partialProducts) {
89 | if (!isActive) {
90 | break
91 | }
92 | result = result.multiply(partialProduct)
93 | }
94 | return@withContext result
95 | }
96 |
97 | private data class ComputationRange(val start: Long, val end: Long)
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise5/Exercise5Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise5
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.*
7 | import android.widget.Button
8 | import android.widget.EditText
9 | import android.widget.TextView
10 | import android.widget.Toast
11 | import androidx.fragment.app.Fragment
12 | import com.techyourchance.coroutines.R
13 | import com.techyourchance.coroutines.common.BaseFragment
14 | import com.techyourchance.coroutines.common.ThreadInfoLogger
15 | import com.techyourchance.coroutines.exercises.exercise1.GetReputationEndpoint
16 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
17 | import kotlinx.coroutines.*
18 |
19 | class Exercise5Fragment : BaseFragment() {
20 |
21 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
22 |
23 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_5.description
24 |
25 | private lateinit var edtUserId: EditText
26 | private lateinit var btnGetReputation: Button
27 | private lateinit var txtElapsedTime: TextView
28 |
29 |
30 | private lateinit var getReputationEndpoint: GetReputationEndpoint
31 |
32 | private var job: Job? = null
33 |
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | getReputationEndpoint = compositionRoot.getReputationEndpoint
37 | }
38 |
39 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
40 | val view = inflater.inflate(R.layout.fragment_exercise_5, container, false)
41 |
42 | txtElapsedTime = view.findViewById(R.id.txt_elapsed_time)
43 |
44 | edtUserId = view.findViewById(R.id.edt_user_id)
45 | edtUserId.addTextChangedListener(object : TextWatcher {
46 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
47 |
48 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
49 | btnGetReputation.isEnabled = !s.isNullOrEmpty()
50 | }
51 |
52 | override fun afterTextChanged(s: Editable?) {}
53 | })
54 |
55 | btnGetReputation = view.findViewById(R.id.btn_get_reputation)
56 | btnGetReputation.setOnClickListener {
57 | logThreadInfo("button callback")
58 | job = coroutineScope.launch {
59 | btnGetReputation.isEnabled = false
60 | val reputation = getReputationForUser(edtUserId.text.toString())
61 | Toast.makeText(requireContext(), "reputation: $reputation", Toast.LENGTH_SHORT).show()
62 | btnGetReputation.isEnabled = true
63 | }
64 | }
65 |
66 | return view
67 | }
68 |
69 | override fun onStop() {
70 | super.onStop()
71 | job?.cancel()
72 | btnGetReputation.isEnabled = true
73 | }
74 |
75 | private suspend fun getReputationForUser(userId: String): Int {
76 | return withContext(Dispatchers.Default) {
77 | logThreadInfo("getReputationForUser()")
78 | getReputationEndpoint.getReputation(userId)
79 | }
80 | }
81 |
82 | private fun logThreadInfo(message: String) {
83 | ThreadInfoLogger.logThreadInfo(message)
84 | }
85 |
86 | companion object {
87 | fun newInstance(): Fragment {
88 | return Exercise5Fragment()
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise6/Exercise6BenchmarkUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise6
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger
4 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.isActive
7 | import kotlinx.coroutines.withContext
8 |
9 | class Exercise6BenchmarkUseCase(private val postBenchmarkResultsEndpoint: PostBenchmarkResultsEndpoint) {
10 |
11 | suspend fun executeBenchmark(benchmarkDurationSeconds: Int) = withContext(Dispatchers.Default) {
12 | logThreadInfo("benchmark started")
13 |
14 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
15 |
16 | var iterationsCount: Long = 0
17 | while (System.nanoTime() < stopTimeNano) {
18 | iterationsCount++
19 | }
20 |
21 | logThreadInfo("benchmark completed")
22 |
23 | postBenchmarkResultsEndpoint.postBenchmarkResults(benchmarkDurationSeconds, iterationsCount)
24 |
25 | logThreadInfo("benchmark results posted to the server")
26 |
27 | iterationsCount
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise6/Exercise6Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise6
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
14 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
15 | import kotlinx.coroutines.*
16 |
17 | class Exercise6Fragment : BaseFragment() {
18 |
19 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
20 |
21 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_6.description
22 |
23 | private lateinit var benchmarkUseCase: Exercise6BenchmarkUseCase
24 |
25 | private lateinit var btnStart: Button
26 | private lateinit var txtRemainingTime: TextView
27 |
28 | private var hasBenchmarkBeenStartedOnce = false
29 |
30 | override fun onCreate(savedInstanceState: Bundle?) {
31 | super.onCreate(savedInstanceState)
32 | benchmarkUseCase = compositionRoot.exercise6BenchmarkUseCase
33 | }
34 |
35 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
36 | val view = inflater.inflate(R.layout.fragment_exercise_6, container, false)
37 |
38 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
39 |
40 | btnStart = view.findViewById(R.id.btn_start)
41 | btnStart.setOnClickListener {
42 | logThreadInfo("button callback")
43 |
44 | val benchmarkDurationSeconds = 5
45 |
46 | coroutineScope.launch {
47 | updateRemainingTime(benchmarkDurationSeconds)
48 | }
49 |
50 | coroutineScope.launch {
51 | btnStart.isEnabled = false
52 | val iterationsCount = benchmarkUseCase.executeBenchmark(benchmarkDurationSeconds)
53 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
54 | btnStart.isEnabled = true
55 | }
56 |
57 | hasBenchmarkBeenStartedOnce = true
58 | }
59 |
60 | return view
61 | }
62 |
63 | override fun onStop() {
64 | logThreadInfo("onStop()")
65 | super.onStop()
66 | coroutineScope.coroutineContext.cancelChildren()
67 | if (hasBenchmarkBeenStartedOnce) {
68 | btnStart.isEnabled = true
69 | txtRemainingTime.text = "done!"
70 | }
71 | }
72 |
73 |
74 | private suspend fun updateRemainingTime(remainingTimeSeconds: Int) {
75 | for (time in remainingTimeSeconds downTo 0) {
76 | if (time > 0) {
77 | logThreadInfo("updateRemainingTime: $time seconds")
78 | txtRemainingTime.text = "$time seconds remaining"
79 | delay(1000)
80 | } else {
81 | txtRemainingTime.text = "done!"
82 | }
83 | }
84 | }
85 |
86 | companion object {
87 | fun newInstance(): Fragment {
88 | return Exercise6Fragment()
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise6/PostBenchmarkResultsEndpoint.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise6
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger
4 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.withContext
7 |
8 | /**
9 | * Existing class which you can't change
10 | */
11 | class PostBenchmarkResultsEndpoint {
12 | fun postBenchmarkResults(timeSeconds: Int, iterations: Long) {
13 | Thread.sleep(500)
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise8/Exercise8Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise8
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import androidx.fragment.app.Fragment
10 | import com.techyourchance.coroutines.R
11 | import com.techyourchance.coroutines.common.BaseFragment
12 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
13 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
14 | import kotlinx.coroutines.*
15 |
16 | class Exercise8Fragment : BaseFragment() {
17 |
18 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
19 |
20 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_8.description
21 |
22 | private lateinit var fetchAndCacheUsersUseCase: FetchAndCacheUsersUseCase
23 |
24 | private lateinit var btnFetch: Button
25 | private lateinit var txtElapsedTime: TextView
26 |
27 | private val userIds = listOf("bmq81", "gfn12", "gla34")
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | fetchAndCacheUsersUseCase = compositionRoot.fetchAndCacheUserUseCase
32 | }
33 |
34 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
35 | val view = inflater.inflate(R.layout.fragment_exercise_8, container, false)
36 |
37 | view.apply {
38 | txtElapsedTime = findViewById(R.id.txt_elapsed_time)
39 | btnFetch = findViewById(R.id.btn_fetch_users)
40 | }
41 |
42 | btnFetch.setOnClickListener {
43 | logThreadInfo("button callback")
44 |
45 | val updateElapsedTimeJob = coroutineScope.launch {
46 | updateElapsedTime()
47 | }
48 |
49 | coroutineScope.launch {
50 | try {
51 | btnFetch.isEnabled = false
52 | fetchAndCacheUsersUseCase.fetchAndCacheUsers(userIds)
53 | updateElapsedTimeJob.cancel()
54 | } catch (e: CancellationException) {
55 | updateElapsedTimeJob.cancelAndJoin()
56 | txtElapsedTime.text = ""
57 | } finally {
58 | btnFetch.isEnabled = true
59 | }
60 | }
61 | }
62 |
63 | return view
64 | }
65 |
66 | override fun onStop() {
67 | logThreadInfo("onStop()")
68 | super.onStop()
69 | coroutineScope.coroutineContext.cancelChildren()
70 | }
71 |
72 |
73 | private suspend fun updateElapsedTime() {
74 | val startTimeNano = System.nanoTime()
75 | while (true) {
76 | delay(100)
77 | val elapsedTimeNano = System.nanoTime() - startTimeNano
78 | val elapsedTimeMs = elapsedTimeNano / 1000000
79 | txtElapsedTime.text = "Elapsed time: $elapsedTimeMs ms"
80 | }
81 | }
82 |
83 | companion object {
84 | fun newInstance(): Fragment {
85 | return Exercise8Fragment()
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise8/FetchAndCacheUsersUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise8
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.withContext
5 |
6 | class FetchAndCacheUsersUseCase(
7 | private val getUserEndpoint: GetUserEndpoint,
8 | private val usersDao: UsersDao
9 | ) {
10 |
11 | suspend fun fetchAndCacheUsers(userIds: List) = withContext(Dispatchers.Default) {
12 | for (userId in userIds) {
13 | val user = getUserEndpoint.getUser(userId)
14 | usersDao.upsertUserInfo(user)
15 | }
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise8/GetUserEndpoint.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise8
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.delay
5 | import kotlinx.coroutines.withContext
6 |
7 | class GetUserEndpoint {
8 |
9 | suspend fun getUser(userId: String): User = withContext(Dispatchers.IO) {
10 | delay(500)
11 | return@withContext User(userId, "user ${userId}")
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise8/User.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise8
2 |
3 | data class User(val id: String, val name: String)
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise8/UsersDao.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise8
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.delay
5 | import kotlinx.coroutines.withContext
6 |
7 | class UsersDao {
8 |
9 | suspend fun upsertUserInfo(user: User) = withContext(Dispatchers.IO) {
10 | delay(500)
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise9/Exercise9Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise9
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import androidx.fragment.app.Fragment
10 | import com.techyourchance.coroutines.R
11 | import com.techyourchance.coroutines.common.BaseFragment
12 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
13 | import com.techyourchance.coroutines.exercises.exercise8.User
14 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
15 | import kotlinx.coroutines.*
16 |
17 | class Exercise9Fragment : BaseFragment() {
18 |
19 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
20 |
21 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_9.description
22 |
23 | private lateinit var fetchAndCacheUsersUseCase: FetchAndCacheUsersUseCaseExercise9
24 |
25 | private lateinit var btnFetch: Button
26 | private lateinit var txtElapsedTime: TextView
27 | private lateinit var txtUsers: TextView
28 |
29 |
30 | private val userIds = listOf("bmq81", "gfn12", "gla34")
31 |
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | fetchAndCacheUsersUseCase = compositionRoot.fetchAndCacheUserUseCaseExercise9
35 | }
36 |
37 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
38 | val view = inflater.inflate(R.layout.fragment_exercise_9, container, false)
39 |
40 | view.apply {
41 | txtElapsedTime = findViewById(R.id.txt_elapsed_time)
42 | btnFetch = findViewById(R.id.btn_fetch_users)
43 | txtUsers = findViewById(R.id.txt_users)
44 | }
45 |
46 | btnFetch.setOnClickListener {
47 | logThreadInfo("button callback")
48 |
49 | val updateElapsedTimeJob = coroutineScope.launch {
50 | updateElapsedTime()
51 | }
52 |
53 | coroutineScope.launch {
54 | try {
55 | btnFetch.isEnabled = false
56 | fetchAndCacheUsersUseCase.fetchAndCacheUsers(userIds)
57 | updateElapsedTimeJob.cancel()
58 | } catch (e: CancellationException) {
59 | updateElapsedTimeJob.cancelAndJoin()
60 | txtElapsedTime.text = ""
61 | txtUsers.text = ""
62 | } finally {
63 | btnFetch.isEnabled = true
64 | }
65 | }
66 | }
67 |
68 | return view
69 | }
70 |
71 | private fun bindUsers(users: List) {
72 | txtUsers.text = users.joinToString("\n") { it.name }
73 | }
74 |
75 | override fun onStop() {
76 | logThreadInfo("onStop()")
77 | super.onStop()
78 | coroutineScope.coroutineContext.cancelChildren()
79 | }
80 |
81 |
82 | private suspend fun updateElapsedTime() {
83 | val startTimeNano = System.nanoTime()
84 | while (true) {
85 | delay(100)
86 | val elapsedTimeNano = System.nanoTime() - startTimeNano
87 | val elapsedTimeMs = elapsedTimeNano / 1000000
88 | txtElapsedTime.text = "Elapsed time: $elapsedTimeMs ms"
89 | }
90 | }
91 |
92 | companion object {
93 | fun newInstance(): Fragment {
94 | return Exercise9Fragment()
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/exercises/exercise9/FetchAndCacheUsersUseCaseExercise9.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise9
2 |
3 | import com.techyourchance.coroutines.exercises.exercise8.GetUserEndpoint
4 | import com.techyourchance.coroutines.exercises.exercise8.User
5 | import com.techyourchance.coroutines.exercises.exercise8.UsersDao
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.async
8 | import kotlinx.coroutines.awaitAll
9 | import kotlinx.coroutines.withContext
10 |
11 | class FetchAndCacheUsersUseCaseExercise9(
12 | private val getUserEndpoint: GetUserEndpoint,
13 | private val usersDao: UsersDao
14 | ) {
15 |
16 | suspend fun fetchAndCacheUsers(userIds: List) = withContext(Dispatchers.Default) {
17 | for (userId in userIds) {
18 | val user = getUserEndpoint.getUser(userId)
19 | usersDao.upsertUserInfo(user)
20 | }
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/home/HomeArrayAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.home
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.ArrayAdapter
8 | import android.widget.TextView
9 | import com.techyourchance.coroutines.R
10 |
11 | class HomeArrayAdapter(context: Context, private val listener: Listener) : ArrayAdapter(context, 0) {
12 |
13 | interface Listener {
14 | fun onScreenClicked(screenReachableFromHome: ScreenReachableFromHome)
15 | }
16 |
17 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
18 | val newConvertView = convertView ?: LayoutInflater.from(context).inflate(R.layout.list_item_screen_reachable_from_home, parent, false)
19 |
20 | val screenReachableFromHome = getItem(position)!!
21 |
22 | // display screen name
23 | val txtName = newConvertView.findViewById(R.id.txt_screen_name)
24 | txtName.text = screenReachableFromHome.description
25 |
26 | // set click listener on individual item view
27 | newConvertView.setOnClickListener { listener.onScreenClicked(screenReachableFromHome) }
28 |
29 | return newConvertView
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/home/HomeFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.home
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.ListView
8 | import androidx.fragment.app.Fragment
9 | import com.techyourchance.coroutines.R
10 | import com.techyourchance.coroutines.common.BaseFragment
11 |
12 | class HomeFragment : BaseFragment(), HomeArrayAdapter.Listener {
13 |
14 | override val screenTitle get() = "Coroutines Course"
15 |
16 | private lateinit var listScreensReachableFromHome: ListView
17 | private lateinit var adapterScreensReachableFromHome: HomeArrayAdapter
18 |
19 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
20 | val view = inflater.inflate(R.layout.fragment_home, container, false)
21 |
22 | adapterScreensReachableFromHome = HomeArrayAdapter(requireContext(), this)
23 |
24 | listScreensReachableFromHome = view.findViewById(R.id.list_screens)
25 | listScreensReachableFromHome.adapter = adapterScreensReachableFromHome
26 |
27 | adapterScreensReachableFromHome.addAll(*ScreenReachableFromHome.values())
28 | adapterScreensReachableFromHome.notifyDataSetChanged()
29 |
30 | return view
31 | }
32 |
33 | override fun onScreenClicked(screenReachableFromHome: ScreenReachableFromHome) {
34 | when (screenReachableFromHome) {
35 | ScreenReachableFromHome.UI_THREAD_DEMO -> screensNavigator.toUiThreadDemo()
36 | ScreenReachableFromHome.BACKGROUND_THREAD_DEMO -> screensNavigator.toBackgroundThreadDemo()
37 | ScreenReachableFromHome.BASIC_COROUTINES_DEMO -> screensNavigator.toBasicCoroutinesDemo()
38 | ScreenReachableFromHome.EXERCISE_1 -> screensNavigator.toExercise1()
39 | ScreenReachableFromHome.COROUTINES_CANCELLATION_DEMO -> screensNavigator.toCoroutinesCancellationDemo()
40 | ScreenReachableFromHome.EXERCISE_2 -> screensNavigator.toExercise2()
41 | ScreenReachableFromHome.CONCURRENT_COROUTINES_DEMO -> screensNavigator.toConcurrentCoroutines()
42 | ScreenReachableFromHome.SCOPE_CHILDREN_CANCELLATION_DEMO -> screensNavigator.toScopeChildrenCancellation()
43 | ScreenReachableFromHome.EXERCISE_3 -> screensNavigator.toExercise3()
44 | ScreenReachableFromHome.SCOPE_CANCELLATION_DEMO -> screensNavigator.toScopeCancellation()
45 | ScreenReachableFromHome.VIEWMODEL_DEMO -> screensNavigator.toViewModel()
46 | ScreenReachableFromHome.EXERCISE_4 -> screensNavigator.toExercise4()
47 | ScreenReachableFromHome.DESIGN_DEMO -> screensNavigator.toDesignDemo()
48 | ScreenReachableFromHome.EXERCISE_5 -> screensNavigator.toExercise5()
49 | ScreenReachableFromHome.COROUTINES_CANCELLATION_COOPERATIVE_DEMO -> screensNavigator.toCoroutinesCancellationCooperativeDemo()
50 | ScreenReachableFromHome.COROUTINES_CANCELLATION_COOPERATIVE_2_DEMO -> screensNavigator.toCoroutinesCancellationCooperative2Demo()
51 | ScreenReachableFromHome.EXERCISE_6 -> screensNavigator.toExercise6()
52 | ScreenReachableFromHome.NON_CANCELLABLE_DEMO -> screensNavigator.toNonCancellable()
53 | ScreenReachableFromHome.EXERCISE_8 -> screensNavigator.toExercise8()
54 | ScreenReachableFromHome.EXERCISE_9 -> screensNavigator.toExercise9()
55 | ScreenReachableFromHome.UNCAUGHT_EXCEPTION_DEMO -> screensNavigator.toUncaughtException()
56 | ScreenReachableFromHome.EXERCISE_10 -> screensNavigator.toExercise10()
57 | }
58 | }
59 |
60 | companion object {
61 | fun newInstance(): Fragment {
62 | return HomeFragment()
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/home/ScreenReachableFromHome.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.home
2 |
3 | enum class ScreenReachableFromHome(val description: String) {
4 | UI_THREAD_DEMO("UI Thread"),
5 | BACKGROUND_THREAD_DEMO("Background Thread"),
6 | BASIC_COROUTINES_DEMO("Basic Coroutines"),
7 | EXERCISE_1("Exercise 1"),
8 | COROUTINES_CANCELLATION_DEMO("Coroutines Cancellation"),
9 | EXERCISE_2("Exercise 2"),
10 | CONCURRENT_COROUTINES_DEMO("Concurrent Coroutines"),
11 | SCOPE_CHILDREN_CANCELLATION_DEMO("Scope Children Cancellation"),
12 | EXERCISE_3("Exercise 3"),
13 | SCOPE_CANCELLATION_DEMO("Scope Cancellation"),
14 | VIEWMODEL_DEMO("ViewModel"),
15 | EXERCISE_4("Exercise 4"),
16 | DESIGN_DEMO("Design Demo"),
17 | EXERCISE_5("Exercise 5"),
18 | COROUTINES_CANCELLATION_COOPERATIVE_DEMO("Coroutines Cancellation Cooperative"),
19 | COROUTINES_CANCELLATION_COOPERATIVE_2_DEMO("Coroutines Cancellation Cooperative 2"),
20 | EXERCISE_6("Exercise 6"),
21 | NON_CANCELLABLE_DEMO("Non-Cancellable"),
22 | EXERCISE_8("Exercise 8"),
23 | EXERCISE_9("Exercise 9"),
24 | UNCAUGHT_EXCEPTION_DEMO("Uncaught Exception"),
25 | EXERCISE_10("Exercise 10"),
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise1/Exercise1SolutionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise1
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.*
7 | import android.widget.Button
8 | import android.widget.EditText
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger
14 | import com.techyourchance.coroutines.exercises.exercise1.GetReputationEndpoint
15 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
16 | import kotlinx.coroutines.CoroutineScope
17 | import kotlinx.coroutines.Dispatchers
18 | import kotlinx.coroutines.launch
19 | import kotlinx.coroutines.withContext
20 |
21 | class Exercise1SolutionFragment : BaseFragment() {
22 |
23 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
24 |
25 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_1.description
26 |
27 | private lateinit var edtUserId: EditText
28 | private lateinit var btnGetReputation: Button
29 |
30 | private lateinit var getReputationEndpoint: GetReputationEndpoint
31 |
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | getReputationEndpoint = compositionRoot.getReputationEndpoint
35 | }
36 |
37 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
38 | val view = inflater.inflate(R.layout.fragment_exercise_1, container, false)
39 |
40 | edtUserId = view.findViewById(R.id.edt_user_id)
41 | edtUserId.addTextChangedListener(object : TextWatcher {
42 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
43 |
44 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
45 | btnGetReputation.isEnabled = !s.isNullOrEmpty()
46 | }
47 |
48 | override fun afterTextChanged(s: Editable?) {}
49 | })
50 |
51 | btnGetReputation = view.findViewById(R.id.btn_get_reputation)
52 | btnGetReputation.setOnClickListener {
53 | logThreadInfo("button callback")
54 | coroutineScope.launch {
55 | btnGetReputation.isEnabled = false
56 | val reputation = getReputationForUser(edtUserId.text.toString())
57 | Toast.makeText(requireContext(), "reputation: $reputation", Toast.LENGTH_SHORT).show()
58 | btnGetReputation.isEnabled = true
59 | }
60 | }
61 |
62 | return view
63 | }
64 |
65 | private suspend fun getReputationForUser(userId: String): Int {
66 | return withContext(Dispatchers.Default) {
67 | logThreadInfo("getReputationForUser()")
68 | getReputationEndpoint.getReputation(userId)
69 | }
70 | }
71 |
72 | private fun logThreadInfo(message: String) {
73 | ThreadInfoLogger.logThreadInfo(message)
74 | }
75 |
76 | companion object {
77 | fun newInstance(): Fragment {
78 | return Exercise1SolutionFragment()
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise2/Exercise2SolutionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise2
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.*
7 | import android.widget.Button
8 | import android.widget.EditText
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger
14 | import com.techyourchance.coroutines.exercises.exercise1.GetReputationEndpoint
15 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
16 | import kotlinx.coroutines.*
17 |
18 | class Exercise2SolutionFragment : BaseFragment() {
19 |
20 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
21 |
22 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_1.description
23 |
24 | private lateinit var edtUserId: EditText
25 | private lateinit var btnGetReputation: Button
26 |
27 | private lateinit var getReputationEndpoint: GetReputationEndpoint
28 |
29 | private var job: Job? = null
30 |
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | getReputationEndpoint = compositionRoot.getReputationEndpoint
34 | }
35 |
36 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
37 | val view = inflater.inflate(R.layout.fragment_exercise_2, container, false)
38 |
39 | edtUserId = view.findViewById(R.id.edt_user_id)
40 | edtUserId.addTextChangedListener(object : TextWatcher {
41 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
42 |
43 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
44 | btnGetReputation.isEnabled = !s.isNullOrEmpty()
45 | }
46 |
47 | override fun afterTextChanged(s: Editable?) {}
48 | })
49 |
50 | btnGetReputation = view.findViewById(R.id.btn_get_reputation)
51 | btnGetReputation.setOnClickListener {
52 | logThreadInfo("button callback")
53 | job = coroutineScope.launch {
54 | btnGetReputation.isEnabled = false
55 | val reputation = getReputationForUser(edtUserId.text.toString())
56 | Toast.makeText(requireContext(), "reputation: $reputation", Toast.LENGTH_SHORT).show()
57 | btnGetReputation.isEnabled = true
58 | }
59 | }
60 |
61 | return view
62 | }
63 |
64 | override fun onStop() {
65 | super.onStop()
66 | job?.cancel()
67 | btnGetReputation.isEnabled = true
68 | }
69 |
70 | private suspend fun getReputationForUser(userId: String): Int {
71 | return withContext(Dispatchers.Default) {
72 | logThreadInfo("getReputationForUser()")
73 | getReputationEndpoint.getReputation(userId)
74 | }
75 | }
76 |
77 | private fun logThreadInfo(message: String) {
78 | ThreadInfoLogger.logThreadInfo(message)
79 | }
80 |
81 | companion object {
82 | fun newInstance(): Fragment {
83 | return Exercise2SolutionFragment()
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise3/Exercise3SolutionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise3
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.*
7 | import android.widget.Button
8 | import android.widget.EditText
9 | import android.widget.TextView
10 | import android.widget.Toast
11 | import androidx.fragment.app.Fragment
12 | import com.techyourchance.coroutines.R
13 | import com.techyourchance.coroutines.common.BaseFragment
14 | import com.techyourchance.coroutines.common.ThreadInfoLogger
15 | import com.techyourchance.coroutines.exercises.exercise1.GetReputationEndpoint
16 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
17 | import kotlinx.coroutines.*
18 |
19 | class Exercise3SolutionFragment : BaseFragment() {
20 |
21 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
22 |
23 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_3.description
24 |
25 | private lateinit var edtUserId: EditText
26 | private lateinit var btnGetReputation: Button
27 | private lateinit var txtElapsedTime: TextView
28 |
29 | private lateinit var getReputationEndpoint: GetReputationEndpoint
30 |
31 | private var jobElapsedTime: Job? = null
32 |
33 | override fun onCreate(savedInstanceState: Bundle?) {
34 | super.onCreate(savedInstanceState)
35 | getReputationEndpoint = compositionRoot.getReputationEndpoint
36 | }
37 |
38 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
39 | val view = inflater.inflate(R.layout.fragment_exercise_3, container, false)
40 |
41 | txtElapsedTime = view.findViewById(R.id.txt_elapsed_time)
42 |
43 | edtUserId = view.findViewById(R.id.edt_user_id)
44 | edtUserId.addTextChangedListener(object : TextWatcher {
45 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
46 |
47 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
48 | btnGetReputation.isEnabled = !s.isNullOrEmpty()
49 | }
50 |
51 | override fun afterTextChanged(s: Editable?) {}
52 | })
53 |
54 | btnGetReputation = view.findViewById(R.id.btn_get_reputation)
55 | btnGetReputation.setOnClickListener {
56 | logThreadInfo("button callback")
57 |
58 | jobElapsedTime = coroutineScope.launch {
59 | updateElapsedTime()
60 | }
61 |
62 | coroutineScope.launch {
63 | btnGetReputation.isEnabled = false
64 | val reputation = getReputationForUser(edtUserId.text.toString())
65 | Toast.makeText(requireContext(), "reputation: $reputation", Toast.LENGTH_SHORT).show()
66 | btnGetReputation.isEnabled = true
67 | jobElapsedTime?.cancel()
68 | }
69 | }
70 |
71 | return view
72 | }
73 |
74 | override fun onStop() {
75 | super.onStop()
76 | coroutineScope.coroutineContext.cancelChildren()
77 | btnGetReputation.isEnabled = true
78 | }
79 |
80 | private suspend fun updateElapsedTime() {
81 | val startTimeNano = System.nanoTime()
82 | while (true) {
83 | delay(100)
84 | val elapsedTimeNano = System.nanoTime() - startTimeNano
85 | val elapsedTimeMs = elapsedTimeNano / 1000000
86 | txtElapsedTime.text = "Elapsed time: $elapsedTimeMs ms"
87 | }
88 | }
89 |
90 | private suspend fun getReputationForUser(userId: String): Int {
91 | return withContext(Dispatchers.Default) {
92 | logThreadInfo("getReputationForUser()")
93 | getReputationEndpoint.getReputation(userId)
94 | }
95 | }
96 |
97 | private fun logThreadInfo(message: String) {
98 | ThreadInfoLogger.logThreadInfo(message)
99 | }
100 |
101 | companion object {
102 | fun newInstance(): Fragment {
103 | return Exercise3SolutionFragment()
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise4/FactorialUseCaseSolution.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise4
2 |
3 | import java.math.BigInteger
4 |
5 | import androidx.annotation.WorkerThread
6 | import kotlinx.coroutines.*
7 | import java.util.concurrent.TimeUnit
8 |
9 | class FactorialUseCaseSolution {
10 |
11 | public sealed class Result {
12 | class Success(val result: BigInteger) : Result()
13 | object Timeout : Result()
14 | }
15 |
16 | suspend fun computeFactorial(argument: Int, timeout: Int) : Result = withContext(Dispatchers.IO) {
17 | try {
18 | withTimeout(timeMillis = timeout.toLong()) {
19 | val computationRanges = getComputationRanges(argument)
20 |
21 | val partialProductsForRanges = computePartialProducts(computationRanges)
22 |
23 | val result = computeFinalResult(partialProductsForRanges)
24 |
25 | Result.Success(result)
26 | }
27 | } catch (e : TimeoutCancellationException) {
28 | Result.Timeout
29 | }
30 |
31 | }
32 |
33 | private fun getComputationRanges(factorialArgument: Int) : Array {
34 | val numberOfThreads = getNumberOfThreads(factorialArgument)
35 |
36 | val threadsComputationRanges = Array(numberOfThreads) { ComputationRange(0, 0) }
37 |
38 | val computationRangeSize = factorialArgument / numberOfThreads
39 |
40 | var nextComputationRangeEnd = factorialArgument.toLong()
41 |
42 | for (i in numberOfThreads - 1 downTo 0) {
43 | threadsComputationRanges[i] = ComputationRange(
44 | nextComputationRangeEnd - computationRangeSize + 1,
45 | nextComputationRangeEnd
46 | )
47 | nextComputationRangeEnd = threadsComputationRanges[i].start - 1
48 | }
49 |
50 | // add potentially "remaining" values to first thread's range
51 | threadsComputationRanges[0] = ComputationRange(1, threadsComputationRanges[0].end)
52 |
53 | return threadsComputationRanges
54 | }
55 |
56 | private fun getNumberOfThreads(factorialArgument: Int): Int {
57 | return if (factorialArgument < 20)
58 | 1
59 | else
60 | Runtime.getRuntime().availableProcessors()
61 | }
62 |
63 | private suspend fun computePartialProducts(computationRanges: Array) : List = coroutineScope {
64 | return@coroutineScope withContext(Dispatchers.IO) {
65 | return@withContext computationRanges.map {
66 | computeProductForRangeAsync(it)
67 | }.awaitAll()
68 | }
69 | }
70 |
71 | private fun CoroutineScope.computeProductForRangeAsync(computationRange: ComputationRange) : Deferred = async(Dispatchers.IO) {
72 | val rangeStart = computationRange.start
73 | val rangeEnd = computationRange.end
74 |
75 | var product = BigInteger("1")
76 | for (num in rangeStart..rangeEnd) {
77 | if (!isActive) {
78 | break
79 | }
80 | product = product.multiply(BigInteger(num.toString()))
81 | }
82 |
83 | return@async product
84 | }
85 |
86 | private suspend fun computeFinalResult(partialProducts: List): BigInteger = withContext(Dispatchers.IO) {
87 | var result = BigInteger("1")
88 | for (partialProduct in partialProducts) {
89 | if (!isActive) {
90 | break
91 | }
92 | result = result.multiply(partialProduct)
93 | }
94 | return@withContext result
95 | }
96 |
97 | private data class ComputationRange(val start: Long, val end: Long)
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise5/Exercise5SolutionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise5
2 |
3 | import android.os.Bundle
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.*
7 | import android.widget.Button
8 | import android.widget.EditText
9 | import android.widget.TextView
10 | import android.widget.Toast
11 | import androidx.fragment.app.Fragment
12 | import com.techyourchance.coroutines.R
13 | import com.techyourchance.coroutines.common.BaseFragment
14 | import com.techyourchance.coroutines.common.ThreadInfoLogger
15 | import com.techyourchance.coroutines.exercises.exercise1.GetReputationEndpoint
16 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
17 | import kotlinx.coroutines.*
18 |
19 | class Exercise5SolutionFragment : BaseFragment() {
20 |
21 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
22 |
23 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_5.description
24 |
25 | private lateinit var getReputationUseCase: GetReputationUseCase
26 |
27 | private lateinit var edtUserId: EditText
28 | private lateinit var btnGetReputation: Button
29 | private lateinit var txtElapsedTime: TextView
30 |
31 | private var job: Job? = null
32 |
33 | override fun onCreate(savedInstanceState: Bundle?) {
34 | super.onCreate(savedInstanceState)
35 | getReputationUseCase = compositionRoot.getReputationUseCase
36 | }
37 |
38 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
39 | val view = inflater.inflate(R.layout.fragment_exercise_5, container, false)
40 |
41 | txtElapsedTime = view.findViewById(R.id.txt_elapsed_time)
42 |
43 | edtUserId = view.findViewById(R.id.edt_user_id)
44 | edtUserId.addTextChangedListener(object : TextWatcher {
45 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
46 |
47 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
48 | btnGetReputation.isEnabled = !s.isNullOrEmpty()
49 | }
50 |
51 | override fun afterTextChanged(s: Editable?) {}
52 | })
53 |
54 | btnGetReputation = view.findViewById(R.id.btn_get_reputation)
55 | btnGetReputation.setOnClickListener {
56 | logThreadInfo("button callback")
57 | job = coroutineScope.launch {
58 | btnGetReputation.isEnabled = false
59 | val reputation = getReputationUseCase.getReputationForUser(edtUserId.text.toString())
60 | Toast.makeText(requireContext(), "reputation: $reputation", Toast.LENGTH_SHORT).show()
61 | btnGetReputation.isEnabled = true
62 | }
63 | }
64 |
65 | return view
66 | }
67 |
68 | override fun onStop() {
69 | super.onStop()
70 | job?.cancel()
71 | btnGetReputation.isEnabled = true
72 | }
73 |
74 | private fun logThreadInfo(message: String) {
75 | ThreadInfoLogger.logThreadInfo(message)
76 | }
77 |
78 | companion object {
79 | fun newInstance(): Fragment {
80 | return Exercise5SolutionFragment()
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise5/GetReputationUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise5
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
4 | import com.techyourchance.coroutines.exercises.exercise1.GetReputationEndpoint
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.withContext
7 |
8 | class GetReputationUseCase(private val getReputationEndpoint: GetReputationEndpoint) {
9 |
10 | suspend fun getReputationForUser(userId: String): Int {
11 | return withContext(Dispatchers.Default) {
12 | logThreadInfo("getReputationForUser()")
13 | getReputationEndpoint.getReputation(userId)
14 | }
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise6/Exercise6SolutionBenchmarkUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise6
2 |
3 | import com.techyourchance.coroutines.common.ThreadInfoLogger
4 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
5 | import com.techyourchance.coroutines.exercises.exercise6.PostBenchmarkResultsEndpoint
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.ensureActive
8 | import kotlinx.coroutines.isActive
9 | import kotlinx.coroutines.withContext
10 |
11 | class Exercise6SolutionBenchmarkUseCase(private val postBenchmarkResultsEndpoint: PostBenchmarkResultsEndpoint) {
12 |
13 | suspend fun executeBenchmark(benchmarkDurationSeconds: Int) = withContext(Dispatchers.Default) {
14 | logThreadInfo("benchmark started")
15 |
16 | val stopTimeNano = System.nanoTime() + benchmarkDurationSeconds * 1_000_000_000L
17 |
18 | var iterationsCount: Long = 0
19 | while (System.nanoTime() < stopTimeNano) {
20 | ensureActive()
21 | iterationsCount++
22 | }
23 |
24 | logThreadInfo("benchmark completed")
25 |
26 | postBenchmarkResultsEndpoint.postBenchmarkResults(benchmarkDurationSeconds, iterationsCount)
27 |
28 | logThreadInfo("benchmark results posted to the server")
29 |
30 | iterationsCount
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise6/Exercise6SolutionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise6
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import android.widget.Toast
10 | import androidx.fragment.app.Fragment
11 | import com.techyourchance.coroutines.R
12 | import com.techyourchance.coroutines.common.BaseFragment
13 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
14 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
15 | import kotlinx.coroutines.*
16 |
17 | class Exercise6SolutionFragment : BaseFragment() {
18 |
19 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
20 |
21 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_6.description
22 |
23 | private lateinit var benchmarkUseCase: Exercise6SolutionBenchmarkUseCase
24 |
25 | private lateinit var btnStart: Button
26 | private lateinit var txtRemainingTime: TextView
27 |
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | super.onCreate(savedInstanceState)
30 | benchmarkUseCase = compositionRoot.exercise6SolutionBenchmarkUseCase
31 | }
32 |
33 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
34 | val view = inflater.inflate(R.layout.fragment_exercise_6, container, false)
35 |
36 | txtRemainingTime = view.findViewById(R.id.txt_remaining_time)
37 |
38 | btnStart = view.findViewById(R.id.btn_start)
39 | btnStart.setOnClickListener {
40 | logThreadInfo("button callback")
41 |
42 | val benchmarkDurationSeconds = 5
43 |
44 | coroutineScope.launch {
45 | updateRemainingTime(benchmarkDurationSeconds)
46 | }
47 |
48 | coroutineScope.launch {
49 | try {
50 | btnStart.isEnabled = false
51 | val iterationsCount = benchmarkUseCase.executeBenchmark(benchmarkDurationSeconds)
52 | Toast.makeText(requireContext(), "$iterationsCount", Toast.LENGTH_SHORT).show()
53 | btnStart.isEnabled = true
54 | } catch (e: CancellationException) {
55 | btnStart.isEnabled = true
56 | txtRemainingTime.text = "done!"
57 | logThreadInfo("benchmark cancelled")
58 | }
59 | }
60 | }
61 |
62 | return view
63 | }
64 |
65 | override fun onStop() {
66 | logThreadInfo("onStop()")
67 | super.onStop()
68 | coroutineScope.coroutineContext.cancelChildren()
69 | }
70 |
71 |
72 | private suspend fun updateRemainingTime(remainingTimeSeconds: Int) {
73 | for (time in remainingTimeSeconds downTo 0) {
74 | if (time > 0) {
75 | logThreadInfo("updateRemainingTime: $time seconds")
76 | txtRemainingTime.text = "$time seconds remaining"
77 | delay(1000)
78 | } else {
79 | txtRemainingTime.text = "done!"
80 | }
81 | }
82 | }
83 |
84 | companion object {
85 | fun newInstance(): Fragment {
86 | return Exercise6SolutionFragment()
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise8/Exercise8SolutionFetchAndCacheUsersUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise8
2 |
3 | import com.techyourchance.coroutines.exercises.exercise8.GetUserEndpoint
4 | import com.techyourchance.coroutines.exercises.exercise8.UsersDao
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.launch
7 | import kotlinx.coroutines.withContext
8 |
9 | class Exercise8SolutionFetchAndCacheUsersUseCase(
10 | private val getUserEndpoint: GetUserEndpoint,
11 | private val usersDao: UsersDao
12 | ) {
13 |
14 | suspend fun fetchAndCacheUsers(userIds: List) = withContext(Dispatchers.Default) {
15 | for (userId in userIds) {
16 | launch {
17 | val user = getUserEndpoint.getUser(userId)
18 | usersDao.upsertUserInfo(user)
19 | }
20 | }
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise8/Exercise8SolutionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise8
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import androidx.fragment.app.Fragment
10 | import com.techyourchance.coroutines.R
11 | import com.techyourchance.coroutines.common.BaseFragment
12 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
13 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
14 | import kotlinx.coroutines.*
15 |
16 | class Exercise8SolutionFragment : BaseFragment() {
17 |
18 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
19 |
20 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_8.description
21 |
22 | private lateinit var fetchAndCacheUsersUseCase: Exercise8SolutionFetchAndCacheUsersUseCase
23 |
24 | private lateinit var btnFetch: Button
25 | private lateinit var txtElapsedTime: TextView
26 |
27 | private val userIds = listOf("bmq81", "gfn12", "gla34")
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | fetchAndCacheUsersUseCase = compositionRoot.exercise8SolutionFetchAndCacheUserUseCase
32 | }
33 |
34 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
35 | val view = inflater.inflate(R.layout.fragment_exercise_8, container, false)
36 |
37 | view.apply {
38 | txtElapsedTime = findViewById(R.id.txt_elapsed_time)
39 | btnFetch = findViewById(R.id.btn_fetch_users)
40 | }
41 |
42 | btnFetch.setOnClickListener {
43 | logThreadInfo("button callback")
44 |
45 | val updateElapsedTimeJob = coroutineScope.launch {
46 | updateElapsedTime()
47 | }
48 |
49 | coroutineScope.launch {
50 | try {
51 | btnFetch.isEnabled = false
52 | fetchAndCacheUsersUseCase.fetchAndCacheUsers(userIds)
53 | updateElapsedTimeJob.cancel()
54 | } catch (e: CancellationException) {
55 | withContext(NonCancellable) {
56 | updateElapsedTimeJob.cancelAndJoin()
57 | txtElapsedTime.text = ""
58 | }
59 | } finally {
60 | withContext(NonCancellable) {
61 | btnFetch.isEnabled = true
62 | }
63 | }
64 | }
65 | }
66 |
67 | return view
68 | }
69 |
70 | override fun onStop() {
71 | logThreadInfo("onStop()")
72 | super.onStop()
73 | coroutineScope.coroutineContext.cancelChildren()
74 | }
75 |
76 |
77 | private suspend fun updateElapsedTime() {
78 | val startTimeNano = System.nanoTime()
79 | while (true) {
80 | delay(100)
81 | val elapsedTimeNano = System.nanoTime() - startTimeNano
82 | val elapsedTimeMs = elapsedTimeNano / 1000000
83 | txtElapsedTime.text = "Elapsed time: $elapsedTimeMs ms"
84 | }
85 | }
86 |
87 | companion object {
88 | fun newInstance(): Fragment {
89 | return Exercise8SolutionFragment()
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise9/Exercise9SolutionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise9
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.Button
8 | import android.widget.TextView
9 | import androidx.fragment.app.Fragment
10 | import com.techyourchance.coroutines.R
11 | import com.techyourchance.coroutines.common.BaseFragment
12 | import com.techyourchance.coroutines.common.ThreadInfoLogger.logThreadInfo
13 | import com.techyourchance.coroutines.exercises.exercise8.User
14 | import com.techyourchance.coroutines.home.ScreenReachableFromHome
15 | import kotlinx.coroutines.*
16 |
17 | class Exercise9SolutionFragment : BaseFragment() {
18 |
19 | private val coroutineScope = CoroutineScope(Dispatchers.Main.immediate)
20 |
21 | override val screenTitle get() = ScreenReachableFromHome.EXERCISE_9.description
22 |
23 | private lateinit var fetchAndCacheUsersUseCase: FetchAndCacheUsersUseCaseSolutionExercise9
24 |
25 | private lateinit var btnFetch: Button
26 | private lateinit var txtElapsedTime: TextView
27 | private lateinit var txtUsers: TextView
28 |
29 |
30 | private val userIds = listOf("bmq81", "gfn12", "gla34")
31 |
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | fetchAndCacheUsersUseCase = compositionRoot.fetchAndCacheUserUseCaseSolutionExercise9
35 | }
36 |
37 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
38 | val view = inflater.inflate(R.layout.fragment_exercise_9, container, false)
39 |
40 | view.apply {
41 | txtElapsedTime = findViewById(R.id.txt_elapsed_time)
42 | btnFetch = findViewById(R.id.btn_fetch_users)
43 | txtUsers = findViewById(R.id.txt_users)
44 | }
45 |
46 | btnFetch.setOnClickListener {
47 | logThreadInfo("button callback")
48 |
49 | val updateElapsedTimeJob = coroutineScope.launch {
50 | updateElapsedTime()
51 | }
52 |
53 | coroutineScope.launch {
54 | try {
55 | btnFetch.isEnabled = false
56 | val users = fetchAndCacheUsersUseCase.fetchAndCacheUsers(userIds)
57 | bindUsers(users)
58 | updateElapsedTimeJob.cancel()
59 | } catch (e: CancellationException) {
60 | withContext(NonCancellable) {
61 | updateElapsedTimeJob.cancelAndJoin()
62 | txtElapsedTime.text = ""
63 | txtUsers.text = ""
64 | }
65 | } finally {
66 | withContext(NonCancellable) {
67 | btnFetch.isEnabled = true
68 | }
69 | }
70 | }
71 | }
72 |
73 | return view
74 | }
75 |
76 | private fun bindUsers(users: List) {
77 | txtUsers.text = users.joinToString("\n") { it.name }
78 | }
79 |
80 | override fun onStop() {
81 | logThreadInfo("onStop()")
82 | super.onStop()
83 | coroutineScope.coroutineContext.cancelChildren()
84 | }
85 |
86 |
87 | private suspend fun updateElapsedTime() {
88 | val startTimeNano = System.nanoTime()
89 | while (true) {
90 | delay(100)
91 | val elapsedTimeNano = System.nanoTime() - startTimeNano
92 | val elapsedTimeMs = elapsedTimeNano / 1000000
93 | txtElapsedTime.text = "Elapsed time: $elapsedTimeMs ms"
94 | }
95 | }
96 |
97 | companion object {
98 | fun newInstance(): Fragment {
99 | return Exercise9SolutionFragment()
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/coroutines/solutions/exercise9/FetchAndCacheUsersUseCaseSolutionExercise9.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise9
2 |
3 | import com.techyourchance.coroutines.exercises.exercise8.GetUserEndpoint
4 | import com.techyourchance.coroutines.exercises.exercise8.User
5 | import com.techyourchance.coroutines.exercises.exercise8.UsersDao
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.async
8 | import kotlinx.coroutines.awaitAll
9 | import kotlinx.coroutines.withContext
10 |
11 | class FetchAndCacheUsersUseCaseSolutionExercise9(
12 | private val getUserEndpoint: GetUserEndpoint,
13 | private val usersDao: UsersDao
14 | ) {
15 |
16 | suspend fun fetchAndCacheUsers(userIds: List): List = withContext(Dispatchers.Default) {
17 | userIds.map { userId ->
18 | async {
19 | val user = getUserEndpoint.getUser(userId)
20 | usersDao.upsertUserInfo(user)
21 | user
22 | }
23 | }.awaitAll()
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/drawable-hdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/drawable-mdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/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-xhdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/drawable-xhdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
24 |
25 |
35 |
36 |
46 |
47 |
48 |
49 |
50 |
51 |
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_3.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
26 |
27 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
16 |
24 |
25 |
33 |
34 |
39 |
40 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_5.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
26 |
27 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_6.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_8.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_9.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
22 |
23 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
25 |
26 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_loop_iterations_demo.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_premium_customer.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_viewmodel_demo.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_screen_reachable_from_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #FFFFFF
7 | #000000
8 | #00000000
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Android Coroutines
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/common/TestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.common
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Job
5 |
6 | object TestUtils {
7 |
8 | fun CoroutineScope.printCoroutineScopeInfo() {
9 | println()
10 | println("CoroutineScope: $this")
11 | println("CoroutineContext: ${this.coroutineContext}")
12 | println("Job: ${this.coroutineContext[Job]}")
13 | println()
14 | }
15 |
16 | fun printJobsHierarchy(job: Job, nestLevel: Int = 0) {
17 | val indent = " ".repeat(nestLevel)
18 | println("$indent- $job")
19 | for (childJob in job.children) {
20 | printJobsHierarchy(childJob, nestLevel + 1)
21 | }
22 | if (nestLevel == 0) {
23 | println()
24 | }
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/async/AsyncCoroutineBuilderDemoTest.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.async
2 |
3 | import kotlinx.coroutines.*
4 | import org.junit.Test
5 | import kotlin.math.pow
6 |
7 | class AsyncCoroutineBuilderDemoTest {
8 |
9 | @Test
10 | fun correctUseOfCoroutinesVariant1() = runBlocking {
11 | withContext(Dispatchers.Default) {
12 | val deferreds = mutableListOf>()
13 |
14 | for (duration in 1..5) {
15 | deferreds.add(
16 | async {
17 | val startTimeNano = System.nanoTime()
18 | var iterations = 0
19 | while (System.nanoTime() < startTimeNano + (duration * 10f.pow(9))) {
20 | iterations++
21 | }
22 | iterations
23 | }
24 | )
25 | }
26 |
27 | var totalIterations = 0
28 | for (deferred in deferreds) {
29 | totalIterations += deferred.await()
30 | }
31 |
32 | println("total iterations: $totalIterations")
33 | }
34 | }
35 |
36 | @Test
37 | fun correctUseOfCoroutinesVariant2() = runBlocking {
38 | withContext(Dispatchers.Default) {
39 | val totalIterations = (1..5).toList().map { duration ->
40 | async {
41 | val startTimeNano = System.nanoTime()
42 | var iterations = 0
43 | while (System.nanoTime() < startTimeNano + (duration * 10f.pow(9))) {
44 | iterations++
45 | }
46 | iterations
47 | }
48 | }.awaitAll().sum()
49 |
50 | println("total iterations: $totalIterations")
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/cancellationonexception/CancellationOnExceptionDemoTest.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.cancellationonexception
2 |
3 | import kotlinx.coroutines.*
4 | import org.junit.Test
5 | import java.lang.RuntimeException
6 |
7 | class CancellationOnExceptionDemoTest {
8 |
9 | @Test
10 | fun concurrentCoroutines() {
11 | runBlocking {
12 | val scopeJob = Job()
13 | val scope = CoroutineScope(scopeJob + Dispatchers.Default)
14 | val job1 = scope.launch {
15 | delay(100)
16 | println("inside coroutine")
17 | }
18 | val job2 = scope.launch {
19 | delay(50)
20 | }
21 | joinAll(job1, job2)
22 | println("scopeJob: $scopeJob")
23 | println("job1: $job1")
24 | println("job2: $job2")
25 | }
26 | Thread.sleep(100)
27 | println("test completed")
28 | }
29 |
30 | @Test
31 | fun uncaughtExceptionInConcurrentCoroutines() {
32 | runBlocking {
33 | val scopeJob = Job()
34 | val scope = CoroutineScope(scopeJob + Dispatchers.Default)
35 | val job1 = scope.launch {
36 | delay(100)
37 | println("inside coroutine")
38 | }
39 | val job2 = scope.launch {
40 | delay(50)
41 | throw RuntimeException()
42 | }
43 | joinAll(job1, job2)
44 | println("scopeJob: $scopeJob")
45 | println("job1: $job1")
46 | println("job2: $job2")
47 | }
48 | Thread.sleep(100)
49 | println("test completed")
50 | }
51 |
52 |
53 | @Test
54 | fun uncaughtExceptionInConcurrentCoroutinesWithExceptionHandler() {
55 | runBlocking {
56 | val coroutineExceptionHandler = CoroutineExceptionHandler{ _, throwable ->
57 | println("Caught exception: $throwable")
58 | }
59 |
60 | val scopeJob = Job()
61 | val scope = CoroutineScope(scopeJob + Dispatchers.Default + coroutineExceptionHandler)
62 | val job1 = scope.launch {
63 | delay(100)
64 | println("inside coroutine")
65 | }
66 | val job2 = scope.launch {
67 | delay(50)
68 | throw RuntimeException()
69 | }
70 | joinAll(job1, job2)
71 | println("scopeJob: $scopeJob")
72 | println("job1: $job1")
73 | println("job2: $job2")
74 | }
75 | Thread.sleep(100)
76 | println("test completed")
77 | }
78 |
79 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/coroutineexceptionhandler/CoroutineExceptionHandlerDemoTest.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.coroutineexceptionhandler
2 |
3 | import com.techyourchance.coroutines.common.TestUtils
4 | import com.techyourchance.coroutines.common.TestUtils.printCoroutineScopeInfo
5 | import com.techyourchance.coroutines.common.TestUtils.printJobsHierarchy
6 | import kotlinx.coroutines.*
7 | import kotlinx.coroutines.test.runBlockingTest
8 | import org.junit.Test
9 | import java.lang.Exception
10 | import java.lang.RuntimeException
11 | import kotlin.concurrent.thread
12 | import kotlin.coroutines.CoroutineContext
13 | import kotlin.coroutines.EmptyCoroutineContext
14 |
15 | class CoroutineExceptionHandlerDemoTest {
16 |
17 | @Test
18 | fun uncaughtException() {
19 | runBlocking {
20 | val scope = CoroutineScope(Dispatchers.Default)
21 | val job = scope.launch {
22 | delay(50)
23 | throw RuntimeException()
24 | }
25 | job.join()
26 | }
27 | Thread.sleep(100)
28 | println("test completed")
29 | }
30 |
31 | @Test
32 | fun caughtException() {
33 | runBlocking {
34 |
35 | val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
36 | println("Caught exception: $throwable")
37 | }
38 |
39 | val scope = CoroutineScope(Dispatchers.Default + coroutineExceptionHandler)
40 | val job = scope.launch {
41 | delay(50)
42 | throw RuntimeException()
43 | }
44 | job.join()
45 | }
46 | Thread.sleep(100)
47 | println("test completed")
48 | }
49 |
50 | @Test
51 | fun caughtExceptionAtCoroutineLevel() {
52 | runBlocking {
53 |
54 | val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
55 | println("Caught exception: $throwable")
56 | }
57 |
58 | val scope = CoroutineScope(Dispatchers.Default)
59 | val job = scope.launch(coroutineExceptionHandler) {
60 | delay(50)
61 | throw RuntimeException()
62 | }
63 | job.join()
64 | }
65 | Thread.sleep(100)
66 | println("test completed")
67 | }
68 |
69 |
70 |
71 |
72 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/exceptionsinasync/ExceptionsInAsyncDemoTest.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.exceptionsinasync
2 |
3 | import kotlinx.coroutines.*
4 | import org.junit.Test
5 | import java.lang.RuntimeException
6 |
7 | class ExceptionsInAsyncDemoTest {
8 |
9 | @Test
10 | fun uncaughtExceptionInConcurrentCoroutinesWithLaunch() {
11 | runBlocking {
12 | val scopeJob = Job()
13 | val scope = CoroutineScope(scopeJob + Dispatchers.Default)
14 | val job1 = scope.launch {
15 | delay(100)
16 | println("inside coroutine")
17 | }
18 | val job2 = scope.launch {
19 | delay(50)
20 | throw RuntimeException()
21 | }
22 | joinAll(job1, job2)
23 | println("scopeJob: $scopeJob")
24 | println("job1: $job1")
25 | println("job2: $job2")
26 | }
27 | Thread.sleep(100)
28 | println("test completed")
29 | }
30 |
31 | @Test
32 | fun uncaughtExceptionInConcurrentCoroutinesWithAsync() {
33 | runBlocking {
34 | val scopeJob = Job()
35 | val scope = CoroutineScope(scopeJob + Dispatchers.Default)
36 | val job1 = scope.launch {
37 | delay(100)
38 | println("inside coroutine")
39 | }
40 | val job2 = scope.async {
41 | delay(50)
42 | throw RuntimeException()
43 | }
44 | joinAll(job1, job2)
45 | println("scopeJob: $scopeJob")
46 | println("job1: $job1")
47 | println("job2: $job2")
48 | }
49 | Thread.sleep(100)
50 | println("test completed")
51 | }
52 |
53 | @Test
54 | fun uncaughtExceptionInConcurrentCoroutinesWithAsyncAndDeferred() {
55 | runBlocking {
56 | val scopeJob = Job()
57 | val scope = CoroutineScope(scopeJob + Dispatchers.Default)
58 | val job1 = scope.launch {
59 | delay(100)
60 | println("inside coroutine")
61 | }
62 | val deferred = scope.async {
63 | delay(50)
64 | throw RuntimeException("My EXCEPTION")
65 | }
66 | joinAll(job1, deferred)
67 | println("scopeJob: $scopeJob")
68 | println("job1: $job1")
69 | println("job2: $deferred")
70 | try {
71 | val result = deferred.await()
72 | } catch (e: Throwable) {
73 | println("Caught exception: $e")
74 | }
75 | }
76 | Thread.sleep(100)
77 | println("test completed")
78 | }
79 |
80 | @Test
81 | fun uncaughtExceptionInConcurrentCoroutinesWithAsyncAndDeferredAndSupervision() {
82 | runBlocking {
83 | val scopeJob = SupervisorJob()
84 | val scope = CoroutineScope(scopeJob + Dispatchers.Default)
85 | val job1 = scope.launch {
86 | delay(100)
87 | println("inside coroutine")
88 | }
89 | val deferred = scope.async {
90 | delay(50)
91 | throw RuntimeException("My EXCEPTION")
92 | }
93 | joinAll(job1, deferred)
94 | println("scopeJob: $scopeJob")
95 | println("job1: $job1")
96 | println("job2: $deferred")
97 | try {
98 | val result = deferred.await()
99 | } catch (e: Throwable) {
100 | println("Caught exception: $e")
101 | }
102 | }
103 | Thread.sleep(100)
104 | println("test completed")
105 | }
106 |
107 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/incorrectparalleldecomposition/IncorrectParallelDecompositionDemoTest.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.incorrectparalleldecomposition
2 |
3 | import kotlinx.coroutines.*
4 | import org.junit.Test
5 | import kotlin.math.pow
6 |
7 | class IncorrectParallelDecompositionDemoTest {
8 |
9 | @Test
10 | fun wrongUseOfCoroutines() = runBlocking {
11 | var totalIterations = 0
12 | withContext(Dispatchers.Default) {
13 | for (duration in 1..5) {
14 | launch {
15 | val startTimeNano = System.nanoTime()
16 | var iterations = 0
17 | while (System.nanoTime() < startTimeNano + (duration * 10f.pow(9))) {
18 | iterations++
19 | }
20 | totalIterations += iterations
21 | }
22 | }
23 | }
24 | println("total iterations: $totalIterations")
25 | }
26 |
27 | @Test
28 | fun atomicityProblemDemo() = runBlocking {
29 | var totalIterations = 0
30 | withContext(Dispatchers.Default) {
31 | repeat(100) {
32 | launch {
33 | delay(100)
34 | totalIterations++
35 | }
36 | }
37 | }
38 | println("total iterations: $totalIterations")
39 | }
40 |
41 |
42 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/java/FibonacciUseCaseAsyncTest.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.java;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import java.math.BigInteger;
7 |
8 | import static org.hamcrest.CoreMatchers.*;
9 | import static org.hamcrest.MatcherAssert.*;
10 |
11 | public class FibonacciUseCaseAsyncTest {
12 |
13 | FibonacciUseCaseAsync.Callback mCallback;
14 | FibonacciUseCaseAsync SUT;
15 |
16 | BigInteger lastResult = null;
17 |
18 | @Before
19 | public void setup() throws Exception {
20 | mCallback = new FibonacciUseCaseAsync.Callback() {
21 | @Override
22 | public void onFibonacciComputed(BigInteger result) {
23 | lastResult = result;
24 | }
25 | };
26 | SUT = new FibonacciUseCaseAsync();
27 | }
28 |
29 | @Test
30 | public void computeFibonacci_0_returns0() throws Exception {
31 | // Arrange
32 | // Act
33 | SUT.computeFibonacci(0, mCallback);
34 | Thread.sleep(10);
35 | // Assert
36 | assertThat(lastResult, is(new BigInteger("0")));
37 | }
38 |
39 | @Test
40 | public void computeFibonacci_1_returns1() throws Exception {
41 | // Arrange
42 | // Act
43 | SUT.computeFibonacci(1, mCallback);
44 | Thread.sleep(10);
45 | // Assert
46 | assertThat(lastResult, is(new BigInteger("1")));
47 | }
48 |
49 | @Test
50 | public void computeFibonacci_10_returnsCorrectAnswer() throws Exception {
51 | // Arrange
52 | // Act
53 | SUT.computeFibonacci(10, mCallback);
54 | Thread.sleep(200);
55 | // Assert
56 | assertThat(lastResult, is(new BigInteger("55")));
57 | }
58 |
59 | @Test
60 | public void computeFibonacci_30_returnsCorrectAnswer() throws Exception {
61 | // Arrange
62 | // Act
63 | SUT.computeFibonacci(30, mCallback);
64 | Thread.sleep(200);
65 | // Assert
66 | assertThat(lastResult, is(new BigInteger("832040")));
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/java/FibonacciUseCaseAsyncUiTest.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.java;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import java.math.BigInteger;
7 |
8 | import static org.hamcrest.CoreMatchers.*;
9 | import static org.hamcrest.MatcherAssert.*;
10 |
11 | public class FibonacciUseCaseAsyncUiTest {
12 |
13 | FibonacciUseCaseAsyncUi.Callback mCallback;
14 | FibonacciUseCaseAsyncUi SUT;
15 |
16 | BigInteger lastResult = null;
17 |
18 | @Before
19 | public void setup() throws Exception {
20 | mCallback = new FibonacciUseCaseAsyncUi.Callback() {
21 | @Override
22 | public void onFibonacciComputed(BigInteger result) {
23 | lastResult = result;
24 | }
25 | };
26 | SUT = new FibonacciUseCaseAsyncUi();
27 | }
28 |
29 | @Test
30 | public void computeFibonacci_0_returns0() throws Exception {
31 | // Arrange
32 | // Act
33 | SUT.computeFibonacci(0, mCallback);
34 | Thread.sleep(10);
35 | // Assert
36 | assertThat(lastResult, is(new BigInteger("0")));
37 | }
38 |
39 | @Test
40 | public void computeFibonacci_1_returns1() throws Exception {
41 | // Arrange
42 | // Act
43 | SUT.computeFibonacci(1, mCallback);
44 | Thread.sleep(10);
45 | // Assert
46 | assertThat(lastResult, is(new BigInteger("1")));
47 | }
48 |
49 | @Test
50 | public void computeFibonacci_10_returnsCorrectAnswer() throws Exception {
51 | // Arrange
52 | // Act
53 | SUT.computeFibonacci(10, mCallback);
54 | Thread.sleep(200);
55 | // Assert
56 | assertThat(lastResult, is(new BigInteger("55")));
57 | }
58 |
59 | @Test
60 | public void computeFibonacci_30_returnsCorrectAnswer() throws Exception {
61 | // Arrange
62 | // Act
63 | SUT.computeFibonacci(30, mCallback);
64 | Thread.sleep(200);
65 | // Assert
66 | assertThat(lastResult, is(new BigInteger("832040")));
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/java/FibonacciUseCaseAsyncUiThreadPosterTest.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.java;
2 |
3 | import com.techyourchance.threadposter.testdoubles.ThreadPostersTestDouble;
4 |
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | import java.math.BigInteger;
9 |
10 | import static org.hamcrest.CoreMatchers.*;
11 | import static org.hamcrest.MatcherAssert.*;
12 |
13 | public class FibonacciUseCaseAsyncUiThreadPosterTest {
14 |
15 | FibonacciUseCaseAsyncUiThreadPoster.Callback mCallback;
16 | FibonacciUseCaseAsyncUiThreadPoster SUT;
17 |
18 | BigInteger lastResult = null;
19 |
20 | ThreadPostersTestDouble mThreadPostersTestDouble;
21 |
22 | @Before
23 | public void setup() throws Exception {
24 | mThreadPostersTestDouble = new ThreadPostersTestDouble();
25 | mCallback = new FibonacciUseCaseAsyncUiThreadPoster.Callback() {
26 | @Override
27 | public void onFibonacciComputed(BigInteger result) {
28 | lastResult = result;
29 | }
30 | };
31 | SUT = new FibonacciUseCaseAsyncUiThreadPoster(
32 | mThreadPostersTestDouble.getBackgroundTestDouble(),
33 | mThreadPostersTestDouble.getUiTestDouble()
34 | );
35 | }
36 |
37 | @Test
38 | public void computeFibonacci_0_returns0() throws Exception {
39 | // Arrange
40 | // Act
41 | SUT.computeFibonacci(0, mCallback);
42 | mThreadPostersTestDouble.join();
43 | // Assert
44 | assertThat(lastResult, is(new BigInteger("0")));
45 | }
46 |
47 | @Test
48 | public void computeFibonacci_1_returns1() throws Exception {
49 | // Arrange
50 | // Act
51 | SUT.computeFibonacci(1, mCallback);
52 | mThreadPostersTestDouble.join();
53 | // Assert
54 | assertThat(lastResult, is(new BigInteger("1")));
55 | }
56 |
57 | @Test
58 | public void computeFibonacci_10_returnsCorrectAnswer() throws Exception {
59 | // Arrange
60 | // Act
61 | SUT.computeFibonacci(10, mCallback);
62 | mThreadPostersTestDouble.join();
63 | // Assert
64 | assertThat(lastResult, is(new BigInteger("55")));
65 | }
66 |
67 | @Test
68 | public void computeFibonacci_30_returnsCorrectAnswer() throws Exception {
69 | // Arrange
70 | // Act
71 | SUT.computeFibonacci(30, mCallback);
72 | mThreadPostersTestDouble.join();
73 | // Assert
74 | assertThat(lastResult, is(new BigInteger("832040")));
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/java/FibonacciUseCaseTest.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.java;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import java.math.BigInteger;
7 |
8 | import static org.hamcrest.CoreMatchers.*;
9 | import static org.hamcrest.MatcherAssert.*;
10 |
11 | public class FibonacciUseCaseTest {
12 |
13 | FibonacciUseCase SUT;
14 |
15 | @Before
16 | public void setup() throws Exception {
17 | SUT = new FibonacciUseCase();
18 | }
19 |
20 | @Test
21 | public void computeFibonacci_0_returns0() throws Exception {
22 | // Arrange
23 | // Act
24 | BigInteger result = SUT.computeFibonacci(0);
25 | // Assert
26 | assertThat(result, is(new BigInteger("0")));
27 | }
28 |
29 | @Test
30 | public void computeFibonacci_1_returns1() throws Exception {
31 | // Arrange
32 | // Act
33 | BigInteger result = SUT.computeFibonacci(1);
34 | // Assert
35 | assertThat(result, is(new BigInteger("1")));
36 | }
37 |
38 | @Test
39 | public void computeFibonacci_10_returnsCorrectAnswer() throws Exception {
40 | // Arrange
41 | // Act
42 | BigInteger result = SUT.computeFibonacci(10);
43 | // Assert
44 | assertThat(result, is(new BigInteger("55")));
45 | }
46 |
47 | @Test
48 | public void computeFibonacci_30_returnsCorrectAnswer() throws Exception {
49 | // Arrange
50 | // Act
51 | BigInteger result = SUT.computeFibonacci(30);
52 | // Assert
53 | assertThat(result, is(new BigInteger("832040")));
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/kotlin/FibonacciUseCaseAsyncUiCoroutinesTest.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.kotlin
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.test.TestCoroutineDispatcher
6 | import kotlinx.coroutines.test.resetMain
7 | import kotlinx.coroutines.test.runBlockingTest
8 | import kotlinx.coroutines.test.setMain
9 | import org.hamcrest.CoreMatchers
10 | import org.hamcrest.CoreMatchers.`is`
11 | import org.hamcrest.MatcherAssert
12 | import org.hamcrest.MatcherAssert.assertThat
13 | import org.junit.After
14 | import org.junit.Before
15 | import org.junit.Test
16 | import java.math.BigInteger
17 |
18 | @ExperimentalCoroutinesApi
19 | class FibonacciUseCaseAsyncUiCoroutinesTest {
20 |
21 | private lateinit var callback: FibonacciUseCaseAsyncUiCoroutines.Callback
22 | private lateinit var SUT: FibonacciUseCaseAsyncUiCoroutines
23 |
24 | var lastResult: BigInteger? = null
25 | private val testCoroutineDispatcher = TestCoroutineDispatcher()
26 |
27 | @Before
28 | fun setup() {
29 | Dispatchers.setMain(testCoroutineDispatcher)
30 | callback = object : FibonacciUseCaseAsyncUiCoroutines.Callback {
31 | override fun onFibonacciComputed(result: BigInteger?) {
32 | lastResult = result
33 | }
34 | }
35 | SUT = FibonacciUseCaseAsyncUiCoroutines(testCoroutineDispatcher)
36 | }
37 |
38 | @After
39 | fun tearDown() {
40 | Dispatchers.resetMain()
41 | }
42 |
43 | @Test
44 | fun computeFibonacci_0_returns0() {
45 | testCoroutineDispatcher.runBlockingTest {
46 | // Arrange
47 | // Act
48 | SUT.computeFibonacci(0, callback)
49 | // Assert
50 | assertThat(lastResult, `is`(BigInteger("0")))
51 | }
52 | }
53 |
54 | @Test
55 | fun computeFibonacci_1_returns1() {
56 | testCoroutineDispatcher.runBlockingTest {
57 | // Arrange
58 | // Act
59 | SUT.computeFibonacci(1, callback)
60 | // Assert
61 | assertThat(lastResult, `is`(BigInteger("1")))
62 | }
63 | }
64 |
65 | @Test
66 | fun computeFibonacci_10_returnsCorrectAnswer() {
67 | testCoroutineDispatcher.runBlockingTest {
68 | // Arrange
69 | // Act
70 | SUT.computeFibonacci(10, callback)
71 | // Assert
72 | assertThat(lastResult, `is`(BigInteger("55")))
73 | }
74 | }
75 |
76 | @Test
77 | fun computeFibonacci_30_returnsCorrectAnswer() {
78 | testCoroutineDispatcher.runBlockingTest {
79 | // Arrange
80 | // Act
81 | SUT.computeFibonacci(30, callback)
82 | // Assert
83 | assertThat(lastResult, `is`(BigInteger("832040")))
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/structuredconcurrency/kotlin/FibonacciUseCaseUiCoroutinesTest.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.structuredconcurrency.kotlin
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.runBlocking
6 | import kotlinx.coroutines.test.TestCoroutineDispatcher
7 | import kotlinx.coroutines.test.resetMain
8 | import kotlinx.coroutines.test.runBlockingTest
9 | import kotlinx.coroutines.test.setMain
10 | import org.hamcrest.CoreMatchers
11 | import org.hamcrest.CoreMatchers.`is`
12 | import org.hamcrest.MatcherAssert
13 | import org.hamcrest.MatcherAssert.assertThat
14 | import org.junit.After
15 | import org.junit.Before
16 | import org.junit.Test
17 | import java.math.BigInteger
18 |
19 | @ExperimentalCoroutinesApi
20 | class FibonacciUseCaseUiCoroutinesTest {
21 |
22 | private lateinit var SUT: FibonacciUseCaseUiCoroutines
23 |
24 | @Before
25 | fun setup() {
26 | SUT = FibonacciUseCaseUiCoroutines()
27 | }
28 |
29 | @Test
30 | fun computeFibonacci_0_returns0() {
31 | runBlocking {
32 | // Arrange
33 | // Act
34 | val result = SUT.computeFibonacci(0)
35 | // Assert
36 | assertThat(result, `is`(BigInteger("0")))
37 | }
38 | }
39 |
40 | @Test
41 | fun computeFibonacci_1_returns1() {
42 | runBlocking {
43 | // Arrange
44 | // Act
45 | val result = SUT.computeFibonacci(1)
46 | // Assert
47 | assertThat(result, `is`(BigInteger("1")))
48 | }
49 | }
50 |
51 |
52 | @Test
53 | fun computeFibonacci_10_returnsCorrectResult() {
54 | runBlocking {
55 | // Arrange
56 | // Act
57 | val result = SUT.computeFibonacci(10)
58 | // Assert
59 | assertThat(result, `is`(BigInteger("55")))
60 | }
61 | }
62 |
63 | @Test
64 | fun computeFibonacci_30_returnsCorrectResult() {
65 | runBlocking {
66 | // Arrange
67 | // Act
68 | val result = SUT.computeFibonacci(30)
69 | // Assert
70 | assertThat(result, `is`(BigInteger("832040")))
71 | }
72 | }
73 |
74 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/demonstrations/supervisorjob/SupervisorJobDemoTest.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.demonstrations.supervisorjob
2 |
3 | import kotlinx.coroutines.*
4 | import org.junit.Test
5 | import java.lang.RuntimeException
6 |
7 | class SupervisorJobDemoTest {
8 |
9 | @Test
10 | fun uncaughtExceptionInConcurrentCoroutines() {
11 | runBlocking {
12 | val scopeJob = Job()
13 | val scope = CoroutineScope(scopeJob + Dispatchers.Default)
14 | val job1 = scope.launch {
15 | delay(100)
16 | println("inside coroutine")
17 | }
18 | val job2 = scope.launch {
19 | delay(50)
20 | throw RuntimeException()
21 | }
22 | joinAll(job1, job2)
23 | println("scopeJob: $scopeJob")
24 | println("job1: $job1")
25 | println("job2: $job2")
26 | }
27 | Thread.sleep(100)
28 | println("test completed")
29 | }
30 |
31 | @Test
32 | fun uncaughtExceptionInConcurrentCoroutinesWithSupervisorJob() {
33 | runBlocking {
34 | val scopeJob = SupervisorJob()
35 | val scope = CoroutineScope(scopeJob + Dispatchers.Default)
36 | val job1 = scope.launch {
37 | delay(100)
38 | println("inside coroutine")
39 | }
40 | val job2 = scope.launch {
41 | delay(50)
42 | throw RuntimeException()
43 | }
44 | joinAll(job1, job2)
45 | println("scopeJob: $scopeJob")
46 | println("job1: $job1")
47 | println("job2: $job2")
48 | }
49 | Thread.sleep(100)
50 | println("test completed")
51 | }
52 |
53 | @Test
54 | fun uncaughtExceptionInConcurrentCoroutinesWithSupervisorJobAndExceptionHandler() {
55 | runBlocking {
56 | val coroutineExceptionHandler = CoroutineExceptionHandler { _, _ ->
57 | println("Caught exception")
58 | }
59 | val scopeJob = SupervisorJob()
60 | val scope = CoroutineScope(scopeJob + Dispatchers.Default + coroutineExceptionHandler)
61 | val job1 = scope.launch {
62 | delay(100)
63 | println("inside coroutine")
64 | }
65 | val job2 = scope.launch {
66 | delay(50)
67 | throw RuntimeException()
68 | }
69 | joinAll(job1, job2)
70 | println("scopeJob: $scopeJob")
71 | println("job1: $job1")
72 | println("job2: $job2")
73 | }
74 | Thread.sleep(100)
75 | println("test completed")
76 | }
77 |
78 |
79 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/exercises/exercise4/FactorialUseCaseTest.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise4
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.runBlocking
6 | import kotlinx.coroutines.test.TestCoroutineDispatcher
7 | import kotlinx.coroutines.test.resetMain
8 | import kotlinx.coroutines.test.runBlockingTest
9 | import kotlinx.coroutines.test.setMain
10 | import org.hamcrest.CoreMatchers
11 | import org.hamcrest.CoreMatchers.`is`
12 | import org.hamcrest.CoreMatchers.instanceOf
13 | import org.hamcrest.MatcherAssert
14 | import org.hamcrest.MatcherAssert.assertThat
15 | import org.junit.After
16 | import org.junit.Assert
17 | import org.junit.Before
18 | import org.junit.Test
19 | import java.math.BigInteger
20 |
21 | class FactorialUseCaseTest {
22 |
23 | private lateinit var SUT: FactorialUseCase
24 |
25 | @Before
26 | fun setup() {
27 | SUT = FactorialUseCase()
28 | }
29 |
30 | @Test
31 | fun computeFactorial_0_returns1() {
32 | runBlocking {
33 | // Arrange
34 | // Act
35 | val result = SUT.computeFactorial(0, 1000)
36 | // Assert
37 | assertThat((result as FactorialUseCase.Result.Success).result, `is`(BigInteger("1")))
38 | }
39 | }
40 |
41 | @Test
42 | fun computeFactorial_1_returns1() {
43 | }
44 |
45 | @Test
46 | fun computeFactorial_10_returnsCorrectAnswer() {
47 | }
48 |
49 | @Test
50 | fun computeFactorial_30_returnsCorrectAnswer() {
51 | }
52 |
53 |
54 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/exercises/exercise7/Exercise7Test.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.exercises.exercise7
2 |
3 | import com.techyourchance.coroutines.common.TestUtils
4 | import com.techyourchance.coroutines.common.TestUtils.printCoroutineScopeInfo
5 | import com.techyourchance.coroutines.common.TestUtils.printJobsHierarchy
6 | import kotlinx.coroutines.*
7 | import kotlinx.coroutines.test.runBlockingTest
8 | import org.junit.Test
9 | import java.lang.Exception
10 | import kotlin.coroutines.EmptyCoroutineContext
11 |
12 | class Exercise7Test {
13 |
14 | /*
15 | Write nested withContext blocks, explore the resulting Job's hierarchy, test cancellation
16 | of the outer scope
17 | */
18 | @Test
19 | fun nestedWithContext() {
20 | runBlocking {
21 | val scopeJob = Job()
22 | val scope = CoroutineScope(scopeJob + CoroutineName("outer scope") + Dispatchers.IO)
23 |
24 |
25 | scopeJob.join()
26 | println("test done")
27 | }
28 | }
29 |
30 | /*
31 | Launch new coroutine inside another coroutine, explore the resulting Job's hierarchy, test cancellation
32 | of the outer scope, explore structured concurrency
33 | */
34 | @Test
35 | fun nestedLaunchBuilders() {
36 | runBlocking {
37 | val scopeJob = Job()
38 | val scope = CoroutineScope(scopeJob + CoroutineName("outer scope") + Dispatchers.IO)
39 |
40 |
41 | scopeJob.join()
42 | println("test done")
43 | }
44 | }
45 |
46 | /*
47 | Launch new coroutine on "outer scope" inside another coroutine, explore the resulting Job's hierarchy,
48 | test cancellation of the outer scope, explore structured concurrency
49 | */
50 | @Test
51 | fun nestedCoroutineInOuterScope() {
52 | runBlocking {
53 | val scopeJob = Job()
54 | val scope = CoroutineScope(scopeJob + CoroutineName("outer scope") + Dispatchers.IO)
55 |
56 |
57 | scopeJob.join()
58 | println("test done")
59 | }
60 | }
61 |
62 |
63 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/techyourchance/coroutines/solutions/exercise4/FactorialUseCaseSolutionTest.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.coroutines.solutions.exercise4
2 |
3 | import kotlinx.coroutines.runBlocking
4 | import org.hamcrest.CoreMatchers.`is`
5 | import org.hamcrest.MatcherAssert.assertThat
6 | import org.junit.Before
7 | import org.junit.Test
8 | import java.math.BigInteger
9 |
10 | class FactorialUseCaseSolutionTest {
11 |
12 | private lateinit var SUT: FactorialUseCaseSolution
13 |
14 | @Before
15 | fun setup() {
16 | SUT = FactorialUseCaseSolution()
17 | }
18 |
19 | @Test
20 | fun computeFactorial_0_returns1() {
21 | runBlocking {
22 | // Arrange
23 | // Act
24 | val result = SUT.computeFactorial(0, 1000)
25 | // Assert
26 | assertThat((result as FactorialUseCaseSolution.Result.Success).result, `is`(BigInteger("1")))
27 | }
28 | }
29 |
30 | @Test
31 | fun computeFactorial_1_returns1() {
32 | runBlocking {
33 | // Arrange
34 | // Act
35 | val result = SUT.computeFactorial(1, 1000)
36 | // Assert
37 | assertThat((result as FactorialUseCaseSolution.Result.Success).result, `is`(BigInteger("1")))
38 | }
39 | }
40 |
41 | @Test
42 | fun computeFactorial_10_returnsCorrectAnswer() {
43 | runBlocking {
44 | // Arrange
45 | // Act
46 | val result = SUT.computeFactorial(10, 1000)
47 | // Assert
48 | assertThat((result as FactorialUseCaseSolution.Result.Success).result, `is`(BigInteger("3628800")))
49 | }
50 | }
51 |
52 | @Test
53 | fun computeFactorial_30_returnsCorrectAnswer() {
54 | runBlocking {
55 | // Arrange
56 | // Act
57 | val result = SUT.computeFactorial(30, 1000)
58 | // Assert
59 | assertThat((result as FactorialUseCaseSolution.Result.Success).result, `is`(BigInteger("265252859812191058636308480000000")))
60 | }
61 | }
62 |
63 |
64 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 |
2 |
3 | buildscript {
4 | ext{
5 | kotlin_version = '1.8.22'
6 | coroutines_version = '1.6.4'
7 | }
8 |
9 | repositories {
10 | google()
11 | mavenCentral()
12 | maven { url 'https://jitpack.io' }
13 | }
14 |
15 | dependencies {
16 | classpath 'com.android.tools.build:gradle:8.4.2'
17 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
18 | }
19 | }
20 |
21 | allprojects {
22 | repositories {
23 | google()
24 | mavenCentral()
25 | maven { url 'https://jitpack.io' }
26 | }
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=false
20 |
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Sep 08 09:45:49 IDT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/release.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-coroutines-course/63d43891ed6048ef01988872acd3a9b2522c5f24/release.keystore
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------