├── .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 |