34. CPS Transformation
fun loadItem(params: Params, cont: Continuation) {
val sm = object : ContinuationImpl { ... }
when (sm.label) {
0 -> {
sm.params = params
sm.label = 1
requestToken(sm)
}
1 -> requestItem(token, params, sm)
2 -> showItem(item)
}
}
State Machine
Continuation
35. CPS Transformation
fun loadItem(params: Params, cont: Continuation) {
val sm = cont as? ThisSM ?: object : ThisSM {
fun resumeWith(...) {
loadItem(null, this) {
}
}
...
}
36. CPS Transformation
fun loadItem(params: Params, cont: Continuation) {
val sm = ...
when (sm.label) {
0 -> { ... }
1 -> {
val params = sm.params
val token = sm.result as Token
sm.label = 2
requestItem(token, params, sm)
}
2 -> { ... }
}
}
37. CPS Transformation
fun loadItem(params: Params, cont: Continuation) {
val sm = ...
when (sm.label) {
0 -> { ... }
1 -> {
val params = sm.params
val token = sm.result as Token
sm.label = 2
requestItem(token, params, sm)
}
2 -> { ... }
}
}
38. CPS Transformation
fun loadItem(params: Params, cont: Continuation) {
val sm = ...
when (sm.label) {
0 -> { ... }
1 -> {
val params = sm.params
val token = sm.result as Token
sm.label = 2
requestItem(token, params, sm)
}
2 -> { ... }
}
}
39. State Machine vs. Callback
•State Machine 은 상태를 저장한 하나의 객체를 공유하지만, Callback 은 매번 closure를 생성해야 함
•복잡한 구조에 대응하기 쉬움
suspend fun loadItems(params: List<Params>) {
for (p in params) {
val token = requestToken()
val item = requestItem(token, p)
showItem(item)
}
}
44. Why Coroutines ?
• Structured concurrency
➡ 구조화된 동시성 처리로 memory leak 방지
• Light weight
➡ 싱글 스레드에서 여러 코루틴을 동시에 실행할 수 있음 (동시성)
45. Kotlin Flow: Reactive scrabble benchmarks
•Java 8 Stream - RxJava 간 성능 비교를 위해 José Paumard 에 의해 개발됨
•Rx 관련 코드는 David Karnok 가 작성
https://github.com/Kotlin/kotlinx.coroutines/tree/master/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble
46. Why Coroutines ?
• Structured concurrency
➡ 구조화된 동시성 처리로 memory leak 방지
• Light weight
➡ 싱글 스레드에서 여러 코루틴을 동시에 실행할 수 있음 (동시성)
• Built-in cancellation
➡ 계층 구조를 따라 취소가 자동으로 전파됨
• Simplify asynchronous code
➡ 비동기 코드를 콜백 대신 순차적(sequential)인 코드로 작성할 수 있음
➡ 다양한 Jetpack 라이브러리에서 extensions 등의 형태로 코루틴을 지원
54. async()
private suspend fun mergeApi(): List<Item> = coroutineScope {
val deferredOne = async { fetchApiOne() }
val deferredTwo = async { fetchApiTwo() }
return deferredOne.await() + deferredTwo.await()
}
suspend
55. GlobalScope
object GlobalScope : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
•앱 전체 생명주기로 동작하는 top-level CoroutineScope
•대부분의 경우에 사용하면 안됨
59. Kotlin Property Delegates for ViewModels
class AwesomeFragment : Fragment() {
private val model: AwesomeViewModel by viewModels()
}
60. Kotlin Property Delegates for ViewModels
class AwesomeFragment : Fragment() {
private val model: AwesomeViewModel by viewModels()
private fun showChildFragment() = childFragmentManager.commit {
replace(R.id.fragment_child, ChildFragment())
}
}
class ChildFragment : Fragment() { ... }
61. Kotlin Property Delegates for ViewModels
class AwesomeFragment : Fragment() {
private val model: AwesomeViewModel by viewModels()
private fun showChildFragment() = childFragmentManager.commit {
replace(R.id.fragment_child, ChildFragment())
}
}
class ChildFragment : Fragment() {
private val model: AwesomeViewModel by viewModels({ requireParentFragment() })
}
62. Kotlin Property Delegates for ViewModels
class AwesomeFragment : Fragment() {
private val model: AwesomeViewModel by viewModels()
private fun showChildFragment() = childFragmentManager.commit {
replace(R.id.fragment_child, ChildFragment())
}
}
class ChildFragment : Fragment() {
private val model: AwesomeViewModel by viewModels({ requireParentFragment() })
}
63. Kotlin Property Delegates for ViewModels
class AwesomeActivity : AppCompatActivity() {
private val sharedModel: SharedViewModel by viewModels()
}
class AwesomeFragment : Fragment() {
private val sharedModel: SharedViewModel by activityViewModels()
}
class ChildFragment : Fragment() {
private val sharedModel: SharedViewModel by activityViewModels()
}
64. Kotlin Property Delegates for ViewModels
class AwesomeActivity : AppCompatActivity() {
private val sharedModel: SharedViewModel by viewModels()
}
class AwesomeFragment : Fragment() {
private val sharedModel: SharedViewModel by activityViewModels()
}
class ChildFragment : Fragment() {
private val sharedModel: SharedViewModel by activityViewModels()
}
70. CoroutineContext
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext { ... }
}
71. CoroutineContext
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext { ... }
}
72. CoroutineContext
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext { ... }
}
73. CoroutineContext
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext { ... }
}
74. CoroutineContext
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext { ... }
}
75. CoroutineContext
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext { ... }
}
76. CoroutineContext
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext { ... }
}
77. CoroutineContext
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext { ... }
}
coroutineContext[Job]
78. CoroutineContext
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext { ... }
}
Job() + Dispathchers.IO
81. Job
class AwesomeViewModel : ViewModel(), CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
•When a child fails, it propagates cancellation to other children
•When a failure is notified, the scope propagates the exception up
82. SupervisorJob
class AwesomeViewModel : ViewModel(), CoroutineScope {
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = job
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
•The failure of a child doesn't affect other children
•When a failure is notified, the scope doesn't do anything
85. Dispatchers
actual object Dispatchers {
@JvmStatic
actual val Default: CoroutineDispatcher = createDefaultDispatcher()
@JvmStatic
actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
@JvmStatic
actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
@JvmStatic
val IO: CoroutineDispatcher = DefaultScheduler.IO
}
86. Main-safe suspend function
suspend fun mainSafeFunc() {
val result = withContext(Dispatchers.IO) {
fetchApi()
}
return result.name
}
• Main(UI) Thread suspend function withContext
➡ e.g. Retrofit 2.6.0+ suspend function
87. Job + Dispatchers
class AwesomeViewModel : ViewModel(), CoroutineScope {
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
88. class AwesomeViewModel : ViewModel(), CoroutineScope {
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
init {
launch {
// Call suspend func.
}
}
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
89. Lifecycle-aware coroutine scopes
•For ViewModelScope, use androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-beta01 or higher.
class PlainViewModel : ViewModel() {
init {
viewModelScope.launch {
// Call suspend func.
}
}
}
90. Lifecycle-aware coroutine scopes
•For LifecycleScope, use androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01 or higher.
// Activity or Fragment
class PlainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
// Call suspend func.
}
lifecycleScope.launchWhenResumed {
// Call suspend func.
}
}
}
93. ViewModel + LiveData
class PlainViewModel : ViewModel() {
private val _result = MutableLiveData<String>()
val result: LiveData<String> = _result
init {
viewModelScope.launch {
val computationResult = doComputation()
_result.value = computationResult
}
}
}
94. LiveData Builder
•For liveData, use androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01 or higher.
class PlainViewModel : ViewModel() {
val result: LiveData {
emit(doComputation())
}
}
95. LiveData Builder
class PlainViewModel : ViewModel() {
// itemId item
private val itemId = MutableLiveData<String>()
val result = itemId.switchMap {
liveData { emit(fetchItem(it)) }
}
// LiveData
val weather = liveData(Dispatchers.IO) {
emit(LOADING_STRING)
emitSource(dataSource.fetchWeather()) // LiveData
}
}
96. StateFlow
• LiveData State holder !
• state StateFlow
interface StateFlow<T> : Flow<T> {
val value: T
// always available, reading it never fails
}
interface MutableStateFlow<T> : StateFlow<T> {
override var value: T
// can read & write value
}
fun <T> MutableStateFlow(value: T): MutableStateFlow<T>
// constructor fun