<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>1 일 1 민호</title>
    <link>https://omod.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 8 Aug 2026 11:30:05 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>개발자_미노</managingEditor>
    <image>
      <title>1 일 1 민호</title>
      <url>https://tistory1.daumcdn.net/tistory/3202950/attach/1237ef3896c6402d8d52cd53c2930bdf</url>
      <link>https://omod.tistory.com</link>
    </image>
    <item>
      <title>[Android] Kotlin Coroutines Flow 2주차 (1/2)</title>
      <link>https://omod.tistory.com/119</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Asynchronous Flow&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;suspend 함수는 비동기적으로 동작하고 단일 값을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flow&lt;/b&gt;는 비동기적으로 여러 개의 값을 반환받고 싶을 때 사용하는 &lt;b&gt;코루틴 빌더&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Representing multiple values&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collections을 사용하여 여러 개의 값을 반환할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628669855032&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): List&amp;lt;Int&amp;gt; = listOf(1, 2, 3)
 
fun main() {
    simple().forEach { value -&amp;gt; println(value) } 
}

// 1
// 2
// 3&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Sequences&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kotlin builder인 sequence를 사용하여 100ms 간격으로 숫자를 찍습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread.sleep을 사용하기 때문에 Thread가 blocking 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628669879445&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Sequence&amp;lt;Int&amp;gt; = sequence { // sequence builder
    for (i in 1..3) {
        Thread.sleep(100) // pretend we are computing it
        yield(i) // yield next value
    }
}

fun main() {
    simple().forEach { value -&amp;gt; println(value) } 
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Suspending functions&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;suspend 키워드를 사용하여 스레드가 차단되는 걸 방지합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628669894349&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun simple(): List&amp;lt;Int&amp;gt; {
    delay(1000) // pretend we are doing something asynchronous here
    return listOf(1, 2, 3)
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    simple().forEach { value -&amp;gt; println(value) } 
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Flows&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List&amp;lt;Int&amp;gt;는 모든 계산이 끝난 다음에 한 번에 결과를 반환하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flow는 비동기적으로 계산하고 끝날 때 마다 하나씩 stream형태로 값을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628669913734&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

              
fun simple(): Flow&amp;lt;Int&amp;gt; = flow { // flow builder
    for (i in 1..3) {
        delay(100) // pretend we are doing something useful here
        emit(i) // emit next value
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    // Launch a concurrent coroutine to check if the main thread is blocked
    launch {
        for (k in 1..3) {
            println(&quot;I'm not blocked $k&quot;)
            delay(100)
        }
    }
    // Collect the flow
    simple().collect { value -&amp;gt; println(value) } 
}

// I'm not blocked 1
// 1
// I'm not blocked 2
// 2
// I'm not blocked 3
// 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드가 blocking 되지 않아서 launch 블록과 flow 블록이 번갈아가면서 출력되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flow 타입의 빌더 함수를 flow라고 부릅니다.&lt;/li&gt;
&lt;li&gt;flow 블록 내의 코드는 일시중지 될 수 있습니다.&lt;/li&gt;
&lt;li&gt;더 이상 suspend 키워드를 안 써도 됩니다.&lt;/li&gt;
&lt;li&gt;emit 함수를 사용하여 flow를 방출합니다.&lt;/li&gt;
&lt;li&gt;collect 함수를 사용하여 flow를 수집합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Flows are cold&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flow&lt;/b&gt;는 &lt;b&gt;cold stream&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow builder 내부의 코드는 flow가 collect 될 때까지 실행되지 않고, 실행되면 처음부터 끝까지 값을 전부 방출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flow&lt;/b&gt;는 내부적으로 코 루틴을 사용하고 있기 때문에 suspend 키워드를 안 붙이고 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628669966358&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow { 
    println(&quot;Flow started&quot;)
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    println(&quot;Calling simple function...&quot;)
    val flow = simple()
    println(&quot;Calling collect...&quot;)
    flow.collect { value -&amp;gt; println(value) } 
    println(&quot;Calling collect again...&quot;)
    flow.collect { value -&amp;gt; println(value) } 
}

// Calling simple function...
// Calling collect...
// Flow started
// 1
// 2
// 3
// Calling collect again...
// Flow started
// 1
// 2
// 3
simple()은 빠르게 반환하고 아무것도 기다리지 않습니다.

flow는 collect 할때 마다 시작됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Flow cancellation basics&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flow는 coroutine cancellation을 따릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flow collection은 delay 같은 cancel이 가능한 일시 중지 함수가 호출되면 일시 중지되면 취소될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launch 나 async 같이 연속적인 작업 같은 경우에는 취소되지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670065741&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow { 
    for (i in 1..3) {
        delay(100)          
        println(&quot;Emitting $i&quot;)
        emit(i)
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    withTimeoutOrNull(250) { // Timeout after 250ms 
        simple().collect { value -&amp;gt; println(value) } 
    }
    println(&quot;Done&quot;)
}

// Emitting 1
// 1
// Emitting 2
// 2
// Done

250ms 뒤에 타임 아웃돼서 2까지만 출력하고 끝이 난다.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Flow builders&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값이 고정되어 있으면 flowOf builder를 이용합니다.&lt;/li&gt;
&lt;li&gt;여러 Collection들은. asFlow()를 사용하여 flow로 변경이 가능합니다.
&lt;pre id=&quot;code_1628670081892&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(1..3).asFlow().collect { value -&amp;gt; println(value) }​&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Intermediate flow operators&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산자를 사용하여 flow를 변환할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 연산자는 다음과 같은 특징이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;upstream flow에 적용되고 downstream flow를 반환합니다.&lt;/li&gt;
&lt;li&gt;cold 하게 동작합니다.&lt;/li&gt;
&lt;li&gt;suspend 함수가 아닙니다.&lt;/li&gt;
&lt;li&gt;매우 빠르게 작동하고 새로운 flow를 반환합니다.&lt;/li&gt;
&lt;li&gt;map과 filter와 같이 collection과 동일하지만 가장 큰 차이점은 연산자 내부 코드 블록에서 suspend 함수를 &lt;br /&gt;호출할 수 있다는 점입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1628670142293&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun performRequest(request: Int): String {
    delay(1000) // imitate long-running asynchronous work
    return &quot;response $request&quot;
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    (1..3).asFlow() // a flow of requests
        .map { request -&amp;gt; performRequest(request) }
        .collect { response -&amp;gt; println(response) }
}

// response 1
// response 2
// response 3

delay 함수가 map 안에서 적용돼서 1초에 한번씩 출력합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Transform operator&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow 변환 연산자 중 &lt;b&gt;transform&lt;/b&gt; 함수는 map이나 filter와 같이 간단하게 값을 변환할 수 있고, &lt;br /&gt;복잡한 변환을 수행할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;transform&lt;/b&gt; 연산자를 사용하여 임의의 값을 여러 번 반복하여 방출할 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670186518&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun performRequest(request: Int): String {
    delay(1000) // imitate long-running asynchronous work
    return &quot;response $request&quot;
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    (1..3).asFlow() // a flow of requests
        .transform { request -&amp;gt;
            emit(&quot;Making request $request&quot;) 
            emit(performRequest(request)) 
        }
        .collect { response -&amp;gt; println(response) }
}

// Making request 1
// response 1
// Making request 2
// response 2
// Making request 3
// response 3&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Size-limiting operators&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;take&lt;/b&gt;는 제한된 개수까지만 flow를 실행하고 cancel 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cancellation은 항상 예외를 던져 수행되므로 모든 리소스 관리 기능 (ex : try catch문) 들은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancel시 정상적으로 작동합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670270541&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun numbers(): Flow&amp;lt;Int&amp;gt; = flow {
    try {                          
        emit(1)
        emit(2) 
        println(&quot;This line will not execute&quot;)
        emit(3)    
    } finally {
        println(&quot;Finally in numbers&quot;)
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    numbers() 
        .take(2) // take only the first two
        .collect { value -&amp;gt; println(value) }
}

// 1
// 2
// Finally in numbers

take 함수로 개수를 2개로 제한했으므로 1,2를 방출하고 cancel 됩니다.
cancel 시켜도 try catch문은 정상적으로 작동하므로 finally 블록이 실행 됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Terminal flow operators&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Terminal&lt;/b&gt; 연산자는 collection을 시작시키는 suspend 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collect는 가장 기본적인 연산자고 그 외에 다른 Terminal 연산자는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;toList 또는 toSet&lt;/b&gt;은 flow를 MutableList 나 MutableSet으로 변환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;first&lt;/b&gt;는 flow의 첫 번째 값만 방출 하고 나머지는 cancel 시킵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;reduce&lt;/b&gt;는 첫번째 값에 주어진 연산자를 이용하여 누적시켜 최종 값을 방출합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;fold&lt;/b&gt;는 초기값을 입력받아 주어진 연산자를 이용하여 누적시켜 최종값을 방출합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1628670312654&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val sum = (1..5).asFlow()
    .map { it * it } // squares of numbers from 1 to 5                           
    .reduce { a, b -&amp;gt; a + b } // sum them (terminal operator)
println(sum)

// 55

map을 사용하여 flow의 값이 {1, 4, 9, 16, 25}로 바뀌고 reduce를 사용하여
((((1 + 4) + 9) + 16) + 25)를 하여 55를 방출한다.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Flows are sequential&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;flow&lt;/b&gt;는 multiple flow로 동작하게 하는 연산자를 사용하지 않는 이상 순차적으로 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 terminal 연산자를 호출하는 코루틴에서 바로 수행되고, 새로운 코 루틴을 생성해서 사용하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 방출된 값은 모든 중간 연산자가 upstream에서 downstream으로 처리한 후 terminal 연산자에게 전달합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670358398&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(1..5).asFlow()
    .filter {
        println(&quot;Filter $it&quot;)
        it % 2 == 0              
    }              
    .map { 
        println(&quot;Map $it&quot;)
        &quot;string $it&quot;
    }.collect { 
        println(&quot;Collect $it&quot;)
    }

// Filter 1
// Filter 2
// Map 2
// Collect string 2
// Filter 3
// Filter 4
// Map 4
// Collect string 4
// Filter 5

순차적으로 연산자를 처리하기 때문에
filter를 수행하다가 true 조건이 발생하면 map으로 변환시키고 terminal 연산자인 
collect를 통해 값을 출력하고 위와 같은 순서를 5가 나올때 까지 반복한다.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Flow context&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow Collection은 항상 호출하는 코루틴의 context에서 수행하는 것을 &lt;b&gt;context preservation&lt;/b&gt;라고 합니다&lt;/p&gt;
&lt;pre id=&quot;code_1628670418108&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;withContext(context) {
    simple().collect { value -&amp;gt;
        println(value) // run in the specified context
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1628670427029&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    log(&quot;Started simple flow&quot;)
    for (i in 1..3) {
        emit(i)
    }
}  

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    simple().collect { value -&amp;gt; log(&quot;Collected $value&quot;) } 
}

// [main @coroutine#1] Started simple flow
// [main @coroutine#1] Collected 1
// [main @coroutine#1] Collected 2
// [main @coroutine#1] Collected 3

simple().collect가 main 스레드에서 호출되기 때문에 
simple()의 내부 블록도 메인 스레드에서 호출된다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Wrong emission withContext&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;long-running CPU-consuming 코드는 Dispatchers.Default의 context에서 실행해야 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 업데이트는 Dispatchers.Main의 context에서 실행해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 withContext를 사용하여 코루틴 코드의 context를 변경하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow에서는 context preservation으로 인해 방출하는 context와 수신하는 context를 다르게 하지 못합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670477964&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    // The WRONG way to change context for CPU-consuming code in flow builder
    kotlinx.coroutines.withContext(Dispatchers.Default) {
        for (i in 1..3) {
            Thread.sleep(100) // pretend we are computing it in CPU-consuming way
            emit(i) // emit next value
        }
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    simple().collect { value -&amp;gt; println(value) } 
}

// Exception in thread &quot;main&quot; java.lang.IllegalStateException: 
// Flow invariant is violated:Flow was collected in 
// [CoroutineId(1), &quot;coroutine#1&quot;:
// BlockingCoroutine{Active}@5511c7f8, BlockingEventLoop@2eac3323],
// but emission happened in [CoroutineId(1), &quot;coroutine#1&quot;:
// DispatchedCoroutine{Active}@2dae0000, Dispatchers.Default].
// Please refer to 'flow' documentation or use 'flowOn' instead at ...

simple().collect를 실행한 스레드는 main 스레드 이지만
simple() 내부에서 withContext를 사용하여 Dispatchers.Default 스레드로 변경했기 때문에
context 보존 법칙으로 인해 오류가 발생한다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;flowOn operator&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;withContext 대신 &lt;b&gt;flowOn&lt;/b&gt; 연산자를 사용하여 방출하는 부분의 context를 변경해 줄 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670506885&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    for (i in 1..3) {
        Thread.sleep(100) // pretend we are computing it in CPU-consuming way
        log(&quot;Emitting $i&quot;)
        emit(i) // emit next value
    }
}.flowOn(Dispatchers.Default) // RIGHT way to change context for CPU-consuming code in flow builder

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    simple().collect { value -&amp;gt;
        log(&quot;Collected $value&quot;) 
    } 
}
// [DefaultDispatcher-worker-1 @coroutine#2] Emitting 1
// [main @coroutine#1] Collected 1
// [DefaultDispatcher-worker-1 @coroutine#2] Emitting 2
// [main @coroutine#1] Collected 2
// [DefaultDispatcher-worker-1 @coroutine#2] Emitting 3
// [main @coroutine#1] Collected 3

withContext를 썼을때와는 다르게 collect는 main 스레드에서,
simple()은 Default에서 잘 동작하고 있는것을 볼 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flowOn은 기본적으로 하나의 코루틴이 방출과 수집을 순차적으로 처리하는 flow를 변경시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 방출과 수집이 각각의 코 루틴에서 동시에 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow를 사용할 때에는 기본적으로 새로운 코루틴을 만들어서 실행하지 않았지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제를 예로 들었을 때 coroutines1, coroutines2 이렇게 다른 코루틴에서 수행되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 스레드도 변경해주었기에 스레드도 각각 다른 곳에서 수행하고 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Buffering&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow에서 방출하는 부분과 수집하는 부분 중 한쪽이나 양쪽 다 속도가 느리다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 부분을 분리해서 처리하여 처리 시간을 향상할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670547917&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    for (i in 1..3) {
        delay(100) // pretend we are asynchronously waiting 100 ms
        emit(i) // emit next value
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; { 
    val time = measureTimeMillis {
        simple().collect { value -&amp;gt; 
            delay(300) // pretend we are processing it for 300 ms
            println(value) 
        } 
    }   
    println(&quot;Collected in $time ms&quot;)
}

숫자 하나를 처리하는데 400ms가 걸린다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;buffer 연산자를 사용하여 순차적으로 실행하는 대신 pipeline을 만들어 방출과 수집을 동시에 처리할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670564790&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    for (i in 1..3) {
        delay(100) // pretend we are asynchronously waiting 100 ms
        emit(i) // emit next value
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; { 
    val time = measureTimeMillis {
        simple()
            .buffer() // buffer emissions, don't wait
            .collect { value -&amp;gt; 
                delay(300) // pretend we are processing it for 300 ms
                println(value) 
            } 
    }   
    println(&quot;Collected in $time ms&quot;)
}

방출하는 부분에 buffer를 만들어 동시에 동작하게 만들어서 처리 시간을 감소 시킨다.
처음 데이터가 발생하는데 100ms 시간과 collect의 300ms * 3을 합쳐 1000ms 시간이 걸린다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flowOn&amp;nbsp;연산자도 CoroutineDispatcher를 변경할 때 buffer mechanism을 사용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Conflation&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow가 연산의 중간값이나 상태 값의 업데이트를 나타내는 경우 각 값을 처리할 필요 없이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 최근 값만 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;conflate 연산자는 collector가 너무 느리면 중간 값을 건너뛸 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670596445&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    for (i in 1..3) {
        delay(100) // pretend we are asynchronously waiting 100 ms
        emit(i) // emit next value
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; { 
    val time = measureTimeMillis {
        simple()
            .conflate() // conflate emissions, don't process each one
            .collect { value -&amp;gt; 
                delay(300) // pretend we are processing it for 300 ms
                println(value) 
            } 
    }   
    println(&quot;Collected in $time ms&quot;)
}

// 1
// 3
// Collected in 746 ms

1번이 처리되고 있는 동안 2번과 3번이 이미 생산되고 중간 값인 2번을 생략하고 
가장 최근인 3번만 collector에 전달 됐다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Processing the latest value&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Conflation은 emitter, collector 둘 다 느릴 때 방출 값을 삭제하여 처리 속도를 높이는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느린 collector가 처리하기 전에 다른 값을 전달받으면 이런 collector를 cancel 하고 새로운 값을 처리하도록 재 시작하는 방법도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collectLatest() 연산자를 사용하면 collector 동작중 새로운 값이 방출되어 전달 받으면 기존 collect()를 cancel하고 &lt;br /&gt;새로운 값을 위한 collector를 재시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xxx로 시작하는 Latest() 연산자 들은 동일하게 기존 동작을 cancel 하고 최근 값을 처리합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670666525&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    for (i in 1..3) {
        delay(100) // pretend we are asynchronously waiting 100 ms
        emit(i) // emit next value
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; { 
    val time = measureTimeMillis {
        simple()
            .collectLatest { value -&amp;gt; // cancel &amp;amp; restart on the latest value
                println(&quot;Collecting $value&quot;) 
                delay(300) // pretend we are processing it for 300 ms
                println(&quot;Done $value&quot;) 
            } 
    }   
    println(&quot;Collected in $time ms&quot;)

// Collecting 1
// Collecting 2
// Collecting 3
// Done 3
Collected in 741 ms

100ms 마다 새로운 값을 보냈지만 마지막 값에 대해서만 완료된다.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Composing multiple flows&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Zip&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 flow를 병합시켜 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소의 개수가 다르다면 적은 원소의 개수에 맞춰 병합됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞쪽 원소부터 순서대로 병합합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670701342&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val nums = (1..3).asFlow() // numbers 1..3
val strs = flowOf(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;) // strings 
nums.zip(strs) { a, b -&amp;gt; &quot;$a -&amp;gt; $b&quot; } // compose a single string
    .collect { println(it) } // collect and print

// 1 -&amp;gt; one
// 2 -&amp;gt; two
// 3 -&amp;gt; three
{1, 2, 3} flow 와 {one, two, three} flow를 { 1 -&amp;gt; one } 형식의 하나의 string flow로 결합&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Combine&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow가 conflation처럼 최신 값이나 최종 연산 값만 사용하는 형태라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 flow에서 최신 값 기준으로 연산하는 작업을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 flow가 서로 다른 타이밍에 방출될 때, 최신 값 기준으로 방출 값을 연산하는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670717261&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt; { 

    val nums = (1..3).asFlow().onEach { delay(300) } // numbers 1..3 every 300 ms
    val strs = flowOf(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;).onEach { delay(400) } // strings every 400 ms
    val startTime = System.currentTimeMillis() // remember the start time 
    nums.zip(strs) { a, b -&amp;gt; &quot;$a -&amp;gt; $b&quot; } // compose a single string with &quot;zip&quot;
        .collect { value -&amp;gt; // collect and print 
            println(&quot;$value at ${System.currentTimeMillis() - startTime} ms from start&quot;) 
        } 
}

1 -&amp;gt; one at 426 ms from start
2 -&amp;gt; two at 826 ms from start
3 -&amp;gt; three at 1228 ms from start&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1628670724876&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val nums = (1..3).asFlow().onEach { delay(300) } // numbers 1..3 every 300 ms
val strs = flowOf(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;).onEach { delay(400) } // strings every 400 ms          
val startTime = System.currentTimeMillis() // remember the start time 
nums.combine(strs) { a, b -&amp;gt; &quot;$a -&amp;gt; $b&quot; } // compose a single string with &quot;combine&quot;
    .collect { value -&amp;gt; // collect and print 
        println(&quot;$value at ${System.currentTimeMillis() - startTime} ms from start&quot;) 
    }

1 -&amp;gt; one at 433 ms from start 
2 -&amp;gt; one at 636 ms from start
2 -&amp;gt; two at 835 ms from start
3 -&amp;gt; two at 937 ms from start
3 -&amp;gt; three at 1236 ms from start

300ms 1 방출
400ms one 방출
600ms 2 방출
800ms two 방출
900ms 3 방출
1200ms three 방출

첫번째 줄은 동일하게 출력된다.
두번째 줄은 2가 방출됐을때 two는 800ms에 방출되므로 최신값은 아직 one이다
따라서 2 -&amp;gt; one이 출력된다.
800ms가 지났을때 드디어  3번째 줄인 2 -&amp;gt; two가 출력된다.
900ms가 지나 3이 방출됐을때 아직 three가 방출되지 않았으므로
4번째줄은 3 -&amp;gt; two이 출력된다.
1200ms가 지났을때 5번째 줄인 3 -&amp;gt; three가 출력된다.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Flattening flows&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow는 비동기적으로 sequences 값을 전달받으므로 각 원소가 다시 다른 sequence를 요청하는 상황이 빈번합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Flow &amp;lt;Flow &amp;lt;String&amp;gt;&amp;gt;형태를 반환하는 상황입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670748924&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun requestFlow(i: Int): Flow&amp;lt;String&amp;gt; = flow {
    emit(&quot;$i: First&quot;)
    delay(500) // wait 500 ms
    emit(&quot;$i: Second&quot;)
}
// flowOf(1,2,3).map { requestFlow(it) } 동일
(1..3).asFlow().map { requestFlow(it) } // Flow&amp;lt;Flow&amp;lt;String&amp;gt;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collection과 유사하지만 비동기적인 특성을 고려하기 위한 flatten 관련 연산자를 지원합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;flatMapConcat&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flatMapConcat과 flattenConcat은 flow를 연결하는 연산자입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 연산자들은 내부 flow가 완료되어야만 외부 collect를 수행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628670824548&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun requestFlow(i: Int): Flow&amp;lt;String&amp;gt; = flow {
    emit(&quot;$i: First&quot;) 
    delay(500) // wait 500 ms
    emit(&quot;$i: Second&quot;)    
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; { 
    val startTime = System.currentTimeMillis() // remember the start time 
    (1..3).asFlow().onEach { delay(100) } // a number every 100 ms 
        .flatMapConcat { requestFlow(it) }                                                                           
        .collect { value -&amp;gt; // collect and print 
            println(&quot;$value at ${System.currentTimeMillis() - startTime} ms from start&quot;) 
        } 
// 1: First at 121 ms from start
// 1: Second at 622 ms from start
// 2: First at 727 ms from start
// 2: Second at 1227 ms from start
// 3: First at 1328 ms from start
// 3: Second at 1829 ms from start

(1..3) flow가 100ms 간격으로 방출된다.
하지만 requestFlow(it)을 수행하므로 Flow&amp;lt;Flow&amp;lt;String&amp;gt;&amp;gt; 형태가 되지만
flatMapConcat을 사용하여 Flow&amp;lt;String&amp;gt;으로 만들어준다.
1의 flow가 실행되고 1의 내부인 requestFlow(1)이 다 실행된 다음
2의 flow가 실행되는 형태를 띈다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;flatMapMerge&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flatMapMerge 또는 flattenMerge는 동시에 방출 가능한 값들을 방출시키고 하나의 flow로 병합하여 수집합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수로 concurrency를 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;concurrency는 기본 값이 DEFAULT_CONCURRENCY로 돼 있고 동시에 받을 수 있는 flow의 개수를 제한하는 값입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672079814&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun requestFlow(i: Int): Flow&amp;lt;String&amp;gt; = flow {
    emit(&quot;$i: First&quot;) 
    delay(500) // wait 500 ms
    emit(&quot;$i: Second&quot;)    
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; { 
    val startTime = System.currentTimeMillis() // remember the start time 
    (1..3).asFlow().onEach { delay(100) } // a number every 100 ms 
        .flatMapMerge { requestFlow(it) }                                                                           
        .collect { value -&amp;gt; // collect and print 
            println(&quot;$value at ${System.currentTimeMillis() - startTime} ms from start&quot;) 
        } 
}
// 1: First at 136 ms from start
// 2: First at 231 ms from start
// 3: First at 333 ms from start
// 1: Second at 639 ms from start
// 2: Second at 732 ms from start
// 3: Second at 833 ms from start

외부 flow와 내부 flow가 동시에 실행되면서 하나의 flow로 합친다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;flatMapLatest&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collectLatest와 유사하게 방출이 되면 이전에 대기 중이거나 동작중인 flow를 cancel 시킵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672095514&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun requestFlow(i: Int): Flow&amp;lt;String&amp;gt; = flow {
    emit(&quot;$i: First&quot;) 
    delay(500) // wait 500 ms
    emit(&quot;$i: Second&quot;)    
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; { 
    val startTime = System.currentTimeMillis() // remember the start time 
    (1..3).asFlow().onEach { delay(100) } // a number every 100 ms 
        .flatMapLatest { requestFlow(it) }                                                                           
        .collect { value -&amp;gt; // collect and print 
            println(&quot;$value at ${System.currentTimeMillis() - startTime} ms from start&quot;) 
        } 
}

// 1: First at 142 ms from start
// 2: First at 322 ms from start
// 3: First at 425 ms from start
// 3: Second at 931 ms from start&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Flow exceptions&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow collection은 emitter나 다른 operator에서 발생하는 예외로&amp;nbsp;완료 처리될 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Collector try and catch&lt;/h3&gt;
&lt;pre id=&quot;code_1628672109731&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    for (i in 1..3) {
        println(&quot;Emitting $i&quot;)
        emit(i) // emit next value
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    try {
        simple().collect { value -&amp;gt;         
            println(value)
            check(value &amp;lt;= 1) { &quot;Collected $value&quot; }
        }
    } catch (e: Throwable) {
        println(&quot;Caught $e&quot;)
    } 
}

// Emitting 1
// 1
// Emitting 2
// 2
// Caught java.lang.IllegalStateException: Collected 2

순서대로 처리하다가 value가 2일때 check(value &amp;lt;= 1)이 성립 안되므로 예외가 발생한다.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Exception transparency&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flow는 예외에 관하여 투명해야 하지만, try-catch문 내부에서&amp;nbsp;flow builder가 값을&amp;nbsp;방출하는 것은 Exception 투명성에 &lt;br /&gt;어긋납니다. 왜냐하면 예외를 던지는 collector가 try/catch를 사용하여 항상 예외를 catch 할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emitter는 예외 투명성을 보존하고 예외 처리의 캡슐화를 허용하는 catch 연산자를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;catch 연산자의 body는 예외를 분석하고 다음과 같은 다양한 방식으로 대응할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예외를 throw를 사용하여 rethrown 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;catch의 body에서 emit 함수를 사용하여 값을 방출할 수 있습니다.&lt;/li&gt;
&lt;li&gt;무시하거나, 로그를 남기거나 다른 처리를 하는 코드를 삽입할 수 있습니다.
&lt;pre id=&quot;code_1628672133707&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;String&amp;gt; = 
    flow {
        for (i in 1..3) {
            println(&quot;Emitting $i&quot;)
            emit(i) // emit next value
        }
    }
    .map { value -&amp;gt;
        check(value &amp;lt;= 1) { &quot;Crashed on $value&quot; }                 
        &quot;string $value&quot;
    }

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    simple()
        .catch { e -&amp;gt; emit(&quot;Caught $e&quot;) } // emit on exception
        .collect { value -&amp;gt; println(value) }
}
// Emitting 1
// string 1
// Emitting 2
// Caught java.lang.IllegalStateException: Crashed on 2

방출하는 값이 2일때 1보다 크므로 check(value &amp;lt;= 1)에서 Crashed on 2가 되고
그것을 catch하여 Caught Crashed on 2를 방출한 것을 출력한다.​&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 이상 try/catch문이 없더라도 동일하게 작동합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Transparent catch&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외 투명성을 지키는 catch 중간 연산자는 오직 upstream 예외만 catch 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672161322&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    for (i in 1..3) {
        println(&quot;Emitting $i&quot;)
        emit(i)
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    simple()
        .catch { e -&amp;gt; println(&quot;Caught $e&quot;) } // does not catch downstream exceptions
        .collect { value -&amp;gt;
            check(value &amp;lt;= 1) { &quot;Collected $value&quot; }                 
            println(value) 
        }
}
// Emitting 1
// 1
// Emitting 2
// Collected 2

catch문이 collect 보다 위에 있으므로 catch보다 아래있는 downstream에서 예외가 발생해도
catch 하지 않는다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Catching declaratively&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 예외를 catch로 다루기 위해서는 collect의 body를 onEach로 옮기고 그 이후에 catch를 연결하면 전체 구문의 외부에 try-catch를 하지 않아도 동일한 처리를 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672176595&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    for (i in 1..3) {
        println(&quot;Emitting $i&quot;)
        emit(i)
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    simple()
        .onEach { value -&amp;gt;
            check(value &amp;lt;= 1) { &quot;Collected $value&quot; }                 
            println(value) 
        }
        .catch { e -&amp;gt; println(&quot;Caught $e&quot;) }
        .collect()
}

// Emitting 1
// 1
// Emitting 2
// Caught java.lang.IllegalStateException: Collected 2&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Flow completion&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow collection이 정상적으로나 예외적으로 완료되면 특정한 작업을 실행해야 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령형 또는 선언형 두 가지 방법이 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Imperative finally block&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;try-catch문 외에도 collector는 finally 블록을 사용하여 collect 완료 시 블록 내부의 작업을 실행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672192621&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = (1..3).asFlow()

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    try {
        simple().collect { value -&amp;gt; println(value) }
    } finally {
        println(&quot;Done&quot;)
    }
}
// 1
// 2
// 3
// Done&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Declarative handling&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언형 방식으로는 flow가 종료됐을 때 onCompletion 중간 연산자를 호출하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672205964&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = (1..3).asFlow()

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    simple()
        .onCompletion { println(&quot;Done&quot;) }
        .collect { value -&amp;gt; println(value) }
}

// 1
// 2
// 3
// Done

try-finally와 동일하게 출력되는 것을 볼 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onCompletion의 큰 장점은 람다식에서 nullable 한 Throwable을 param으로 넘겨주기 때문에 collect의 완료가 &lt;br /&gt;정상적으로 되었는지 예외가 발생했는지 판단할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672223587&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = flow {
    emit(1)
    throw RuntimeException()
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    simple()
        .onCompletion { cause -&amp;gt; if (cause != null) println(&quot;Flow completed exceptionally&quot;) }
        .catch { cause -&amp;gt; println(&quot;Caught exception&quot;) }
        .collect { value -&amp;gt; println(value) }
}
// 1
// Flow completed exceptionally
// Caught exception

1을 방출 했을때는 정상적으로 동작했으므로
Flow completed exceptionally을 출력했고
그다음 예외를 던졌을때 cause가 null이 아니므로 onCompletion의 구문이 출력되지 않고
예외를 아래 catch로 전달하여 catch구문에서 Caught exception을 출력한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onCompletion은 예외를 다루지 않고 downstream으로 전달합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Successful completion&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;catch 연산자와 또 다른 차이점은 onCompletion은 모든 예외를 확인하고 upstream flow가 cancell이나 failure 없이 성공적으로 완료한 경우에만 null 예외를 수신한다는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672238154&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun simple(): Flow&amp;lt;Int&amp;gt; = (1..3).asFlow()

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    simple()
        .onCompletion { cause -&amp;gt; println(&quot;Flow completed with $cause&quot;) }
        .collect { value -&amp;gt;
            check(value &amp;lt;= 1) { &quot;Collected $value&quot; }                 
            println(value) 
        }
}

// 1
// Flow completed with Collected 2
// Exception in thread &quot;main&quot; java.lang.IllegalStateException: Collected 2&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Imperative versus declarative&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명시적이든 암시적이든 어떤 방법이 더 좋다고 할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 본인의 코딩 스타일이다 기호에 따라 선택해서 사용하면 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Launching flow&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 소스에서 전달되는 비동기적인 이벤트를 flow를 사용하여 쉽게 표현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수신하는 쪽에서 이벤트를 발생할 때마다 특정 동작을 처리하도록 하고 처리 후 다른 코드를 계속 진행되도록 하기 위해서 addEventListener 같은 기능이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onEach 연산자가 이러한 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onEach 연산자는 중간 연산자 이므로 flow를 수집할 terminal 연산자도 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;terminal 연산자를 호출하지 않으면 onEach를 호출하는 것 만으로는 아무런 효과가 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672254915&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Imitate a flow of events
fun events(): Flow&amp;lt;Int&amp;gt; = (1..3).asFlow().onEach { delay(100) }

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    events()
        .onEach { event -&amp;gt; println(&quot;Event: $event&quot;) }
        .collect() // &amp;lt;--- Collecting the flow waits
    println(&quot;Done&quot;)
}

// Event : 1
// Event : 2
// Event : 3
// Done&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collect 함수가 호출되면 모든 작업이 끝날 때까지 대기합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 리스너를 등록하는 이유는 코드는 계속 진행되지만, 특정 이벤트가 발생하면 등록된 코드를 동작시키기 위함입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collect 대신에 launchin을 사용하면 분리된 코 루틴으로 시작하고 코드는 바로 실행되도록 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672266796&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Imitate a flow of events
fun events(): Flow&amp;lt;Int&amp;gt; = (1..3).asFlow().onEach { delay(100) }

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    events()
        .onEach { event -&amp;gt; println(&quot;Event: $event&quot;) }
        .launchIn(this) // &amp;lt;--- Launching the flow in a separate coroutine
    println(&quot;Done&quot;)
}

// Done
// Event : 1
// Event : 2
// Event : 3

events()가 별도의 코루틴에서 실행되므로 100ms 딜레이로 인하여
Done이 먼저 출력되고 순서대로 events가 출력된다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launchIn을 사용할 때에는 매개 변수로 coroutineScope를 명시적으로 넣어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launchIn은 job을 리턴합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 scope의 취소 없이 flow만을 취소할 수도 있고, join을 이용하여 완료될 때까지 대기시킬 수 도 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Flow cancellation checks&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편의를 위해서 flow builder는 방출된 값에 대한 cancel을 sureActive 검사를 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow {}에서 사용 중인 반복문을 사용하는 방출이 cancell 가능함을 의미합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672279674&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun foo(): Flow&amp;lt;Int&amp;gt; = flow { 
    for (i in 1..5) {
        println(&quot;Emitting $i&quot;) 
        emit(i) 
    }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    foo().collect { value -&amp;gt; 
        if (value == 3) cancel()  
        println(value)
    } 
}

// Emitting 1
// 1
// Emitting 2
// 2
// Emitting 3
// 3
// Emitting 4
// Exception in thread &quot;main&quot; kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job=&quot;coroutine#1&quot;:BlockingCoroutine{Cancelled}@6d7b4f4c

값이 3일때 cancel 시켰으므로 4를 방출하려할때 예외가 발생한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 대부분 flow 연산자는 성능상의 이유로 자체적으로 additional cancellation check 하지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628672291163&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    (1..5).asFlow().cancellable().collect { value -&amp;gt; 
        if (value == 3) cancel()  
        println(value)
    } 
}

// 1
// 2
// 3
// 예외&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancellable()을 사용하면 cancel 된 것을 감지할 수 있습니다.&lt;/p&gt;</description>
      <category>Android</category>
      <author>개발자_미노</author>
      <guid isPermaLink="true">https://omod.tistory.com/119</guid>
      <comments>https://omod.tistory.com/119#entry119comment</comments>
      <pubDate>Wed, 11 Aug 2021 17:58:31 +0900</pubDate>
    </item>
    <item>
      <title>[Android] Kotlin Coroutines 1주차</title>
      <link>https://omod.tistory.com/118</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Asynchronous programming techniques&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 사용할 때 기다리거나 병목현상이 발생하는 것을 피하고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 문제를 해결하기 위해서 다음과 같은 방식이 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Threading&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 보편적인 방식입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458076479&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun postItem(item: Item) {
    val token = preparePost()
    val post = submitPost(token, item)
    processPost(post)
}

fun preparePost(): Token {
    // makes a request and consequently blocks the main thread
    return token
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 UI를 차단한다고 한다면 별도의 스레드를 실행해 UI가 차단되는 것을 막을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다음과 같은 단점이 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비용이 많이 듭니다.&lt;/li&gt;
&lt;li&gt;개수가 무제한이 아닙니다.&lt;/li&gt;
&lt;li&gt;지원하지 않는 플랫폼도 있습니다.&lt;/li&gt;
&lt;li&gt;사용하는 것이 쉽지 않습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Callbacks&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백은 하나의 함수를 다른 함수의 매개변수로 전달하고, 하나가 완료되면 다른 하나를 호출합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458128143&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun postItem(item: Item) {
    val token = preparePost()
    val post = submitPost(token, item)
    processPost(post)
}

fun preparePost(): Token {
    // makes a request and consequently blocks the main thread
    return token
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백에는 다음과 같은 단점이 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;콜백이 중첩될 수 있습니다.&lt;/li&gt;
&lt;li&gt;중첩이 되면 오류 처리가 복잡해집니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Futures, promises&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Futures는 미래에 실행이 완료될 것으로 예상되는 객체를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 실행되지 않은 경우나, 실행 중이지만 아직 완료되지 않는 경우를 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Futres는 외부에서 완료된 결과가 들어오기만 하므로 read-only 지만 Promises는 강제로 complete 할 수 있다는 차이점이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458143453&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun postItem(item: Item) {
    preparePostAsync()
        .thenCompose { token -&amp;gt;
            submitPostAsync(token, item)
        }
        .thenAccept { post -&amp;gt;
            processPost(post)
        }

}

fun preparePostAsync(): Promise&amp;lt;Token&amp;gt; {
    // makes request and returns a promise that is completed later
    return promise
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reactive Extensions&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든것은 stream이고 관찰이 가능하다는 것을 바탕으로 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵서버 패턴을 사용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Coroutines&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin의 비동기 작업을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 일시 중단하고 나중에 다시 시작할 수 있는 개념입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458156758&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun postItem(item: Item) {
    launch {
        val token = preparePost()
        val post = submitPost(token, item)
        processPost(post)
    }
}

suspend fun preparePost(): Token {
    // makes a request and suspends the coroutine
    return suspendCoroutine { /* ... */ }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일시 중단 함수라는 뜻으로 suspend 키워드를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴은 다음과 같은 특징을 가집니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수는 동일하게 유지되고 차이점은 suspend가 추가된다는 것입니다.&lt;/li&gt;
&lt;li&gt;코루틴을 시작하는 launch 함수 외에 동기식 코드와 동일하게 작성됩니다.&lt;/li&gt;
&lt;li&gt;루프나 예외 처리등 계속 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;플랫폼에 독립적입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Coroutines basics&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴은 &lt;b&gt;일시 중단이 가능한 인스턴스입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드와 유사하지만 코루틴은 특정 스레드에 구속되지 않고 한 스레드에서 일시 중단하고 &lt;br /&gt;다른 스레드에서 다시 시작할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458253869&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking { // CoroutineScope
    launch { // 새로운 코루틴을 실행
        delay(1000L) // 1초 비차단 지연
        println(&quot;World!&quot;) // delay 후 출력
    }
    println(&quot;Hello&quot;) // 이전 코루틴이 지연되는 동안 메인 코루틴은 진행됩니다.
}

// 출력
&quot;Hello&quot;
&quot;World!&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;launch&lt;/b&gt; &amp;rarr; 새 코루틴을 시작하는 코루틴 빌더입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;delay&lt;/b&gt; &amp;rarr; 특정 시간 동안 코루틴을 일시 중지 시키는 함수입니다. 스레드가 차단되지 않고 다른 코루틴을 실행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;runBlocking&lt;/b&gt; &amp;rarr; fun main()의 비 코루틴 영역과 runBlocking 내부의 코루틴이 있는 코드를 연결하는 코루틴 빌더입니다. runBlocking 내부의 코루틴들이 완료될 때까지 이를 실행하는 스레드를 차단합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴은 &lt;b&gt;동시성의 원칙&lt;/b&gt;을 따릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 새로운 코루틴은 코루틴의 수명을 제한하는 특정 CoroutineScope에서만 시작될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;suspend 키워드&lt;/b&gt;를 함수 앞에 추가하면 일시 중단 함수가 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458443204&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking { // this: CoroutineScope
    launch { doWorld() }
    println(&quot;Hello&quot;)
}

// this is your first suspending function
suspend fun doWorld() {
    delay(1000L)
    println(&quot;World!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;runBlocking, coroutineScope 차이점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;runBlocking&lt;/b&gt; : 자식 스레드가 완료될 때까지 현재 스레드를 block 한다. (일반 함수)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;coroutineScope&lt;/b&gt; : 자식 스레드가 완료될 때 까지 현재 스레드를 block 하지 않는다. (일시 중단 함수)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1627458464757&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking { // this: CoroutineScope
    launch { 
        delay(200L)
        println(&quot;Task from runBlocking&quot;)
    }

    coroutineScope { // Creates a new coroutine scope
        launch {
            delay(900L) 
            println(&quot;Task from nested launch&quot;)
        }

        delay(100L)
        println(&quot;Task from coroutine scope&quot;) // This line will be printed before nested launch
    }

    println(&quot;Coroutine scope is over&quot;) // This line is not printed until nested launch completes

//
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Job&lt;/span&gt;&lt;/b&gt;은 코루틴에 대한 객체를 반환받아 제어할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458607326&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val job = launch { // launch a new coroutine and keep a reference to its Job
    delay(1000L)
    println(&quot;World!&quot;)
}
println(&quot;Hello&quot;)
job.join() // wait until child coroutine completes
println(&quot;Done&quot;) 

// Hello
// World!
// Done&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Intro to coroutines and channels - hands on tutorial&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 통신 시 스레드를 차단하지 않고 코루틴을 일시 중단합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458637406&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;block -&amp;gt; suspend
thread -&amp;gt; coroutine&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴이 일시 중지되면 작업 중이던 계산이 일시 중지되고 스레드에서 된 다음 메모리에 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 시작이 되면 스레드로 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async는 새로운 코루틴을 시작하고 결과 반환 받을 수 있는 Deferred를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deferred는 Job을 확장하기 때문에 상태 제어도 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 얻으려면 await() 함수를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launch는 새로운 코루틴을 시작하지만 결과를 반환받지 못하고 Job을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458652205&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    val deferred: Deferred&amp;lt;Int&amp;gt; = async(Dispatchers.Default) {
        loadData()
    }
    println(&quot;waiting...&quot;)
    println(deferred.await())
}

suspend fun loadData(): Int {
    println(&quot;loading...&quot;)
    delay(1000L)
    println(&quot;loaded!&quot;)
    return 42
}
//main 시작 -&amp;gt; loadData 시작 -&amp;gt; waiting 출력 -&amp;gt; main 일시정지 -&amp;gt; loading 출력 
// -&amp;gt; loadData 1초 일시정지 -&amp;gt; loadData 다시시작 -&amp;gt; loaded 출력 -&amp;gt; 42 반환 -&amp;gt; loadData 종료
// -&amp;gt; main 다시시작 -&amp;gt; 42 출력


// waiting...
// loading...
// loaded!
// 42&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runBlocking은 regular과 suspend, blocking과 non-blocking 사이의 다리 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async를 사용할 때 flatMap을 사용할 필요가 없고 map은 list of list가 아니라 list of Deferred입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;awaitAll()은 List &amp;lt;List &amp;lt;any&amp;gt;&amp;gt;를 반환하기 때문에 결과를 얻으려면 flatten(). aggregate()를 호출하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CoroutineDispatcher는 코 루틴이 실행되어야 하는 스레드를 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 지정하지 않으면 외부 scope의 dispatcher를 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Dispatchers.Default : CPU 사용량이 많은 작업에 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://Dispatchers.IO&quot;&gt;Dispatchers.IO&lt;/a&gt; : 네트워크나 로컬 DB 등 통신을 할 때 주로 사용합니다.&lt;/li&gt;
&lt;li&gt;Dispatchers.Main : Main 스레드를 사용합니다. ( UI 스레드)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;withContext는 coroutine context를 지정하여 주어진 코드를 호출하고 완료될 때까지 일시 중단하고 결과를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Cancellation and timeouts&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cancelling coroutine execution&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launch는 실행 중인 코루틴을 취소하는 데 사용하는 Job을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458703214&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val job = launch {
    repeat(1000) { i -&amp;gt;
        println(&quot;job: I'm sleeping $i ...&quot;)
        delay(500L)
    }
}
delay(1300L) // delay a bit
println(&quot;main: I'm tired of waiting!&quot;)
job.cancel() // cancels the job
job.join() // waits for job's completion 
println(&quot;main: Now I can quit.&quot;)

// job: I'm sleeping 0 ...
// job: I'm sleeping 1 ...
// job: I'm sleeping 2 ...
// main: I'm tired of waiting!
// main: Now I can quit.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cancellation is cooperative&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 코루틴은 취소되면, &lt;b&gt;CancellationException&lt;/b&gt;을 발생시킵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458725046&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
    var nextPrintTime = startTime
    var i = 0
    while (i &amp;lt; 5) { // computation loop, just wastes CPU
        // print a message twice a second
        if (System.currentTimeMillis() &amp;gt;= nextPrintTime) {
            println(&quot;job: I'm sleeping ${i++} ...&quot;)
            nextPrintTime += 500L
        }
    }
}
delay(1300L) // delay a bit
println(&quot;main: I'm tired of waiting!&quot;)
job.cancelAndJoin() // cancels the job and waits for its completion
println(&quot;main: Now I can quit.&quot;)

// job: I'm sleeping 0 ...
// job: I'm sleeping 1 ...
// job: I'm sleeping 2 ...
// main: I'm tired of waiting!
// job: I'm sleeping 3 ...
// job: I'm sleeping 4 ...
// main: Now I can quit.&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;join : 현재의 coroutine 동작이 끝날 때까지 대기한다.&lt;/li&gt;
&lt;li&gt;cancel : 현재 coroutine을 즉시 종료하도록 유도만 하고 대기하지 않는다. 다만 타이트하게 동작하는 단순 루프에서는 delay가 없다면 종료하지 못한다.&lt;/li&gt;
&lt;li&gt;cancelAndJoin : 현재 coroutine에 종료하라는 신호를 보내고, 정상 종료할 때까지 대기한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Making computation code cancellable&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 computation 코드를 보면 cancel을 시켰음에도 불구하고 반복문을 전부 돌고 나서야 종료되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 computation 코드를 cancel 시키는 방법은 두 가지가 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명시적으로 cancel 상태를 체크한다. (&lt;b&gt;isActive&lt;/b&gt;&amp;nbsp;이용)&lt;/li&gt;
&lt;li&gt;yield&amp;nbsp;function을 이용하여 추기적으로 cancel을 체크하는 suspend function을 invoke 시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isActive는 CoroutineScope 객체를 통해 코루틴 내부에서 사용할 수 있는 확장 속성입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Closing resources with finally&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;coroutine을 cancel한다음, 마지막으로 해야 할 작업이 필요할 때는&amp;nbsp;try - finally&amp;nbsp;구문을 이용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458760581&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val job = launch {
    try {
        repeat(1000) { i -&amp;gt;
            println(&quot;job: I'm sleeping $i ...&quot;)
            delay(500L)
        }
    } finally {
        println(&quot;job: I'm running finally&quot;)
    }
}
delay(1300L) // delay a bit
println(&quot;main: I'm tired of waiting!&quot;)
job.cancelAndJoin() // cancels the job and waits for its completion
println(&quot;main: Now I can quit.&quot;)

// job: I'm sleeping 0 ...
// job: I'm sleeping 1 ...
// job: I'm sleeping 2 ...
// main: I'm tired of waiting!
// job: I'm running finally
// main: Now I can quit.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Run non-cancellable block&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 finally 내부에 다시 suspending function을 사용한다면 cancellationException이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특이 케이스로 finally에서 일시중지 함수를 사용해야 한다면 withContext와 NonCancellable로 감싸줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458777725&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val job = launch {
    try {
        repeat(1000) { i -&amp;gt;
            println(&quot;job: I'm sleeping $i ...&quot;)
            delay(500L)
        }
    } finally {
        withContext(NonCancellable) {
            println(&quot;job: I'm running finally&quot;)
            delay(1000L)
            println(&quot;job: And I've just delayed for 1 sec because I'm non-cancellable&quot;)
        }
    }
}
delay(1300L) // delay a bit
println(&quot;main: I'm tired of waiting!&quot;)
job.cancelAndJoin() // cancels the job and waits for its completion
println(&quot;main: Now I can quit.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Timeout&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴을 하나하나 Job으로 cencel하지 않고, 특정 시간 이후에 cencel 되도록 하려면 withTimeout을 사용하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458797102&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;withTimeout(1300L) {
    repeat(1000) { i -&amp;gt;
        println(&quot;I'm sleeping $i ...&quot;)
        delay(500L)
    }
}

// I'm sleeping 0 ...
// I'm sleeping 1 ...
// I'm sleeping 2 ...
// Exception in thread &quot;main&quot; kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TimeoutCancellationException은 CancellationException의 하위 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;timeout 처리가 필요하다면 try {...} catch 문을 사용해야 되지만 withTimeoutOrNull을 사용하면 exception throw 대신 null을 return 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458813462&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val result = withTimeoutOrNull(1300L) {
    repeat(1000) { i -&amp;gt;
        println(&quot;I'm sleeping $i ...&quot;)
        delay(500L)
    }
    &quot;Done&quot; // will get cancelled before it produces this result
}
println(&quot;Result is $result&quot;)

// I'm sleeping 0 ...
// I'm sleeping 1 ...
// I'm sleeping 2 ...
// Result is null&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Composing suspending functions&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sequential by default&lt;/h3&gt;
&lt;pre id=&quot;code_1627458851846&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순차적으로 코드를 실행할 두 개의 일시 중지 함수가 있다고 가정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 작업을 하는데 시간이 필요하므로 1초의 딜레이로 표현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458865670&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val time = measureTimeMillis {
    val one = doSomethingUsefulOne()
    val two = doSomethingUsefulTwo()
    println(&quot;The answer is ${one + two}&quot;)
}
println(&quot;Completed in $time ms&quot;)

// The answer is 42
// Completed in 2017 ms&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Concurrent using async&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 코드에서 의존성이 없고 두 함수를 동시에 수행하여 더 빠른 시간에 종료시키고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async 함수를 사용하여 두 개의 작업을 동시에 비동기적으로 처리할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458882542&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val time = measureTimeMillis {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    println(&quot;The answer is ${one.await() + two.await()}&quot;)
}
println(&quot;Completed in $time ms&quot;)

// The answer is 42
// Completed in 1017 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 함수는 동시에 실행되기 때문에 약 두배 가까이 빨라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async로 시작되는 코 루틴은 블록 내 코드의 수행을 지연시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수인 start 값을 CoroutineStart.LAZY를 설정하면 코루틴이 지연됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업을 수행하려면 start()를 호출하거나 await()를 호출하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458899350&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val time = measureTimeMillis { 
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() } 
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() } 

one.start() 
two.start() 
println(&quot;The answer is ${one.await() + two.await()}&quot;) 
} 
println(&quot;Completed in $time ms&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Async-style functions&lt;/h3&gt;
&lt;pre id=&quot;code_1627458912230&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// The result type of somethingUsefulOneAsync is Deferred&amp;lt;Int&amp;gt;
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulOneAsync() = GlobalScope.async {
    doSomethingUsefulOne()
}

// The result type of somethingUsefulTwoAsync is Deferred&amp;lt;Int&amp;gt;
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulTwoAsync() = GlobalScope.async {
    doSomethingUsefulTwo()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GlobalScope에서 async coroutine builder을 통해 명시적으로 비동기 함수를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 일시 중지 함수는 아닙니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458925918&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// note that we don't have `runBlocking` to the right of `main` in this example
fun main() {
    val time = measureTimeMillis {
        // we can initiate async actions outside of a coroutine
        val one = somethingUsefulOneAsync()
        val two = somethingUsefulTwoAsync()
        // but waiting for a result must involve either suspending or blocking.
        // here we use `runBlocking { ... }` to block the main thread while waiting for the result
        runBlocking {
            println(&quot;The answer is ${one.await() + two.await()}&quot;)
        }
    }
    println(&quot;Completed in $time ms&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 사용은 kotlin에서는 다음과 같은 이유로 강력하게 비추천합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val one = somthingUsefullOneAsync()과 one.await() 사이에서 exception이 발생하면 프로그램이&amp;nbsp;종료됩니다.&lt;/li&gt;
&lt;li&gt;외부의 try-catch로 처리하여 exception handling은 할 수 있으나 비동기 작업이 유지된 채로 남습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Structured concurrency with async&lt;/h3&gt;
&lt;pre id=&quot;code_1627458941110&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main(){
	val time = measureTimeMillis {
	    println(&quot;The answer is ${concurrentSum()}&quot;)
	}
	println(&quot;Completed in $time ms&quot;)
}

suspend fun concurrentSum(): Int = coroutineScope {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    one.await() + two.await()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async&amp;nbsp;coroutine builder는 CotourineScope의 확장 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드처럼 새로운 coroutineScope 안에서 async를 실행한다면, 두 개의 aysnc는 같은 scope에서 수행하는&amp;nbsp;작업들이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scope 안에서 exception이 발생하면 해당 scope을 빠져나가면서 해당 coroutineScope에서 수행되었던 자식 coroutine들도 다 취소됩니다. 그렇게 되면 프로그램이 종료되는 일이 발생하지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458958502&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    try {
        failedConcurrentSum()
    } catch(e: ArithmeticException) {
        println(&quot;Computation failed with ArithmeticException&quot;)
    }
}

suspend fun failedConcurrentSum(): Int = coroutineScope {
    val one = async&amp;lt;Int&amp;gt; { 
        try {
            delay(Long.MAX_VALUE) // Emulates very long computation
            42
        } finally {
            println(&quot;First child was cancelled&quot;)
        }
    }
    val two = async&amp;lt;Int&amp;gt; { 
        println(&quot;Second child throws an exception&quot;)
        throw ArithmeticException()
    }
    one.await() + two.await()
}

// Second child throws an exception
// First child was cancelled
// Computation failed with ArithmeticException&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cancel은 항상 코루틴 계층 구조를 통해 전파됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 위의 코드는 two 변수에서 exception을 발생시키면 one 변수와 main 함수로 exception이 전파됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Coroutine context and dispatchers&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dispatchers and threads&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;coroutine context는 어떤 스레드에서 코루틴을 실행할지에 대한 coroutine dispatcher를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;coroutine dispatcher는 코루틴을 실행시킬 스레드를 지정하거나 스레드 풀을 지정하거나, 지정하지 않고 실행되도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launch와&amp;nbsp;async 같은 coroutine builder들은 명시적으로 dispatcher를 지정할 수 있는 CoroutineContext 매개변수를 선택할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627458989255&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;launch { // context of the parent, main runBlocking coroutine
    println(&quot;main runBlocking      : I'm working in thread ${Thread.currentThread().name}&quot;)
}
launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
    println(&quot;Unconfined            : I'm working in thread ${Thread.currentThread().name}&quot;)
}
launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher 
    println(&quot;Default               : I'm working in thread ${Thread.currentThread().name}&quot;)
}
launch(newSingleThreadContext(&quot;MyOwnThread&quot;)) { // will get its own new thread
    println(&quot;newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}&quot;)
}

// Unconfined            : I'm working in thread main
// Default               : I'm working in thread DefaultDispatcher-worker-1
// newSingleThreadContext: I'm working in thread MyOwnThread
// main runBlocking      : I'm working in thread main&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파라미터 없이 launch를 정의하면 부모 CoroutineScope의 context와 dispatcher를 상속받습니다.&lt;/li&gt;
&lt;li&gt;Dispatchers.Default는 명시적으로 지정된 dispatchers가 없을 때 사용되는 기본입니다. GlobalScope.launch()와 동일합니다. 공유해서 사용하는 스레드 풀을 이용합니다.&lt;/li&gt;
&lt;li&gt;newSingleThreadContext는 코루틴을 실행할 스레드를 생성하지만 매우 비싼 리소스입니다. 사용이 끝나면 무조건 해제시켜야 하고, top level에 변수로 선언하여 재활용해서 사용해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unconfined vs confined dispatcher&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unconfined는 해당 코루틴을 callar thread에서 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일시 중지된 후 다시 시작되면 적절한 스레드에 재 할당되어 시작됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, CPU 시간을 소비하지 않고 특정 스레드에 제한된 공유 데이터를 업데이트하지 않는 코루틴에 적합합니다. ( UI 같은 작업은 실행하면 안 됨)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dispatcher는 기본적으로 외부 CoroutineScope를 상속합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runBlocking의 default dispatcher는 invoker thread로 제한되므로, 해당 코루틴들이 FIFO scheduling 될 거라고 예상할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627459007159&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
    println(&quot;Unconfined      : I'm working in thread ${Thread.currentThread().name}&quot;)
    delay(500)
    println(&quot;Unconfined      : After delay in thread ${Thread.currentThread().name}&quot;)
}
launch { // context of the parent, main runBlocking coroutine
    println(&quot;main runBlocking: I'm working in thread ${Thread.currentThread().name}&quot;)
    delay(1000)
    println(&quot;main runBlocking: After delay in thread ${Thread.currentThread().name}&quot;)
}

// Unconfined      : I'm working in thread main
// main runBlocking: I'm working in thread main
// Unconfined      : After delay in thread kotlinx.coroutines.DefaultExecutor
// main runBlocking: After delay in thread main&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Unconfined로 지정했기 때문에 runblocking이 시작된 main thead에서 수행합니다.&lt;/li&gt;
&lt;li&gt;아무것도 지정하지 않았기 때문에 runblocking의 dispatcher를 사용합니다.&lt;/li&gt;
&lt;li&gt;일시 중지된 후 다시 시작된 거이기 때문에 main이 아닌 다른 스레드에서 수행합니다.&lt;/li&gt;
&lt;li&gt;아무것도 지정하지 않았기 때문에 runblocking의 dispatcher를 사용합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Debugging coroutines and threads&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;coroutine debugger를 사용하여 다음과 같은 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각의 코루틴의 상태를 확인합니다.&lt;/li&gt;
&lt;li&gt;실행 중인 코루틴과 일시 중지된 코루틴의 로컬 및 캡쳐된 변수의 값을 확인합니다.&lt;/li&gt;
&lt;li&gt;코루틴 생성 스택과 코루틴 내부 호출 스택을 확인합니다.&lt;/li&gt;
&lt;li&gt;각각의 코루틴과 해당 스택의 상태가 포함된 보고서를 가져옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Dkotlinx.coroutines.debug를 JVM option에 추가하면 coroutine에 대한 정보도 로그로 남길 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627459025014&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val a = async {
    log(&quot;I'm computing a piece of the answer&quot;)
    6
}
val b = async {
    log(&quot;I'm computing another piece of the answer&quot;)
    7
}
log(&quot;The answer is ${a.await() * b.await()}&quot;)

// [main @coroutine#2] I'm computing a piece of the answer
// [main @coroutine#3] I'm computing another piece of the answer
// [main @coroutine#1] The answer is 42&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Job in the context&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Job은 context의 일부입니다. coroutineContext [Job]을 사용하여 Job을 꺼낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CoroutineScope의 isActive는 coroutineContext [Job]?. isActive == true와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627459039062&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;println(&quot;My job is ${coroutineContext[Job]}&quot;)

// My job is &quot;coroutine#1&quot;:BlockingCoroutine{Active}@5b80350b&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Children of a coroutine&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CoroutineScope에서 다른 코루틴을 launch 하면 CoroutineScope.coroutineContext에 의해 context를 상속하고 새로 생성되는 Job은 부모 coroutine Job의 자식이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 코루틴이 cancel 되면 자식 코루틴들도 재귀적으로 전부 cancel 됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코루틴을 실행할 때 명시적으로 다른 scope를 지정하면 부모 scope의 Job을 상속하지 않습니다.&lt;/li&gt;
&lt;li&gt;다른 Job이 새 코루틴에 대한 context로 전달되면 부모 scope의 Job을 재정의 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1627459056541&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// launch a coroutine to process some kind of incoming request
val request = launch {
    // it spawns two other jobs
    launch(Job()) { 
        println(&quot;job1: I run in my own Job and execute independently!&quot;)
        delay(1000)
        println(&quot;job1: I am not affected by cancellation of the request&quot;)
    }
    // and the other inherits the parent context
    launch {
        delay(100)
        println(&quot;job2: I am a child of the request coroutine&quot;)
        delay(1000)
        println(&quot;job2: I will not execute this line if my parent request is cancelled&quot;)
    }
}
delay(500)
request.cancel() // cancel processing of the request
delay(1000) // delay a second to see what happens
println(&quot;main: Who has survived request cancellation?&quot;)

// job1: I run in my own Job and execute independently!
// job2: I am a child of the request coroutine
// job1: I am not affected by cancellation of the request
// main: Who has survived request cancellation?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;job1은 명시적으로 지정해줬기 때문에 request가 cancel 돼도 정상적으로 실행되고 종료됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;job2은 request 코루틴의 자식 Job이므로 request가 cancel 된 시점에 job2도 cancel 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Parental responsibilities&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 코루틴은 항상 자식 코루틴이 전부 완료될 때까지 기다립니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627459072335&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// launch a coroutine to process some kind of incoming request
val request = launch {
    repeat(3) { i -&amp;gt; // launch a few children jobs
        launch  {
            delay((i + 1) * 200L) // variable delay 200ms, 400ms, 600ms
            println(&quot;Coroutine $i is done&quot;)
        }
    }
    println(&quot;request: I'm done and I don't explicitly join my children that are still active&quot;)
}
request.join() // wait for completion of the request, including all its children
println(&quot;Now processing of the request is complete&quot;)

// request: I'm done and I don't explicitly join my children that are still active
// Coroutine 0 is done
// Coroutine 1 is done
// Coroutine 2 is done
// Now processing of the request is complete&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Naming coroutines for debugging&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴에도 이름을 지정해 줄 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627459085398&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;log(&quot;Started main coroutine&quot;)
// run two background value computations
val v1 = async(CoroutineName(&quot;v1coroutine&quot;)) {
    delay(500)
    log(&quot;Computing v1&quot;)
    252
}
val v2 = async(CoroutineName(&quot;v2coroutine&quot;)) {
    delay(1000)
    log(&quot;Computing v2&quot;)
    6
}
log(&quot;The answer for v1 / v2 = ${v1.await() / v2.await()}&quot;)

// [main @main#1] Started main coroutine
// [main @v1coroutine#2] Computing v1
// [main @v2coroutine#3] Computing v2
// [main @main#1] The answer for v1 / v2 = 42&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Combining context elements&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;coroutine context는 여러 요소를 가질 수 있고 이를 위해서 + 연산자를 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627459100470&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;launch(Dispatchers.Default + CoroutineName(&quot;test&quot;)) {
    println(&quot;I'm working in thread ${Thread.currentThread().name}&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스터디 후 정리 내용&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Coroutine -&amp;gt; 일시 중단이 가능한 인스턴스&lt;/li&gt;
&lt;li&gt;CoroutineScope -&amp;gt; Coroutine을 실행시킬 수 있는 블록(범위)&lt;/li&gt;
&lt;li&gt;CoroutineContext -&amp;gt; 해당 CoroutineScope의 정보를 담고 있는 집합체&lt;/li&gt;
&lt;li&gt;CoroutineDispatchers -&amp;gt; 해당 CoroutineScope를 어떤 스레드에서 실행시킬지에 대한 정보를 담고 있다.&lt;/li&gt;
&lt;li&gt;Job -&amp;gt; CoroutineScope를 담고 있는 변수. 상태 값을 알고 있어서 담고 있는 CoroutineScope 제어가 가능하다.&lt;/li&gt;
&lt;li&gt;launch -&amp;gt; 코루틴을 시작하는 코루틴 빌더, 결과 값을 반환하지 않고 Job을 반환한다.&lt;/li&gt;
&lt;li&gt;async -&amp;gt; 코루틴을 시작하는 코루틴 빌더, 결과 값을 반환받을 수 있는 Deferred를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>개발자_미노</author>
      <guid isPermaLink="true">https://omod.tistory.com/118</guid>
      <comments>https://omod.tistory.com/118#entry118comment</comments>
      <pubDate>Wed, 28 Jul 2021 17:14:53 +0900</pubDate>
    </item>
    <item>
      <title>[Android] Clean Architecture 학습기</title>
      <link>https://omod.tistory.com/105</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[프로젝트]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Minoyam/Clean_Coroutines_Hilt_Sample&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/Minoyam/Clean_Coroutines_Hilt_Sample&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623026574579&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Minoyam/Clean_Coroutines_Hilt_Sample&quot; data-og-description=&quot;Contribute to Minoyam/Clean_Coroutines_Hilt_Sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Minoyam/Clean_Coroutines_Hilt_Sample&quot; data-og-url=&quot;https://github.com/Minoyam/Clean_Coroutines_Hilt_Sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8vwN9/hyKuCzbzvz/bFOvHdvwFCbtmq5FjnBok0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Minoyam/Clean_Coroutines_Hilt_Sample&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Minoyam/Clean_Coroutines_Hilt_Sample&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8vwN9/hyKuCzbzvz/bFOvHdvwFCbtmq5FjnBok0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Minoyam/Clean_Coroutines_Hilt_Sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to Minoyam/Clean_Coroutines_Hilt_Sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Clean Architecture를 배워보기 위해 간단한 Sample 하나를 만들어 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 방식은 Github에 들어가면 확인하실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p.s : Coroutine flow까지 학습하여 적용을 해보는 것이 목표입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Clean Architecture 구조&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k4LrH/btq6APT80BW/XYvFkQKmXT99PDyUv3zJ2K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k4LrH/btq6APT80BW/XYvFkQKmXT99PDyUv3zJ2K/img.jpg&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;567&quot; width=&quot;312&quot; height=&quot;229&quot; style=&quot;width: 52.5353%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k4LrH/btq6APT80BW/XYvFkQKmXT99PDyUv3zJ2K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk4LrH%2Fbtq6APT80BW%2FXYvFkQKmXT99PDyUv3zJ2K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;772&quot; height=&quot;567&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TdQUl/btq6BDyAgph/b5hrd9NjfcLz7iDcAj4OWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TdQUl/btq6BDyAgph/b5hrd9NjfcLz7iDcAj4OWK/img.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;750&quot; width=&quot;330&quot; height=&quot;275&quot; style=&quot;width: 46.3019%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TdQUl/btq6BDyAgph/b5hrd9NjfcLz7iDcAj4OWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTdQUl%2Fbtq6BDyAgph%2Fb5hrd9NjfcLz7iDcAj4OWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;900&quot; height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Clean Architecture를 검색을 하시면 이러한 모양의 구조를 많이 확인하실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위의 이미지들은 오리지널 Clean Architecture 구조로써 안드로이드의 Clean Architecture 구조라고 이해를 하기에는 다소 어려운 부분들이 많습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;249&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k5qxY/btq6BDTcuWq/Tg07oNO4MYWKoNg5GdUHrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k5qxY/btq6BDTcuWq/Tg07oNO4MYWKoNg5GdUHrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k5qxY/btq6BDTcuWq/Tg07oNO4MYWKoNg5GdUHrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk5qxY%2Fbtq6BDTcuWq%2FTg07oNO4MYWKoNg5GdUHrK%2Fimg.png&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;249&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 저는 안드로이드에서는 위에 이미지와 같이 App, Domain, Data 3가지로 나누면 된다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;App&lt;/b&gt;&amp;nbsp; &lt;b&gt;:&lt;/b&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;UI(Activity, Fragment), ViewModel등 View와 관련된 부분들이 속합니다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #555555;&quot;&gt;화면과 입력에 대한 처리 등 UI와 직접적으로 관련된 부분을 담당합니다. &lt;br /&gt;App은 Domain과 Data 레이어를 포함하고 있다는 특징이 있습니다.&lt;/span&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Domain&lt;/b&gt; : 비즈니스 로직을 담당하는 부분입니다. &lt;br /&gt;보통 Dto 와 UseCase, Repository를&amp;nbsp; 포함합니다.&lt;br /&gt;UseCase는 개별 기능 또는 비즈니스 논리 단위를 뜻합니다.&lt;br /&gt;UseCase는 한 개의 행동을 담당하고 이름만 보고 어떤 기능을 하는지 구분할 수 있어야 합니다. &amp;nbsp;&lt;br /&gt;Domain 레이어는 App, Data 레이어와 어떤 의존성도 맺지 않고 독립적이다는 특징이 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Data&lt;/b&gt; : Repositoy 구현체, DB, Response, Source, API를 포함합니다.&lt;br /&gt;로컬 또는 서버 API와 통신하여 데이터 처리 역할을 합니다. &lt;br /&gt;Domain 레이어를 포함하고있다는 특징이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;계층을 분리하여 의존성을 분리할 수 있고, Clean Architecture를 적용하여 이와 같은 장점을 얻을 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 테스트 용이&lt;/li&gt;
&lt;li&gt;유지 보수 용이&lt;/li&gt;
&lt;li&gt;UI의 독립&lt;/li&gt;
&lt;li&gt;DB의 독립&lt;/li&gt;
&lt;li&gt;외부와의 독립&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;353&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3KKYX/btq6BDZ0GUz/yqVSEX9nvK7Ge8calq5Uv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3KKYX/btq6BDZ0GUz/yqVSEX9nvK7Ge8calq5Uv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3KKYX/btq6BDZ0GUz/yqVSEX9nvK7Ge8calq5Uv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3KKYX%2Fbtq6BDZ0GUz%2FyqVSEX9nvK7Ge8calq5Uv1%2Fimg.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;353&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 구조를 짜본다면 위와 같은 형식으로 짤 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response는 각 레이어에서 필요한 Data만 Mapping 하여 Dto - Vo로 변환하여 사용하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 구현&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;152&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JOkG8/btq6APl6gpc/kpelFmBZjUEKjJXCouICT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JOkG8/btq6APl6gpc/kpelFmBZjUEKjJXCouICT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JOkG8/btq6APl6gpc/kpelFmBZjUEKjJXCouICT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJOkG8%2Fbtq6APl6gpc%2FkpelFmBZjUEKjJXCouICT0%2Fimg.png&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;152&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app, data, domain을 Multi Module 형태로 나눴습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;App&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;654&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c60UPr/btq6JwZpd1P/az4aNUjlGvki6oMIXvKhzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c60UPr/btq6JwZpd1P/az4aNUjlGvki6oMIXvKhzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c60UPr/btq6JwZpd1P/az4aNUjlGvki6oMIXvKhzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc60UPr%2Fbtq6JwZpd1P%2Faz4aNUjlGvki6oMIXvKhzk%2Fimg.png&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;654&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View와 관련된 부분들은 App에서 처리를 하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;361&quot; data-origin-height=&quot;153&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJUJca/btq6HQRlvKl/YyNPnprfkM4aSYSIXKg2KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJUJca/btq6HQRlvKl/YyNPnprfkM4aSYSIXKg2KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJUJca/btq6HQRlvKl/YyNPnprfkM4aSYSIXKg2KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJUJca%2Fbtq6HQRlvKl%2FYyNPnprfkM4aSYSIXKg2KK%2Fimg.png&quot; data-origin-width=&quot;361&quot; data-origin-height=&quot;153&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 data는 Mapper를 통해 Vo 형식으로 받아옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;257&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AwLu9/btq6FuubTkL/gRv1LVBXoOoMKyhGOXACx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AwLu9/btq6FuubTkL/gRv1LVBXoOoMKyhGOXACx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AwLu9/btq6FuubTkL/gRv1LVBXoOoMKyhGOXACx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAwLu9%2Fbtq6FuubTkL%2FgRv1LVBXoOoMKyhGOXACx1%2Fimg.png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;257&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UseCase 구현체를 통해 Domain과 연결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(requset 부분도 data class를 만들어서 사용하면 좋습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Domain&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;304&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NsaJa/btq6BDZ0Wje/1w5RatFnyWgd6ZBxdtvPX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NsaJa/btq6BDZ0Wje/1w5RatFnyWgd6ZBxdtvPX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NsaJa/btq6BDZ0Wje/1w5RatFnyWgd6ZBxdtvPX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNsaJa%2Fbtq6BDZ0Wje%2F1w5RatFnyWgd6ZBxdtvPX0%2Fimg.png&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;304&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Domain은 Dto와 Interface로 구성되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interface 구현체를 App과 Data 레이어에서 구현함으로서 Domain 레이어는 어떤 레이어와 의존관계를 형성하지 않습니다. 이러한 형태를 &lt;b&gt;의존성 역전원칙(DIP)&lt;/b&gt;라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의존성 역전 원칙이란&lt;/b&gt; 고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대로 의존해서는 안되는 걸 뜻하고 대신 세부사항이 정책에 의존해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Data&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;394&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v1w1Y/btq6ArZ2tfc/i6X5Um3qS01kfYjNzVXAKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v1w1Y/btq6ArZ2tfc/i6X5Um3qS01kfYjNzVXAKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v1w1Y/btq6ArZ2tfc/i6X5Um3qS01kfYjNzVXAKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv1w1Y%2Fbtq6ArZ2tfc%2Fi6X5Um3qS01kfYjNzVXAKK%2Fimg.png&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;394&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data는 DB / Api와 관련된 부분들로 구성되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Repository 구현체를 통해 Domain과 연결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;160&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SiTPI/btq6DDZBeKN/YToxYPdnRkhB7oxSgi0op0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SiTPI/btq6DDZBeKN/YToxYPdnRkhB7oxSgi0op0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SiTPI/btq6DDZBeKN/YToxYPdnRkhB7oxSgi0op0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSiTPI%2Fbtq6DDZBeKN%2FYToxYPdnRkhB7oxSgi0op0%2Fimg.png&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;160&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 Response는 Mapper를 통해 Dto로 변환되어 전달됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 간단한 Sample로 Clean Architecture를 공부 해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것저것 찾으면서 독학으로 공부한거라 다소 틀린 점이 있거나 보완할 부분이 있다면 댓글로 알려주시면 &lt;br /&gt;감사하겠습니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 코드 부분은 Github을 통해 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youngest-programming.tistory.com/484&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://youngest-programming.tistory.com/484&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623031794152&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[안드로이드] 클린 아키텍처(Clean Architecture) 정리 및 구현&quot; data-og-description=&quot;[2021-04-28 업데이트] [프로젝트] github.com/mtjin/mtjin-android-clean-architecture-movieapp mtjin/mtjin-android-clean-architecture-movieapp Clean Architecture 학습 및 구현. Contribute to mtjin/mtjin..&quot; data-og-host=&quot;youngest-programming.tistory.com&quot; data-og-source-url=&quot;https://youngest-programming.tistory.com/484&quot; data-og-url=&quot;https://youngest-programming.tistory.com/484&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pGlaN/hyKuzbseJB/CUz1qfuGjgsAOnSNxGNflk/img.png?width=786&amp;amp;height=479&amp;amp;face=0_0_786_479,https://scrap.kakaocdn.net/dn/rZmYG/hyKuvUrO1e/KukEwlHGa2XTusE6i3keXK/img.png?width=786&amp;amp;height=479&amp;amp;face=0_0_786_479,https://scrap.kakaocdn.net/dn/NNcHl/hyKuzJiSYn/fFVi2LHw4ajy8l7UoIhfN0/img.jpg?width=700&amp;amp;height=1245&amp;amp;face=0_0_700_1245&quot;&gt;&lt;a href=&quot;https://youngest-programming.tistory.com/484&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://youngest-programming.tistory.com/484&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pGlaN/hyKuzbseJB/CUz1qfuGjgsAOnSNxGNflk/img.png?width=786&amp;amp;height=479&amp;amp;face=0_0_786_479,https://scrap.kakaocdn.net/dn/rZmYG/hyKuvUrO1e/KukEwlHGa2XTusE6i3keXK/img.png?width=786&amp;amp;height=479&amp;amp;face=0_0_786_479,https://scrap.kakaocdn.net/dn/NNcHl/hyKuzJiSYn/fFVi2LHw4ajy8l7UoIhfN0/img.jpg?width=700&amp;amp;height=1245&amp;amp;face=0_0_700_1245');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[안드로이드] 클린 아키텍처(Clean Architecture) 정리 및 구현&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[2021-04-28 업데이트] [프로젝트] github.com/mtjin/mtjin-android-clean-architecture-movieapp mtjin/mtjin-android-clean-architecture-movieapp Clean Architecture 학습 및 구현. Contribute to mtjin/mtjin..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;youngest-programming.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.slideshare.net/RyanHKang/clean-architecture-on-android-243156618&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.slideshare.net/RyanHKang/clean-architecture-on-android-243156618&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623031809252&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;slideshare:presentation&quot; data-og-title=&quot;Clean Architecture on Android&quot; data-og-description=&quot;무의식적으로 개발하다보면 높은 결합도와 낮은 응집도를 가진 코드를 작성하게 되고, 결국 유지보수성이 떨어지고 큰 개발 공수로 이어집니다. 이러한 문제점을 보완하기 위해 클린 아키텍처&quot; data-og-host=&quot;www.slideshare.net&quot; data-og-source-url=&quot;https://www.slideshare.net/RyanHKang/clean-architecture-on-android-243156618&quot; data-og-url=&quot;https://www.slideshare.net/RyanHKang/clean-architecture-on-android-243156618&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cTDX3j/hyKuCsuUKH/1Hw6VCpIMKuPlcEQeSU7mk/img.jpg?width=768&amp;amp;height=432&amp;amp;face=0_0_768_432,https://scrap.kakaocdn.net/dn/iVSS0/hyKunozOQY/LFbL5XhN8KOwb6VkhHPdu1/img.jpg?width=638&amp;amp;height=359&amp;amp;face=0_0_638_359&quot;&gt;&lt;a href=&quot;https://www.slideshare.net/RyanHKang/clean-architecture-on-android-243156618&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.slideshare.net/RyanHKang/clean-architecture-on-android-243156618&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cTDX3j/hyKuCsuUKH/1Hw6VCpIMKuPlcEQeSU7mk/img.jpg?width=768&amp;amp;height=432&amp;amp;face=0_0_768_432,https://scrap.kakaocdn.net/dn/iVSS0/hyKunozOQY/LFbL5XhN8KOwb6VkhHPdu1/img.jpg?width=638&amp;amp;height=359&amp;amp;face=0_0_638_359');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Clean Architecture on Android&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;무의식적으로 개발하다보면 높은 결합도와 낮은 응집도를 가진 코드를 작성하게 되고, 결국 유지보수성이 떨어지고 큰 개발 공수로 이어집니다. 이러한 문제점을 보완하기 위해 클린 아키텍처&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.slideshare.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <author>개발자_미노</author>
      <guid isPermaLink="true">https://omod.tistory.com/105</guid>
      <comments>https://omod.tistory.com/105#entry105comment</comments>
      <pubDate>Mon, 7 Jun 2021 11:10:38 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 코틀린이란</title>
      <link>https://omod.tistory.com/104</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 대상 플랫폼 : 자바가 실행되는 모든 곳&lt;/h3&gt;
&lt;p&gt;코틀린의 주목적은 현재 자바가 사용되고 있는 모든 용도에 적합하면서도 더 간결하고 생산적이며 안전한 대체 언어를 제공하는 것이다.&lt;/p&gt;
&lt;p&gt;주로 자바가 많이 사용되는 백엔드, 안드로이드 app에서 주로 쓰인다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 특징&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp;정적 타입 지정 언어&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;자바와 마찬가지로 &lt;b&gt;정적 타입 지정 언어&lt;/b&gt;다.&lt;br /&gt;코틀린은 &lt;b&gt;타입 추론&lt;/b&gt;을 지원하므로 &lt;b&gt;정적 타입 지정 언어&lt;/b&gt;에서 타입을 직접 선언해야 하는 불편함이 사라진다.&lt;br /&gt;&lt;b&gt;정적 타입 지정 언어&lt;/b&gt;는 프로그램 구성 요소의 타입을 컴파일 시점에 알 수 있고 &lt;br /&gt;프로그램 안에서 객체의 필드나 메소드를 사용할 때마다 &lt;b&gt;컴파일러가 타입을 검증&lt;/b&gt;해준다.&lt;br /&gt;&lt;b&gt;정적 타입 지정 언어&lt;/b&gt;의 특징은 다음과 같다.
&lt;ol style=&quot;list-style-type: decimal;&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;성능&lt;/b&gt; - 실행 시점에 어떤 메소드를 호출할지 찾는 과정이 필요 없으므로 메소드 호출이 빠르다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;신뢰성&lt;/b&gt; - 컴파일러가 프로그램의 정확성을 검증하므로 실행 시 프로그램이 오류로 중단될 가능성이 적다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;유지 보수성&lt;/b&gt; - 다루는 객체가 어떤 타입에 속하는지 알 수 있기 때문에 처음 보는 코드를 다룰 때도 쉽다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;도구 지원&lt;/b&gt; - 더 안전하게 리팩토링 할 수 있고, 더 정확한 코드 완성 기능을 제공할 수 있다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp;함수형 프로그래밍&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;코틀린은 &lt;b&gt;함수 타입&lt;/b&gt;을 지원한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;b&gt;&lt;b&gt;함수형 프로그래밍의 핵심 개념&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;1. &lt;b&gt;일급 시민 함수&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 함수를 일반 값처럼 다룰 수 있다. 함수를 변수에 저장할 수 있고, 함수를 인자로 다른 함수에 전달 할 수 있으며, 함수에서 새로운 함수를 만들어서 반환할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1617237091586&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val test = fun() { println(&quot;test&quot;) }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;불변성&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 일단 만들어지면 내부 상태가 불변 객체를 사용해 프로그램을 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;3. &lt;b&gt;부수 효과 없음&amp;nbsp;&lt;/b&gt;- 입력이 같으면 항상 같은 출력을 내놓고 다른 객체의 상태를 변경하지 않으며, 함수 외부나&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다른 바깥 환경과 상호 작용하지 않는 순수 함수를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;b&gt;&amp;nbsp; 함수형 프로그래밍의 특징&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp;간결성&lt;/b&gt; - 함수를 값처럼 활용하여 추상화를 더 강력하게 할 수 있고 코드 중복을 막을 수 있다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;다중 스레드 안전&amp;nbsp;&lt;/b&gt;- 적절한 동기화 없이 같은 데이터를 여러 스레드가 변경하는 경우 문제가 많이 생긴다.&lt;br /&gt;하지만 불변 데이터 구조를 사용하고 순수 함수를 그 데이터 구조에 적용한다면 같은 데이터를 &lt;br /&gt;여러 스레드가 변경 할 수 없다. 그러므로 다중 스레드를 사용할때 안전하게 사용 할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 용이&amp;nbsp;&lt;/b&gt;- 부수 효과가 있는 함수는 그 함수를 실행 할 때 환경을 구성하는 준비 코드가&amp;nbsp;&lt;br /&gt;따로 필요하지만, 순수 함수는 준비 코드 없이 독립적으로 테스트 할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;코틀린은 함수형 스타일로 프로그램을 짤 수 있게 다음과 같은 지원을 해준다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;함수 타입&lt;/b&gt;을 지원하여 함수에서 다른 함수를 파라미터로 받거나 함수가 새로운 함수를 반환 할 수 있다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;람다 식&lt;/b&gt;을 지원하여 준비 코드를 작성하지 않아도 코드 블록을 쉽게 정의하고 전달 할 수 있다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;데이터 클래스&lt;/b&gt;를 지원하여 불변적인 값 객체를 간편하게 만들 수 있다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;b&gt;코틀린 표준 라이브러리&lt;/b&gt;는 객체와 컬렉션을 함수형 스타일로 다룰 수 있는 API를 제공한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 철학&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실용성&lt;/b&gt;&lt;b&gt;&lt;/b&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;- 코틀린은 실제 문제를 해결하기 위해 만들어진 실용적인 언어다.&lt;br /&gt;&lt;br /&gt;- 다른 프로그래밍 언어가 채택한 이미 검증된 해법과 기능에 의존한다.&lt;br /&gt;&amp;nbsp; 이로 인해 언어의 복잡도가 줄어들고 기존 개념을 통해 쉽게 배울 수 있다.&lt;br /&gt;&lt;br /&gt;- 특정 프로그래밍 스타일이나 패러다임을 사용할 것을 강제하지 않는다.&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;간결성&lt;/b&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;- 자바의 게터, 세터등 여러 가지 준비 코드를 묵시적으로 제공한다.&lt;br /&gt;&lt;br /&gt;- 람다를 지원하여 반복되거나 길어질 수 있는 코드를 작은 코드 블록으로 대체 할 수 있다.&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;안정성&lt;/b&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;- 애플리케이션의 타입 안전성을 보장하면서 자바보다 더 적은 비용으로 타입 안전성을 사용할 수 있다.&lt;br /&gt;&lt;br /&gt;- 실행 시점에 오류를 발생시키는 대신 컴파일 시점 검사를 통해 오류를 더 많이 방지해준다.&lt;br /&gt;&lt;br /&gt;- 타입 시스템은 null이 될 수 없는 값을 추적하며, 실행 시점에 NullPointerException이 발생할 수 있는 &lt;br /&gt;&amp;nbsp; 코드를 금지 한다.&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;상호운용성&lt;/b&gt;
&lt;div style=&quot;caret-color: auto; background-color: #fafafa; padding: 20px 20px 22px; margin: 20px 0px; border: 1px dashed #c5c5c5; color: #333333;&quot; data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;- 라이브러리가 어떤 API를 제공하던 활용할 수 있다.&lt;br style=&quot;margin-top: 0px;&quot; /&gt;&lt;br /&gt;- 자바와 코틀린 코드를 원하는 대로 섞어 쓸 수 있다.&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Kotlin</category>
      <author>개발자_미노</author>
      <guid isPermaLink="true">https://omod.tistory.com/104</guid>
      <comments>https://omod.tistory.com/104#entry104comment</comments>
      <pubDate>Fri, 2 Apr 2021 10:22:50 +0900</pubDate>
    </item>
    <item>
      <title>설계 원칙 - SOLID</title>
      <link>https://omod.tistory.com/103</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;1. SRP (단일 책임의 원칙: Single Responsibility Principle)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;소프트웨어 시스템이 가질 수 있는 최적의 구조는 시스템을 만드는 조직의 사회적 구조에 커다란 영향을 받는다.&lt;/p&gt;
&lt;p&gt;따라서 각 소프트웨어 모듈은 변경의 이유가 하나, 단 하나여야만 한다.&lt;/p&gt;
&lt;p&gt;-&amp;gt;&amp;nbsp;&lt;b&gt;하나의 모듈&lt;/b&gt;은&amp;nbsp;&lt;b&gt;하나의 액터&lt;/b&gt;에 대해서만 책임져야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;액터 - 변경을 요청하는 한 명 이상의 사람&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;징후 1 : 우발적 중복&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1617078357202&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Employee(){
    fun calculatePay(){
        /*
            회계팀에서 기능을 정의, CFO 보고를 위해 사용
            regularHours() 메서드 사용
         */
    }
    fun reportHours(){
        /*
            인사팀에서 기능을 정의, COO 보고를 위해 사용
            regularHours() 메서드 사용
         */
    }
    fun save(){
        /*
            데이터베이스 관리자가 기능을 정의, CTO 보고를 위해 사용
         */
    }
    fun regularHours(){
        /*
            업무 시간을 계산 하기 위해 사용
         */
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Employee 단일 클래스 안에 세 액터가 서로 결합이 되어 있다.&lt;/p&gt;
&lt;p&gt;CFO 팀에서 결정한 조치가 COO팀이 의존하는 무언가에 영향을 줄 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 CFO 팀에서 regularHours() 메서드를 수정을 하려 하고 COO팀에서 동일하게 사용하고 있는지를 모른다면 변화가 일어나면 COO팀은 regularHours()에서 잘못된 데이터를 받아오게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;징후 2 : 병합&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;소스 파일에 다양하고 많은 메서드를 포함하면 병합이 자주 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;서로 다른 액터를 책임진다면 병합이 발생할 가능성은 훨씬 높아진다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;병합은 여러 사람이 서로 다른 목적으로 같은 소스 파일을 변경하는 경우 발생한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어 CTO 팀에서 Employee 테이블 스키마를 수정하기로 했다.&lt;/p&gt;
&lt;p&gt;동시에 COO팀에서 reportHours() 메서드를 수정하기로 했다.&lt;/p&gt;
&lt;p&gt;두 팀이 작업을 끝내고 변경사항을 적용한다면 병합이 발생한다.&lt;/p&gt;
&lt;p&gt;두 팀뿐만 아니라 CFO팀에도 영향이 갈 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;해결책&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;데이터와 메서드를 분리하는 방식이 가장 확실한 해결책이다.&lt;/li&gt;
&lt;li&gt;단점은 클래스를 인스턴스 화하고 추적해야 한다.&lt;/li&gt;
&lt;li&gt;이런 단점을 위해 퍼사드 패턴을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617078821314&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class PayCalculator{
    fun calculatePay(){}
}
class HourReporter{
    fun reportHours(){}
}
class EmployeeSaver{
    fun save(){}

}
class EmployeeFacade(private val payCalculator: PayCalculator, 
					 private val hourReporter: HourReporter, 
					 private val employeeSaver: EmployeeSaver){
    fun regularHours(){
        payCalculator.calculatePay()
        hourReporter.reportHours()
        employeeSaver.save()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;퍼사드 패턴&lt;/span&gt;&lt;/b&gt;은 인터페이스를 간단하게 바꾸기 위한 디자인 패턴이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. OCP (개방 폐쇄의 원칙: Open Close Principle)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;기존 코드를 수정하기보다는 반드시 새로운 코드를 추가하는 방식으로 시스템의 행위를 변경할 수 있도록 설계해야만 소프트웨어 시스템을 쉽게 변경할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;즉, 소프트웨어 개체는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 된다는 뜻이다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는 것이 목표이다. 시스템을&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;컴포넌트 단위로 분리&lt;/span&gt;&lt;/b&gt;하고, 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span data-token-index=&quot;3&quot; data-reactroot=&quot;&quot;&gt;의존적 계층구조&lt;/span&gt;&lt;/b&gt;가 만들어지도록 해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. LSP (리스 코브 치환의 원칙: The Liskov Substitution Principle)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;상호 대체 가능한 구성요소를 이용해 소프트웨어 시스템을 만들 수 있으려면, 구성요소는 반드시 서로 &lt;b&gt;&lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;치환 가능&lt;/span&gt;&lt;/b&gt;해야&lt;/p&gt;
&lt;p&gt;한다. 조금이라도 위배하면 시스템 아키텍처가 오염되어 상당량의 별도 메커니즘을 추가해야 할 수 있기 때문에&lt;/p&gt;
&lt;p&gt;아키텍처 수준까지 확장해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. ISP (인터페이스 분리의 원칙: Interface Segregation Principle)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;소프트웨어 설계자는 사용하지 않은 것에 의존하지 않아야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1617079005105&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User1 : OPS()

class User2 : OPS()

class User3 : OPS()

open class OPS {
    fun op1() {}

    fun op2() {}

    fun op3() {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 코드는 User들이 각자 번호에 맞는 op만 사용하지만 다른 번호의 메서드도 의존하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1617079020562&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User1 : U1Ops { override fun op1() {} }

class User2 : U2Ops { override fun op2() {} }

class User3 : U3Ops { override fun op3() {} }

class OPS : U1Ops, U2Ops, U3Ops {
    override fun op1() {}

    override fun op2() {}

    override fun op3() {}
}

interface U1Ops { fun op1() }

interface U2Ops { fun op2() }

interface U3Ops { fun op3() }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이런 식으로 분리를 하면 각자 사용하는 메서드만 의존하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. DIP (의존성 역전의 원칙: Dependency Inversion Principle)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대로 의존해서는 안된다.&lt;/p&gt;
&lt;p&gt;대신 세부사항이 정책에 의존해야 한다.&lt;/p&gt;
&lt;p&gt;'유연성이 극대화된 시스템'이란 소스 코드 의존성이 추상에 의존하며 구체에는 의존하지 않는 시스템이다.&lt;/p&gt;</description>
      <category>Android</category>
      <author>개발자_미노</author>
      <guid isPermaLink="true">https://omod.tistory.com/103</guid>
      <comments>https://omod.tistory.com/103#entry103comment</comments>
      <pubDate>Tue, 30 Mar 2021 13:40:04 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 람다</title>
      <link>https://omod.tistory.com/102</link>
      <description>&lt;p&gt;람다(lambda)는 &lt;b&gt;다른 함수에 넘길 수 있는 작은 코드 조각&lt;/b&gt;을 뜻한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1616738501411&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Person(val name : String, val age : Int)

fun findTheOldest(people : List&amp;lt;Person&amp;gt;){
    var maxAge = 0 // 연장자 나이
    var theOldest : Person? = null // 연장자를 저장할 변수
    for(person in people){
        if(person.age &amp;gt; maxAge){
            maxAge = person.age
            theOldest = person
        }
    }
    println(theOldest)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;람다를 사용하지 않고 가장 연장자인 사람을 찾는 코드를 작성한다면 위와 같은 코드처럼 작성 할 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 코드가 짧지않고 비교연산자를 반대로 쓰는등 실수가 생길 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1616738637675&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val people = listOf(Person(&quot;Alice&quot;,29),Person(&quot;Bob&quot;,31))
    println(people.maxBy {it.age })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;코틀린의 라이브러리중 maxBy를 사용하면 쉽게 연장자를 구할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1616738731138&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public inline fun &amp;lt;T, R : Comparable&amp;lt;R&amp;gt;&amp;gt; Iterable&amp;lt;T&amp;gt;.maxBy(selector: (T) -&amp;gt; R): T?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;maxBy는 &lt;b&gt;가장 큰 원소를 찾기 위해 비교에 사용할 값을 돌려주는 함수를 인자&lt;/b&gt;로 받는다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;라이브러리를 보면 (selector : (T) -&amp;gt; R)에서 함수를 인자로 받는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;이런 식으로 함수나 프로퍼티를 반환하는 역할을 수행하는 &lt;b&gt;람다&lt;/b&gt;는 &lt;b&gt;멤버 참조&lt;/b&gt;로 대치할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1616738976227&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ x: Int, y: Int -&amp;gt; x + y }

val sum = { x: Int, y: Int -&amp;gt; x + y }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;람다는 항상 중괄호 사이에 위치하며 x,y 같은 인자를 가질수 있고 화살표로 &lt;b&gt;인자 목록&lt;/b&gt;과 &lt;b&gt;람다 본문&lt;/b&gt;을 구분해준다.&lt;/p&gt;
&lt;p&gt;또한, 변수에 람다 식을 저장하여 사용할 수도 있다.&lt;/p&gt;
&lt;p&gt;인자의 타입은 생략할 수 있지만, 변수에 저장하여 사용할 때는 타입 추론이 불가능 하므로 타입을 반드시 명시 해줘야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1616740952805&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val sum = { x: Int, y: Int -&amp;gt;
        println(&quot;$x and $y&quot;)
        x + y
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;람다의 본문이 여러 줄로 이뤄진 경우 본문의 맨 마지막에 있는 식이 &lt;b&gt;람다의 결과 값&lt;/b&gt;이 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드 블록을 다른 함수에게 인자로 넘기는 방법 말고 넘기려는 코드가 이미 함수로 선언된 경우에는 &lt;b&gt;멤버 참조&lt;/b&gt;를 사용 할 수 있다. &lt;b&gt;이중 콜론&lt;/b&gt;을 사용하면 멤버참조를 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;멤버 참조는 인자나 메소드를 단 하나만 호출하는 함수 값을 만들어준다.&lt;/p&gt;
&lt;pre id=&quot;code_1616744810983&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val getAge = Person::age

val getAge = { person: Person -&amp;gt; person.age }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Person은 클래스, age는 멤버를 뜻한다.&lt;/p&gt;
&lt;p&gt;첫번째 줄 코드 와 두번째 줄 코드는 같은 역할을 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;람다를 사용하면 코드 조각을 다른 함수에게 인자로 넘길 수 있다.&lt;/li&gt;
&lt;li&gt;람다가 함수 인자인 경우 괄호 밖으로 빼낼 수 있고, 람다의 인자가 하나뿐인 경우 it이라는 이름으로 부를 수 있다.&lt;/li&gt;
&lt;li&gt;람다 안에 있는 코드는 그 람다가 들어있는 바깥 함수의 변수를 읽겨나 쓸 수 있다.&lt;/li&gt;
&lt;li&gt;메소드, 생성자, 프로퍼티의 이름 앞에 ::을 붙이면 각각에 대한 참조를 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kotlin</category>
      <author>개발자_미노</author>
      <guid isPermaLink="true">https://omod.tistory.com/102</guid>
      <comments>https://omod.tistory.com/102#entry102comment</comments>
      <pubDate>Mon, 29 Mar 2021 16:04:54 +0900</pubDate>
    </item>
    <item>
      <title>Android App Bundle이 서명되지 않았습니다</title>
      <link>https://omod.tistory.com/101</link>
      <description>&lt;p&gt;구글 콘솔에 앱을 배포할때 &quot;Android App Bundle&quot;이 서명되지 않았습니다. 라는 오류가 뜬다면&lt;/p&gt;
&lt;p&gt;gradle 파일 안에 &lt;span&gt;buildTypes - release 안에&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;debuggable = true -&amp;gt; true로 설정되있는지 확인하자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;debuggable = false로 바꿔주면 오류 없이 앱 등록이 잘된다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Android</category>
      <author>개발자_미노</author>
      <guid isPermaLink="true">https://omod.tistory.com/101</guid>
      <comments>https://omod.tistory.com/101#entry101comment</comments>
      <pubDate>Thu, 14 Jan 2021 18:40:08 +0900</pubDate>
    </item>
    <item>
      <title>[Android] 카카오 로그인  v2 API with kotlin - 2</title>
      <link>https://omod.tistory.com/100</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://omod.tistory.com/94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://omod.tistory.com/94&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1608793203107&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Android] 카카오 로그인  v2 API with kotlin - 1&quot; data-og-description=&quot;카카오 로그인을 공부하면서 사용하는 방법을 정리 해볼까 합니다. 일단 카카오 내어플리케이션에 들어가서 앱을 등록 해줘야합니다. 앱 아이콘에는 앱에 사용할 아이콘 이미지를 넣어 주시면&quot; data-og-host=&quot;omod.tistory.com&quot; data-og-source-url=&quot;https://omod.tistory.com/94&quot; data-og-url=&quot;https://omod.tistory.com/94&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bgi7mM/hyIGwOsE4I/4jxdCCgnnPy0o0LXPKpek1/img.png?width=672&amp;amp;height=519&amp;amp;face=0_0_672_519,https://scrap.kakaocdn.net/dn/pfIxe/hyIGjuQAlW/E6Ie6Vnxbn5qyVUwH85VGK/img.png?width=672&amp;amp;height=519&amp;amp;face=0_0_672_519,https://scrap.kakaocdn.net/dn/08PkM/hyIGm59fCq/ikEz4cEGkA2LqC1WbCIyt1/img.png?width=1336&amp;amp;height=709&amp;amp;face=0_0_1336_709&quot;&gt;&lt;a href=&quot;https://omod.tistory.com/94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://omod.tistory.com/94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bgi7mM/hyIGwOsE4I/4jxdCCgnnPy0o0LXPKpek1/img.png?width=672&amp;amp;height=519&amp;amp;face=0_0_672_519,https://scrap.kakaocdn.net/dn/pfIxe/hyIGjuQAlW/E6Ie6Vnxbn5qyVUwH85VGK/img.png?width=672&amp;amp;height=519&amp;amp;face=0_0_672_519,https://scrap.kakaocdn.net/dn/08PkM/hyIGm59fCq/ikEz4cEGkA2LqC1WbCIyt1/img.png?width=1336&amp;amp;height=709&amp;amp;face=0_0_1336_709');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;[Android] 카카오 로그인 v2 API with kotlin - 1&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;카카오 로그인을 공부하면서 사용하는 방법을 정리 해볼까 합니다. 일단 카카오 내어플리케이션에 들어가서 앱을 등록 해줘야합니다. 앱 아이콘에는 앱에 사용할 아이콘 이미지를 넣어 주시면&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;omod.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;로그인 구현 부분 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1608793455865&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LoginActivity : BaseActivity&amp;lt;ActivityLoginBinding&amp;gt;(R.layout.activity_login) {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val callback: (OAuthToken?, Throwable?) -&amp;gt; Unit = { token, error -&amp;gt;
            if (error != null) {
                loginErrorCode(error)
            } else if (token != null) {
                UserApiClient.instance.me { user, error -&amp;gt;
                    if (error != null) {
                        Log.e(error.toString(), &quot;사용자 정보 요청 실패&quot;)
                    } else {
                    	/* 로그인 성공시 작업 부분 */
                        
                        // 카카오 토큰
                    	val userToken = token.accessToken 
                        // 카카오 id
                        val userId = user?.id.toString() 
                        // 카카오 이메일
                        val userEmail = user?.kakaoAccount?.email.toString() 
                         // 카카오 닉네임
                        val userNickname = user?.kakaoAccount?.profile?.nickname.toString()
                        )

                    }

                }
            }
        }

		// 카카오 로그인 버튼
        binding.btLoginKakao.setOnClickListener {
        	// 카카오 앱이 있는지 판별
            if (LoginClient.instance.isKakaoTalkLoginAvailable(this)) {
            	// 있는 경우 앱을 사용하여 로그인
                LoginClient.instance.loginWithKakaoTalk(this, callback = callback)
            } else {
            	// 없을 경우 웹 페이지를 띄움
                LoginClient.instance.loginWithKakaoAccount(this, callback = callback)
            }
        }
    }

	// 에러 코드별 toast
    private fun loginErrorCode(error: Throwable) {
        when {
            error.toString() == AuthErrorCause.AccessDenied.toString() -&amp;gt; {
                Toast.makeText(this, &quot;접근이 거부 됨(동의 취소)&quot;, Toast.LENGTH_SHORT).show()
            }
            error.toString() == AuthErrorCause.InvalidClient.toString() -&amp;gt; {
                Toast.makeText(this, &quot;유효하지 않은 앱&quot;, Toast.LENGTH_SHORT).show()
            }
            error.toString() == AuthErrorCause.InvalidGrant.toString() -&amp;gt; {
                Toast.makeText(this, &quot;인증 수단이 유효하지 않아 인증할 수 없는 상태&quot;, Toast.LENGTH_SHORT).show()
            }
            error.toString() == AuthErrorCause.InvalidRequest.toString() -&amp;gt; {
                Toast.makeText(this, &quot;요청 파라미터 오류&quot;, Toast.LENGTH_SHORT).show()
            }
            error.toString() == AuthErrorCause.InvalidScope.toString() -&amp;gt; {
                Toast.makeText(this, &quot;유효하지 않은 scope ID&quot;, Toast.LENGTH_SHORT).show()
            }
            error.toString() == AuthErrorCause.Misconfigured.toString() -&amp;gt; {
                Toast.makeText(this, &quot;설정이 올바르지 않음(android key hash)&quot;, Toast.LENGTH_SHORT).show()
            }
            error.toString() == AuthErrorCause.ServerError.toString() -&amp;gt; {
                Toast.makeText(this, &quot;서버 내부 에러&quot;, Toast.LENGTH_SHORT).show()
            }
            error.toString() == AuthErrorCause.Unauthorized.toString() -&amp;gt; {
                Toast.makeText(this, &quot;앱이 요청 권한이 없음&quot;, Toast.LENGTH_SHORT).show()
            }
            else -&amp;gt; { // Unknown
                Toast.makeText(this, &quot;$error&quot;, Toast.LENGTH_SHORT).show()
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;카카오 연결을 끊는 방법입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1608793544763&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UserApiClient.instance.unlink { error -&amp;gt;
                    if (error != null) {
                        Log.e(&quot;Logout&quot;, &quot;연결 끊기 실패&quot;, error)
                    } else {
                        // 연결 끊기 성공 한뒤 작업 부분
                    }
                }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Android</category>
      <author>개발자_미노</author>
      <guid isPermaLink="true">https://omod.tistory.com/100</guid>
      <comments>https://omod.tistory.com/100#entry100comment</comments>
      <pubDate>Thu, 24 Dec 2020 16:05:49 +0900</pubDate>
    </item>
    <item>
      <title>[Android] TextView 부분 글자 바꾸기 (Span)</title>
      <link>https://omod.tistory.com/99</link>
      <description>&lt;p&gt;* 부분 글자 색상 바꾸기&lt;/p&gt;
&lt;pre id=&quot;code_1607932711269&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun TextView.textColorSomeChange(changeText: String, @ColorRes color: Int) {
    val index = text.toString().indexOf(changeText)
    if (index != -1) {
        val ssb = SpannableString(text)
        ssb.setSpan(
            ForegroundColorSpan(
                ContextCompat.getColor(
                    App.instance.context(),
                    color
                )
            ),
            index,
            index + changeText.length,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
        )
        text = ssb
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;* 부분 글자 사이즈 바꾸기&lt;/p&gt;
&lt;pre id=&quot;code_1607932729070&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun TextView.textSizeSomeChange(changeText: String,size : Int) {
    val index = text.toString().indexOf(changeText)
    if (index != -1) {
        val ssb = SpannableString(text)
        ssb.setSpan(
            AbsoluteSizeSpan(size,true),
            index,
            index+changeText.length,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
        )
        text = ssb
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;* 부분 글자 스타일 바꾸기&lt;/p&gt;
&lt;pre id=&quot;code_1607932740286&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun TextView.textPartTypeface(changeText: String, @StyleRes style: Int, isLast: Boolean = false) {
    val index = if (isLast) {
        text.toString().lastIndexOf(changeText)
    } else {
        text.toString().indexOf(changeText)
    }
    if (index != -1) {
        val ssb = SpannableString(text)

        ssb.setSpan(
            StyleSpan(style),
            index,
            index + changeText.length,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
        )
        text = ssb
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Android</category>
      <author>개발자_미노</author>
      <guid isPermaLink="true">https://omod.tistory.com/99</guid>
      <comments>https://omod.tistory.com/99#entry99comment</comments>
      <pubDate>Mon, 14 Dec 2020 16:59:04 +0900</pubDate>
    </item>
    <item>
      <title>Retrofit2 + RxJava 에서 Header 가져오기</title>
      <link>https://omod.tistory.com/98</link>
      <description>&lt;p&gt;서버와 통신을 할때 header에 있는 토큰을 가져와야 할때가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1607242524785&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST(&quot;/register&quot;) 
fun registerUser(@Body registerPostData: RegistrationForm): Single&amp;lt;RegistrationResponse&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;보통 통신을 할때 반환을 보통 위에 처럼 하는데&lt;/p&gt;
&lt;pre id=&quot;code_1607242555581&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST(&quot;/register&quot;) 
fun registerUser(@Body registerPostData: RegistrationForm): Single&amp;lt;Response&amp;lt;RegistrationResponse&amp;gt;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;반환&amp;nbsp; 타입에 Response로 감싸주면 header를 가져 올 수 있게 된다.&lt;/p&gt;</description>
      <category>Android</category>
      <author>개발자_미노</author>
      <guid isPermaLink="true">https://omod.tistory.com/98</guid>
      <comments>https://omod.tistory.com/98#entry98comment</comments>
      <pubDate>Sun, 6 Dec 2020 17:16:40 +0900</pubDate>
    </item>
  </channel>
</rss>