diff --git a/Tutorial1-1CoroutinesBasics/build.gradle b/Tutorial1-1CoroutinesBasics/build.gradle index 5047957..a0e8af1 100644 --- a/Tutorial1-1CoroutinesBasics/build.gradle +++ b/Tutorial1-1CoroutinesBasics/build.gradle @@ -11,7 +11,7 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" - + multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -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..5bdf1e7 100644 --- a/Tutorial1-1CoroutinesBasics/src/main/AndroidManifest.xml +++ b/Tutorial1-1CoroutinesBasics/src/main/AndroidManifest.xml @@ -22,14 +22,16 @@ - - + + + + \ No newline at end of file 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..60d1686 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,14 +14,17 @@ 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.chapter2_scopes.Activity2CoroutineScope4 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 import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter7_database.Activity7Database import com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter8_single_source_of_truth.Activity8SingleSourceOfTruth import com.smarttoolfactory.tutorial1_1coroutinesbasics.model.ActivityClassModel +import com.smarttoolfactory.tutorial1_1coroutinesbasics.retrofitexample.RetrofitActivity import java.util.* class MainActivity : AppCompatActivity(), BaseAdapter.OnRecyclerViewItemClickListener { @@ -41,6 +44,8 @@ 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(Activity2CoroutineScope4::class.java)) activityClassModels.add(ActivityClassModel(Activity3CoroutineLifecycle::class.java)) activityClassModels.add(ActivityClassModel(Activity4SupervisorJob::class.java)) activityClassModels.add(ActivityClassModel(Activity5ViewModelScope::class.java)) @@ -48,6 +53,7 @@ class MainActivity : AppCompatActivity(), BaseAdapter.OnRecyclerViewItemClickLis activityClassModels.add(ActivityClassModel(Activity6Network::class.java)) activityClassModels.add(ActivityClassModel(Activity7Database::class.java)) activityClassModels.add(ActivityClassModel(Activity8SingleSourceOfTruth::class.java)) + activityClassModels.add(ActivityClassModel(RetrofitActivity::class.java)) val recyclerView = activityMainBinding.recyclerView 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..8848a42 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope3.kt @@ -0,0 +1,228 @@ +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.guide.status +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 ?: currentJob?.status() + binding.tvChildJobResult1.text = childJob1?.status() + binding.tvChildJobResult2.text = childJob2?.status() + } + + } + } + + } + + 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") + + } + childJob2?.invokeOnCompletion { + binding.tvChildJobResult2.text = it?.message ?: childJob2?.status() + } + + 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/chapter2_scopes/Activity2CoroutineScope4.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope4.kt new file mode 100644 index 0000000..c60feb7 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter2_scopes/Activity2CoroutineScope4.kt @@ -0,0 +1,105 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.chapter2_scopes + +import android.os.Bundle +import android.util.Log +import android.widget.ProgressBar +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.smarttoolfactory.tutorial1_1basics.R +import com.smarttoolfactory.tutorial1_1basics.databinding.Activity2Scope4Binding +import com.smarttoolfactory.tutorial1_1coroutinesbasics.util.dataBinding +import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main + +/* +* The following example started from Mitch Tabian's github repo from branch Completable-Job-with-Cancellation-and-Progress-Bar +* https://github.com/mitchtabian/Kotlin-Coroutine-Examples/tree/Completable-Job-with-Cancellation-and-Progress-Bar +* */ +class Activity2CoroutineScope4 : AppCompatActivity(R.layout.activity2_scope_4) { + + private val TAG: String = "AppDebug" + + private val PROGRESS_MAX = 100 + private val PROGRESS_START = 0 + private val JOB_TIME = 4000 // ms + private lateinit var job: CompletableJob + + private val binding: Activity2Scope4Binding by dataBinding() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding.jobButton.setOnClickListener { + if (!::job.isInitialized) { + initjob() + } + binding.jobProgressBar.startJobOrCancel(job) + } + } + + + fun initjob() { + binding.jobButton.text = "Start Job #1" + updateJobCompleteTextView("") + job = Job() + job.invokeOnCompletion { + it?.message.let { + var msg = it + if (msg.isNullOrBlank()) { + msg = "Unknown cancellation error." + } + Log.e(TAG, "${job} was cancelled. Reason: ${msg}") + showToast(msg) + } + } + binding.jobProgressBar.max = PROGRESS_MAX + binding.jobProgressBar.progress = PROGRESS_START + } + + + fun ProgressBar.startJobOrCancel(job: Job) { + if (this.progress > 0) { + Log.d(TAG, "${job} is already active. Cancelling...") + resetjob() + } else { + binding.jobButton.text = "Cancel Job #1" + CoroutineScope(IO + job).launch { + Log.d(TAG, "coroutine ${this} is activated with job ${job}.") + + for (i in PROGRESS_START..PROGRESS_MAX) { + delay((JOB_TIME / PROGRESS_MAX).toLong()) + this@startJobOrCancel.progress = i + } + updateJobCompleteTextView("Job is complete!") + } + } + } + + fun resetjob() { + if (job.isActive || job.isCompleted) { + job.cancel(CancellationException("Resetting job")) + } + initjob() + } + + private fun updateJobCompleteTextView(text: String) { + GlobalScope.launch(Main) { + binding.jobCompleteText.text = text + } + } + + private fun showToast(text: String) { + GlobalScope.launch(Main) { + Toast.makeText(this@Activity2CoroutineScope4, text, Toast.LENGTH_SHORT).show() + } + } + + override fun onDestroy() { + super.onDestroy() + if (::job.isInitialized) + job.cancel() + } + +} + + diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter3_lifecycle/Activity3CoroutineLifecycle.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter3_lifecycle/Activity3CoroutineLifecycle.kt index d67c974..18fba29 100644 --- a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter3_lifecycle/Activity3CoroutineLifecycle.kt +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter3_lifecycle/Activity3CoroutineLifecycle.kt @@ -12,9 +12,12 @@ class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope { private lateinit var job: Job override val coroutineContext: CoroutineContext - get() = job + Dispatchers.Main + CoroutineName("🙄 Activity Scope") + CoroutineExceptionHandler { coroutineContext, throwable -> - println("🤬 Exception $throwable in context:$coroutineContext") - } + get() = job + + Dispatchers.Main + + CoroutineName("🙄 Activity Scope") + + CoroutineExceptionHandler { coroutineContext, throwable -> + println("🤬 Exception $throwable in context:$coroutineContext") + } private val binding by lazy { @@ -33,7 +36,7 @@ class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope { // 🔥⚠️ This scope lives as long as Application is alive GlobalScope.launch { for (i in 0..300) { - println("🤪 Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this") + println("$TAG 🤪 Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this") delay(300) } } @@ -41,10 +44,10 @@ class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope { // This scope is canceled whenever this Activity's onDestroy method is called launch { for (i in 0..300) { - println("😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this") + println("$TAG 😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this") withContext(Dispatchers.Main) { binding.tvResult.text = - "😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this" + "Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this" } delay(300) } @@ -55,17 +58,11 @@ class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope { override fun onDestroy() { super.onDestroy() + // cancel() // this is the cancel method of coroutineScope. You can call coroutineScope.cancel() or parentJob.cancel() in order to cancel the parent job. job.cancel() } - - private suspend fun printNumbers(coroutineScope: CoroutineScope) { - - for (i in 0..100) { - println("Progress: $i in thread: ${Thread.currentThread().name}, scope: $coroutineScope") - delay(300) - } - + companion object { + const val TAG = "Test-Activity3CoroutineLifecycle" } - } diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter4_supervisorjob/Activity4SupervisorJob.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter4_supervisorjob/Activity4SupervisorJob.kt index 59f2e5b..5946d93 100644 --- a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter4_supervisorjob/Activity4SupervisorJob.kt +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/chapter4_supervisorjob/Activity4SupervisorJob.kt @@ -6,6 +6,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import com.smarttoolfactory.tutorial1_1basics.R import com.smarttoolfactory.tutorial1_1basics.databinding.Activity4SupervisorJobBinding +import com.smarttoolfactory.tutorial1_1coroutinesbasics.guide.status import kotlinx.coroutines.* @@ -124,9 +125,9 @@ class Activity4SupervisorJob : AppCompatActivity() { it?.let { runOnUiThread { - binding.tvJobResult.text = it.message - binding.tvChildJobResult1.text = it.message - binding.tvChildJobResult2.text = it.message + binding.tvJobResult.text = it.message ?: currentJob?.status() + binding.tvChildJobResult1.text = childJob1?.status() + binding.tvChildJobResult2.text = childJob2?.status() } } @@ -164,7 +165,7 @@ class Activity4SupervisorJob : AppCompatActivity() { */ val childSupervisorJob = SupervisorJob() - childJob2 = myCoroutineScope.launch(childSupervisorJob ) { + childJob2 = myCoroutineScope.launch(childSupervisorJob) { launch { displayNumbers(binding.tvChildJobResult2) @@ -174,6 +175,9 @@ class Activity4SupervisorJob : AppCompatActivity() { throw RuntimeException("Child 2 threw RuntimeException") } + childJob2?.invokeOnCompletion { + binding.tvChildJobResult2.text = it?.message ?: childJob2?.status() + } withContext(Dispatchers.Main) { binding.btnCancelChildJob1.isEnabled = true diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/coroutineScope-1.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/coroutineScope-1.kt new file mode 100644 index 0000000..0c0b018 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/coroutineScope-1.kt @@ -0,0 +1,45 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +/* +* 🔥 launch returns immediately but inside of launch not eecuted immediately. non-blocking code after launch is executed firstly, then inside of launch is executed. +* 🐲 coroutineScope is a blocking code. Code execution does not go to following line, +* waits for completion of inside of coroutineScope and coroutines started before coroutineScope. Thus printed 3 1 2. +* +* https://stackoverflow.com/questions/53535977/coroutines-runblocking-vs-coroutinescope/53536713#53536713 önce buna bak +* */ + +fun main() = runBlocking { + launch { + println("1") + } + + coroutineScope { + launch { + println("2") + } + + println("3") + } + + coroutineScope { + launch { + println("4") + } + + println("5") + } + + launch { + println("6") + } + + for (i in 7..100) { + println(i.toString()) + } + + println("101") +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-01-coroutineScope.cancel().kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-01-coroutineScope.cancel().kt new file mode 100644 index 0000000..05f43e8 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-01-coroutineScope.cancel().kt @@ -0,0 +1,37 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * 🐲 Cancellation of CoroutineScope 🐲 + * If you cancel a CoroutineScope, then all coroutines executing inside this scope is cancelled. + * */ + +fun main() { + cancelParentJob2() +} + +fun cancelParentJob2() { + val coroutineScope = CoroutineScope(Dispatchers.Unconfined) + + val job1 = coroutineScope.launch { + delay(500) + } + val job2 = coroutineScope.launch { + delay(500) + } + + coroutineScope.cancel() + + println("Job 1 state: ${job1.status()}") + println("Job 2 state: ${job2.status()}") + println("Is coroutineScope active: ${coroutineScope.isActive}") +} + + +fun Job.status(): String = when { + isCancelled -> "cancelled" + isActive -> "Active" + isCompleted -> "Complete" + else -> "Nothing" +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-02-parentJob.cancel().kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-02-parentJob.cancel().kt new file mode 100644 index 0000000..d3fdcc2 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-02-parentJob.cancel().kt @@ -0,0 +1,48 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * Cancellation is propagated from parent to child. + * + * A parent job’s cancellation causes its children to cancel. + * + * A child’s cancellation won’t affect its parent and other childs + * A child’s failure(throwing exception) causes its parent and other childs to cancel. + * + * 🤡Cancellation of parent job🤡 + * 🔥 In this case, parent job is cancelled, then all child jobs cancelled automatically. + * */ + +fun main() { + + val parentJob = Job() + + val coroutineScope = CoroutineScope(Dispatchers.IO + parentJob) + + var child1: Job? = null + var child2: Job? = null + + + coroutineScope.launch { + child1 = launch { + delay(500) + } + child2 = launch { + delay(500) + } + + + // cancel() + parentJob.cancel() + + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("parentJob.isActive : ${parentJob.isActive}") + println("coroutineScope.isActive : ${coroutineScope.isActive}") + println("coroutineScope.isActive : $isActive") + } + + + Thread.sleep(2000L) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-03-childJob.cancel().kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-03-childJob.cancel().kt new file mode 100644 index 0000000..56290bf --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-03-childJob.cancel().kt @@ -0,0 +1,40 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * 🤡Cancellation of child job🤡 + * 🔥 In this case, child job is cancelled, but other child jobs and parent job are still active and continue execution. + * */ + +fun main() { + + val parentJob = Job() + + val coroutineScope = CoroutineScope(Dispatchers.IO + parentJob) + + var child1: Job? = null + var child2: Job? = null + + + coroutineScope.launch { + child1 = launch { + delay(500) + } + child2 = launch { + delay(500) + } + + + child1?.cancel() + + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("parentJob.isActive : ${parentJob.isActive}") + println("coroutineScope.isActive : ${coroutineScope.isActive}") + println("coroutineScope.isActive : $isActive") + } + + + Thread.sleep(2000L) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-04-throw-exception-in-parent-Job.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-04-throw-exception-in-parent-Job.kt new file mode 100644 index 0000000..13d9ad8 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-04-throw-exception-in-parent-Job.kt @@ -0,0 +1,46 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * 💀Throw exception in parent job💀 + * 🔥 In this case, exception is thrown in the parent job, then all child jobs cancelled automatically. + * */ + +fun main() { + + var child1: Job? = null + var child2: Job? = null + + // Parent Job and Coroutine Exception Handler + val parentJob = Job() + + val handler = CoroutineExceptionHandler { _, exception -> + println("Caught $exception") + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("Parent job is active: ${parentJob.isActive}") + } + + // CoroutineScope + val coroutineScope = CoroutineScope(Dispatchers.IO + parentJob + handler) + + + // Use + coroutineScope.launch { + + child1 = launch { + delay(500) + } + child2 = launch { + delay(500) + } + + delay(200) + throw RuntimeException() + + } + + + Thread.sleep(2000L) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-05-throw-exception-in-child-Job-without-SupervisorJob.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-05-throw-exception-in-child-Job-without-SupervisorJob.kt new file mode 100644 index 0000000..7ed357b --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-05-throw-exception-in-child-Job-without-SupervisorJob.kt @@ -0,0 +1,53 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * 💀 Throw exception in child job💀 + * 🔥 In this case, exception is thrown in the first child job, then parent job and second child job ARE cancelled automatically. + * + * ❤️ If you want parent job and second child job to continue work even if the first child throws an exception, then you have 2 choices + * 1 - You can use try catch inside child1's launch scope. If you throw exception inside try catch inside child1's launch scope, + * parent job and second child job are not affected from this exception and continue to work + * 2 - You can create a SupervisorJob object, and give this object to context of parent job. + * Then exception is catched by CoroutineExceptionHandler. Parent job and second child job are not affected from this exception and continue to work. + * ❤️ + * */ + +fun main() { + + var child1: Job? = null + var child2: Job? = null + + // Parent Job and Coroutine Exception Handler + val parentJob = Job() + + val handler = CoroutineExceptionHandler { _, exception -> + println("Caught $exception") + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("Parent job is active: ${parentJob.isActive}") + } + + // CoroutineScope + val coroutineScope = CoroutineScope(Dispatchers.IO + parentJob + handler) + + + coroutineScope.launch { + + child1 = launch { + delay(100) + throw Exception("Errorrr!!!") + } + + child2 = launch { + delay(500) + } + + delay(500) + + } + + + Thread.sleep(2000L) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-06-throw-exception-in-child-Job-with-wrong-usage-of-SupervisorJob.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-06-throw-exception-in-child-Job-with-wrong-usage-of-SupervisorJob.kt new file mode 100644 index 0000000..f47d112 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-06-throw-exception-in-child-Job-with-wrong-usage-of-SupervisorJob.kt @@ -0,0 +1,65 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * 🎷🎺🎸🥁 This example is the clone of the previous example with only difference is the following line: + * val supervisorJob = SupervisorJob() + * + * 😱😱 This example illustrates the WRONG usage of SupervisorJob ️😱😱 + * + * + * 🔥 In this example, exception is thrown in the child job, then what do you expect ? + * SupervisorJob is not work as you expected in this example. + * For more information please read the article by Manuel Vivo https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c + * + * + * + * Lets explain shortly: + * + * val coroutineScope = CoroutineScope(Dispatchers.IO + supervisorJob + coroutineExceptionHandler) + * coroutineScope.launch{} launches a child job for supervisor job. If you throw exception in this direct child job of supervisor job, other child jobs continue to work. + * + * coroutineScope.launch{ + * launch{ } launches a child job for child job of supervisor job. If you throw exception in this job, since this is not the direct child of supervisor job, + * exception does not reach to supervisor job. Thus child2 and parent job are cancelled automatically. Supervisor job is useless in this example. Because child1 and + * child2 is not direct child job of SupervisorJob. The job returned by coroutineScope.launch{} is the direct child of SupervisorJob. + * + * } + * */ + +fun main() { + + var child1: Job? = null + var child2: Job? = null + + val supervisorJob = SupervisorJob() + + val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> + println("Caught $exception") + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("supervisorJob.isActive : ${supervisorJob.isActive}") + } + + val coroutineScope = CoroutineScope(Dispatchers.IO + supervisorJob + coroutineExceptionHandler) + + + coroutineScope.launch { + + child1 = launch { + delay(100) + throw Exception() + } + + child2 = launch { + delay(500) + } + + delay(500) + + } + + + Thread.sleep(2000L) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-07-parentJob.cancel()-alternative-1.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-07-parentJob.cancel()-alternative-1.kt new file mode 100644 index 0000000..ccec7a5 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-07-parentJob.cancel()-alternative-1.kt @@ -0,0 +1,42 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * + * 🤡 Cancellation of parent job with alternative way 1 🤡 + * 🔥 In this case, parent job is cancelled, then all child jobs cancelled automatically. + * */ + +fun main() { + + // Parent Job and Coroutine Exception Handler + val parentJob = Job() + + // CoroutineScope + val coroutineScope = CoroutineScope(Dispatchers.IO + parentJob) + + var child1: Job? = null + var child2: Job? = null + + // Use + val currentJob = coroutineScope.launch { + child1 = launch { + delay(500) + } + child2 = launch { + delay(500) + } + } + + Thread.sleep(300L) + + currentJob.cancel() + + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("Parent job is active: ${currentJob.isActive}") + + + Thread.sleep(2000L) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-08-parentJob.cancel()-alternative-2.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-08-parentJob.cancel()-alternative-2.kt new file mode 100644 index 0000000..e325985 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-08-parentJob.cancel()-alternative-2.kt @@ -0,0 +1,38 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * + * 🤡 Cancellation of parent job with alternative way 2 🤡 + * 🔥 In this case, parent job is cancelled, then all child jobs cancelled automatically. + * */ + +fun main() { + + // CoroutineScope + val coroutineScope = CoroutineScope(Dispatchers.IO) + + var child1: Job? = null + var child2: Job? = null + + // Use + val currentJob = coroutineScope.launch { + child1 = launch { + delay(500) + } + child2 = launch { + delay(500) + } + } + + Thread.sleep(300L) + + currentJob.cancel() + + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("Parent job is active: ${currentJob.isActive}") + + Thread.sleep(2000L) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-09-coroutineContext[Job].kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-09-coroutineContext[Job].kt new file mode 100644 index 0000000..41e3471 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-09-coroutineContext[Job].kt @@ -0,0 +1,44 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * + * The coroutine's Job is part of its context, and can be retrieved from it using the coroutineContext[Job] expression: + * println("My job is ${coroutineContext[Job]}") + * + * In the debug mode, it outputs something like this: + * My job is "coroutine#1":BlockingCoroutine{Active}@6d311334 + * + * To Enable the logging in IntelliJ toolbar menu: + * Run -> Edit Configuration and add the following in VM options + * -Dkotlinx.coroutines.debug + * */ + +fun main() { + + // Parent Job and Coroutine Exception Handler + val parentJob = Job() + + // CoroutineScope + val coroutineScope = CoroutineScope(Dispatchers.IO + parentJob) + + + coroutineScope.launch { + coroutineScope.launch(CoroutineName("cocuk1")) { + delay(500) + println("My job is ${coroutineContext[Job]}") + + } + coroutineScope.launch { + delay(500) + println("My job is ${coroutineContext[Job]}") + } + + println("My job is ${coroutineContext[Job]}") + + + } + + Thread.sleep(2000L) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-10-invokeOnCompletion.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-10-invokeOnCompletion.kt new file mode 100644 index 0000000..bfd4140 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-10-invokeOnCompletion.kt @@ -0,0 +1,39 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * usage of invokeOnCompletion + * */ + +fun main() { + + CoroutineScope(Dispatchers.IO + CoroutineName("Mr.Coroutine")).launch { + delay(500) + }.invokeOnCompletion { + println("${Thread.currentThread().name} \t\t\t Inside invokeOnCompletion") + } + + + val parentJob = Job() + CoroutineScope(Dispatchers.IO + parentJob + CoroutineName("Miss.Coroutine")).launch { + delay(500) + }.invokeOnCompletion { + println("${Thread.currentThread().name} \t\t Inside invokeOnCompletion : \t\t ${it?.message} ") + } + parentJob.cancel(CancellationException("Resetting job")) + + + val parentJob2 = Job() + val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> + println("${Thread.currentThread().name} \t\t Inside CoroutineExceptionHandler : $exception ") + } + CoroutineScope(Dispatchers.IO + parentJob2 + coroutineExceptionHandler + CoroutineName("Senior.Coroutine")).launch { + delay(100) + throw Exception("hata") + }.invokeOnCompletion { + println("${Thread.currentThread().name} \t\t Inside invokeOnCompletion : \t\t ${it?.message}") + } + + Thread.sleep(2000L) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-11-invokeOnCompletion.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-11-invokeOnCompletion.kt new file mode 100644 index 0000000..2b9457d --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-11-invokeOnCompletion.kt @@ -0,0 +1,38 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * usage of invokeOnCompletion + * + * You can call invokeOnCompletion() on a Job and register a lambda to be evaluated when the job is completed for any reason. + * The parameter passed to the lambda will be: + * 🏁 null if the job completed normally + * 🚩 a CancellationException (or subclass) if the job was canceled + * ‍☠ ️some other type of exception if the job failed + * + * https://klassbook.commonsware.com/lessons/Coroutine%20Basics/invokeOnCompletion.html + * */ + +fun main() { + + val job = GlobalScope.launch(Dispatchers.IO) { + withTimeout(2000L) { + println("This is executed before the delay") + stallForTime() + println("This is executed after the delay") + } + } + + job.invokeOnCompletion { cause -> println("We were canceled due to ---> $cause <---") } + + println("This is executed immediately") + + Thread.sleep(5000) +} + +suspend fun stallForTime() { + withContext(Dispatchers.Default) { + delay(10000L) + } +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-12 - using try catch rather than supervisorJob.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-12 - using try catch rather than supervisorJob.kt new file mode 100644 index 0000000..41720ca --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-12 - using try catch rather than supervisorJob.kt @@ -0,0 +1,41 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + + +/* +* try catch can be used rather then using SupervisorJob or SupervisorScope in order to prevent cancellation of other child jobs. +* */ + +fun main() { + + /** + * Exception is caught by try catch + * */ + GlobalScope.launch { + try { + println("1 - Throwing exception from launch") + throw IndexOutOfBoundsException() + println("1 - Unreached") + } catch (e: IndexOutOfBoundsException) { + println("1 - Caught IndexOutOfBoundsException") + } + } + + /* + * Exception is thrown and crash occurs. + * */ + GlobalScope.launch { + println("2 - Throwing exception from launch") + throw Exception("ERROR!!!") + println("2 - Unreached") + } + /*val deferred = GlobalScope.async { + println("Throwing exception from async") + throw ArithmeticException() + println("Unreached") + }*/ + + Thread.sleep(2000) +} diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-13.1 - supervisorJob.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-13.1 - supervisorJob.kt new file mode 100644 index 0000000..ba89a52 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-13.1 - supervisorJob.kt @@ -0,0 +1,45 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/* +* Look at also the following examples: +* jobs-06-throw-exception-in-child-Job-with-SupervisorJob +* example-supervision-01.kt +* example-supervision-02.kt +* example-supervision-03.kt +* */ + + + +fun main() { + + var child1: Job? = null + var child2: Job? = null + + // Parent Job + val supervisorJob = SupervisorJob() + + val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> + println("Caught $exception") + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("supervisorJob.isActive : ${supervisorJob.isActive}") + } + + + val coroutineScope = CoroutineScope(Dispatchers.IO + supervisorJob + coroutineExceptionHandler) + + // creates 1st child Job for SupervisorJob + child1 = coroutineScope.launch { + delay(100) + throw Exception("Error happened in child 1 suddenly!") + } + + // creates 2nd child Job for SupervisorJob + child2 = coroutineScope.launch { + delay(500) + } + + Thread.sleep(2000L) +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-13.2 - supervisorJob.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-13.2 - supervisorJob.kt new file mode 100644 index 0000000..728f7bf --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-13.2 - supervisorJob.kt @@ -0,0 +1,43 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/* +* * Can you do the previous example by using supervisorScope and not using supervisorJob? +* * Absolutely! +* */ + + + +fun main() { + + runBlocking { + + var child1: Job? = null + var child2: Job? = null + + // Parent Job + val supervisorJob = SupervisorJob() + + val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> + println("Caught $exception") + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("supervisorJob.isActive : ${supervisorJob.isActive}") + } + + supervisorScope { + // creates 1st child Job for SupervisorJob + child1 = launch(Dispatchers.IO + coroutineExceptionHandler) { + delay(100) + throw Exception("Error happened in child 1 suddenly!") + } + + // creates 2nd child Job for SupervisorJob + child2 = launch(Dispatchers.IO + coroutineExceptionHandler) { + delay(500) + } + } + + } +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-14.1 - supervisorJob.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-14.1 - supervisorJob.kt new file mode 100644 index 0000000..a7a746a --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-14.1 - supervisorJob.kt @@ -0,0 +1,51 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/* +* This example is a simulation of Activity4SupervisorJob.kt example. +* +* 🔥 a Job is created as a direct child of SupervisorJob as the following: +* 🔥 CoroutineScope(Dispatchers.Default).launch(parentSupervisorJob + coroutineExceptionHandler) +* +* 🤙 child1 and child2 is the child of child of SupervisorJob. +* +* 🐲 When child2 throws an exception, SupervisorJob won’t propagate the exception up in the hierarchy and will let the child2 handle it. +* 🐲 Thus 1st child job and parent job continue to work. +* +* Please read Manuel Vivo's article for further information: +* https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c +* +* */ + +fun main() { + + var child1: Job? = null + var child2: Job? = null + + // Parent Job and Coroutine Exception Handler + val parentSupervisorJob = SupervisorJob() + + val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> + println("Caught $exception") + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("supervisorJob.isActive : ${parentSupervisorJob.isActive}") + } + + + CoroutineScope(Dispatchers.Default).launch(parentSupervisorJob + coroutineExceptionHandler) { + + child1 = launch { + delay(1000) + } + + val childSupervisorJob = SupervisorJob() + child2 = launch(childSupervisorJob) { + delay(500) + throw RuntimeException("Child 2 threw RuntimeException") + } + } + + Thread.sleep(2000L) +} diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-14.2 - supervisorJob.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-14.2 - supervisorJob.kt new file mode 100644 index 0000000..a714d2a --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-14.2 - supervisorJob.kt @@ -0,0 +1,54 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/* +* Can you do the previous example by using supervisorScope and not using supervisorJob? +* Absolutely! +* +* This example is a simulation of Activity4SupervisorJob.kt example. +* +* 🔥 a Job is created as a direct child of SupervisorJob as the following: +* 🔥 CoroutineScope(Dispatchers.Default).launch(parentSupervisorJob + coroutineExceptionHandler) +* +* 🤙 child1 and child2 is the child of child of SupervisorJob. +* +* 🐲 When child2 throws an exception, SupervisorJob won’t propagate the exception up in the hierarchy and will let the child2 handle it. +* 🐲 Thus 1st child job and parent job continue to work. +* +* Please read Manuel Vivo's article for further information: +* https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c +* +* */ + +fun main() = runBlocking { + + var child1: Job? = null + var child2: Job? = null + + // Parent Job and Coroutine Exception Handler + val parentSupervisorJob = SupervisorJob() + + val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> + println("Caught $exception") + println("Job 1 state: ${child1?.status()}") + println("Job 2 state: ${child2?.status()}") + println("supervisorJob.isActive : ${parentSupervisorJob.isActive}") + } + + + supervisorScope { + child1 = launch(Dispatchers.Default + coroutineExceptionHandler) { + delay(1000) + } + + supervisorScope { + child2 = launch(Dispatchers.Default + coroutineExceptionHandler) { + delay(500) + throw RuntimeException("Child 2 threw RuntimeException") + } + } + } + + Thread.sleep(2000L) +} diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-15.1 - supervisorJob.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-15.1 - supervisorJob.kt new file mode 100644 index 0000000..31273fc --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-15.1 - supervisorJob.kt @@ -0,0 +1,42 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +fun main() = runBlocking { + val supervisorJob = SupervisorJob() + var firstChild: Job? = null + var secondChild: Job? = null + + val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> + println("Caught $exception") + println("Job 1 state: ${firstChild?.status()}") + println("Job 2 state: ${secondChild?.status()}") + println("supervisorJob.isActive : ${supervisorJob.isActive}") + } + + val coroutineScope = CoroutineScope(supervisorJob + coroutineExceptionHandler) + + // launch the first child -- its exception is ignored for this example (don't do this in practice!) + firstChild = coroutineScope.launch { + delay(500) + throw AssertionError("First child is cancelled") + } + + // launch the second child + secondChild = coroutineScope.launch { + + try { + for (i in 20 downTo 0) { + delay(300) + println("still active $i") + } + throw Exception("Error inside 2nd child job") + } finally { + println("Second child is cancelled because supervisor is cancelled") + } + } + + + // wait until the first child fails & completes + secondChild.join() +} diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-15.2 - supervisorScope.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-15.2 - supervisorScope.kt new file mode 100644 index 0000000..6d9e116 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-15.2 - supervisorScope.kt @@ -0,0 +1,46 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/* +* Can you do the previous example by using supervisorScope and not using supervisorJob? +* Absolutely! +* */ + +fun main() = runBlocking { + val supervisorJob = SupervisorJob() + var firstChild: Job? = null + var secondChild: Job? = null + + val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> + println("Caught $exception") + println("Job 1 state: ${firstChild?.status()}") + println("Job 2 state: ${secondChild?.status()}") + println("supervisorJob.isActive : ${supervisorJob.isActive}") + } + + + supervisorScope { + // launch the first child -- its exception is ignored for this example (don't do this in practice!) + firstChild = launch(coroutineExceptionHandler) { + delay(500) + throw AssertionError("First child is cancelled") + } + + // launch the second child + secondChild = launch(coroutineExceptionHandler) { + + try { + for (i in 20 downTo 0) { + delay(300) + println("still active $i") + } + throw Exception("Error inside 2nd child job") + } finally { + println("Second child is cancelled because supervisor is cancelled") + } + } + } + + println("done") +} diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-16 - yield.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-16 - yield.kt new file mode 100644 index 0000000..e12b663 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/jobs-16 - yield.kt @@ -0,0 +1,40 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield + +/* +* Look at also the example-exceptions-03.kt +* +* +* It temporarily deprioritises the current long running CPU task, giving other tasks(coroutines) a fair opportunity to run. +* +* If the work you’re doing is 1) CPU heavy, 2) may exhaust the thread pool and 3) you want to allow the thread to do other work without having to add more threads to the pool, then use yield(). +* +* */ + +fun main() = runBlocking { + + + val job1 = launch { + repeat(10) { + delay(1000) + println("$it. step done in job 1 ") + yield() + } + } + + val job2 = launch { + repeat(10) { + delay(1000) + println("$it. step done in job 2 ") + yield() + } + } + + job1.join() + job2.join() + println("done") +} 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-10-order-of-execution-.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-10-order-of-execution-.kt new file mode 100644 index 0000000..3069ee0 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-10-order-of-execution-.kt @@ -0,0 +1,30 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +fun main() = runBlocking { + launch { + println("5") + } + + launch { + println("6") + } + + for (i in 7..10) { + println(i.toString()) + } + + launch { + println("4") + } + + launch { + println("3") + } + + for (i in 11..14) { + println(i.toString()) + } +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-11-order-of-execution-coroutineScope-and-launch-together.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-11-order-of-execution-coroutineScope-and-launch-together.kt new file mode 100644 index 0000000..09a98dd --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-11-order-of-execution-coroutineScope-and-launch-together.kt @@ -0,0 +1,46 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +/* +* Clone of coroutineScope-1.kt +* +* 🔥 launch returns immediately but inside of launch not eecuted immediately. non-blocking code after launch is executed firstly, then inside of launch is executed. +* 🐲 coroutineScope is a blocking code. Code execution does not go to following line, +* waits for completion of inside of coroutineScope and coroutines started before coroutineScope. Thus printed 3 1 2. +* +* https://stackoverflow.com/questions/53535977/coroutines-runblocking-vs-coroutinescope/53536713#53536713 önce buna bak +* */ +fun main() = runBlocking { + launch { + println("1") + } + + coroutineScope { + launch { + println("2") + } + + println("3") + } + + coroutineScope { + launch { + println("4") + } + + println("5") + } + + launch { + println("6") + } + + for (i in 7..100) { + println(i.toString()) + } + + println("101") +} \ 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/guide/random-practice-7-give-name-to-a-coroutine.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-7-give-name-to-a-coroutine.kt new file mode 100644 index 0000000..3b3ad9c --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-7-give-name-to-a-coroutine.kt @@ -0,0 +1,28 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +/** + * + * 1 - Give name to a coroutine -> CoroutineName("my-custom-name") + * 2 - Print name of the coroutine -> Thread.currentThread().name - or - ${coroutineContext[Job]} + * + * To Enable the logging in IntelliJ toolbar menu: + * Run -> Edit Configuration and add the following in VM options + * -Dkotlinx.coroutines.debug + * */ + +fun main() = runBlocking { + println(Thread.currentThread().name) + + val job = launch(CoroutineName("my-custom-name")) { + println(Thread.currentThread().name) + println("${coroutineContext[Job]}") + + } + + job.join() +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-8-CoroutineStart.LAZY.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-8-CoroutineStart.LAZY.kt new file mode 100644 index 0000000..c68293b --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-8-CoroutineStart.LAZY.kt @@ -0,0 +1,29 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + +import kotlinx.coroutines.* + +/** + * + * Calling start() on a coroutine that was started with CoroutineStart.LAZY causes the lazy flag to be removed. + * At that point, the coroutine will be eligible to be executed. Exactly when it will be executed is up to the dispatcher and platform, and it will depend on what other coroutines exist and are running. + * + * https://klassbook.commonsware.com/lessons/Coroutine%20Basics/lazy-then-active.html + * */ + +fun main() { + val job = CoroutineScope(Dispatchers.IO).launch(start = CoroutineStart.LAZY) { + stallForTime2() + println("This is executed after the previous suspend fun stallForTime2 returns") + } + + println("Before starting the job") + job.start() + println("After starting the job") + Thread.sleep(5000) +} + +suspend fun stallForTime2() { + withContext(Dispatchers.Default) { + delay(2000L) + } +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-9-CoroutineStart.LAZY.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-9-CoroutineStart.LAZY.kt new file mode 100644 index 0000000..15c2320 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/guide/random-practice-9-CoroutineStart.LAZY.kt @@ -0,0 +1,30 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.guide + + +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlin.system.measureTimeMillis + +fun main() = runBlocking { + val time = measureTimeMillis { + val one = async(start = CoroutineStart.LAZY) { fun1() } + val two = async(start = CoroutineStart.LAZY) { fun2() } + // some computation + one.start() // start the first one + two.start() // start the second one + println("The answer is ${one.await() + two.await()}") + } + println("Completed in $time ms") +} + +suspend fun fun1(): Int { + delay(1000L) // pretend we are doing something useful here + return 13 +} + +suspend fun fun2(): Int { + delay(1000L) // pretend we are doing something useful here, too + return 29 +} diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/retrofitexample/ReqResAPI.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/retrofitexample/ReqResAPI.kt new file mode 100644 index 0000000..5ba5b4e --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/retrofitexample/ReqResAPI.kt @@ -0,0 +1,13 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.retrofitexample + +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface ReqResAPI { + + @GET("users") + suspend fun getUsers( + @Query("page") page: Int = 1 + ): Response +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/retrofitexample/RetrofitActivity.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/retrofitexample/RetrofitActivity.kt new file mode 100644 index 0000000..a34f19a --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/retrofitexample/RetrofitActivity.kt @@ -0,0 +1,74 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.retrofitexample + +import android.os.Bundle +import android.util.Log +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.smarttoolfactory.tutorial1_1basics.R +import kotlinx.android.synthetic.main.activity_retrofit.* +import kotlinx.coroutines.* +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +/** example taken from https://github.com/coding-blocks-archives/Retrofit-Coroutines-Sample-Android */ +class RetrofitActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_retrofit) + + val r = Retrofit.Builder() + .baseUrl("https://reqres.in/api/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + + val api = r.create(ReqResAPI::class.java) + + GlobalScope.launch(Dispatchers.IO) { + try { + val res1 = async { api.getUsers(1) } + Log.d(TAG, "first page done") + val res2 = async { api.getUsers(2) } + Log.d(TAG, "second page done") + + val res = awaitAll(res1, res2) + Log.d( + TAG, + "both pages done. ${res[0].body()?.users?.size} users retrieved from first url. ${res[1].body()?.users?.size} users retrieved from second url." + ) + + val users = ArrayList() + res[0].body()?.users?.let { users.addAll(it) } + res[1].body()?.users?.let { users.addAll(it) } + + withContext(Dispatchers.Main) { + Toast.makeText(this@RetrofitActivity, "Request Success", Toast.LENGTH_SHORT) + .show() + + printUsers(users) + } + + + } catch (e: Exception) { + GlobalScope.launch(Dispatchers.Main) { + Toast.makeText(this@RetrofitActivity, "Request Success", Toast.LENGTH_SHORT) + .show() + } + } + } + } + + private fun printUsers(users: List) { + lvPeople.adapter = ArrayAdapter( + this, + android.R.layout.simple_list_item_1, + android.R.id.text1, + users.map { it.firstName } + ) + } + + companion object { + const val TAG = "RetrofitActivityExample" + } +} \ No newline at end of file diff --git a/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/retrofitexample/UsersResponse.kt b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/retrofitexample/UsersResponse.kt new file mode 100644 index 0000000..52e5852 --- /dev/null +++ b/Tutorial1-1CoroutinesBasics/src/main/java/com/smarttoolfactory/tutorial1_1coroutinesbasics/retrofitexample/UsersResponse.kt @@ -0,0 +1,23 @@ +package com.smarttoolfactory.tutorial1_1coroutinesbasics.retrofitexample + + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class UsersResponse( + @SerializedName("data") val users: List, + @SerializedName("page") val page: Int, + @SerializedName("per_page") val perPage: Int, + @SerializedName("total") val total: Int, + @SerializedName("total_pages") val totalPages: Int +) { + @Keep + data class User( + @SerializedName("avatar") val avatar: String, + @SerializedName("email") val email: String, + @SerializedName("first_name") val firstName: String, + @SerializedName("id") val id: Int, + @SerializedName("last_name") val lastName: String + ) +} \ No newline at end of file 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">