나의 추측) 

concurrent is started when we use ‘launch’ or ‘async’
withContext() is not start new concurrent. it just change thread

launch, async를 사용할때 새로운 concurrent가 만들어지고 동시 작업이 수행된다. withContext()는 새로 concurrent가 만들어지지 않고 thread만 바꾼다. 옮겨진 thread에서의 작업이 완수 될때 까지 기다렸다 진행된다. 예제 코드 아래 참조

import kotlinx.coroutines.*

fun main(args: Array<String>){
   println("main functions started")

   val job1 = SupervisorJob()
//        val scope = CoroutineScope(Dispatchers.Default + job1)
   val scope = CoroutineScope(job1)

   runBlocking {



       scope.launch {
           databasejob1()
           databasejob2()
       }

       scope.launch{

       }

       delay(50000L)
   }


}

private suspend fun databasejob1(){
   withContext(Dispatchers.IO){
       delay(2000L)
       println("databasejob1===================")
   }
}

private suspend fun databasejob2(){
   withContext(Dispatchers.IO){
       delay(2000L)
       println("databasejob2===================")
   }
}

CoroutineScope()와 coroutineScope{} 의 차이

nested 된 형태의 coroutines 인경우는 일반 coroutines의 흐름과 좀 다르다. nested된 coroutine의 경우 concurrent하게 진행되는데 nested된 coroutine이 완료될때까지 멈췄다 진행하려는 경우  coroutineScope{}를 이용한 Structured concurrency 를 이용한다.

https://stackoverflow.com/questions/59368838/difference-between-coroutinescope-and-coroutinescope-in-kotlin

CoroutineScope() is nothing but a factory of CoroutineScope objects, and a CoroutineScope object is nothing but a holder of a CoroutineContext. It has no active role in coroutines, but it’s an important part of the infrastructure that makes it easy to do structured concurrency properly. This comes from the fact that all coroutine builders like launch or async are extension functions on CoroutineScope and inherit its context.

You will rarely, if ever, have the need to call CoroutineScope() because usually you either pick up an existing coroutine scope or have one created for you by other convenience functions (like MainScope on Android) or Kotlin internals.

coroutineScope(), on the other hand, is one of the coroutine builders, which means it executes the block you pass it inside a sub-coroutine. It is basically an alias for withContext(this.coroutineContext) and you should primarily use it when you want to launch one or more background coroutines while you continue some work in the foreground, and then join on the background coroutines when completing the block.

.

.

==========================================================

Best difference between CoroutineScope (Capital C version) vs coroutineScope (Smaller c version), I could figure out and which was easily understandable was correlating them with Unstructured vs Structured concurrency

Let me share an example :

class MainActivity extends Activity {
    private Button btn;
    public void onCreate(Bundle b) {
        setContentView(R.layout.activity_main);
        btn = (Button) findViewById(R.id.start_btn);
        btn.setOnClickListener( () -> {
        // Starting a coroutine in Main scope, to download user data, and will print it
        CoroutineScope(Dispatchers.Main).launch {
            int result = downloadUserData()
            Toast.makeText(applicationContext, "Result : " + result, Toast.LENGTH_LONG).show()
        });
    }

    private suspend int downloadUserData() {
        int result = 0;
        // Here, we use CoroutineScope (Capital C version) which will start a new scope and 
        // launch coroutine in new scope Dispatchers.IO, Not In Parent Scope which is Dispatchers.Main
        // Thus, this function would directly return without waiting for loop completion and will return 0
        CoroutineScope(Dispatchers.IO).launch {
            for (int i = 0; i < 2000; i++) {
                kotlinx.coroutines.delay(400);
                result++;
            }
        }

        return result;
    }
}

Output : Result : 0

This is an example of Unstructured Concurrency where it is not guaranteed that child coroutine would complete before returning. Thus, caller/parent coroutine would get wrong value returned by child coroutine. Even, when child coroutine has returned already, child coroutine may be running (in Active state) in the background which may lead to Memory Leaks in certain cases.

Solution :

When we need to communicate between multiple coroutines, we need to make sure Structured Concurrency (Recommended)

This can be done by re-using parent/caller coroutine scope inside child/callee coroutine. This can be achieved by coroutineScope {} (Smaller c) version inside child/callee coroutine.

    private suspend int downloadUserData() {
        int result = 0;
        // By using coroutineScope (Smaller c version) below, we ensure that this coroutine would execute in the
        // parent/caller coroutine's scope, so it would make sure that the for loop would complete
        // before returning from this suspended function. This will return 20000 properly
        coroutineScope {
            for (int i = 0; i < 20000; i++) {
                kotlinx.coroutines.delay(400);
                result++;
            }
        }
        return result;
    }

Output : Result : 20000

.

.

.

.

Exceptions in Coroutines

https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c

A coroutine suddenly failed! What now? 😱

When a coroutine fails with an exception, it will propagate said exception up to its parent! Then, the parent will 1) cancel the rest of its children, 2) cancel itself and 3) propagate the exception up to its parent. (exception발생시 기본 작동)

The exception will reach the root of the hierarchy and all the coroutines that the CoroutineScope started will get cancelled too.

While propagating an exception can make sense in some cases, there are other cases when that’s undesirable. Imagine a UI-related CoroutineScope that processes user interactions. If a child coroutine throws an exception, the UI scope will be cancelled and the whole UI component will become unresponsive as a cancelled scope cannot start more coroutines.

What if you don’t want that behavior? Alternatively, you can use a different implementation of   Job  , namely   SupervisorJob  , in the CoroutineContext of the CoroutineScope that creates these coroutines.

SupervisorJob to the rescue

With a   SupervisorJob, the failure of a child doesn’t affect other children. A SupervisorJob won’t cancel itself or the rest of its children. Moreover, SupervisorJob won’t propagate the exception either, and will let the child coroutine handle it. (그러나 처리 하지 않으면 exception은 위로 올라간다.)

You can create a CoroutineScope like this 

val uiScope = CoroutineScope(SupervisorJob()) 

to not propagate cancellation when a coroutine fails as this image depicts:

If the exception is not handled and the CoroutineContext doesn’t have a CoroutineExceptionHandler (as we’ll see later), it will reach the default thread’s ExceptionHandler. it will make your app crash regardless of the Dispatcher this happens on.

💥 Uncaught exceptions will always be thrown regardless of the kind of Job you use

coroutineScope 나 supervisorScope를 scope builder라고 한다. structured coroutine을 만들때 사용하며 { } 안의 코드가 완료될때 까지 nesting coroutine은 기다린다.

The same behavior applies to the scope builders coroutineScope and supervisorScope. These will create a sub-scope (with a Job or a SupervisorJob accordingly as a parent) with which you can logically group coroutines (e.g. if you want to do parallel computations or you want them to be or not be affected by each other).

Warning: A SupervisorJob only works as described when it’s part of a scope: either created using supervisorScope or CoroutineScope(SupervisorJob()).

CoroutineScope(SupervisorJob())  이런식으로 하거나 supervisorScope { }를 통해서만 적용할수 있다는 이야기


Job or SupervisorJob? 🤔

When should you use a Job or a SupervisorJob? Use a SupervisorJob or supervisorScope when you don’t want a failure to cancel the parent and siblings.

Some examples:

// Scope handling coroutines for a particular layer of my app
val scope = CoroutineScope(SupervisorJob())
scope.launch {
   // Child 1
}scope.launch {
   // Child 2
}

In this case, if child#1 fails, neither scope nor child#2 will be cancelled.

Another example:

// Scope handling coroutines for a particular layer of my app
val scope = CoroutineScope(Job())
scope.launch {
   supervisorScope {
       launch {
           // Child 1
       }
       launch {
           // Child 2
       }
   }
}

In this case, as supervisorScope creates a sub-scope with a SupervisorJob, if child#1 fails, child#2 will not be cancelled. If instead you use a coroutineScope in the implementation, the failure will get propagated and will end up cancelling scope too.

Watch out quiz! Who’s my parent? 🎯

Given the following snippet of code, can you identify what kind of Job child#1 has as a parent?

위에서 말했듯이 CoroutineScope(SupervisorJob())  이런식으로 하거나 supervisorScope { }를 통해서만 적용할수 있다.

val scope = CoroutineScope(Job())
scope.launch(SupervisorJob()) {
   // new coroutine -> can suspend
  launch {
       // Child 1
   }
   launch {
       // Child 2
   }
}

child#1’s parentJob is of type Job! Hope you got it right! Even though at first impression, you might’ve thought that it can be a SupervisorJob, it is not because a new coroutine always gets assigned a new Job() which in this case overrides the SupervisorJob. SupervisorJob is the parent of the coroutine created with scope.launch; so literally, SupervisorJob does nothing in that code!

Therefore, if either child#1 or child#2 fails, the failure will reach scope and all work started by that scope will be cancelled.

Remember that a SupervisorJob only works as described when it’s part of a scope: either created using supervisorScope or CoroutineScope(SupervisorJob()). Passing a SupervisorJob as a parameter of a coroutine builder will not have the desired effect you would’ve thought for cancellation.

Regarding exceptions, if any child throws an exception, that SupervisorJob won’t propagate the exception up in the hierarchy and will let its coroutine handle it.

Under the hood

If you’re curious about how Job works under the hood, check out the implementation of the functions childCancelled and notifyCancelling in the JobSupport.kt file.

In the SupervisorJob implementation, the childCancelled method just returns false, meaning that it doesn’t propagate cancellation but it doesn’t handle the exception either.

Dealing with Exceptions 👩‍🚒

Coroutines use the regular Kotlin syntax for handling exceptions: try/catch or built-in helper functions like runCatching (which uses try/catch internally).

We said before that uncaught exceptions will always be thrown. However, different coroutines builders treat exceptions in different ways.

Launch

With launch, exceptions will be thrown as soon as they happen. Therefore, you can wrap the code that can throw exceptions inside a try/catch, like in this example:

scope.launch {
   try {
       codeThatCanThrowExceptions()
   } catch(e: Exception) {
       // Handle exception
   }
}

With launch, exceptions will be thrown as soon as they happen

Async

When async is used as a root coroutine (coroutines that are a direct child of a CoroutineScope instance or supervisorScope), exceptions are not thrown automatically, instead, they’re thrown when you call .await().

CoroutineScope 내에서 async가 direct child 인 경우에 그 안에서 발생하는 exception은 바로 throw되지 않고 await() 호출될때까지 유보된다.

To handle exceptions thrown in async whenever it’s a root coroutine, you can wrap the .await() call inside a try/catch:

supervisorScope {
   val deferred = async {
       codeThatCanThrowExceptions()
   }    try {
       deferred.await()
   } catch(e: Exception) {
       // Handle exception thrown in async
   }
}

In this case, notice that calling async will never throw the exception, that’s why it’s not necessary to wrap it as well. await will throw the exception that happened inside the async coroutine.

When async is used as a root coroutine, exceptions are thrown when you call .await

Also, notice that we’re using a supervisorScope to call async and await. As we said before, a SupervisorJob lets the coroutine handle the exception; as opposed to Job that will automatically propagate it up in the hierarchy so the catch block won’t be called:

coroutineScope {
   try {
       val deferred = async {
           codeThatCanThrowExceptions()
       }
       deferred.await()
   } catch(e: Exception) {
       // Exception thrown in async WILL NOT be caught here
       // but propagated up to the scope
   }
}

Furthermore, exceptions that happen in coroutines created by other coroutines will always be propagated regardless of the coroutine builder. For example:

val scope = CoroutineScope(Job())
scope.launch {
   async {
       // If async throws, launch throws without calling .await()
   }
}

위의 코드는 CoroutineScope 내에서 async가 direct child 가 아닌 경우에 해당 바로 exception이 throw된다.

In this case, if async throws an exception, it will get thrown as soon as it happens because the coroutine that is the direct child of the scope is launch. The reason is that async (with a Job in its CoroutineContext) will automatically propagate the exception up to its parent (launch) that will throw the exception.

⚠️ Exceptions thrown in a coroutineScope builder or in coroutines created by other coroutines won’t be caught in a try/catch!

In the SupervisorJob section, we mention the existence of CoroutineExceptionHandler. Let’s dive into it!

CoroutineExceptionHandler

The   CoroutineExceptionHandler   is an optional element of a   CoroutineContext   allowing you to handle uncaught exceptions.

Here’s how you can define a CoroutineExceptionHandler, whenever an exception is caught, you have information about the CoroutineContext where the exception happened and the exception itself:

val handler = CoroutineExceptionHandler {
   context, exception -> println("Caught $exception")
}

Exceptions will be caught if these requirements are met:

  • When ⏰: The exception is thrown by a coroutine that automatically throws exceptions (works with launch, not with async).
  • Where 🌍: If it’s in the CoroutineContext of a CoroutineScope or a root coroutine (direct child of CoroutineScope or a supervisorScope).

Let’s see some examples using the CoroutineExceptionHandler defined above. In the following example, the exception will be caught by the handler:

val scope = CoroutineScope(Job())
scope.launch(handler) {
   launch {
       throw Exception("Failed coroutine")
   }
}

In this other case in which the handler is installed in a inner coroutine, it won’t be caught:

val scope = CoroutineScope(Job())
scope.launch {
   launch(handler) {
       throw Exception("Failed coroutine")
   }
}

The exception isn’t caught because the handler is not installed in the right CoroutineContext. The inner launch will propagate the exception up to the parent as soon as it happens, since the parent doesn’t know anything about the handler, the exception will be thrown.

Dealing with exceptions gracefully in your application is important to have a good user experience, even when things don’t go as expected.

Remember to use SupervisorJob when you want to avoid propagating cancellation when an exception happens, and Job otherwise.

Uncaught exceptions will be propagated, catch them to provide a great UX!

.

.

.

.

android 와 Coroutines를 같이 사용하는 경우의 유의점 (80% 이해)

https://medium.com/@FreddieWang/some-tips-of-kotlin-coroutines-for-android-developers-f0988fbeb246

Tip1: Never use GlobalScope to launch the coroutines

According to the official document,

Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Application code usually should use application-defined CoroutineScope, using async or launch on the instance of GlobalScope is highly discouraged.

Instead, we should use Activity, Fragment scope like below.

class CoroutineActivity : AppCompatActivity() {    // MainScope() = ContextScope(SupervisorJob() + Dispatchers.Main)    private val scope: CoroutineScope = MainScope()    override fun onDestroy() {        super.onDestroy()        coroutineContext.cancelChildren()    }    fun loadSomething() = scope.launch {        // code    }}

see more explanation about the GlobalScope.

Tip2: Always use SupervisorJob on Android

If you want to run the background task which would throw the exceptions, you may write the function like below

val job: Job = Job()val scope = CoroutineScope(Dispatchers.Default + job)fun backgroundTask(): Deferred<String> = scope.async { … }fun loadData() = scope.launch {  try {    doWork().await()  } catch (e: Exception) { … }}

Unfortunately, it can’t catch the exception, so the app will crash. It is because the backgroundTask would create a child job and the failure of the child job leads to an immediate failure of its parent.

The solution is to use SupervisorJob .

val job: Job = SupervisorJob()val scope = CoroutineScope(Dispatchers.Default + job)fun backgroundTask(): Deferred<String> = scope.async { … }fun loadData() = scope.launch {  try {    backgroundTask().await()  } catch (e: Exception) { … }}

and it only works if you explicitly run the async on the coroutine scope with SupervisorJob. It will still crash the app if the async is launched in the scope of parent coroutine like below.

val job: Job = SupervisorJob()val scope = CoroutineScope(Dispatchers.Default + job)fun loadData() = scope.launch {  try {    async { … }.await() // this is running in the scope of parent.  } catch (e: Exception) { … } // Can’t catch the exception}

if you want to launch the async in the parent scope, you should also assign the coroutine context explicitly.

val job: Job = SupervisorJob()val scope = CoroutineScope(Dispatchers.Default + job)fun loadData() = scope.launch {  try {    async(scope.coroutineContext) { … }.await() 
   // Now the context is SupervisorJob.  } catch (e: Exception) { … } // Can catch the exception now.}

Tip3: Assign the context explicitly for the suspend functions

If we want to make the function “suspendable”, we can just add the suspend for a function like below.

suspend fun doSomething(): Result {  // Running for long time  return result}

It seems fine, but sometimes it brings implicit errors especially on Android. For example:

suspend fun loadingSomething(): Result {  loadingView.show()  val result = withContext(Dispatchers.IO) {    doSomething()  }  loadingView.hide()  return result}

the loadingView.show() and loadingView.hide() must be running in main thread. Therefore it would crash the application like this

val scope = CoroutineScope(Dispatchers.Default + job)scope.launch {  loadingSomething() // oops, it would crash the app.}

So the better way is that we assign the explicit dispatcher for the suspend functions

suspend fun doSomething(): Result = withContext(Dispatchers.IO) {  // Running for long time  return@withContext result}suspend fun loadingSomething(): Result =
 withContext(Dispatchers.Main) {  loadingView.show()  val result = doSomething()  loadingView.hide()  return@withContext result}val scope = CoroutineScope(Dispatchers.Default + job)scope.launch {  loadingSomething()
 // safe, because loadingSomething would run in main thread.}

So if we assign the dispatcher explicitly, the function is safe and looks concise.

Tip4: Do not cancel the scope job directly.

Assume that we have implemented some job manager.

class JobManager {
   private val parentJob = SupervisorJob()
   private val scope = CoroutineScope(Dispatchers.Default + job)
   
   fun job1() = scope.launch { /* do work */ }
   
   fun job2() = scope.launch { /* do work */ }
   
   fun cancelAllJobs() {
       parentJob.cancel()
   }
}fun main() {
   val jobManager = JobManager()
   
   jobManager.job1()
   jobManager.job2()
   jobManager.cancelAllJobs()
   jobManager.job1() // can't run the job here
}

And you would get the error at second job1(). It is because that when we cancel the parentJob , we put the parentJob into COMPLETED state. The coroutines launched in the scope of completed job won’t be executed.

Instead, we should use cancelChildren function to cancel the jobs.

class JobManager {
   private val parentJob = SupervisorJob()
   private val scope = CoroutineScope(Dispatchers.Default + job)
   
   fun job1() = scope.launch { /* do work */ }
   
   fun job2() = scope.launch { /* do work */ }
   
   fun cancelAllJobs() {
       scope.coroutineContext.cancelChildren()
   }
}fun main() {
   val jobManager = JobManager()
   
   jobManager.job1()
   jobManager.job2()
   jobManager.cancelAllJobs()
   jobManager.job1() // No problem now.
}

Tip5: Use Android ktx for coroutine scope.

Android KTX has provided some useful extensions for coroutines. It can support lifecycle automatically so we don’t need to do the same thing again. The coroutineContext in lifecycleScope is SupervisorJob() + DispatchersMain.immediate and is cancelled when the lifecycle ended.

But if we need to run the suspended functions in the lifecyleScope. We should still need to assign the context as Tips3 mentioned. Especially the suspended functions include network calls.

lifecycleScope.launch {
 doSomething()
}suspend fun doSomething = withContext(Dispatchers.IO) {   // Network calls should not run in main thread}

Tip6: Use async.await() and withContext for different purpose.

If you want to wait for the result from other suspended functions, you have two choices.

  1. Use the async { }.await
val result = async { doSomething() }.await()

2. Use the withContext

val result = withContext(Dispatchers.IO) {  doSomething()}

So what is the difference between async.await and withContext??

Let’s check the source code.

public fun <T> CoroutineScope.async(
   context: CoroutineContext = EmptyCoroutineContext,
   start: CoroutineStart = CoroutineStart.DEFAULT,
   block: suspend CoroutineScope.() -> T
): Deferred<T> {
   val newContext = newCoroutineContext(context)
   val coroutine = if (start.isLazy)
       LazyDeferredCoroutine(newContext, block) else
       DeferredCoroutine<T>(newContext, active = true)
   coroutine.start(start, coroutine, block)
   return coroutine
}public suspend fun <T> withContext(
   context: CoroutineContext,
   block: suspend CoroutineScope.() -> T
): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->...}

By the implementation, the async returns a Deferred object and withContext return the value directly.

So the caller of withContext would suspend immediately and wait for the result. The caller of async WOULD NOT suspend immediately. Therefore we can have a conclusion

If you need to run the coroutines sequentially, use withContext

If you need to run the coroutines in parallel, use async

In fact, if you use async.await directly, IntelliJ IDEA would show a warning and suggest you use withContext directly.

Tip7: Use suspendCancellableCoroutine to wrap callback-based interfaces.

Before Kotlin Coroutines is out, there are many projects using the callbacks for async programming. If we want to use Kotlin Coroutines to call the async functions with callback, is there any way to support the callback-based API?

Yes, we can wrap the callback interface by suspendCancellableCoroutine. Here is the example for Android’s CameraDevice.StateCallback

suspend fun CameraManager.openCamera(cameraId: String): CameraDevice? =
   suspendCancellableCoroutine { cont ->
       val callback = object : CameraDevice.StateCallback() {
           override fun onOpened(camera: CameraDevice) {
               cont.resume(camera)
           }

           override fun onDisconnected(camera: CameraDevice) {
               cont.resume(null)
           }

           override fun onError(camera: CameraDevice, error: Int) {
               // assuming that we don't care about the error in this example
               cont.resume(null)
           }
       }
       openCamera(cameraId, callback, null)
   }

This pattern is extremely useful for Android applications because there are many callback-based API in Android frameworks.

References:

.

.

.

.

https://www.youtube.com/playlist?list=PLQkwcJG4YTCQcFEPuYGuv54nYai_lwil_

basic coroutines사용법, 매우 간단하고 쉽다. 각 10분 분량 1-9 , 10 완료

.

.

job.join()에서 실행되는 것이 아닌 GlobalScope.launch 에서 이미 실행

.

.

깔끔하게 cancel하는 방법

cancel했지만 그래도 아래와 같이 복잡한 연산을 하는 경우 멈추지 않고 계속 작동한다. 

아래는 정확하게 원하는 시점이 아니지만 결국 멈추게 된 경우

.

.

아래는 좋은 방법은 아니다. async를 이용한 방법이 좋다.

.

.

아래는 안드로이드를 위한 lifecycleScope를 이용한 경우 

아래는 안드로이드를 위한 lifecycleScope를 사용하지 않은 안좋은 예시

.

retorfit의 일반 방법

coroutines을 이용한 retrofit

.

.

.

.

https://youtu.be/F63mhZk-1-Y

codingwithmith 20분 분량

package com.codingwithmitch.coroutineexamples

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener {
            setNewText("Click!")

            CoroutineScope(IO).launch {
                fakeApiRequest()
            }
        }
        
    }

    private fun setNewText(input: String){
        val newText = text.text.toString() + "n$input"
        text.text = newText
    }
    private suspend fun setTextOnMainThread(input: String) {
        withContext (Main) {
            setNewText(input)
        }
    }

    private suspend fun fakeApiRequest() {
        logThread("fakeApiRequest")

        val result1 = getResult1FromApi() // wait until job is done

        if ( result1.equals("Result #1")) {

            setTextOnMainThread("Got $result1")

            val result2 = getResult2FromApi() // wait until job is done

            if (result2.equals("Result #2")) {
                setTextOnMainThread("Got $result2")
            } else {
                setTextOnMainThread("Couldn't get Result #2")
            }
        } else {
            setTextOnMainThread("Couldn't get Result #1")
        }
    }


    private suspend fun getResult1FromApi(): String {
        logThread("getResult1FromApi")
        delay(1000) // Does not block thread. Just suspends the coroutine inside the thread
        return "Result #1"
    }

    private suspend fun getResult2FromApi(): String {
        logThread("getResult2FromApi")
        delay(1000)
        return "Result #2"
    }

    private fun logThread(methodName: String){
        println("debug: ${methodName}: ${Thread.currentThread().name}")
    }

}

위에서 getResult2FromApi는 getResult1FromApi 가 완료된후에 실행된다. 하나의 launch에서 여러개의 suspend function호출은 순차적으로 진행된다. 새로운 concurrent 를 이용해 처리하는 것이 아니다.

.

.

.

.

https://youtu.be/cu0_fHFQGbM

time out codingwithmitch 

12분분량

package com.codingwithmitch.coroutineexamples

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main


class MainActivity : AppCompatActivity() {

    private val JOB_TIMEOUT = 2100L

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener {
            setNewText("Click!")

            CoroutineScope(IO).launch {
                fakeApiRequest()
            }
        }

    }

    private fun setNewText(input: String){
        val newText = text.text.toString() + "n$input"
        text.text = newText
    }
    private suspend fun setTextOnMainThread(input: String) {
        withContext (Main) {
            setNewText(input)
        }
    }

    private suspend fun fakeApiRequest() {
        withContext(IO) {

            val job = withTimeoutOrNull(JOB_TIMEOUT) {

                val result1 = getResult1FromApi() // wait until job is done
                setTextOnMainThread("Got $result1")

                val result2 = getResult2FromApi() // wait until job is done
                setTextOnMainThread("Got $result2")

            } // waiting for job to complete...

            if(job == null){
                val cancelMessage = "Cancelling job...Job took longer than $JOB_TIMEOUT ms"
                println("debug: ${cancelMessage}")
                setTextOnMainThread(cancelMessage)
            }

        }
    }

    private suspend fun getResult1FromApi(): String {
        delay(1000) // Does not block thread. Just suspends the coroutine inside the thread
        return "Result #1"
    }

    private suspend fun getResult2FromApi(): String {
        delay(1000)
        return "Result #2"
    }

}

withTimeoutOrNull, Job

======================================================================================================================

이하는 나중에 확인할 내용들이다.

@synchronized   @Volatile   Lock   Atomic primitives

multi thread에서 안전하게 사용하는 방법을 총정리한 블로그

대충 읽다가 멈춤

https://proandroiddev.com/synchronization-and-thread-safety-techniques-in-java-and-kotlin-f63506370e6d

.

@Volatile

In order to force changes in a variable to be immediately visible to other threads, we can use the annotation @Volatile, a in the following example:

.

Synchronized methods

Java has the synchronized keyword, which can be applied to methods to ensure that only one thread at a time can access them. A thread that enters a synchronized method obtains a lock (an object being locked is the instance of the containing class) and no other thread can enter the method until the lock is released. Kotlin offers the same functionality with the @Synchronized annotation. When applied to a method, it will produce the same bytecode as Java would with the synchronized keyword:

@Synchronizedfun threadSafeFunction() {}

.

synchronized   volatile    kotlin’s Any   Java’s Object wait() notify()  notifyAll() methods

https://blog.egorand.me/concurrency-primitives-in-kotlin/

.

.

.

.

Kotlin Coroutines patterns & anti-patterns

Coroutines사용시에 좋은 방법 안좋은 방법 예를 들어 보여줌

– 조금 읽다가 멈춤

https://proandroiddev.com/synchronization-and-thread-safety-techniques-in-java-and-kotlin-f63506370e6d

.

.

.

.

Async Operations with Kotlin Coroutines — Part 1

Coroutines 전반에 관한 기초내용 – 나중에 읽어 볼만 하다고 생각

https://proandroiddev.com/async-operations-with-kotlin-coroutines-part-1-c51cc581ad33#:~:text=async%3A%20Async%20coroutine%20builder%20is,to%20get%20the%20eventual%20result.

2.4 data bindings

original source: https://codelabs.developers.google.com/codelabs/kotlin-android-training-data-binding-basics/index.html?index=..%2F..android-kotlin-fundamentals#5

Steps to use data binding to replace calls to findViewById():

  1. Enable data binding in the android section of the build.gradle file:
    dataBinding { enabled = true }
  2. Use <layout> as the root view in your XML layout.
  3. Define a binding variable:
    private lateinit var binding: ActivityMainBinding
  4. Create a binding object in MainActivity, replacing setContentView:
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  5. Replace calls to findViewById() with references to the view in the binding object. For example:
    findViewById<Button>(R.id.done_button)binding.doneButton
    (In the example, the name of the view is generated camel case from the view’s id in the XML.)

Steps for binding views to data:

  1. Create a data class for your data.
  2. Add a <data> block inside the <layout> tag.
  3. Define a <variable> with a name, and a type that is the data class.
<data>
   <variable
       name="myName"
       type="com.example.android.aboutme.MyName" />
</data>
  1. In MainActivity, create a variable with an instance of the data class. For example:
    private val myName: MyName = MyName("Aleks Haecky")
  1. In the binding object, set the variable to the variable you just created:
    binding.myName = myName
  1. In the XML, set the content of the view to the variable that you defined in the <data> block. Use dot notation to access the data inside the data class.
    android:text="@={myName.name}"

.

.

.

.

3.1 fragment

https://codelabs.developers.google.com/codelabs/kotlin-android-training-create-and-add-fragment/index.html?index=..%2F..android-kotlin-fundamentals#3

.

.

.

.

3.2  Navigation components

original source : https://codelabs.developers.google.com/codelabs/kotlin-android-training-add-navigation/index.html?index=..%2F..android-kotlin-fundamentals#0

To use the Android navigation library, you need to do some setup:

  • Add dependencies for navigation-fragment-ktx and navigation-ui-ktx in the module-level build.gradle file.
  • Add an ext variable for the navigationVersion in the project-level build.gradle file.

Navigation destinations are fragments, activities, or other app components that the user navigates to. A navigation graph defines the possible paths from one navigation destination to the next.

  • To create a navigation graph, create a new Android resource file of type Navigation. This file defines the navigation flow through the app. The file is in the res/navigation folder, and it’s typically called navigation.xml.
  • To see the navigation graph in the Navigation Editor, open the navigation.xml file and click the Design tab.
  • Use the Navigation Editor to add destinations such as fragments to the navigation graph.
  • To define the path from one destination to another, use the Navigation Graph to create an action that connects the destinations. In the navigation.xml file, each of these connections is represented as an action that has an ID.

A navigation host fragment, usually named NavHostFragment, acts as a host for the fragments in the navigation graph:

  • As the user moves between destinations defined in the navigation graph, the NavHostFragment swaps the fragments in and out and manages the fragment back stack.
  • In the activity_main.xml layout file, the NavHostFragment is represented by a fragment element with the name android:name="androidx.navigation.fragment.NavHostFragment".

To define which fragment is displayed when the user taps a view (for example a button), set the onClick listener for the view:

  • In the onClick listener, call findNavController().navigate() on the view.
  • Specify the ID of the action that leads to the destination.

Conditional navigation navigates to one screen in one case, and to a different screen in another case. To create conditional navigation:

  1. Use the Navigation Editor to create a connection from the starting fragment to each of the possible destination fragments.
  2. Give each connection a unique ID.
  3. In the click-listener method for the View, add code to detect the conditions. Then call findNavController().navigate() on the view, passing in the ID for the appropriate action.

참고자료)  popup popupto PopUpToInclusive 설명 10분분량 매우 간결하다.

https://youtu.be/mLfWvSGG5c8

The Back button

The system’s Back button is usually at the bottom of the device. By default, the Back button navigates the user back to the screen they viewed most recently. In some situations, you can control where the Back button takes the user:

  • In the Navigation Editor, you can use the Attributes pane to change an action’s Pop To setting. This setting removes destinations from the back stack, which has the effect of determining where the Back button takes the user.
  • The Pop To setting appears as the popUpTo attribute in the navigation.xml file.
  • Selecting the Inclusive checkbox sets the popUpToInclusive attribute to true. All destinations up to and including this destination are removed from the back stack.
  • If an action’s popUpTo attribute is set to the app’s the starting destination and popUpToInclusive is set to true, the Back button takes the user all the way out of the app.

The Up button

Screens in an Android app can have an on-screen Up button that appears at the top left of the app bar. (The app bar is sometimes called the action bar.) The Up button navigates “upwards” within the app’s screens, based on the hierarchical relationships between screens.

The navigation controller’s NavigationUI library integrates with the app bar to allow the user to tap the Up button on the app bar to get back to the app’s home screen from anywhere in the app.

To link the navigation controller to the app bar:

  1. In onCreate(), call setupActionBarWithNavController() on the NavigationUI class, passing in the navigation controller:
val navController = this.findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this,navController)
  1. Override the onSupportNavigateUp() method to call navigateUp() in the navigation controller:
override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.myNavHostFragment)
        return navController.navigateUp()
    }
}

The options menu

The options menu is a menu that the user accesses from the app bar by tapping the icon with the three vertical dots . To create an options menu with a menu item that displays a fragment, make sure the fragment has an ID. Then define the options menu and code the onOptionsItemSelected() handler for the menu items.

1. Make sure the fragment has an ID:

  • Add the destination fragment to the navigation graph and note the ID of the fragment. (You can change the ID if you like.)

2. Define the options menu:

  • Create an Android resource file of type Menu, typically named options_menu.xml. The file is stored in the Res > Menu folder.
  • Open the options_menu.xml file in the design editor and drag a Menu Item widget from the Palette pane to the menu.
  • For convenience, make the ID of the menu item the same as the ID of the fragment to display when the user clicks this menu item. This step is not required, but it makes it easier to code the onClick behavior for the menu item.

3. Code the onClick handler for the menu item:

  • In the fragment or activity that displays the options menu, in onCreateView(), call
    setHasOptionsMenu(true) to enable the options menu.
  • Implement onCreateOptionsMenu() to inflate the options menu:
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
   super.onCreateOptionsMenu(menu, inflater)
   inflater?.inflate(R.menu.options_menu, menu)
}
  • Override the onOptionsItemSelected() method to take the appropriate action when the menu item is clicked. The following code displays the fragment that has the same ID as the menu item. (This code only works if the menu item and the fragment have identical ID values.)
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
   return NavigationUI.onNavDestinationSelected(item!!,
           view!!.findNavController())
           || super.onOptionsItemSelected(item)
}

The navigation drawer

The navigation drawer is a panel that slides out from the edge of the screen. There are two ways for the user to open the navigation drawer:

  • Swipe from the starting edge (usually the left) on any screen.
  • Use the drawer button (three lines) on the app bar at the top of the app.

To add a navigation drawer to your app:

  1. Add dependencies to build.gradle (app).
  2. Make sure each destination fragment has an ID.
  3. Create the menu for the drawer.
  4. Add the drawer to the layout for the fragment.
  5. Connect the drawer to the navigation controller.
  6. Set up the drawer button in the app bar.

These steps are explained in more detail below.

1. Add dependencies to build.gradle:

  • The navigation drawer is part of the Material Components for Android library. Add the Material library to the build.gradle (app) file:
dependencies {
    ...
    implementation "com.google.android.material:material:$supportlibVersion"
    ...
}

2. Give each destination fragment an ID:

  • If a fragment is reachable from the navigation drawer, open it in the navigation graph to make sure that it has an ID.

3. Create the menu for the drawer:

  • Create an Android resource file of type Menu (typically called navdrawer_menu) for a navigation drawer menu. This creates a new navdrawer_menu.xml file in the Res > Menu folder.
  • In the design editor, add Menu Item widgets to the Menu.

4. Add the drawer to the layout for the fragment:

  • In the layout that contains the navigation host fragment (which is typically the main layout), use <androidx.drawerlayout.widget.DrawerLayout> as the root view.
  • Add a <com.google.android.material.navigation.NavigationView> view to the layout.

5. Connect the drawer to the navigation controller:

  • Open the activity that creates the navigation controller. (The main activity is typically the one you want here.) In onCreate(), use NavigationUI.setupWithNavController()to connect the navigation drawer with the navigation controller:
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
       this, R.layout.activity_main)
NavigationUI.setupWithNavController(binding.navView, navController)

6. Set up the drawer button in the app bar:

  • In onCreate() in the activity that creates the navigation controller (which is typically the main activity), pass the drawer layout as the third parameter to NavigationUI.setupActionBarWithNavController:
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(
    this, R.layout.activity_main)

NavigationUI.setupActionBarWithNavController(
    this, navController, binding.drawerLayout)
  • To make the Up button work with the drawer button, edit onSupportNavigateUp() to return NavigationUI.navigateUp(). Pass the navigation controller and the drawer layout to navigateUp().
override fun onSupportNavigateUp(): Boolean {
   val navController = this.findNavController(R.id.myNavHostFragment)
   return NavigationUI.navigateUp(navController, drawerLayout)
}

.

.

.

.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-start-external-activity/index.html?index=..%2F..android-kotlin-fundamentals#0

3.3

Safe Args navigation pass arguments fragment NavDirection

https://codelabs.developers.google.com/codelabs/kotlin-android-training-start-external-activity/index.html?index=..%2F..android-kotlin-fundamentals#2

.

..

.

.

4.1 Lifecycle   onSaveInstanceState   Bundle 

https://codelabs.developers.google.com/codelabs/kotlin-android-training-complex-lifecycle/index.html?index=..%2F..android-kotlin-fundamentals#2

Lifecycle tips

  • If you set up or start something in a lifecycle callback, stop or remove that thing in the corresponding callback. By stopping the thing, you make sure it doesn’t keep running when it’s no longer needed. For example, if you set up a timer in onStart(), you need to pause or stop the timer in onStop().
  • Use onCreate() only to initialize the parts of your app that run once, when the app first starts. Use onStart() to start the parts of your app that run both when the app starts, and each time the app returns to the foreground.

Lifecycle library

  • Use the Android lifecycle library to shift lifecycle control from the activity or fragment to the actual component that needs to be lifecycle-aware.
  • Lifecycle owners are components that have (and thus “own”) lifecycles, including Activity and Fragment. Lifecycle owners implement the LifecycleOwner interface.
  • Lifecycle observers pay attention to the current lifecycle state and perform tasks when the lifecycle changes. Lifecycle observers implement the LifecycleObserver interface.
  • Lifecycle objects contain the actual lifecycle states, and they trigger events when the lifecycle changes.

To create a lifecycle-aware class:

  • Implement the LifecycleObserver interface in classes that need to be lifecycle-aware.
  • Initialize a lifecycle observer class with the lifecycle object from the activity or fragment.
  • In the lifecycle observer class, annotate lifecycle-aware methods with the lifecycle state change they are interested in.

    For example, the @OnLifecycleEvent(Lifecycle.Event.ON_START)annotation indicates that the method is watching the onStart lifecycle event.

Process shutdowns and saving activity state

  • Android regulates apps running in the background so that the foreground app can run without problems. This regulation includes limiting the amount of processing that apps in the background can do, and sometimes even shutting down your entire app process.
  • The user cannot tell if the system has shut down an app in the background. The app still appears in the recents screen and should restart in the same state in which the user left it.
  • The Android Debug Bridge (adb) is a command-line tool that lets you send instructions to emulators and devices attached to your computer. You can use adb to simulate a process shutdown in your app.
  • When Android shuts down your app process, the onDestroy() lifecycle method is not called. The app just stops.

Preserving activity and fragment state

  • When your app goes into the background, just after onStop() is called, app data is saved to a bundle. Some app data, such as the contents of an EditText, is automatically saved for you.
  • The bundle is an instance of Bundle, which is a collection of keys and values. The keys are always strings.
  • Use the onSaveInstanceState() callback to save other data to the bundle that you want to retain, even if the app was automatically shut down. To put data into the bundle, use the bundle methods that start with put, such as putInt().
  • You can get data back out of the bundle in the onRestoreInstanceState() method, or more commonly in onCreate(). The onCreate() method has a savedInstanceState parameter that holds the bundle.
  • If the savedInstanceState variable contains null, the activity was started without a state bundle and there is no state data to retrieve.
  • To retrieve data from the bundle with a key, use the Bundle methods that start with get, such as getInt().

Configuration changes

  • A configuration change happens when the state of the device changes so radically that the easiest way for the system to resolve the change is to shut down and rebuild the activity.
  • The most common example of a configuration change is when the user rotates the device from portrait to landscape mode, or from landscape to portrait mode. A configuration change can also occur when the device language changes or a hardware keyboard is plugged in.
  • When a configuration change occurs, Android invokes all the activity lifecycle’s shutdown callbacks. Then Android restarts the activity from scratch, running all the lifecycle startup callbacks.
  • When Android shuts down an app because of a configuration change, it restarts the activity with the state bundle that is available to onCreate().
  • As with process shutdown, save your app’s state to the bundle in onSaveInstanceState().

.

.

.

.

5.1 view model factory provider에 대한 참고사항

어떤 방법이 옳고 어떻게 해야 boiler plate code를 줄일수 있는지 설명한다.

https://proandroiddev.com/view-model-creation-in-android-android-architecture-components-kotlin-ce9f6b93a46b

.

.

.

.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-live-data/index.html?index=..%2F..android-kotlin-fundamentals#10

5.2 LiveData

  • LiveData is an observable data holder class that is lifecycle-aware, one of the Android Architecture Components.
  • You can use LiveData to enable your UI to update automatically when the data updates.
  • LiveData is observable, which means that an observer like an activity or an fragment can be notified when the data held by the LiveData object changes.
  • LiveData holds data; it is a wrapper that can be used with any data.
  • LiveData is lifecycle-aware, meaning that it only updates observers that are in an active lifecycle state such as STARTED or RESUMED.

To add LiveData

  • Change the type of the data variables in ViewModel to LiveData or MutableLiveData.

MutableLiveData is a LiveData object whose value can be changed. MutableLiveData is a generic class, so you need to specify the type of data that it holds.

  • To change the value of the data held by the LiveData, use the setValue() method on the LiveData variable.

To encapsulate LiveData

  • The LiveData inside the ViewModel should be editable. Outside the ViewModel, the LiveData should be readable. This can be implemented using a Kotlin backing property.
  • A Kotlin backing property allows you to return something from a getter other than the exact object.
  • To encapsulate the LiveData, use private MutableLiveData inside the ViewModel and return a LiveData backing property outside the ViewModel.

Observable LiveData

  • LiveData follows an observer pattern. The “observable” is the LiveData object, and the observers are the methods in the UI controllers, like fragments. Whenever the data wrapped inside LiveData changes, the observer methods in the UI controllers are notified.
  • To make the LiveData observable, attach an observer object to the LiveData reference in the observers (such as activities and fragments) using the observe() method.
  • This LiveData observer pattern can be used to communicate from the ViewModel to the UI controllers.

.

.

.

.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-live-data-data-binding/index.html?index=..%2F..android-kotlin-fundamentals#6

5.3 Data binding and LiveData

Summary

  • The Data Binding Library works seamlessly with Android Architecture Components like ViewModel and LiveData.
  • The layouts in your app can bind to the data in the Architecture Components, which already help you manage the UI controller’s lifecycle and notify about changes in the data.

ViewModel data binding

  • You can associate a ViewModel with a layout by using data binding.
  • ViewModel objects hold the UI data. By passing ViewModel objects into the data binding, you can automate some of the communication between the views and the ViewModel objects.

How to associate a ViewModel with a layout:

  • In the layout file, add a data-binding variable of the type ViewModel.
   <data>

       <variable
           name="gameViewModel"
           type="com.example.android.guesstheword.screens.game.GameViewModel" />
   </data>
  • In the GameFragment file, pass the GameViewModel into the data binding.
binding.gameViewModel = viewModel

Listener bindings

  • Listener bindings are binding expressions in the layout that run when click events such as onClick() are triggered.
  • Listener bindings are written as lambda expressions.
  • Using listener bindings, you replace the click listeners in the UI controllers with listener bindings in the layout file.
  • Data binding creates a listener and sets the listener on the view.
 android:onClick="@{() -> gameViewModel.onSkip()}"

Adding LiveData to data binding

  • LiveData objects can be used as a data-binding source to automatically notify the UI about changes in the data.
  • You can bind the view directly to the LiveData object in the ViewModel. When the LiveData in the ViewModel changes, the views in the layout can be automatically updated, without the observer methods in the UI controllers.
android:text="@{gameViewModel.word}"
  • To make the LiveData data binding work, set the current activity (the UI controller) as the lifecycle owner of the binding variable in the UI controller.
binding.lifecycleOwner = this

String formatting with data binding

  • Using data binding, you can format a string resource with placeholders like %s for strings and %d for integers.
  • To update the text attribute of the view, pass in the LiveData object as an argument to the formatting string.
 android:text="@{@string/quote_format(gameViewModel.word)}"

.

.

.

.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-live-data-transformations/index.html?index=..%2F..android-kotlin-fundamentals#7

5.4 LiveData and Transformations

Transforming LiveData

  • Sometimes you want to transform the results of LiveData. For example, you might want to format a Date string as “hours:mins:seconds,” or return the number of items in a list rather than returning the list itself. To perform transformations on LiveData, use helper methods in the Transformations class.
  • The Transformations.map() method provides an easy way to perform data manipulations on the LiveData and return another LiveData object. The recommended practice is to put data-formatting logic that uses the Transformations class in the ViewModel along with the UI data.

Displaying the result of a transformation in a TextView

  • Make sure the source data is defined as LiveData in the ViewModel.
  • Define a variable, for example newResult. Use Transformation.map() to perform the transformation and return the result to the variable.
val newResult = Transformations.map(someLiveData) { input ->
   // Do some transformation on the input live data
   // and return the new value
}
  • Make sure the layout file that contains the TextView declares a <data> variable for the ViewModel.
<data>
   <variable
       name="MyViewModel"
       type="com.example.android.something.MyViewModel" />
</data>
  • In the layout file, set the text attribute of the TextView to the binding of the newResult of the ViewModel. For example:
android:text="@{SomeViewModel.newResult}"

Formatting dates

.

.

.

.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-room-database/index.html?index=..%2F..android-kotlin-fundamentals#8

6.1  Room

참고자료) official docs 

https://developer.android.com/training/data-storage/room/accessing-data

  • Define your tables as data classes annotated with @Entity. Define properties annotated with @ColumnInfo as columns in the tables.
  • Define a data access object (DAO) as an interface annotated with @Dao. The DAO maps Kotlin functions to database queries.
  • Use annotations to define @Insert, @Delete, and @Update functions.
  • Use the @Query annotation with an SQLite query string as a parameter for any other queries.
  • Create an abstract class that has a getInstance() function that returns a database.
  • Use instrumented tests to test that your database and DAO are working as expected. You can use the provided tests as a template.

.

.

.

.

일반적으로 Coroutines를 이용한 database작업의 패턴

https://codelabs.developers.google.com/codelabs/kotlin-android-training-coroutines-and-room/index.html?index=..%2F..android-kotlin-fundamentals#5

  1. Launch a coroutine that runs on the main or UI thread, because the result affects the UI.
  2. Call a suspend function to do the long-running work, so that you don’t block the UI thread while waiting for the result.
  3. The long-running work has nothing to do with the UI. Switch to the I/O context, so that the work can run in a thread pool that’s optimized and set aside for these kinds of operations.
  4. Then call the database function to do the work.

The pattern is shown below.

fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

.

.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-coroutines-and-room/index.html?index=..%2F..android-kotlin-fundamentals#7

6.2 Corouitnes과 room database, LiveData

  • Use ViewModel, ViewModelFactory, and data binding to set up the UI architecture for the app.
  • To keep the UI running smoothly, use coroutines for long-running tasks, such as all database operations.
  • Coroutines are asynchronous and non-blocking. They use suspend functions to make asynchronous code sequential.
  • When a coroutine calls a function marked with suspend, instead of blocking until that function returns like a normal function call, it suspends execution until the result is ready. Then it resumes where it left off with the result.
  • The difference between blocking and suspending is that if a thread is blocked, no other work happens. If the thread is suspended, other work happens until the result is available.

To launch a coroutine, you need a job, a dispatcher, and a scope:

  • Basically, a job is anything that can be canceled. Every coroutine has a job, and you can use a job to cancel a coroutine.
  • The dispatcher sends off coroutines to run on various threads. Dispatcher.Main runs tasks on the main thread, and Dispartcher.IO is for offloading blocking I/O tasks to a shared pool of threads.
  • The scope combines information, including a job and dispatcher, to define the context in which the coroutine runs. Scopes keep track of coroutines.

To implement click handlers that trigger database operations, follow this pattern:

  1. Launch a coroutine that runs on the main or UI thread, because the result affects the UI.
  2. Call a suspend function to do the long-running work, so that you don’t block the UI thread while waiting for the result.
  3. The long-running work has nothing to do with the UI, so switch to the I/O context. That way, the work can run in a thread pool that’s optimized and set aside for these kinds of operations.
  4. Then call the database function to do the work.

Use a Transformations map to create a string from a LiveData object every time the object changes.

.

.

.

.

6.3 LiveData 와 control buttons

https://codelabs.developers.google.com/codelabs/kotlin-android-training-quality-and-states/index.html?index=..%2F..android-kotlin-fundamentals#6

  • Create a ViewModel and a ViewModelFactory and set up a data source.
  • Trigger navigation. To separate concerns, put the click handler in the view model and the navigation in the fragment.
  • Use encapsulation with LiveData to track and respond to state changes.
  • Use transformations with LiveData.
  • Create a singleton database.
  • Set up coroutines for database operations.

Triggering navigation

You define possible navigation paths between fragments in a navigation file. There are some different ways to trigger navigation from one fragment to the next. These include:

  • Define onClick handlers to trigger navigation to a destination fragment.
  • Alternatively, to enable navigation from one fragment to the next:
  • Define a LiveData value to record if navigation should occur.
  • Attach an observer to that LiveData value.
  • Your code then changes that value whenever navigation needs to be triggered or is complete.

Setting the android:enabled attribute

  • The android:enabled attribute is defined in TextView and inherited by all subclasses, including Button.
  • The android:enabled attribute determines whether or not a View is enabled. The meaning of “enabled” varies by subclass. For example, a non-enabled EditText prevents the user from editing the contained text, and a non-enabled Button prevents the user from tapping the button.
  • The enabled attribute is not the same as the visibility attribute.
  • You can use transformation maps to set the value of the enabled attribute of buttons based on the state of another object or variable.

Other points covered in this codelab:

  • To trigger notifications to the user, you can use the same technique as you use to trigger navigation.
  • You can use a Snackbar to notify the user.

.

.

.

.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-recyclerview-fundamentals/index.html?index=..%2F..android-kotlin-fundamentals#7

7.1  recyclerView

  • Displaying a list or grid of data is one of the most common UI tasks in Android. RecyclerView is designed to be efficient even when displaying extremely large lists.
  • RecyclerView does only the work necessary to process or draw items that are currently visible on the screen.
  • When an item scrolls off the screen, its views are recycled. That means the item is filled with new content that scrolls onto the screen.
  • The adapter pattern in software engineering helps an object work together with another API. RecyclerView uses an adapter to transform app data into something it can display, without the need for changing how the app stores and processes data.

To display your data in a RecyclerView, you need the following parts:

  • RecyclerView
    To create an instance of RecyclerView, define a <RecyclerView> element in the layout file.
  • LayoutManager
    A RecyclerView uses a LayoutManager to organize the layout of the items in the RecyclerView, such as laying them out in a grid or in a linear list.

    In the <RecyclerView> in the layout file, set the app:layoutManager attribute to the layout manager (such as LinearLayoutManager or GridLayoutManager).

    You can also set the LayoutManager for a RecyclerView programmatically. (This technique is covered in a later codelab.)

  • Layout for each item
    Create a layout for one item of data in an XML layout file.
  • Adapter
    Create an adapter that prepares the data and how it will be displayed in a ViewHolder. Associate the adapter with the RecyclerView.

    When RecyclerView runs, it will use the adapter to figure out how to display the data on the screen.

    The adapter requires you to implement the following methods:
    getItemCount() to return the number of items.
    onCreateViewHolder() to return the ViewHolder for an item in the list.
    onBindViewHolder() to adapt the data to the views for an item in the list.

  • ViewHolder
    A ViewHolder contains the view information for displaying one item from the item’s layout.
  • The onBindViewHolder() method in the adapter adapts the data to the views. You always override this method. Typically, onBindViewHolder() inflates the layout for an item, and puts the data in the views in the layout.
  • Because the RecyclerView knows nothing about the data, the Adapter needs to inform the RecyclerView when that data changes. Use notifyDataSetChanged()to notify the Adapter that the data has changed.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-recyclerview-fundamentals/index.html?index=..%2F..android-kotlin-fundamentals#5

refactor refactoring 

특정코드 외부로 extract 해서 새로 function만들기

.

.

.

.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/index.html?index=..%2F..android-kotlin-fundamentals#2

7.2  DiffUtil , data binding, ListAdapter

https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/index.html?index=..%2F..android-kotlin-fundamentals#3

DiffUtil에 사용예시

https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/index.html?index=..%2F..android-kotlin-fundamentals#4

ListAdapter 사용 예시. 

ListAdapter를 RecyclerView 사용하면 몇몇 편해지는 부분이 있다. getItemCount()를 구현하지 않아도 된다. 보여줄 data list를 adapter에 전달할때는 adapter.submitList(it)를 이용한다. adapter안에 data list를 저장할 variable을 만들 필요가 없다. 

https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/index.html?index=..%2F..android-kotlin-fundamentals#5

data binding 과 recycler view를 같이 사용하는 예시

refactor refactoring 하는 예시 몇개 있음

https://codelabs.developers.google.com/codelabs/kotlin-android-training-diffutil-databinding/index.html?index=..%2F..android-kotlin-fundamentals#6

data binding 과 recycler view를 같이 사용하는 예시



DiffUtil:

  • RecyclerView has a class called DiffUtil which is for calculating the differences between two lists.
  • DiffUtil has a class called ItemCallBack that you extend in order to figure out the difference between two lists.
  • In the ItemCallback class, you must override the areItemsTheSame() and areContentsTheSame() methods.

ListAdapter:

  • To get some list management for free, you can use the ListAdapter class instead of RecyclerView.Adapter. However, if you use ListAdapter you have to write your own adapter for other layouts, which is why this codelab shows you how to do it.
  • To open the intention menu in Android Studio, place the cursor on any item of code and press Alt+Enter (Option+Enter on a Mac). This menu is particularly helpful for refactoring code and creating stubs for implementing methods. The menu is context-sensitive, so you need to place cursor exactly to get the correct menu.

Data binding:

  • Use data binding in the item layout to bind data to the views.

Binding adapters:

  • You previously used Transformations to create strings from data. If you need to bind data of different or complex types, provide binding adapters to help data binding use them.
  • To declare a binding adapter, define a method that takes an item and a view, and annotate the method with @BindingAdapter. In Kotlin, you can write the binding adapter as an extension function on the View. Pass in the name of the property that the adapter adapts. For example:
@BindingAdapter("sleepDurationFormatted")
  • In the XML layout, set an app property with the same name as the binding adapter. Pass in a variable with the data. For example:
.app:sleepDurationFormatted="@{sleep}"

.

.

.

.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-grid-layout/index.html?index=..%2F..android-kotlin-fundamentals#5

GridLayout and RecyclerView

  • Layout managers manage how the items in the RecyclerView are arranged.
  • RecyclerView comes with out-of-the-box layout managers for common use cases such as LinearLayout for horizontal and vertical lists, and GridLayout for grids.
  • For more complicated use cases, implement a custom LayoutManager.
  • From a design perspective, GridLayout is best used for lists of items that can be represented as icons or images.
  • GridLayout arranges items in a grid of rows and columns. Assuming vertical scrolling, each item in a row takes up what’s called a “span.”
  • You can customize how many spans an item takes up, creating more interesting grids without the need for a custom layout manager.
  • Create an item layout for one item in the grid, and the layout manager takes care of arranging the items.
  • You can set the LayoutManager for the RecyclerView either in the XML layout file that contains the <RecyclerView> element, or programmatically.

.

.

.

.

https://codelabs.developers.google.com/codelabs/kotlin-android-training-interacting-with-items/index.html?index=..%2F..android-kotlin-fundamentals#6

interaction with recyclerview

To make items in a RecyclerView respond to clicks, attach click listeners to list items in the ViewHolder, and handle clicks in the ViewModel.

To make items in a RecyclerView respond to clicks, you need to do the following:

  • Create a listener class that takes a lambda and assigns it to an onClick() function.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
   fun onClick(night: SleepNight) = clickListener(night.nightId)
}
  • Set the click listener on the view.
android:onClick="@{() -> clickListener.onClick(sleep)}"
  • Pass the click listener to the adapter constructor, into the view holder, and add it to the binding object.
class SleepNightAdapter(val clickListener: SleepNightListener):
       ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()
holder.bind(getItem(position)!!, clickListener)
binding.clickListener = clickListener
  • In the fragment that shows the recycler view, where you create the adapter, define a click listener by passing a lambda to the adapter.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
      sleepTrackerViewModel.onSleepNightClicked(nightId)
})
  • Implement the click handler in the view model. For clicks on list items, this commonly triggers navigation to a detail fragment.

original source : https://deep-dive-dev.tistory.com/39

설명이 매우 간결하고 이해하기 쉽다.

Generic 사용의 장점 –

  1. Type casting is evitable- typecasting을 하지 않고 객체를 사용할 수 있다.
  2. Type safety- Generic allows only single type of object at a time.
  3. Compile time safety- 런타임 에러를 방지하기 위해 Generics code는 컴파일 타임에 체크된다.

이런 Generic에서 사용할 수 있는 타입의 범위를 지정하는것이 Type Bound이다. Modern Language들은 대부분 Type Bound 개념을 제공한다. Type Bound는 세 가지로 분류할 수 있다.

Type Bound (Parameter Variance) 분류 –

  1. 불변성(무공변성, invariant)
  2. 공변성(covariant)
  3. 반공변성(contravariant)

1. 불변성(무공변성, invariant)

상속 관계에 상관없이 자신의 타입만 허용하는 것을 뜻한다. Kotlin에서는 따로 지정해주지 않으면 기본적으로 모든 Generic Class는 무공변이다. Java에서의 <T>와 같다.

open class Alcohol
class Soju : Alcohol()

interface Drinker<T> {
    fun drink()
}

fun varianceTest(input: Drinker<Alcohol>){
    input.drink()
}

fun main() {

    val alchol: Drinker<Alcohol> = object:Drinker<Alcohol>{
        override fun drink(){
            println("Drink!")
        }
    }

    val soju: Drinker<Soju> = object:Drinker<Soju>{
        override fun drink(){
            println("Drink Soju!")
        }
    }

    // Success
    println(varianceTest(alchol)) // Drink!

    // Error
    println(varianceTest(soju)) // Type mismatch: inferred type is Drinker<Soju> but Drinker<Alcohol> was expected

}

예시에서 SojuAlcohol을 상속받았지만, 별도로 공변성을 지정하지 않았으므로 무공변이다. 따라서 입력으로 Drinker<Alcohol> 타입을 받는 함수 varianceTest에서 Drinker<Soju>를 입력하면 Type mismatch 에러가 발생한다.

2. 공변성(covariant)

자기 자신과 자식 객체를 허용한다. Java에서의 <? extends T>와 같다. Kotlin에서는 out 키워드를 사용해서 이를 표시한다.

open class Alcohol
class Soju : Alcohol()

interface Drinker<T> {
    fun drink()
}

fun varianceTest(input: Drinker<out Alcohol>){ // out keyword 추가
    input.drink()
}

fun main() {

    val alchol: Drinker<Alcohol> = object:Drinker<Alcohol>{
        override fun drink(){
            println("Drink!")
        }
    }

    val soju: Drinker<Soju> = object:Drinker<Soju>{
        override fun drink(){
            println("Drink Soju!")
        }
    }

    val any: Drinker<Any> = object:Drinker<Any>{
        override fun drink(){
            println("Drink Any!")
        }
    }

    // Success
    println(varianceTest(alchol)) // Drink!

    // Success
    println(varianceTest(soju)) // Drink Soju!

    // Error
    println(varianceTest(any)) //Type mismatch: inferred type is Drinker<Any> but Drinker<out Alcohol> was expected

}

SojuAlcohol을 상속받아 구현한 하위 타입이다. varianceTest 함수에서 입력을 Drinker<out Alcohol>로 지정하였으므로 AlcoholAlcohol의 하위 타입인 Soju를 모두 입력으로 사용할 수 있다.

cf. SojuAlcohol의 하위 타입일 때 Drinker<Soju>Drinker<Alcohol>의 하위 타입이므로 Drinker는 타입 인자 T에 대해 공변적이다.

cf. 공변/반공변에 대해 설명하기 위해 작성한 위 코드는 오직 개념 설명만을 위한 예시이다. 실제 코드를 작성할 때에는 in, out 키워드에 대한 개념을 확실히 이해하는 것이 중요하다.

3. 반공변성(contravariant)

공변성의 반대 – 자기 자신과 부모 객체만 허용한다. Java에서의 <? super T>와 같다. Kotlin에서는 in 키워드를 사용해서 표현한다.

open class Alcohol
class Soju : Alcohol()

interface Drinker<T> {
    fun drink()
}

fun varianceTest(input: Drinker<in Alcohol>){ // in keyword 추가
    input.drink()
}

fun main() {

    val alchol: Drinker<Alcohol> = object:Drinker<Alcohol>{
        override fun drink(){
            println("Drink!")
        }
    }

    val soju: Drinker<Soju> = object:Drinker<Soju>{
        override fun drink(){
            println("Drink Soju!")
        }
    }

    val any: Drinker<Any> = object:Drinker<Any>{
        override fun drink(){
            println("Drink Any!")
        }
    }

    // Success
    println(varianceTest(alchol)) // Drink!

    // Error
    println(varianceTest(soju)) // Type mismatch: inferred type is Drinker<Soju> but Drinker<in Alcohol> was expected

    // Success
    println(varianceTest(any)) // Drink Any!

}

varianceTest 함수에서 입력을 Drinker<in Alcohol>로 지정하였으므로 AlcoholAlcohol의 상위 타입(예시 코드에서는 별도로 지정된 상위 타입이 없음, Any는 모든 객체의 상위 타입이므로 사용 가능)을 입력으로 사용할 수 있다.

open class Alcohol
class Soju : Alcohol()

interface Drinker<T> {
    fun drink()
}

fun varianceTest(input: Drinker<in Soju>){ // in Soju로 변경
    input.drink()
}

fun main() {

    val alchol: Drinker<Alcohol> = object:Drinker<Alcohol>{
        override fun drink(){
            println("Drink!")
        }
    }

    val soju: Drinker<Soju> = object:Drinker<Soju>{
        override fun drink(){
            println("Drink Soju!")
        }
    }

    val any: Drinker<Any> = object:Drinker<Any>{
        override fun drink(){
            println("Drink Any!")
        }
    }

    // Success
    println(varianceTest(alchol)) // Drink!

    // Success
    println(varianceTest(soju)) // Drink Soju!

    // Success
    println(varianceTest(any)) // Drink Any!

}

varianceTest 함수에서 입력을 Drinker<in Soju>로 지정하였으므로 SojuSoju의 상위 타입인 Alcohol도 입력 가능하다.

How to use?

여기서 왜 outin 이라는 키워드를 사용하는지 살펴보자면, 이는 Producer(생산자, read-only)와 Consumer(소비자, write-only)에 빗댄것이라는 것을 알 수 있다. – C#에서도 Kotlin과 같이 공변성과 반공변성을 out, in 키워드를 사용한다. Kotlin이 이 개념을 따서 만든듯하다. Kotlin에 비해 C# 레퍼런스가 더 많으니 한번 찾아볼 만 하다.

In “clever words” they say that the class C is covariant in the parameter T, or that T is a covariant type parameter. You can think of C as being a producer of T’s, and NOT a consumer of T’s. In addition to out, Kotlin provides a complementary variance annotation: in. It makes a type parameter contravariant: it can only be consumed and never produced.

공식 홈페이지에서는 이렇게 설명하고 있다. – class Ctype parameter T에 대해 공변성(out)을 가지면 CT의 소비자가 아닌 생산자로 볼 수 있다. 또한 T가 반공변성(in)을 가지면 그것은 소비만 할 수 있고 생산될 수는 없다.

out = Producer

// Java
interface Source<T> {
    T nextT();
}

void demo(Source<String> strs) {
    Source<Object> objects = strs; // !!! Not allowed in Java
    // ...
}

위 코드는 Java에서의 Generic 예시이다. Source<T>T를 parameter로 쓰지 않고 오직 리턴만 한다. 그러므로 Source<String> 인스턴스에 대한 레퍼런스를 Source<Object>에 저장하는 것은 안전하다. 어차피 Consumer 호출이 없기 때문이다 .하지만 Java는 이를 알지 못하기 때문에 금지한다. 그래서 Source<? extends Object> 타입으로 선언해서 써야하는데, 이는 변수에 대해 동일한 method를 호출할 수 없으므로 큰 이득이 없고 타입만 더 복잡해진다. 또한 컴파일러가 이를 모른다.

abstract class Source<out T> {
   abstract fun nextT(): T
}

fun demo(strs: Source<String>) {
   val objects: Source<Any> = strs // This is OK, since T is an out-parameter
   // ...
}

그래서 Kotlin은 이를 컴파일러에게 알려준다. 이 방식을 선언 위치 변성(declaration-site variance)이라고 부른다. Source의 type parameter T에 대해 Source<T>의 멤버에서 T를 리턴만 하고 소비하지 않는다는 것을 명시한다. 이를 out 키워드로 표현한다.

그러므로 위에서 공변/반공변이 무엇인지 설명하면서 작성한 코드는 Java에서의 개념을 그대로 답습한 코틀린스럽지 못한 코드이다!!

in = Consumer

interface Comparable<in T> {
    operator fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
    x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
    // Thus, we can assign x to a variable of type Comparable<Double>
    val y: Comparable<Double> = x // OK!
}

위 예제코드는 반공변의 대표적인 예시인 compareTo이다. Comparable의 type parameter T는 소비되기만 하고 생산되지는 않는다.

외우기 쉽게 정리하자면 class C에 대해

  • class C가 type T를 생산 = class C가 type T를 리턴 = out
  • class C가 type T를 소비 = class C가 type T를 파라미터로 사용 = in

Reference

====================================================================================================================================

추가 내용) 좀더 심화된 내용이지만 읽지는 않았다.

https://medium.com/@elye.project/in-and-out-type-variant-of-kotlin-587e4fa2944c

original source : Retrofit Android Tutorial using Kotlin with RESTful We
https://www.youtube.com/playlist?list=PLlxmoA0rQ-LzEmWs4T99j2w6VnaQVGEtR

본격적인 내용은 3부터 이며 중간 중간 빨리 감으면서 보면된다.

대부분 10분 이내이다. 각 내용이 간결하고 명확하고 기본개념 잡기에 좋다.

아래는 android를 개발하면서 사용가능한 http libraries에 대해 소개하고 있다.

image
image
image
image

.

.

.

image
image

.

.

구성3개중에 service builder 부분 작성법

image

.

.

구성3개중에 interface 부분 작성법

image

.

.

interceptor를 위한 내용

image
image

service builder 에 추가한다.

.

.

image
image

.

.

image

위에서 getDestination()함수에 id parameter를 유의해서 본다.

image

destinationService.getDestination()함수에 id parameter를 전달함에 유의해서 본다.

.

.

query string 1개변수

image

.

.

query string 2개 변수

image

.

.

query string에 여러개의 변수를 전달해야 하는 경우 HashMap을 이용할수 있다.

image
image

.

.

path parameter, query parameter 둘다 사용한 경우

image

.

.

post시에 data 를 server에 전달하는 방법은 크게 json, formurlencoded 2가지 이다.

image
image
image
image

.

.

image
image

.

.

image

.

.

request에 header를 추가하는 방법

방법은 두가지이다. 상단에 hardcoded된 것과 함수 constructor에 dynamic하게 정의 되는 방법

image

.

.

다이나믹하게 정의 되는 경우 parameter로 전달된다. constructor에 전달되므로

image

.

.

interceptors의 다양한 사용예

image

.

.

사용자 정의 interceptor의 예

image

위의 과정을 아래와 같이 interceptor에 넣을수 있다.

image
image

.

.

image

timeout시에 onFailure함수가 호출된다.

image

.

.

request를 cancel하는 방법

image
image

google android official doc 기본설명

https://developer.android.com/topic/libraries/architecture/livedata

LiveData with coroutines – official doc

https://developer.android.com/topic/libraries/architecture/coroutines#livedata

When using LiveData, you might need to calculate values asynchronously. For example, you might want to retrieve a user’s preferences and serve them to your UI. In these cases, you can use the liveData builder function to call a suspend function, serving the result as a LiveData object.

val user: LiveData<User> = liveData {
   val data = database.loadUser() // loadUser is a suspend function.
   emit(data)
}

emit() 과 emitSource()의 차이점

https://stackoverflow.com/a/58950866/3151712

original source : https://youtu.be/-Lha9n8VJf0

예제 코드: https://resocoder.com/2018/06/01/kotlin-apply-let-run-with-also-higher-order-standard-functions/

also, let은 {}안에서 it으로 접근가능하다. 

apply, also 는 들어온 obj를 처리한후 같은 종류의 obj를 리턴한다. 

original source : https://youtu.be/ArSOSm1P9do

thread , coroutines에 대해 굉장히 쉽게 자세하게 알려준다. 45분분량

위 그림은 용어 설명이다. 

코루틴을 사용해 특정 루틴의 블로킹을 방지하고 비동기적 협력형 멀티태스크를 구현할수 있게 된다.

.

.

위그림은 각 루틴들이 지연되면서 순차처리하므로 시간은 순차처리하는 것과 같은 시간이 걸렸다.

.

.

위그림은 동시성을 구현해서 시간이 줄어들었다.

.

.

코루틴은 멀티스레드를 이용할수도 있지만 특별히 지정하지 않으면 하나의 스레드를이용한다. 위의 경우 하나의 스레드에서 돌아가는 두 루틴이 있는데 스레드 자체를 지연해서 처리속도가 느려졌다.

.

.