From 03f48388c2ee010bb48f4c713ed2d17c1273adec Mon Sep 17 00:00:00 2001 From: Omer Iyioz Date: Thu, 11 Jun 2020 17:38:36 +0300 Subject: [PATCH 1/5] added new examples. used activity and fragment constructors in activity2 examples. used databinding extension in activity2 examples too. --- Tutorial1-1CoroutinesBasics/build.gradle | 1 + .../src/main/AndroidManifest.xml | 4 +- .../MainActivity.kt | 4 +- .../chapter1_basics/Fragment3Basics.kt | 94 -------- .../Activity2CoroutineScope1.kt | 25 +- .../Activity2CoroutineScope2.kt | 228 ++++-------------- .../Activity2CoroutineScope3.kt | 224 +++++++++++++++++ .../guide/random-practice-1.kt | 31 +++ .../guide/random-practice-2.kt | 44 ++++ .../guide/random-practice-3.kt | 36 +++ .../guide/random-practice-4.kt | 26 ++ .../guide/random-practice-5.kt | 33 +++ .../guide/random-practice-6.kt | 22 ++ .../util/ActivityDataBinding.kt | 27 +++ .../util/FragmentDataBinding.kt | 26 ++ .../src/main/res/layout/activity2_scope_2.xml | 81 ++----- .../src/main/res/layout/activity2_scope_3.xml | 74 ++++++ 17 files changed, 629 insertions(+), 351 deletions(-) delete mode 100644 Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter1_basics/Fragment3Basics.kt create mode 100644 Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope3.kt create mode 100644 Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-1.kt create mode 100644 Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-2.kt create mode 100644 Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-3.kt create mode 100644 Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-4.kt create mode 100644 Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-5.kt create mode 100644 Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-6.kt create mode 100644 Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/util/ActivityDataBinding.kt create mode 100644 Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/util/FragmentDataBinding.kt create mode 100644 Tutorial1-1CoroutinesBasics/src/main/res/layout/activity2_scope_3.xml diff --git a/Tutorial1-1CoroutinesBasics/build.gradle b/Tutorial1-1CoroutinesBasics/build.gradle index 5047957..431e7d3 100644 --- a/Tutorial1-1CoroutinesBasics/build.gradle +++ b/Tutorial1-1CoroutinesBasics/build.gradle @@ -95,6 +95,7 @@ dependencies { // androidx.fragment implementation "androidx.fragment:fragment:$fragmentVersion" + implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha05' // **** TESTING **** diff --git a/Tutorial1-1CoroutinesBasics/src/main/AndroidManifest.xml b/Tutorial1-1CoroutinesBasics/src/main/AndroidManifest.xml index 4de90bc..65066e0 100644 --- a/Tutorial1-1CoroutinesBasics/src/main/AndroidManifest.xml +++ b/Tutorial1-1CoroutinesBasics/src/main/AndroidManifest.xml @@ -22,9 +22,9 @@ - - + + diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/MainActivity.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/MainActivity.kt index ab7a78e..28cdfa7 100644 --- a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/MainActivity.kt +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/MainActivity.kt @@ -14,8 +14,9 @@ import com.smarttoolfactory.tutorial1_1coroutinesbasics.adapter.ChapterSelection import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter1_basics.Activity1Basics import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter2_scopes.Activity2CoroutineScope1 import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter2_scopes.Activity2CoroutineScope2 -import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter4_supervisorjob.Activity4SupervisorJob +import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter2_scopes.Activity2CoroutineScope3 import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter3_lifecycle.Activity3CoroutineLifecycle +import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter4_supervisorjob.Activity4SupervisorJob import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter5_viewmodel.Activity5ViewModelRxJava import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter5_viewmodel.Activity5ViewModelScope import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter6_network.Activity6Network @@ -41,6 +42,7 @@ class MainActivity : AppCompatActivity(), BaseAdapter.OnRecyclerViewItemClickLis activityClassModels.add(ActivityClassModel(Activity1Basics::class.java)) activityClassModels.add(ActivityClassModel(Activity2CoroutineScope1::class.java)) activityClassModels.add(ActivityClassModel(Activity2CoroutineScope2::class.java)) + activityClassModels.add(ActivityClassModel(Activity2CoroutineScope3::class.java)) activityClassModels.add(ActivityClassModel(Activity3CoroutineLifecycle::class.java)) activityClassModels.add(ActivityClassModel(Activity4SupervisorJob::class.java)) activityClassModels.add(ActivityClassModel(Activity5ViewModelScope::class.java)) diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter1_basics/Fragment3Basics.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter1_basics/Fragment3Basics.kt deleted file mode 100644 index dfb4e07..0000000 --- a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter1_basics/Fragment3Basics.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter1_basics - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.fragment.app.Fragment -import com.smarttoolfactory.tutorial1_1basics.R -import com.smarttoolfactory.tutorial1_1basics.databinding.Fragment3BasicsBinding -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.newSingleThreadContext -import kotlinx.coroutines.runBlocking - - -/** - * πŸ”₯runBlocking - * Runs a new coroutine and blocks the current thread interruptibly until its completion. This function should not be used from a coroutine. - * 😱😱😱runBlocking is almost never a tool you use in production, because It undoes the asynchronous, non-blocking nature of coroutines. - * You can use it if you happen to already have some coroutine-based code that you want to use in a context where coroutines provide no value: in blocking calls. - * There are 2 typical use case for runBlocking : - * 1 - JUnit testing, where the test method must just sit and wait for the coroutine to complete. - * 2 - Play around with coroutines, inside your main method. - * - * πŸ”₯ CoroutineDispatcher - * CoroutineDispatcher determines what thread or threads the corresponding coroutine uses for its execution. - * - * πŸ”₯ CoroutineContext includes a coroutine dispatcher - * The coroutine context includes a coroutine dispatcher (see CoroutineDispatcher) that determines what thread or threads the corresponding coroutine uses for its execution. - * The coroutine dispatcher can confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined. - * - * πŸ”₯ launch { } - * fun CoroutineScope.launch( - * context: CoroutineContext = EmptyCoroutineContext, - * start: CoroutineStart = CoroutineStart.DEFAULT, - * block: suspend CoroutineScope.() -> Unit - * ): Job (source) - * - * Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a Job. The coroutine is cancelled when the resulting job is cancelled. - * The coroutine context is inherited from a CoroutineScope. Additional context elements can be specified with context argument. - * - * All coroutine builders like launch and async accept an optional CoroutineContext parameter that can be used to explicitly specify the dispatcher for the new coroutine and other context elements. - * When launch { ... } is used without parameters, it inherits the context (and thus dispatcher) from the CoroutineScope it is being launched from. - * In this case, it inherits the context of the main runBlocking coroutine which runs in the main thread. - * - * */ -class Fragment3Basics : Fragment() { - - companion object { - fun newInstance() = Fragment3Basics() - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val binding = DataBindingUtil.inflate( - inflater, - R.layout.fragment3_basics, - container, - false - ) - - binding.button5.setOnClickListener { - - runBlocking { - - launch { // context of the parent, main runBlocking coroutine - println("main runBlocking : I'm working in thread ${Thread.currentThread().name}") - } - - launch(Dispatchers.Unconfined) { // not confined -- will work with main thread - println("Unconfined : I'm working in thread ${Thread.currentThread().name}") - } - - launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher - println("Default : I'm working in thread ${Thread.currentThread().name}") - } - - launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread - println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}") - } - - } - - } - - return binding.root - } - - -} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope1.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope1.kt index 54c44d9..60ed8cc 100644 --- a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope1.kt +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope1.kt @@ -2,21 +2,18 @@ package com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter2_scopes import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.databinding.DataBindingUtil import com.smarttoolfactory.tutorial1_1basics.R import com.smarttoolfactory.tutorial1_1basics.databinding.Activity2Scope1Binding +import com.smarttoolfactory.tutorial1_1coroutinesbasics.util.dataBinding import kotlinx.coroutines.* -class Activity2CoroutineScope1 : AppCompatActivity() { +class Activity2CoroutineScope1 : AppCompatActivity(R.layout.activity2_scope_1) { - private lateinit var binding: Activity2Scope1Binding + private val binding: Activity2Scope1Binding by dataBinding() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = - DataBindingUtil.setContentView(this, R.layout.activity2_scope_1) - binding.button4.setOnClickListener { CoroutineScope(Dispatchers.IO).launch { fakeApiRequest() @@ -25,21 +22,29 @@ class Activity2CoroutineScope1 : AppCompatActivity() { } private suspend fun fakeApiRequest() { - val result1 = getResult1FromApi() + val result1 = + getResult1FromApi() // the following line is not executed until the suspending function getResult1FromApi() returns println("test123 - result : $result1") - setTextOnMainThread(result1) + setTextOnMainThread(result1) // the following line is not executed until the suspending function setTextOnMainThread() returns - val result2 = getResult2FromApi() - setTextOnMainThread(result2) + val result2 = + getResult2FromApi() // the following line is not executed until the suspending function getResult2FromApi() returns + setTextOnMainThread(result2) // the following line is not executed until the suspending function setTextOnMainThread() returns println("test123 - result: $result2") } + /** Simulates network request + * delay(2000) simulates the waiting for response from server + * */ private suspend fun getResult1FromApi(): String { println("test123 - getResult1FromApi : ${Thread.currentThread().name}") delay(2000) return "\nResult 1" } + /** + * delay(2000) simulates the waiting for response from server + * Simulates network request */ private suspend fun getResult2FromApi(): String { println("test123 - getResult2FromApi : ${Thread.currentThread().name}") delay(2000) diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope2.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope2.kt index b0e189c..70e283e 100644 --- a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope2.kt +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope2.kt @@ -1,222 +1,78 @@ package com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter2_scopes import android.os.Bundle -import android.widget.TextView import androidx.appcompat.app.AppCompatActivity -import androidx.databinding.DataBindingUtil import com.smarttoolfactory.tutorial1_1basics.R import com.smarttoolfactory.tutorial1_1basics.databinding.Activity2Scope2Binding +import com.smarttoolfactory.tutorial1_1coroutinesbasics.util.dataBinding import kotlinx.coroutines.* - /** - * ## Scopes - * This sample displays some important topics below: - * * When a [CoroutineScope.launch] is fired a new Job instance is created unless [Job] - * is passed to builder([CoroutineScope.launch]) method as parameter. - * - * ## Cancellations - * * If a parent job is canceled children jobs are also canceled - * * If a child job is canceled other children jobs continue - * - * ## Exceptions - * * If a child job throws an exception parent job is canceled and other children jobs are also canceled - * * Parent should have an exception handling [CoroutineExceptionHandler] or manual exception handling - * for application NOT to crash. - * - */ -class Activity2CoroutineScope2 : AppCompatActivity() { + * Many coroutines can run in a single thread. + * This example illustrates the usage of withTimeoutOrNull() function with a simple job + * */ - // Jobs - - /** - * Job of the coroutineContext, [CoroutineScope.cancel] calls this [Job] cancel method. - * And when this job is on completed state which does not let currentJob to be active - */ - private var parentJob: Job = Job() - - /** - * This is the jub that runs when button is clicked - */ - private var currentJob: Job? = null - private var childJob1: Job? = null - private var childJob2: Job? = null - - // Scope - /** - * [Job] of [CoroutineScope] is canceled if [CoroutineScope.cancel] is called - * and is NOT launching a coroutine using this scope - */ - private val coroutineScope = CoroutineScope(parentJob) - - private lateinit var binding: Activity2Scope2Binding +class Activity2CoroutineScope2 : AppCompatActivity(R.layout.activity2_scope_2) { + private val binding: Activity2Scope2Binding by dataBinding() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - println("🀨 onCreate() parentJob: $parentJob") - - binding = - DataBindingUtil.setContentView(this, R.layout.activity2_scope_2) - - bindViews() - - } - - private fun bindViews() { - - var isJobStarted = false - - binding.btnStartJob.setOnClickListener { - - if (!isJobStarted) { - - startJob() - - isJobStarted = true - binding.btnStartJob.text = "Cancel" - - } else { - cancelJob() - isJobStarted = false - binding.btnStartJob.text = "Start Job" - } - } - - binding.btnCancelChildJob1.setOnClickListener { - - childJob1?.let { - if (childJob1!!.isActive) { - childJob1!!.cancel() - binding.btnCancelChildJob1.isEnabled = false - binding.tvChildJobResult1.text = "Progress canceled" - } + binding.button4.setOnClickListener { + CoroutineScope(Dispatchers.IO).launch { + fakeApiRequest() } } - - binding.btnCancelChildJob2.setOnClickListener { - childJob2?.let { - if (childJob2!!.isActive) { - childJob2!!.cancel() - binding.btnCancelChildJob2.isEnabled = false - binding.tvChildJobResult2.text = "Progress canceled" - } - } - } - } - private fun startJob() { + private suspend fun fakeApiRequest() { + val job = withTimeoutOrNull(1900) { + val result1 = + getResult1FromApi() // the following line is not executed until the suspending function getResult1FromApi() returns + setTextOnMainThread(result1) // the following line is not executed until the suspending function setTextOnMainThread() returns - - // Handle works in thread that exception is caught - val handler = CoroutineExceptionHandler { coroutineContext, throwable -> - println("🀬 Parent Caught $throwable in thread ${Thread.currentThread().name}, and coroutineContext: $coroutineContext") + val result2 = + getResult2FromApi() // the following line is not executed until the suspending function getResult2FromApi() returns + setTextOnMainThread(result2) // the following line is not executed until the suspending function setTextOnMainThread() returns } - currentJob = CoroutineScope(parentJob).launch(Dispatchers.Default + handler) { - - println("πŸŽƒ startJob() parentJob: $parentJob, currentJob: $currentJob") - println("πŸ˜› Inside coroutineScope.launch() scope: $this, coroutineContext job: ${this.coroutineContext[Job]}}") - - // This scope is not scope with coroutineScope used to call launch function - startChildrenJobs(this@launch) - displayAlphabet(binding.tvJobResult) - + if (job == null) { + val cancelMessage = "\nCancelling job ... Job took longer than 1900 ms" + println("test123 : $cancelMessage") + setTextOnMainThread(cancelMessage) } - - // Invoked when a job completes from πŸ”₯ Thread exception caught - currentJob?.invokeOnCompletion { - - println("invokeOnCompletion() 🀬 Exception $it, in thread: ${Thread.currentThread().name}") - - it?.let { - - runOnUiThread { - binding.tvJobResult.text = it.message - binding.tvChildJobResult1.text = it.message - binding.tvChildJobResult2.text = it.message - } - - } - } - } - private fun cancelJob() { - - binding.btnCancelChildJob1.isEnabled = false - binding.btnCancelChildJob2.isEnabled = false - - currentJob?.cancel() - // πŸ”₯πŸ”₯πŸ”₯ Calling this method cancels Job of this scope also and does not - // start a coroutine using this scope next time -// coroutineScope.cancel() -// parentJob.cancel() - + /** Simulates network request + * delay(2000) simulates the waiting for response from server + * */ + private suspend fun getResult1FromApi(): String { + println("test123 - getResult1FromApi : ${Thread.currentThread().name}") + delay(2000) + return "\nResult 1" } - - private suspend fun startChildrenJobs(myCoroutineScope: CoroutineScope) { - - childJob1 = myCoroutineScope.launch { - displayAlphabet(binding.tvChildJobResult1) - } - - childJob2 = myCoroutineScope.launch { - - launch { - displayNumbers(binding.tvChildJobResult2) - } - - delay(6000) - // πŸ”₯ Causes crash if parent does not catch this exception with handler - // πŸ”₯πŸ”₯ try catch on parent coroutine DOES NOT WORK - throw RuntimeException("Child 2 threw RuntimeException") - - } - - withContext(Dispatchers.Main) { - binding.btnCancelChildJob1.isEnabled = true - binding.btnCancelChildJob2.isEnabled = true - } - - println("πŸ€ͺ startChildrenJobs() parentJob: $parentJob, currentJob: $currentJob" + - ", childJob1: $childJob1, childJob2: $childJob2") + /** + * delay(2000) simulates the waiting for response from server + * Simulates network request */ + private suspend fun getResult2FromApi(): String { + println("test123 - getResult2FromApi : ${Thread.currentThread().name}") + delay(2000) + return "\nResult 2" } - - private suspend fun displayAlphabet(textView: TextView) { - - for (i in 'A'..'Z') { - withContext(Dispatchers.Main) { - textView.text = "Progress: $i" - } - delay(400) - - - } - - withContext(Dispatchers.Main) { - textView.text = "Job Completed" - } - + private fun setNewText(newString: String) { + val newText = binding.textView.text.toString() + newString + binding.textView.text = newText } - private suspend fun displayNumbers(textView: TextView) { - - for (i in 0..100) { - withContext(Dispatchers.Main) { - textView.text = "Progress: $i" - } - delay(300) - } - + private suspend fun setTextOnMainThread(newString: String) { withContext(Dispatchers.Main) { - textView.text = "Job Completed" + println("test123 - setTextOnMainThread : ${Thread.currentThread().name}") + delay(2000) + setNewText(newString) } - } } diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope3.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope3.kt new file mode 100644 index 0000000..5602cf0 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope3.kt @@ -0,0 +1,224 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter2_scopes + +import android.os.Bundle +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import com.smarttoolfactory.tutorial1_1basics.R +import com.smarttoolfactory.tutorial1_1basics.databinding.Activity2Scope3Binding +import com.smarttoolfactory.tutorial1_1coroutinesbasics.util.dataBinding +import kotlinx.coroutines.* + + +/** + * ## Scopes + * This sample displays some important topics below: + * * When a [CoroutineScope.launch] is fired a new Job instance is created unless [Job] + * is passed to builder([CoroutineScope.launch]) method as parameter. + * + * ## Cancellations + * * If a parent job is canceled children jobs are also canceled + * * If a child job is canceled other children jobs continue + * + * ## Exceptions + * * If a child job throws an exception parent job is canceled and other children jobs are also canceled + * * Parent should have an exception handling [CoroutineExceptionHandler] or manual exception handling + * for application NOT to crash. + * + */ +class Activity2CoroutineScope3 : AppCompatActivity(R.layout.activity2_scope_3) { + + private val TAG = "Activity2CoroutineScope3" + + // Jobs + + /** + * Job of the coroutineContext, [CoroutineScope.cancel] calls this [Job] cancel method. + * And when this job is on completed state which does not let currentJob to be active + */ + private var parentJob: Job = Job() + + /** + * This is the jub that runs when button is clicked + */ + private var currentJob: Job? = null + private var childJob1: Job? = null + private var childJob2: Job? = null + + // Scope + /** + * [Job] of [CoroutineScope] is canceled if [CoroutineScope.cancel] is called + * and is NOT launching a coroutine using this scope + */ + private val coroutineScope = CoroutineScope(parentJob) + + private val binding: Activity2Scope3Binding by dataBinding() + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + println("$TAG 🀨 onCreate() parentJob: $parentJob") + + bindViews() + + } + + private fun bindViews() { + + var isJobStarted = false + + binding.btnStartJob.setOnClickListener { + + if (!isJobStarted) { + + startJob() + + isJobStarted = true + binding.btnStartJob.text = "Cancel" + + } else { + cancelJob() + isJobStarted = false + binding.btnStartJob.text = "Start Job" + } + } + + binding.btnCancelChildJob1.setOnClickListener { + + childJob1?.let { + if (childJob1!!.isActive) { + childJob1!!.cancel() + binding.btnCancelChildJob1.isEnabled = false + binding.tvChildJobResult1.text = "Progress canceled" + } + } + } + + binding.btnCancelChildJob2.setOnClickListener { + childJob2?.let { + if (childJob2!!.isActive) { + childJob2!!.cancel() + binding.btnCancelChildJob2.isEnabled = false + binding.tvChildJobResult2.text = "Progress canceled" + } + } + } + + } + + private fun startJob() { + + + // Handle works in thread that exception is caught + val handler = CoroutineExceptionHandler { coroutineContext, throwable -> + println("$TAG 🀬 Parent Caught $throwable in thread ${Thread.currentThread().name}, and coroutineContext: $coroutineContext") + } + + currentJob = CoroutineScope(parentJob).launch(Dispatchers.Default + handler) { + + println("$TAG πŸŽƒ startJob() parentJob: $parentJob, currentJob: $currentJob") + println("$TAG πŸ˜› Inside coroutineScope.launch() scope: $this, coroutineContext job: ${this.coroutineContext[Job]}}") + + // This scope is not scope with coroutineScope used to call launch function + startChildrenJobs(this@launch) + displayAlphabet(binding.tvJobResult) + + } + + // Invoked when a job completes from πŸ”₯ Thread exception caught + currentJob?.invokeOnCompletion { + + println("$TAG invokeOnCompletion() 🀬 Exception $it, in thread: ${Thread.currentThread().name}") + + it?.let { + + runOnUiThread { + binding.tvJobResult.text = it.message + binding.tvChildJobResult1.text = it.message + binding.tvChildJobResult2.text = it.message + } + + } + } + + } + + private fun cancelJob() { + + binding.btnCancelChildJob1.isEnabled = false + binding.btnCancelChildJob2.isEnabled = false + + currentJob?.cancel() + // πŸ”₯πŸ”₯πŸ”₯ Calling this method cancels Job of this scope also and does not + // start a coroutine using this scope next time +// coroutineScope.cancel() +// parentJob.cancel() + + } + + + private suspend fun startChildrenJobs(myCoroutineScope: CoroutineScope) { + + childJob1 = myCoroutineScope.launch { + displayAlphabet(binding.tvChildJobResult1) + } + + childJob2 = myCoroutineScope.launch { + + launch { + displayNumbers(binding.tvChildJobResult2) + } + + delay(6000) + // πŸ”₯ Causes crash if parent does not catch this exception with handler + // πŸ”₯πŸ”₯ try catch on parent coroutine DOES NOT WORK + throw RuntimeException("Child 2 threw RuntimeException") + + } + + withContext(Dispatchers.Main) { + binding.btnCancelChildJob1.isEnabled = true + binding.btnCancelChildJob2.isEnabled = true + } + + println( + "$TAG πŸ€ͺ startChildrenJobs() parentJob: $parentJob, currentJob: $currentJob" + + ", childJob1: $childJob1, childJob2: $childJob2" + ) + } + + + private suspend fun displayAlphabet(textView: TextView) { + + for (i in 'A'..'Z') { + withContext(Dispatchers.Main) { + textView.text = "Progress: $i" + } + delay(400) + + + } + + withContext(Dispatchers.Main) { + textView.text = "Job Completed" + } + + } + + private suspend fun displayNumbers(textView: TextView) { + + for (i in 0..100) { + withContext(Dispatchers.Main) { + textView.text = "Progress: $i" + } + delay(300) + } + + withContext(Dispatchers.Main) { + textView.text = "Job Completed" + } + + } + +} + diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-1.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-1.kt new file mode 100644 index 0000000..b057756 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-1.kt @@ -0,0 +1,31 @@ +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext + +/** examples from resocoder */ +fun main() { + exampleWithContext() +} + +fun exampleWithContext() = runBlocking { + val startTime = System.currentTimeMillis() + + val result1 = withContext(Dispatchers.Default) { calculateHardThings(10) } + println("1 done") + val result2 = withContext(Dispatchers.IO) { calculateHardThings(20) } + println("2 done") + val result3 = withContext(Dispatchers.IO) { calculateHardThings(30) } + println("3 done") + + val sum = result1 + result2 + result3 + println("async/await result = $sum") + + val endTime = System.currentTimeMillis() + println("Time taken: ${endTime - startTime}") +} + +suspend fun calculateHardThings(startNum: Int): Int { + delay(1000) + return startNum * 10 +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-2.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-2.kt new file mode 100644 index 0000000..80f9760 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-2.kt @@ -0,0 +1,44 @@ +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +/** examples from resocoder */ +fun main() { + exampleLaunchCoroutineScope1() + exampleLaunchCoroutineScope2() +} + +fun exampleLaunchCoroutineScope1() = runBlocking { + println("one - from thread ${Thread.currentThread().name}") + + launch { + delay(4000) + println("two - from thread ${Thread.currentThread().name}") + } + + println("three - from thread ${Thread.currentThread().name}") +} + +fun exampleLaunchCoroutineScope2() = runBlocking { + println("one - from thread ${Thread.currentThread().name}") + + val customDispatcher = Executors.newFixedThreadPool(2).asCoroutineDispatcher() + + launch(customDispatcher) { + printlnDelayed("two - from thread ${Thread.currentThread().name}") + } + + println("three - from thread ${Thread.currentThread().name}") + + (customDispatcher.executor as ExecutorService).shutdown() +} + + +suspend fun printlnDelayed(message: String) { + // Complex calculation + delay(1000) + println(message) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-3.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-3.kt new file mode 100644 index 0000000..7c0987d --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-3.kt @@ -0,0 +1,36 @@ +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking + +/** examples from resocoder */ +fun main() { + exampleAsyncAwait1() + exampleAsyncAwait2() +} + +fun exampleAsyncAwait1() = runBlocking { + val startTime = System.currentTimeMillis() + + val result1 = async { calculateHardThings(10) }.await() + val result2 = async { calculateHardThings(20) }.await() + val result3 = async { calculateHardThings(30) }.await() + + val sum = result1 + result2 + result3 + println("async/await result = $sum") + + val endTime = System.currentTimeMillis() + println("Time taken: ${endTime - startTime}") +} + +fun exampleAsyncAwait2() = runBlocking { + val startTime = System.currentTimeMillis() + + val deferred1 = async { calculateHardThings(10) } + val deferred2 = async { calculateHardThings(20) } + val deferred3 = async { calculateHardThings(30) } + + val sum = deferred1.await() + deferred2.await() + deferred3.await() + println("async/await result = $sum") + + val endTime = System.currentTimeMillis() + println("Time taken: ${endTime - startTime}") +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-4.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-4.kt new file mode 100644 index 0000000..a499bfd --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-4.kt @@ -0,0 +1,26 @@ +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking + +/** examples from resocoder */ +fun main() { + exampleBlocking() + exampleBlockingDispatcher() +} + + +fun exampleBlocking() = runBlocking { + println("one") + printlnDelayed("two") + println("three") +} + +// Running on another thread but still blocking the main thread +fun exampleBlockingDispatcher() { + runBlocking(Dispatchers.Default) { + println("one - from thread ${Thread.currentThread().name}") + printlnDelayed("two - from thread ${Thread.currentThread().name}") + } + // Outside of runBlocking to show that it's running in the blocked main thread + println("three - from thread ${Thread.currentThread().name}") + // It still runs only after the runBlocking is fully executed. +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-5.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-5.kt new file mode 100644 index 0000000..8212087 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-5.kt @@ -0,0 +1,33 @@ +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +/** examples from resocoder */ +fun main() { + exampleLaunchGlobal() + exampleLaunchGlobalWaiting() +} + + +fun exampleLaunchGlobal() = runBlocking { + println("one - from thread ${Thread.currentThread().name}") + + GlobalScope.launch { + printlnDelayed("two - from thread ${Thread.currentThread().name}") + } + + println("three - from thread ${Thread.currentThread().name}") + delay(3000) +} + +fun exampleLaunchGlobalWaiting() = runBlocking { + println("one - from thread ${Thread.currentThread().name}") + + val job = GlobalScope.launch { + printlnDelayed("two - from thread ${Thread.currentThread().name}") + } + + println("three - from thread ${Thread.currentThread().name}") + job.join() +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-6.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-6.kt new file mode 100644 index 0000000..71189d9 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-6.kt @@ -0,0 +1,22 @@ +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking + +/** To get the result from coroutine, you need to start the coroutine using async{ } + * await() function awaits until the job is finished and gives you the result back + * The last statement in the async block becomes your return statement. + * */ +fun main() { + runBlocking { + val deferredResult: Deferred = async { + println("context of async : ${Thread.currentThread().name}") + delay(3000L) + "Veggie treat" + } + println("Your coke is ready, waiting for burger..") + println("Here is your burger, ${deferredResult.await()}") + println("--THE END--") + } +} + diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/util/ActivityDataBinding.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/util/ActivityDataBinding.kt new file mode 100644 index 0000000..1e70a53 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/util/ActivityDataBinding.kt @@ -0,0 +1,27 @@ +@file:JvmName("ActivityDataBinding") + +package com.smarttoolfactory.tutorial1_1coroutinesbasics.util + +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.FragmentActivity + +fun FragmentActivity.dataBinding(): Lazy = object : Lazy { + private var binding: T? = null + override fun isInitialized(): Boolean = binding != null + override val value: T + get() = binding ?: bind(getContentView()).also { + it.lifecycleOwner = this@dataBinding + binding = it + } + + private fun FragmentActivity.getContentView(): View { + return checkNotNull(findViewById(android.R.id.content).getChildAt(0)) { + "Call setContentView or Use Activity's secondary constructor passing layout res id." + } + } + + private fun bind(view: View): T = DataBindingUtil.bind(view)!! +} diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/util/FragmentDataBinding.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/util/FragmentDataBinding.kt new file mode 100644 index 0000000..5e15e7d --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/util/FragmentDataBinding.kt @@ -0,0 +1,26 @@ +@file:JvmName("FragmentDataBinding") + +package com.smarttoolfactory.tutorial1_1coroutinesbasics.util + +import android.view.View +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +fun Fragment.dataBinding(): ReadOnlyProperty { + return object : ReadOnlyProperty { + @Suppress("UNCHECKED_CAST") + override fun getValue(thisRef: Fragment, property: KProperty<*>): T { + (requireView().getTag(property.name.hashCode()) as? T)?.let { return it } + return bind(requireView()).also { + it.lifecycleOwner = thisRef.viewLifecycleOwner + it.root.setTag(property.name.hashCode(), it) + } + } + + private fun bind(view: View): T = DataBindingUtil.bind(view)!! + } +} + diff --git a/Tutorial1-1CoroutinesBasics/src/main/res/layout/activity2_scope_2.xml b/Tutorial1-1CoroutinesBasics/src/main/res/layout/activity2_scope_2.xml index ad9965e..26dfe5e 100644 --- a/Tutorial1-1CoroutinesBasics/src/main/res/layout/activity2_scope_2.xml +++ b/Tutorial1-1CoroutinesBasics/src/main/res/layout/activity2_scope_2.xml @@ -1,75 +1,40 @@ - - + + + + + - + android:layout_height="match_parent" + android:background="@color/md_cyan_600_dark" + tools:context="com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter2_scopes.Activity2CoroutineScope2">