<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>day_0893_.log</title>
        <link>https://velog.io/</link>
        <description>??</description>
        <lastBuildDate>Sun, 12 Nov 2023 11:06:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. day_0893_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/day_0893_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Java to Kotlin Migration]]></title>
            <link>https://velog.io/@day_0893_/Java-to-Kotlin-Migration</link>
            <guid>https://velog.io/@day_0893_/Java-to-Kotlin-Migration</guid>
            <pubDate>Sun, 12 Nov 2023 11:06:44 GMT</pubDate>
            <description><![CDATA[<p>사내 프로젝트를 Java 에서 Kotlin으로 Migration 작업을 해보려고한다.</p>
<ul>
<li>목표<ul>
<li>Java -&gt; Kotlin 언어로 변경</li>
<li>Jetpack Compose 적용 시키기 </li>
<li>MVVM 패턴 적용 시키기</li>
</ul>
</li>
</ul>
<h4 id="코드-변경-위험성-예방-전략">코드 변경 위험성 예방 전략</h4>
<p>코드가 상당히 방대하기 때문에 한번에 모든 코드를 변화시키기엔 위험성이 크다.</p>
<ul>
<li>서로의 코드에 영향이 가지 않도록 Module을 새로 만들어 작업한다.</li>
<li>Git Branch를 만들어 작업 한다.</li>
<li>기능 별로 구현을 위해 메인화면 부터 코드를 바꿔나간다. 
연결된 코드들이 계속 경고를 띄우기 때문에 todo 주석과 함께 임시로 주석 처리 후 나중에 처리한다. </li>
</ul>
<h3 id="activity-선택">Activity 선택</h3>
<h4 id="componentactivity-vs-appcompactactivity">ComponentActivity vs AppCompactActivity</h4>
<ul>
<li><p>AppCompactActivity</p>
<ul>
<li>FragmentActivity를 상속하고 있는 ComponentActivity 이다. </li>
<li>import를 위해 androidX로 refactoring을 해야한다.
com.android.support:appcompat-v7:28.0.0
기존 사용하던  레이아웃 리소스를 사용하기 위해 사용하던 Activity 이다. </li>
</ul>
</li>
<li><p>ComponentActivity</p>
<ul>
<li><p>Compose만을 사용하도록 제한한다. Fragment를 사용하기 위해서는 AppCompactAcitivity 를 사용해야한다.</p>
</li>
<li><p>ComponentActivity: import가 두개를 사용할 수 있다.
<img src="https://velog.velcdn.com/images/day_0893_/post/fb2afa48-83e7-4bb3-b631-2b47a1891e18/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<h4 id="check-companion-object-is-null">check companion object is null?</h4>
<h4 id=""></h4>
<p>작성중</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android coroutine Test]]></title>
            <link>https://velog.io/@day_0893_/Android-coroutine-Test</link>
            <guid>https://velog.io/@day_0893_/Android-coroutine-Test</guid>
            <pubDate>Sun, 17 Sep 2023 11:58:02 GMT</pubDate>
            <description><![CDATA[<p><a href="https://developer.android.com/kotlin/coroutines/test?hl=ko">안드로이드 디벨로퍼 URL</a></p>
<p>1.dependencies 추가
testImplementation (&quot;org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3&quot;)</p>
<h4 id="테스트에서-정지-함수-호출">테스트에서 정지 함수 호출</h4>
<p>suspend 함수를 호출을 위해 runTest를 사용할 수 있다.</p>
<pre><code>suspend fun fetchData(): String {
    delay(1000L)
    return &quot;Hello world&quot;
}

@Test
fun dataShouldBeHelloWorld() = runTest {
    val data = fetchData()
    assertEquals(&quot;Hello world&quot;, data)
}</code></pre><ul>
<li><p>최상위 테스트 코루틴 외 새로운 코루틴을 만들 때 적절한 TestDispatcher을 선택하여 새 코루틴이 예약되는 방식을 제어해야 합니다.</p>
</li>
<li><p>코루틴 실행을 다른 디스패처리 이동하면 runTest는 일반적으로 작동되지만 여러 스레드 실행의 예측가능성이 떨어집니다. -&gt; TestDispatcher을 사용해야합니다.</p>
</li>
</ul>
<h4 id="testdispatcher">TestDispatcher</h4>
<p>TestDispatcher는 테스트 목적으로 CorutoinDisaptcher 구현입니다.
코루틴의 실행을 예측할 수 있게 도와줍니다. 새로 코루틴을 만들경우 TestDispatchers 를 사용해야 합니다.</p>
<p>핵심 사항: runTest는 TestScope에서 테스트 코루틴을 실행합니다. TestDispatchers는 TestCoroutineScheduler를 사용하여 가상 시간을 제어하고 테스트에서 새 코루틴을 예약합니다. 테스트의 모든 TestDispatchers는 동일한 스케줄러 인스턴스를 사용해야 합니다.</p>
<h4 id="standardtestdispatcher">StandardTestDispatcher</h4>
<pre><code>    @Test
    fun standardTest()= runTest{
        val userRepo = UserRepository()
        launch { userRepo.register(&quot;Alice&quot;) }
        launch { userRepo.register(&quot;Bob&quot;) }

         assertEquals(listOf(&quot;Alice&quot;, &quot;Bob&quot;), userRepo.getAllUsers()) //error
</code></pre><p><img src="https://velog.velcdn.com/images/day_0893_/post/21bd9b11-4842-4bcd-bd4e-bbca31a0d1ad/image.png" alt=""></p>
<p>launch{}는 비동기 실행기기 때문에
advanceUntilIdle()를 사용하여 비동기 상태를 기다린 후 실행한다.</p>
<pre><code>  @Test
    fun standardTest()= runTest{
        val userRepo = UserRepository()
        launch { userRepo.register(&quot;Alice&quot;) }
        launch { userRepo.register(&quot;Bob&quot;) }

//         assertEquals(listOf(&quot;Alice&quot;, &quot;Bob&quot;), userRepo.getAllUsers()) //error

        advanceUntilIdle() // Yields to perform the registrations
        assertEquals(listOf(&quot;Alice&quot;, &quot;Bob&quot;), userRepo.getAllUsers())
    }</code></pre><p><img src="https://velog.velcdn.com/images/day_0893_/post/d402b230-5261-443e-ae73-9028afca1131/image.png" alt=""></p>
<ul>
<li>참고: launch 호출에서 반환된 Job 인스턴스를 join하여 어설션 실행 전에 새 코루틴이 완료되도록 할 수도 있습니다.</li>
</ul>
<h4 id="unconfinedtestdispatcher">UnconfinedTestDispatcher</h4>
<p>새 코루틴이 UnconfinedTestDispatcher에서 실행되면서 빠르게 시작됩니다. 코루틴 빌더가 반환될 때 까지 기다리지 않고 즉시 실행됩니다.</p>
<p>핵심 사항: UnconfinedTestDispatcher는 새 코루틴을 빠르게 실행하며 코루틴을 사용한 간단한 테스트에 적합합니다.</p>
<pre><code>@Test
fun unconfinedTest() = runTest(UnconfinedTestDispatcher()) {
    val userRepo = UserRepository()

    launch { userRepo.register(&quot;Alice&quot;) }
    launch { userRepo.register(&quot;Bob&quot;) }

    assertEquals(listOf(&quot;Alice&quot;, &quot;Bob&quot;), userRepo.getAllUsers()) // ✅ Passes
}</code></pre><p>UnconfinedTestDispatcher는 새 코루틴을 빠르게 시작하지만 그렇다고 해서 완료될 때까지 빠르게 실행하는 것은 아닙니다. 새 코루틴이 정지되면 다른 코루틴이 실행을 다시 시작합니다.</p>
<p>예를 들어 이 테스트 내에서 실행된 새 코루틴은 Alice를 등록하지만 delay가 호출되면 정지됩니다. 이를 통해 최상위 코루틴이 어설션을 계속 진행할 수 있고 테스트는 Bob이 아직 등록되지 않았으므로 실패합니다.</p>
<pre><code>@Test
fun yieldingTest() = runTest(UnconfinedTestDispatcher()) {
    val userRepo = UserRepository()

    launch {
        userRepo.register(&quot;Alice&quot;)
        delay(10L)
        userRepo.register(&quot;Bob&quot;)
    }

    assertEquals(listOf(&quot;Alice&quot;, &quot;Bob&quot;), userRepo.getAllUsers()) // ❌ Fails
}</code></pre><h4 id="테스트-디스패처-삽입">테스트 디스패처 삽입</h4>
<p>테스트 중인 코드는 디스패처를 사용하여 스레드를 전환하거나 새 코루틴을 시작할 수 있습니다.</p>
<ul>
<li>핵심 사항: 테스트에서 실제 디스패처를 TestDispatchers 인스턴스로 바꿔 모든 코드가 단일 테스트 스레드에서 실행되도록 합니다.</li>
<li>참고: 테스트에서 Main 디스패처를 교체하는 방법에 관한 자세한 내용은 Main 디스패처 설정 섹션을 참고하세요.</li>
<li>주의: 테스트 내에서 TestDispatchers를 얼마든지 만들어 사용할 수 있지만 모두 동일한 스케줄러를 공유해야 합니다. 여러 개의 스케줄러를 만들지 않도록 주의하세요.</li>
</ul>
<p>다음 예시에서는 initialize 메서드에서 IO 디스패처를 사용하여 새 코루틴을 만들고 fetchData 메서드에서 호출자를 IO 디스패처로 전환하는 Repository 클래스를 확인할 수 있습니다.</p>
<pre><code>// Example class demonstrating dispatcher use cases
class Repository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
    private val scope = CoroutineScope(ioDispatcher)
    val initialized = AtomicBoolean(false)

    // A function that starts a new coroutine on the IO dispatcher
    fun initialize() {
        scope.launch {
            initialized.set(true)
        }
    }

    // A suspending function that switches to the IO dispatcher
    suspend fun fetchData(): String = withContext(ioDispatcher) {
        require(initialized.get()) { &quot;Repository should be initialized first&quot; }
        delay(500L)
        &quot;Hello world&quot;
    }
}</code></pre><p>테스트에서 TestDispatcher 구현을 삽입하여 IO 디스패처를 대체할 수 있습니다.</p>
<p>아래 예시에서는 저장소에 StandardTestDispatcher를 삽입하고 advanceUntilIdle을 사용하여 계속 진행하기 전에 initialize에서 시작된 새 코루틴이 완료되도록 합니다.</p>
<ul>
<li>주의: 새 코루틴이 완료될 때까지 진행하는 것이 가능한 유일한 이유는 새 코루틴이 TestDispatcher를 사용하기 때문입니다. 이를 통해 위 예시에서 initialize 메서드가 잘 설계된 API가 아님을 알 수 있습니다. 호출자가 기다려야 하는 비동기 작업을 시작하지만 이 작업이 완료되면 호출자에게 알릴 방법이 없습니다.</li>
</ul>
<pre><code>class RepositoryTest {
    @Test
    fun repoInitWorksAndDataIsHelloWorld() = runTest {
        val dispatcher = StandardTestDispatcher(testScheduler)
        val repository = Repository(dispatcher)

        repository.initialize()
        advanceUntilIdle() // Runs the new coroutine
        assertEquals(true, repository.initialized.get())

        val data = repository.fetchData() // No thread switch, delay is skipped
        assertEquals(&quot;Hello world&quot;, data)
    }
}</code></pre><p>TestDispatcher에서 시작된 코루틴은 initialize를 사용하여 수동으로 진행할 수 있습니다. 그러나 프로덕션 코드에서는 불가능하거나 바람직하지 않습니다.
대신 이 메서드는 정지 되거나 또는 Deffered 값을 반환하도록 다시 설계해야합니다.</p>
<pre><code>class BetterRepository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
    private val scope = CoroutineScope(ioDispatcher)

    fun initialize() = scope.async {
        // ...
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Kotlin Coroutine 추가 리소스 (코루틴 기초) 작성중]]></title>
            <link>https://velog.io/@day_0893_/Kotlin-Coroutine-%EC%B6%94%EA%B0%80-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B8%B0%EC%B4%88-%EC%9E%91%EC%84%B1%EC%A4%91</link>
            <guid>https://velog.io/@day_0893_/Kotlin-Coroutine-%EC%B6%94%EA%B0%80-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B8%B0%EC%B4%88-%EC%9E%91%EC%84%B1%EC%A4%91</guid>
            <pubDate>Sat, 16 Sep 2023 05:38:11 GMT</pubDate>
            <description><![CDATA[<p> <a href="https://developer.android.com/kotlin/coroutines/additional-resources?hl=ko">디벨로퍼 URL</a></p>
<p> <a href="https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb">문서 URL</a>
Android의 코루틴(시리즈 첫 번째 글 링크됨): 이 글은 Kotlin 코루틴에 관해 가르쳐 주는 시리즈 중 첫 번째입니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Coroutine 추가 리소스 (시작)]]></title>
            <link>https://velog.io/@day_0893_/Coroutine-%EC%B6%94%EA%B0%80-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@day_0893_/Coroutine-%EC%B6%94%EA%B0%80-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Sat, 16 Sep 2023 05:33:20 GMT</pubDate>
            <description><![CDATA[<h2 id="추가-리소스">추가 리소스</h2>
<p>시작
<a href="https://medium.com/androiddevelopers/coroutines-first-things-first-e6187bf3bb21">URL</a></p>
<p>CoroutineScope, Job, CoroutineContext 등 기본 코루틴 개념을 알아봅니다.</p>
<h4 id="coroutinescope">CoroutineScope</h4>
<p>CoroutinScope는 launch 나 async를 사용한 coroutine의 동작을 추적한다.
scope.cancel()을 통해 작업은 취소될 수 있다.
Job이나 Dispatcher 는 CoroutinContext와 함께 결합되어있다.</p>
<pre><code>CoroutinScope를 사용할 때 CoroutinContext를 파라미터로 생성합니다.
val scope = CoroutineScope(Job() +Dispatchers.Main)
val job = scope.launch{
    /new coroutine
}</code></pre><h4 id="job">Job</h4>
<p>job은 코루틴을 다루기위한 핸들러이다. lauch나 async를 통해 만든 모든 coroutine은 job을 반환한다 그리고 job은 독립적이며 coroutine의 생명주기를 관리한다.</p>
<h4 id="coroutinecontext">CoroutineContext</h4>
<p>CoroutinContext는 Coroutine의 동작을 정의한 요소들의 집합이다.
CoroutinContext는 아래 내용들로 구성되어있다.</p>
<ul>
<li>Job: 코루틴의 생명주기를 제어한다.</li>
<li>CoroutinDispatcher: dispatcher는 적절한 쓰레드를 작동시킨다.</li>
<li>CoroutinName: 코루틴의 이름으로 디버깅에 유용하게 사용한다.</li>
<li>CoroutinExceptionHandler: 예상치 못한 예외를 다룬다.</li>
</ul>
<p>새로운 Coroutine의 CourutinContext는 무엇인가? 우리는 job 객체가 새로 생성됨을 알고있다, 그리고 lifecycle을 관리할 수 있다.
나머지 요소로 ConroutinContext는 다른 Coroutine 또는 CoroutineScope에 상속될 수 있다. </p>
<p>CoroutinScope 생성 후 CoroutinScope 내부에서 더 많은 Coroutin을 생성할 수 있다.</p>
<pre><code>val scope = CoroutinScope(Job() + Dispatchers.Main)

val job = scope.launch{
    val result = async{
    }.await()
}</code></pre><h4 id="job-lifecycler">Job Lifecycler</h4>
<p>Job은 New, Activte, Completing, Completed, Cancelling, Cancelled 상태를 거친다.
우리는 Job의 isActive, isCanclled, isCompleted 상태만 접근할 수있다.
<img src="https://velog.velcdn.com/images/day_0893_/post/47b806bb-dc12-4503-b68c-97a6886b8a7e/image.png" alt=""></p>
<p>만약 Coroutine이 Active상태이다, 그리고 coroutine의 실패, 또는 job.cancel() ghcnftl job은 Cancelling 상태가된다. (isActive = false, isCancelled = true)
모든 자식 동작이 완료되면 Cancelled 상태가되고 isCompleted =true 가된다.</p>
<h4 id="parent-coroutinecontext-explained">Parent CoroutineContext explained</h4>
<p>task 계층에서 Coroutine은 하나의 CoroutinScope를 가지거 또는 다른 Coroutine을 부모로 가지고 있다. 그러나 CourotineContext를 부모로가진 CoroutineContext는 다를 수 있다.</p>
<p>Parent context = Defaults + inherited CoroutineContext + arguments</p>
<p>참고 :CoroutineContexts는 연산자를 사용하여 결합할 수 있습니다+. 은CoroutineContext요소 집합CoroutineContext왼쪽에 있는 요소를 재정의하고 오른쪽에 있는 요소로 새 요소가 생성됩니다. 예:(Dispatchers.Main, “name”) + (Dispatchers.IO) = (Dispatchers.IO, “name”)
<img src="https://velog.velcdn.com/images/day_0893_/post/6dce81c6-22e7-461b-954c-21b7a652cd86/image.png" alt="">이 CoroutineScope에 의해 시작되는 모든 코루틴은 최소한 CoroutineContext에 해당 요소를 갖습니다. CoroutineName은 기본값에서 왔기 때문에 회색입니다.</p>
<p>CoroutineContext이제 우리는 새 코루틴의 상위가 무엇인지 알았으므로 실제는 CoroutineContext다음과 같습니다.</p>
<pre><code>New coroutine context = parent CoroutineContext + Job()</code></pre><p><img src="https://velog.velcdn.com/images/day_0893_/post/9a1eb983-a4bb-4ff0-8214-87a405994809/image.png" alt=""></p>
<h2 id="cancellation-and-exceptions-in-coroutines-part-2">Cancellation and Exceptions in Coroutines (Part 2)</h2>
<p><a href="https://medium.com/androiddevelopers/cancellation-in-coroutines-aa6b90163629">URL</a>
메모리 관리를 위해 Coroutine은 취소될 수 있다.</p>
<p>Calling cancel
여러개의 Coroutine 을 실행할 때 그것은 개별적으로 취소 될 수 있도록 계획되어있다. 반면에 Scope를 취소할 경우 자식 코루틴들은 모두 취소된다.</p>
<pre><code>// assume we have a scope defined for this layer of the app
val job1 = scope.launch { … }
val job2 = scope.launch { … }
scope.cancel()</code></pre><p>하나의 코루틴만 취소하고싶은 경우 아래와 같이 job.cancel()을 사용할 수 있다.</p>
<pre><code>// assume we have a scope defined for this layer of the app
val job1 = scope.launch { … }
val job2 = scope.launch { … }
// First coroutine will be cancelled and the other one won’t be affected
job1.cancel()</code></pre><p>코루틴을 취소할경우 CancellationException이 발생하며 형제 코루틴에 영향을 미치지 않는다.</p>
<p>ViewModel의 lifecyclerScope에 Coroutine을 연결시키면 ViewModel의 생명 주기에 따라 Coroutine이 동작 됩니다.</p>
<p>cancel()을 호출한다고 즉시 중지 되지 않는다.
아래 코드를 보면 cancel()을 호출해도 hello가 2번만 호출되지 않고 3번 또는 4번 호출 된다.</p>
<pre><code>import kotlinx.coroutines.*

fun main(args: Array&lt;String&gt;) = runBlocking&lt;Unit&gt; {
   val startTime = System.currentTimeMillis()
    val job = launch (Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i &lt; 5) {
            // print a message twice a second
            if (System.currentTimeMillis() &gt;= nextPrintTime) {
                println(&quot;Hello ${i++}&quot;)
                nextPrintTime += 500L
            }
        }
    }
    delay(1000L)
    println(&quot;Cancel!&quot;)
    job.cancel()
    println(&quot;Done!&quot;)
}

//=&gt;Hello 0
//=&gt;Hello 1
//=&gt;Hello 2</code></pre><p>코루틴 취소는 협조가 필요합니다.
-&gt; 코루틴 취소 상태를 체크하여 멈추고 싶은 시점을 명확히 할 수 있다.</p>
<pre><code>while (i &lt; 5 &amp;&amp; isActive)
// =&gt;
fun Job.ensureActive(): Unit {
    if (!isActive) {
         throw getCancellationException()
    }
}
// =&gt;
while (i &lt; 5) {
    ensureActive()
    …
}</code></pre><p>yeild()를 사용해서 coroutine을  취소하는 경우</p>
<ul>
<li>cpu heavy</li>
<li>쓰레드풀 고갈</li>
<li>풀에 스레드를 추가하지 않고 다른 작업을 하고싶은 경우</li>
</ul>
<p>Job.join vs Deferred.await 취소
Job: .launch =&gt; join </p>
<ul>
<li>join 후 cancel 호출시 job이 completed 가 될 떄 까지 기다린다.</li>
<li>join 후 cancle은 영향을 미치지 않는다. 이미 completed 상태가 됐기 때문이다.</li>
</ul>
<p>Deffered: .async =&gt; await</p>
<ul>
<li>await 는 completed 상태일 때 값을 반환한다. await 상태일 때 cancel을 호출할 경우 JobCancellationException을 발생시킨다.</li>
</ul>
<p>Handling cancellation side effects</p>
<p>isActive를 뺴거나 추가해서 실행시켜보자 </p>
<pre><code>fun main(args: Array&lt;String&gt;) = runBlocking&lt;Unit&gt; {
   val startTime = System.currentTimeMillis()
    val job = launch (Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i &lt; 5 &amp;&amp; isActive) {
            // print a message twice a second
            if (System.currentTimeMillis() &gt;= nextPrintTime) {
                println(&quot;Hello ${i++}&quot;)
                nextPrintTime += 500L
            }
        }
        // the coroutine work is completed so we can cleanup
        println(&quot;Clean up!&quot;)

    }
    delay(1000L)
    println(&quot;Cancel!&quot;)
    job.cancel()
    println(&quot;Done!&quot;)
}</code></pre><p>try catch로 코드를 다룰 수 있다</p>
<pre><code>import kotlinx.coroutines.*

suspend fun work(){
    val startTime = System.currentTimeMillis()
    var nextPrintTime = startTime
    var i = 0
    while (i &lt; 5) {
        yield()
        // print a message twice a second
        if (System.currentTimeMillis() &gt;= nextPrintTime) {
            println(&quot;Hello ${i++}&quot;)
            nextPrintTime += 500L
        }
    }
}
fun main(args: Array&lt;String&gt;) = runBlocking&lt;Unit&gt; {
   val job = launch (Dispatchers.Default) {
        try {
            work()
        } catch (e: CancellationException){
            println(&quot;Work cancelled!&quot;)
        } finally {
            println(&quot;Clean up!&quot;)
        }
    }
    delay(1000L)
    println(&quot;Cancel!&quot;)
    job.cancel()
    println(&quot;Done!&quot;)
}</code></pre><p>Coroutin cancel 상태는 suspend 상태에서 사용할 수 없다.</p>
<pre><code>val job = launch {
   try {
      work()
   } catch (e: CancellationException){
      println(“Work cancelled!”)
    } finally {
      withContext(NonCancellable){
         delay(1000L) // or some other suspend fun 
         println(“Cleanup done!”)
      }
    }
}
delay(1000L)
println(“Cancel!”)
job.cancel()
println(“Done!”)</code></pre><h3 id="exceptions-in-coroutines-cancellation-and-exceptions-in-coroutines-part-3--gotta-catch-em-all">Exceptions in coroutines Cancellation and Exceptions in coroutines (Part 3) — Gotta catch ’em all!</h3>
<p><a href="https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c">url</a></p>
<p>A Coroutine suddenly failed! what now?
자식 코루틴이 cancel 되면 자식 코루틴은 취소하고 예외를 부모에게 전파한다.
<img src="https://velog.velcdn.com/images/day_0893_/post/2ede5fc3-7e7d-4cda-83d2-bc515d65e466/image.png" alt=""></p>
<p>이렇게 동작되는것을 원치 않을경우
CoroutineScope 의 CoroutinContext 안의 SupervisorJpb을 사용하여 처리할 수 있다.
<img src="https://velog.velcdn.com/images/day_0893_/post/69676b6c-74b9-4514-aa01-f1e4b58a08ea/image.png" alt=""></p>
<p>child1이 실패해도 child2는 실패하지 않습니다.</p>
<pre><code>// Scope handling coroutines for a particular layer of my app
val scope = CoroutineScope(SupervisorJob())
scope.launch {
    // Child 1
}
scope.launch {
    // Child 2
}</code></pre><p>아래의 경우도 동일하다.</p>
<pre><code>// Scope handling coroutines for a particular layer of my app
val scope = CoroutineScope(Job())
scope.launch {
    supervisorScope {
        launch {
            // Child 1
        }
        launch {
            // Child 2
        }
    }
}</code></pre><p>아래 코드의 child1 과 child2의 부모는 Job() 이다</p>
<pre><code>val scope = CoroutineScope(Job())
scope.launch(SupervisorJob()) {
    // new coroutine -&gt; can suspend
   launch {
        // Child 1
    }
    launch {
        // Child 2
    }
}</code></pre><p>scope를 재정의한다고해서 scope가 바뀌지 않는다.
<img src="https://velog.velcdn.com/images/day_0893_/post/cacc421b-1ca7-4e7e-86c3-5090a8a50c32/image.png" alt=""></p>
<p>Under the hood (내부적으로 어떻게 동작하는가)
SuperviorJob은 childCanclled method 에서 return false 를 한다. 이는 예외를 처리하지 않는다는 뜻이다.</p>
<p>Dealing With Exceptions(예외다루기)</p>
<p>Launch
scope.launc 안에서 예외가 발생할 경우 단순 try catch 처리할 수 있다.</p>
<pre><code>scope.launch {
    try {
        codeThatCanThrowExceptions()
    } catch(e: Exception) {
        // Handle exception
    }
}</code></pre><p>Async
supervisorScope 내에서 선언된 async의 예외는 await 함수에 던져진다. </p>
<pre><code>supervisorScope {
    val deferred = async {
        codeThatCanThrowExceptions()
    }
    try {
        deferred.await()
    } catch(e: Exception) {
        // Handle exception thrown in async
    }
}</code></pre><p>Async에 예외처리를해도 부모로 예외를 던지기 때문에 catch 할 수 없다.</p>
<pre><code>coroutineScope {
    try {
        val deferred = async {
            codeThatCanThrowExceptions()
        }
        deferred.await()
    } catch(e: Exception) {
        // Exception thrown in async WILL NOT be caught here 
        // but propagated up to the scope
    }
}</code></pre><p>더군다나 async 예외는 Coroutine builder와 관계없이 항상 전파된다.</p>
<pre><code>val scope = CoroutineScope(Job())
scope.launch {
    async {
        // If async throws, launch throws without calling .await()
    }
}</code></pre><p>CoroutineExceptionHandler
CoroutineExceptionHandler는 Coroutine 예외를 처리하기 위한 CoroutineContext의 옵션 요소이다.</p>
<pre><code>val handler = CoroutineExceptionHandler {
    context, exception -&gt; println(&quot;Caught $exception&quot;)
}

val scope = CoroutineScope(Job())
scope.launch(handler) {
    launch {
        throw Exception(&quot;Failed coroutine&quot;)
    }
}</code></pre><p>아래 같은 경우에는 예외를 받을 수 없다.
CoroutinContext에 적절히 배치되지 못했기 때문이다.</p>
<pre><code>val scope = CoroutineScope(Job())
scope.launch {
    launch(handler) {
        throw Exception(&quot;Failed coroutine&quot;)
    }
}</code></pre><p><a href="https://medium.com/androiddevelopers/coroutines-patterns-for-work-that-shouldnt-be-cancelled-e26c40f142ad">URL 공부 예정</a></p>
<h2 id="coroutines--patterns-for-work-that-shouldnt-be-cancelled">Coroutines &amp; Patterns for work that shouldn’t be cancelled</h2>
<h3 id="cancellation-and-exceptions-in-coroutines-part-4">Cancellation and Exceptions in Coroutines (Part 4)</h3>
<p>part2에서 코루틴 취소의 중요성에 대해 배웠다.
Android 에서는 viewModelScope 또는 lifecycleScope에서 제공해주는 CoroutineScope를 사용할 수 있다. 이것들은 scope가 완료될 때 (acitivity/ fragment/ lifecycle 의 완료) coroutine이 cancel 된다.</p>
<p>만약 화면이 종료되어도 실행시키고싶다면 어떻게 해야할까?
(예: 데이터베이스 쓰기 서버에 특정 네트워크 요청 만들기)</p>
<h4 id="coroutine-or-workmanager">Coroutine or WorkManager?</h4>
<p>Coroutine은 애플리케이션 프로세스가 살아있는 한 실행됩니다. 프로세스보다 오래 지속되야하는 작업을(예: 원격 서버에 로그전송)을 실행해야하는 경우 Workmanager을 사용하세요.
workmanager는 향후에 실행될 것으로 예상되는 중요한 작업에 사용하는 라이브러리 입니다.</p>
<p>현재 프로세스에서 유효하고 사용자가 앱을 종료하면 취소될 수 있는 작업에 Coroutine을 사용하세요. (예: 캐시하려는 네트워크 요청 생성)</p>
<h4 id="coroutine-모범-사례">Coroutine 모범 사례</h4>
<ol>
<li>Inject Dispatchers into class (Dispatcher을 class에 주입)
Coroutine을 새로 생성하거나 WithContext와 함께 호출할때 하드코딩하지 마시오</li>
</ol>
<p>장점: 테스트에 유용하다 (unit단위 테스트 과 instrumetation test 계측 테스트 둘다 쉽게 교체될 수 있다)</p>
<ol start="2">
<li>ViewModel/Presenter layer should create coroutines
UI 전용 작업인 경우 UI 레이어에서 이를 수행할 수 있습니다. 프로젝트에서 이것이 불가능하다고 생각하면 모범사례1을 따르지 않고있을 가능성이 높습니다.
(즉 Dispatchers를 주입하지 않는 VM 을 테스트하는 것이 더 어렵습니다. 이 경우 suspend functions을 노출하면 가능해집니다.)</li>
</ol>
<p>장점: UI 레이어는 단순해야하며 비지니스 로직을 직접적으로 실행해서는 안됩니다. 대신 ViewModel/Presenter 레이어에 맡기세요. UI 레이어를 테스트하려면 애뮬레이터를 실행해야하는 Android instrumentation test가 필요합니다.</p>
<ol start="3">
<li>ViewModel/Presenter 아래의 레이어는 일시 중지 기능과 흐름을 노출해야합니다.
만약 Coroutine을 생성한다면 coroutineScope 또는 supervisorScope를 사용해야한다. 만약 너가 다른 Scope를 필요한다면 다음 내용을 숙지해라</li>
</ol>
<p>장점: Caller (일반적으로 ViewModle layer)는 실행을 제어할 수 있다. 그리고 라이프 사이클에서 일어나는 동작도 제어할 수 있다.</p>
<h4 id="operations는-coroutine을-취소하면-안된다">Operations는 Coroutine을 취소하면 안된다.</h4>
<p>아래 ViewModel 그리고 Repositiory를 가진 로직이 있다 생각해보자
아래 veryImportantOperation는 viewModelScope에서 동작되고 있다.
ViewModelScope는 언제든 취소될수 있기 때문에 ViewModel Scope의 바깥에서 동작시키고싶다.</p>
<pre><code>class MyViewModel(private val repo: Repository) : ViewModel() {
  fun callRepo() {
    viewModelScope.launch {
      repo.doWork()
    }
  }
}
class Repository(private val ioDispatcher: CoroutineDispatcher) {
  suspend fun doWork() {
    withContext(ioDispatcher) {
      doSomeOtherWork()
      veryImportantOperation() // This shouldn’t be cancelled
    }
  }
}</code></pre><p>어떻게 해야할까?
Application class에 operatione을 coroutine안에서 실행시킨다. 이 scope는 필요한 클래스에 injected 되어야한다.</p>
<p>CoroutineExceptionHandler가 필요한가? 너는 자신의 ? 너가 사용할 Dispatcher 가 필요한 Thread Pool을 가지고 있나?
이러한 환경들은 CoroutineContext에 넣어라</p>
<p>너는 applicationScope를 부를 수 있다 그리고 SupervisorJob()을 호출해야한다. 이것은 계층 내에서 Coroutine failure를 전달 시키지 않도록 방지한다.</p>
<pre><code>class MyApplication : Application() {
  // No need to cancel this scope as it&#39;ll be torn down with the process
  val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}</code></pre><p>우리는 이 scope를 취소할 필요가 없다. 우리는 프로세스가 살아 있는동안 coroutineScope를 계속 유지시킬것이다. 그래서 우리는 SupervisorJob을 참조할 필요가 없다. 이 코루틴은 긴 생명주기를 같든다. </p>
<p>Operation이 취소되지 않기 위해서 application CoroutinScope에서 생성된 coroutine을 호출해라</p>
<p>언제든 지 Repository 객체를 applicationScope에 전달하여 생성할 수 있다.
테스트를 위한 아래 테스트 색션을 확인해라 </p>
<h4 id="어떤-코루틴-빌더를-사용하는가">어떤 코루틴 빌더를 사용하는가?</h4>
<p>매우 중요한 기능을 실행하기 위해서 너는 launch 또는 async를 사용한 새로운 coroutins을 시작할 필요가 있다.</p>
<ul>
<li>만약 리턴값이 필요하면 async나 await을 호출하여 끝나길 기다린다.</li>
<li>만약 필요 없다면, launch를 실행한다 그리고 join을 사용하여 끝나길 기다린다.
part3에서 exception 예외처리를 해야한다.</li>
</ul>
<p>아래 코드는 launch 사용 방법이다.</p>
<pre><code>class Repository(
  private val externalScope: CoroutineScope,
  private val ioDispatcher: CoroutineDispatcher
) {
  suspend fun doWork() {
    withContext(ioDispatcher) {
      doSomeOtherWork()
      externalScope.launch {
        // if this can throw an exception, wrap inside try/catch
        // or rely on a CoroutineExceptionHandler installed
        // in the externalScope&#39;s CoroutineScope
        veryImportantOperation()
      }.join()
    }
  }
}</code></pre><p>아래는 async 사용방법이다.</p>
<pre><code>class Repository(
  private val externalScope: CoroutineScope,
  private val ioDispatcher: CoroutineDispatcher
) {
  suspend fun doWork(): Any { // Use a specific type in Result
    withContext(ioDispatcher) {
      doSomeOtherWork()
      return externalScope.async {
        // Exceptions are exposed when calling await, they will be
        // propagated in the coroutine that called doWork. Watch
        // out! They will be ignored if the calling context cancels.
        veryImportantOperation()
      }.await()
    }
  }
}</code></pre><p>어떤 경우에도 ViewModel 코드는 변경되지 않으며 위의 경우 ViewModelScope가 파괴되더라도 externalScope는 계속 실행됩니다.
또한 어떤 suspend가 불려진다해도 veryImportantOperation()이 완료된 후 값이 반환됩니다.</p>
<h4 id="또다른-패턴">또다른 패턴</h4>
<pre><code>class Repository(
  private val externalScope: CoroutineScope,
  private val ioDispatcher: CoroutineDispatcher
) {
  suspend fun doWork() {
    withContext(ioDispatcher) {
      doSomeOtherWork()
      withContext(externalScope.coroutineContext) {
        veryImportantOperation()
      }
    }
  }
}</code></pre><p>위 패턴에는 주의점이 있다.</p>
<ul>
<li>doWork는 취소될 수 있다.(veryImportantOperation이 실행될 때, 이것은 cancellation시점까지 유지된다. veryImportantOperation이 이 끝날때까지 실행되지 않는다.)</li>
<li>CoroutineExceoptionHandler는 WithContext에서 사용되는 context가 사용될 때 이 Exception는 다시 던져진다.</li>
</ul>
<h4 id="testing">Testing</h4>
<p>Dispatchers 그리고 CoroutinScopes는 주입될 필요가 있다. 어떤경우에 어떤걸 주입할 것인가?
<img src="https://velog.velcdn.com/images/day_0893_/post/4e684cd5-0a1b-4245-b6a6-212cbdd323f6/image.png" alt=""></p>
<h4 id="대안">대안</h4>
<ul>
<li><p>글로벌 스코프 사용하면 안되는 이유</p>
<ul>
<li>하드코딩값을 장려하게된다.</li>
<li>테스트를 어렵게 만든다.</li>
<li>모든 코루틴에 대한 공통 CoroutineContext를 가질 수 없다.</li>
</ul>
</li>
<li><p>Android 의 ProcessLIfecyclerOwner 범위</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android Material Design]]></title>
            <link>https://velog.io/@day_0893_/Android-Material-Design</link>
            <guid>https://velog.io/@day_0893_/Android-Material-Design</guid>
            <pubDate>Fri, 15 Sep 2023 11:43:04 GMT</pubDate>
            <description><![CDATA[<p><a href="https://m3.material.io/components/sliders/guidelines">Sliders</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[kotlin - 범위 지정 함수]]></title>
            <link>https://velog.io/@day_0893_/kotlin</link>
            <guid>https://velog.io/@day_0893_/kotlin</guid>
            <pubDate>Sun, 10 Sep 2023 04:38:42 GMT</pubDate>
            <description><![CDATA[<p>범위 지정함수 : 특정 객체에 대한 작업을 블록 안에 넣어 실행할 수 있도록 하는 함수 </p>
<ul>
<li>apply </li>
<li>run</li>
<li>with</li>
<li>let</li>
<li>also</li>
</ul>
<p>run</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android Kotlin Coroutine]]></title>
            <link>https://velog.io/@day_0893_/Android-Kotlin-Coroutine</link>
            <guid>https://velog.io/@day_0893_/Android-Kotlin-Coroutine</guid>
            <pubDate>Sun, 10 Sep 2023 04:28:31 GMT</pubDate>
            <description><![CDATA[<p>안드로이드 디벨로퍼: <a href="https://developer.android.com/kotlin/coroutines?hl=ko">https://developer.android.com/kotlin/coroutines?hl=ko</a></p>
<p>코루틴 Cancellation <a href="https://medium.com/androiddevelopers/coroutines-first-things-first-e6187bf3bb21">URL</a></p>
<h2 id="--코루틴-소개">- 코루틴 소개</h2>
<h4 id="기능">기능</h4>
<p> 안드로이드 비동기 프로그래밍에 권장되는 솔루션입니다.</p>
<ul>
<li>경량:</li>
<li>메모리 누수 감소</li>
<li>기본으로 제공되는 취소 지원</li>
<li>Jetpack 통합</li>
</ul>
<h4 id="예시-개요">예시 개요</h4>
<p>LoginRepository 에서는 Coroutine I/O 쓰레드를 사용한다.
LoginViewModel 에서는 Coroutine Default 쓰레드를 사용한다.</p>
<pre><code>class LoginRepository(...) {
    ...
    suspend fun makeLoginRequest(
        jsonBody: String
    ): Result&lt;LoginResponse&gt; {

        // Move the execution of the coroutine to the I/O dispatcher
        return withContext(Dispatchers.IO) {
            // Blocking network request code
        }
    }
}</code></pre><pre><code>class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun makeLoginRequest(username: String, token: String) {
        viewModelScope.launch {
            val jsonBody = &quot;{ username: \&quot;$username\&quot;, token: \&quot;$token\&quot;}&quot;
            val result = try {
                loginRepository.makeLoginRequest(jsonBody)
            } catch(e: Exception) {
                Result.Error(Exception(&quot;Network request failed&quot;))
            }
            when (result) {
                is Result.Success&lt;LoginResponse&gt; -&gt; // Happy path
                else -&gt; // Show error in UI
            }
        }
    }
}</code></pre><h2 id="고급-코루틴-개념">고급 코루틴 개념</h2>
<h3 id="장기-실행작업-관리">장기 실행작업 관리</h3>
<p>invoke call suspend resume</p>
<ul>
<li>suspend 는 현재 코루틴 실행을 일시중지하고 모든 로컬 변수를 저장합니다.</li>
<li>resume은 저장된 위치로부터 정지된 코루틴을 계속 실행합니다.</li>
</ul>
<pre><code>suspend fun fetchDocs() {                             // Dispatchers.Main
    val result = get(&quot;https://developer.android.com&quot;) // Dispatchers.IO for `get`
    show(result)                                      // Dispatchers.Main
}

suspend fun get(url: String) = withContext(Dispatchers.IO) { /* ... */ }</code></pre><h3 id="기본-안전을-위해-코루틴-사용">기본 안전을 위해 코루틴 사용</h3>
<ul>
<li>Dispatchers.Main</li>
<li>Dispatchers.IO</li>
<li>Dispatchers.Default</li>
</ul>
<p>withContext 는 정지함수이므로 위의 get함수도 정지함수가 된다.</p>
<h3 id="withcontext의-성능">withContext()의 성능</h3>
<p>네트워크 호출을 10번 호출하는 경우 withContext를 한번만 사용하면 콜백 구현 필요없이 스레드를 한번만 전환 하도록 Kotlin 에 지시할 수있습니다.
withContext를 여러번 호출해도 동일한 디스페처에 유지되고 스레드가 전환되지 않습니다. Default, IO 간의 전환을 최적화 합니다.</p>
<h2 id="코루틴-시작">코루틴 시작</h2>
<ul>
<li>launch: 결과를 반환하지 않습니다. </li>
<li>async: 새 코루틴을 시작하고 await라는 정지 함수로 결과를 반환합니다.
async 정지 함수 이므로 정지 함수 또는 코루틴 내부에서 사용할 수 있습니다.</li>
</ul>
<h3 id="병렬분해">병렬분해</h3>
<p>다른 코루틴 스코프를 정의했을 때 awiat()를 호출하여 두 async 작업이 모두 완료되도록 보장 합니다.</p>
<pre><code>suspend fun fetchTwoDocs() =
    coroutineScope {
        val deferredOne = async { fetchDoc(1) }
        val deferredTwo = async { fetchDoc(2) }
        deferredOne.await()
        deferredTwo.await()

        val deferreds = listOf(     // fetch two docs at the same time
            async { fetchDoc(1) },  // async returns a result for the first doc
            async { fetchDoc(2) }   // async returns a result for the second doc
        )
         deferreds.awaitAll()        // use awaitAll to wait for both network requests
    }</code></pre><h2 id="코루틴-개념">코루틴 개념</h2>
<h3 id="coroutinescope">CoroutineScope</h3>
<p>CoroutineScope 는 launch 또는 async를 사용하여 만든 코루틴을 추적합니다.</p>
<p>scope.cancel()을 호출하여 취소할 수 있습니다.
ViewModel 에는 ViewModelScope가 있고 Lifecycle에는 LifecycleScope가 있습니다.
그러나 CoroutineScope를 만들어  앱의 특정 레이어에서 코루틴 수명 주기를 제어해야하면 아래와같이 만들면 됩니다.</p>
<pre><code>class ExampleClass {

    // Job and Dispatcher are combined into a CoroutineContext which
    // will be discussed shortly
    val scope = CoroutineScope(Job() + Dispatchers.Main)

    fun exampleMethod() {
        // Starts a new coroutine within the scope
        scope.launch {
            // New coroutine that can call suspend functions
            fetchDocs()
        }
    }

    fun cleanUp() {
        // Cancel the scope to cancel ongoing coroutines work
        scope.cancel()
    }
}</code></pre><h3 id="작업">작업</h3>
<p>Job은 코루틴의 핸들입니다.
 launch 또는 async로 만드는 각 코루틴은 코루틴을 고유하게 식별하고 수명 주기를 관리하는 Job 인스턴스를 반환합니다. Job을 CoroutineScope에 전달하여 코루틴의 수명주기를 추가로 관리할 수 있습니다.</p>
<pre><code>class ExampleClass {
    ...
    fun exampleMethod() {
        // Handle to the coroutine, you can control its lifecycle
        val job = scope.launch {
            // New coroutine
        }

        if (...) {
            // Cancel the coroutine started above, this doesn&#39;t affect the scope
            // this coroutine was launched in
            job.cancel()
        }
    }
}</code></pre><h3 id="coroutinecontext">CoroutineContext</h3>
<ul>
<li>Job: 코루틴의 수명 주기를 제어합니다.</li>
<li>CoroutineDispatcher: 적절한 스레드에 작업을 전달합니다.</li>
<li>CoroutineName: 디버깅에 유용한 코루틴 이름입니다.</li>
<li>CoroutineExceptionHandler: 포착되지 않은 예외를 처리합니다.</li>
</ul>
<pre><code>class ExampleClass {
    val scope = CoroutineScope(Job() + Dispatchers.Main)

    fun exampleMethod() {
        // Starts a new coroutine on Dispatchers.Main as it&#39;s the scope&#39;s default
        val job1 = scope.launch {
            // New coroutine with CoroutineName = &quot;coroutine&quot; (default)
        }

        // Starts a new coroutine on Dispatchers.Default
        val job2 = scope.launch(Dispatchers.Default + CoroutineName(&quot;BackgroundCoroutine&quot;)) {
            // New coroutine with CoroutineName = &quot;BackgroundCoroutine&quot; (overridden)
        }
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JetPack - UI 이벤트 (작성중)]]></title>
            <link>https://velog.io/@day_0893_/JetPack-UI-%EC%9D%B4%EB%B2%A4%ED%8A%B8</link>
            <guid>https://velog.io/@day_0893_/JetPack-UI-%EC%9D%B4%EB%B2%A4%ED%8A%B8</guid>
            <pubDate>Tue, 05 Sep 2023 16:20:39 GMT</pubDate>
            <description><![CDATA[<p><a href="https://developer.android.com/topic/architecture/ui-layer/events?hl=ko">Devoloper URL</a></p>
<h4 id="ui-이벤트">UI 이벤트</h4>
<p>UI 이벤트는 UI 레이어에서 UI 또는 ViewModel로 처리해야 하는 작업입니다. 가장 일반적인 이벤트 유형은 사용자 이벤트입니다. 사용자는 화면 탭하기 또는 동작 생성과 같은 앱과의 상호작용을 통해 사용자 이벤트를 생성합니다. 그러면 UI에서 onClick() 리스너와 같은 콜백을 통해 이러한 이벤트를 사용합니다</p>
<p>핵심 용어:</p>
<ul>
<li>UI: 사용자 인터페이스를 처리하는 뷰 기반 또는 Compose 코드입니다.</li>
<li>UI 이벤트: UI 레이어에서 처리해야 하는 작업입니다.</li>
<li>사용자 이벤트: 사용자가 앱과 상호작용할 때 생성하는 이벤트입니다.</li>
</ul>
<p>ViewModel은 일반적으로 사용자의 비지니스 로직을 처리합니다.(예: 사용자가 데이터를 새로고침하는 경우) 사용자 이벤트에는 UI에서 직접 처리할수있는 UI 동작 로직이 있을 수도 있습니다. (예: 다른화면 이동, snackbar 표시)</p>
<ul>
<li>비즈니스 로직은 결제 또는 사용자 환경설정 저장과 같은 상태 변경과 관련하여 필요한 조치를 말합니다. 도메인과 데이터 레이어는 일반적으로 이 로직을 처리합니다. 이 가이드에서는 아키텍처 구성요소 ViewModel 클래스가 비즈니스 로직을 처리하는 클래스의 추천 솔루션으로 사용됩니다.</li>
<li>UI 동작 로직 또는 UI 로직은 탐색 로직 또는 사용자에게 메시지를 표시하는 방법과 같이 상태 변경사항을 표시하는 방법을 나타냅니다. 이 로직은 UI에서 처리합니다.</li>
</ul>
<h4 id="ui-이벤트-결정-트리">UI 이벤트 결정 트리</h4>
<p><img src="https://velog.velcdn.com/images/day_0893_/post/4c6a542a-1876-409e-be38-401c361cb0a9/image.png" alt=""></p>
<h4 id="사용자-이벤트-처리">사용자 이벤트 처리</h4>
<p>데이터에 영향이 없는 뷰가 보이고 안보이고처리는 UI에서 처리하고
데이터에 영향이 가는 갱신 기능은 ViewModel에서 처리한다.</p>
<pre><code>class LatestNewsActivity : AppCompatActivity() {

    private lateinit var binding: ActivityLatestNewsBinding
    private val viewModel: LatestNewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        /* ... */

        // The expand details event is processed by the UI that
        // modifies a View&#39;s internal state.
        binding.expandButton.setOnClickListener {
            binding.expandedSection.visibility = View.VISIBLE
        }

        // The refresh event is processed by the ViewModel that is in charge
        // of the business logic.
        binding.refreshButton.setOnClickListener {
            viewModel.refreshNews()
        }
    }
}</code></pre><h4 id="recyclerview의-사용자-이벤트">RecyclerView의 사용자 이벤트</h4>
<p>RecyclerView 항목 또는 맞춤 VIew와 같이 UI 트리 아래쪽에서 작업되는 경우도 ViewModel을 통해 사용자 이벤트를 처리한다.</p>
<p>예를 들어 NewsActivity 의 모든 뉴스 항목에 북마크가 있다고 가정한다. RecyclerView Adapter의 북마크 버튼을 누를때 ViewModel의 addBookmark함수를 호출하지 않으며, ViewModel의 종속 항목이 필요한다. 대신 ViewModel은 이벤트 처리를 위한 구현이 포함된 NewsItemUiState라는 상태 객체를 노출한다.</p>
<pre><code>data class NewsItemUiState(
    val title: String,
    val body: String,
    val bookmarked: Boolean = false,
    val publicationDate: String,
    val onBookmark: () -&gt; Unit
)

class LatestNewsViewModel(
    private val formatDateUseCase: FormatDateUseCase,
    private val repository: NewsRepository
)
    val newsListUiItems = repository.latestNews.map { news -&gt;
        NewsItemUiState(
            title = news.title,
            body = news.body,
            bookmarked = news.bookmarked,
            publicationDate = formatDateUseCase(news.publicationDate),
            // Business logic is passed as a lambda function that the
            // UI calls on click events.
            onBookmark = {
                repository.addBookmark(news.id)
            }
        )
    }
}</code></pre><p>이렇게하면 RecyclerView 어댑터가 NewsItemUiState 객체 목록과 같이 필요한 데이터만 사용할 수 있다. 어댑터가 전체ViewModel에 억세스 할 수 없으므로 VIewModel에 의해 노출된 기능을 악용할 가능성이 낮다. Activity클래스에서만 ViewModel을 사용하도록 허용하는 경우 책임이 분리된다.</p>
<ul>
<li>경고: ViewModel을 RecyclerView 어댑터에 전달하면 이를 ViewModel 클래스와 긴밀하게 결합하게 되므로 좋지 않습니다.</li>
<li>참고: 또 다른 일반적인 패턴은 RecyclerView 어댑터가 사용자 작업을 위한 Callback 인터페이스를 갖는 것입니다. 이 경우 활동이나 프래그먼트가 바인딩을 처리하고 콜백 인터페이스에서 직접 ViewModel 함수를 호출할 수 있습니다.</li>
</ul>
<h4 id="사용자-이벤트-함수의-이름-지정-규칙">사용자 이벤트 함수의 이름 지정 규칙</h4>
<p>ViewModel 함수는 처리하는 작업에 따라 동사를 포함해 이름이 지정됩니다(예: addBookmark(id) 또는 logIn(username, password)).</p>
<h3 id="viewmodel-이벤트-처리">ViewModel 이벤트 처리</h3>
<p>ViewModel에서 발생하는 Ui 작업은 항상 UI 상태 업데이트로 이뤄진다. -&gt; 데이터 단방향 흐름 원칙에 의해 구성 변경후 이벤트를 재현할 수 있으며 UI 작업이 손실되지 않는다.</p>
<p>로그인후 홈 화면으로 이동하는 경우 코드
(corutine를 수명주기와 함께 사용하는 방법 추후 공부 예정)</p>
<pre><code>data class LoginUiState(
    val isLoading: Boolean = false,
    val errorMessage: String? = null,
    val isUserLoggedIn: Boolean = false
)

class LoginViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(LoginUiState())
    val uiState: StateFlow&lt;LoginUiState&gt; = _uiState.asStateFlow()
    /* ... */
}

class LoginActivity : AppCompatActivity() {
    private val viewModel: LoginViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        /* ... */

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { uiState -&gt;
                    if (uiState.isUserLoggedIn) {
                        // Navigate to the Home screen.
                    }
                    ...
                }
            }
        }
    }
}</code></pre><h4 id="이벤트를-소비하면-상태-업데이트가-트리거될-수-있음">이벤트를 소비하면 상태 업데이트가 트리거될 수 있음</h4>
<p>UI에서 특정 ViewModel 이벤트를 소비하면 다른 UI 상태가 업데이트될 수 있습니다. 
예) 화면에 임시 메시지를 표시하여 사용자에게 무언가 발생했음을 알리는 경우 (데이터 상태에 따라 메시지가 표시되고 닫히기 때문에 ViewModel을 통해서 모델링한다.</p>
<pre><code>// Models the UI state for the Latest news screen.
data class LatestNewsUiState(
    val news: List&lt;News&gt; = emptyList(),
    val isLoading: Boolean = false,
    val userMessage: String? = null
)</code></pre><p>비즈니스 로직이 사용자에게 임시 메시지를 새로 표시해야 하는 경우 ViewModel은 다음과 같이 UI 상태를 업데이트합니다.</p>
<pre><code>class LatestNewsViewModel(/* ... */) : ViewModel() {

    private val _uiState = MutableStateFlow(LatestNewsUiState(isLoading = true))
    val uiState: StateFlow&lt;LatestNewsUiState&gt; = _uiState

    fun refreshNews() {
        viewModelScope.launch {
            // If there isn&#39;t internet connection, show a new message on the screen.
            if (!internetConnection()) {
                _uiState.update { currentUiState -&gt;
                    currentUiState.copy(userMessage = &quot;No Internet connection&quot;)
                }
                return@launch
            }

            // Do something else.
        }
    }

    fun userMessageShown() {
        _uiState.update { currentUiState -&gt;
            currentUiState.copy(userMessage = null)
        }
    }
}</code></pre><p>tip: viewModelScope: ViewModel이 destroy 될 때 자식 코루틴을 자동으로 취소하는 기능을 제공한다.</p>
<p>ViewModel은 UI가 화면에 메시지를 표시하는 방식을 알 필요가 없습니다. 표시해야 하는 사용자 메시지가 있다는 사실만 알면 됩니다. 임시 메시지가 표시되면 UI가 ViewModel에 이를 알려야 하며 그러면 userMessage 속성을 삭제하기 위해 또 다른 UI 상태 업데이트가 발생합니다.</p>
<pre><code>class LatestNewsActivity : AppCompatActivity() {
    private val viewModel: LatestNewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        /* ... */

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { uiState -&gt;
                    uiState.userMessage?.let {
                        // TODO: Show Snackbar with userMessage.

                        // Once the message is displayed and
                        // dismissed, notify the ViewModel.
                        viewModel.userMessageShown()
                    }
                    ...
                }
            }
        }
    }
}</code></pre><p>메시지가 일시적이더라도 UI 상태는 모든 시점에 화면에 표시되는 내용을 충실하게 표현합니다. 사용자 메시지는 표시되거나 표시되지 않습니다.</p>
<h3 id="탐색-이벤트">탐색 이벤트</h3>
<p>이벤트 소비로 상태 업데이트 트리거 가능 섹션에서는 UI 상태를 사용하여 화면에 사용자 메시지를 표시하는 방법을 자세히 설명합니다. 탐색 이벤트는 Android 앱의 일반적인 이벤트 유형이기도 합니다.</p>
<ul>
<li><p>UI에서 이벤트가 트리거 되는 경우 </p>
<pre><code>class LoginActivity : AppCompatActivity() {

  private lateinit var binding: ActivityLoginBinding
  private val viewModel: LoginViewModel by viewModels()

  override fun onCreate(savedInstanceState: Bundle?) {
      /* ... */

      binding.helpButton.setOnClickListener {
          navController.navigate(...) // Open help screen
      }
  }
}</code></pre></li>
<li><p>탐색 전에 데이터 입력에 비즈니스 로직 확인이 필요하면 ViewModel은 UI에 상태를 노출해야 합니다. UI는 상태 변경에 반응하고 적절하게 이동합니다. ViewModel 이벤트 처리 섹션에서는 이 사용 사례를 다룹니다. 다음은 비슷한 코드입니다.</p>
<pre><code>class LoginActivity : AppCompatActivity() {
  private val viewModel: LoginViewModel by viewModels()

  override fun onCreate(savedInstanceState: Bundle?) {
      /* ... */

      lifecycleScope.launch {
          repeatOnLifecycle(Lifecycle.State.STARTED) {
              viewModel.uiState.collect { uiState -&gt;
                  if (uiState.isUserLoggedIn) {
                      // Navigate to the Home screen.
                  }
                  ...
              }
          }
      }
  }
}</code></pre><h4 id="대상이-백-스택에-유지된-경우-탐색-이벤트">대상이 백 스택에 유지된 경우 탐색 이벤트</h4>
<p>코드 이해가 안됨.</p>
</li>
</ul>
<h3 id="기타-사용-사례">기타 사용 사례</h3>
<p>UI 상태 업데이트로 UI 이벤트 사용 사례를 해결할 수 없다고 생각되면 앱의 데이터 흐름 방식을 다시 고려해야 할 수도 있습니다. 다음 원칙을 고려하세요.</p>
<ul>
<li>각 클래스에서 각자의 역할만을 수행해야 합니다.
UI는 탐색 호출, 클릭 이벤트, 권한 요청 가져오기와 같은 화면별 동작 로직을 담당합니다. ViewModel은 비즈니스 로직을 포함하며 계층 구조의 하위 레이어에서 얻은 결과를 UI 상태로 변환합니다.</li>
<li>이벤트가 발생하는 위치를 생각해 보세요. 
이 가이드의 시작 부분에 있는 결정 트리를 따르고 각 클래스가 담당하는 역할을 처리하게 합니다. 예를 들어 이벤트가 UI에서 발생하고 그 결과 탐색 이벤트가 발생하면 이 이벤트는 UI에서 처리되어야 합니다. 일부 로직이 ViewModel에 위임될 수 있지만, 이벤트 처리는 ViewModel에 완전히 위임될 수 없습니다.</li>
<li>소비자가 여러 명이고 이벤트가 여러 번 소비될 것이 우려된다면 앱 아키텍처를 다시 고려해야 할 수도 있습니다.
동시 실행 소비자가 여럿인 경우 정확히 한 번 제공되는 계약을 보장하기가 매우 어려워지므로 복잡성과 미묘한 동작의 양이 폭발적으로 증가합니다. 이 문제가 발생하면 UI 트리의 위쪽으로 문제를 푸시해 보세요. 계층 구조의 상위로 범위가 지정된 다른 항목이 필요할 수 있습니다.</li>
<li>상태를 소비해야 하는 경우를 생각해 보세요.
어떤 상황에서는 앱이 백그라운드에 있다면 계속 소비하지 않는 것이 좋을 수 있습니다(예: Toast 표시). 이 경우 UI가 포그라운드에 있을 때 상태를 소비하는 것이 좋습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CodeLab (ViewModel과 함께 LiveData 사용하기)]]></title>
            <link>https://velog.io/@day_0893_/CodeLab-ViewModel%EA%B3%BC-%ED%95%A8%EA%BB%98-LiveData-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@day_0893_/CodeLab-ViewModel%EA%B3%BC-%ED%95%A8%EA%BB%98-LiveData-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 05 Sep 2023 13:34:25 GMT</pubDate>
            <description><![CDATA[<p>CodeLab (ViewModel과 함께 LiveData 사용하기)</p>
<h4 id="3livedata란">3.Livedata란</h4>
<ul>
<li>LiveData는 데이터를 보유합니다. DLiveData는 모든 유형의 데이터에 사용할 수 있는 Wraper입니다.</li>
<li>LiveData는 관찰 가능합니다. LiveData가 변경될시 관찰하는 곳에 변경사항이 알려집니다.</li>
<li>LiveData는 수명주기를 인식합니다. LiveData에 관찰자를 연결하면 관찰자는 LifecyclerOwner와 연결됩니다. LifecyclerOwner(일반적으로 Activity 또는 Fragment), LiveData는 STARTED 또는 RESUMED 같은 활성 수명주기 상태인 관찰자만 업데이트 합니다. STARTED, RESUMED=&gt; Lifecycler.State 변수로 Activity 와 Fragment에서 onstart 또는 Resume 같은 생명주기 함수가 호출될 때 지정된다.<h4 id="4-글자가-뒤섞인-현재-단어에-livedata-추가하기">4. 글자가 뒤섞인 현재 단어에 LiveData 추가하기</h4>
</li>
</ul>
<ol>
<li>MutableLiveData한 변경 가능한 LiveData에 private val을 선언하여 외부로부터 변수를 은닉</li>
<li>외부에서 관찰 가능한 LiveData에 get()함수를 주어 변경 불가능한 변수를 노출</li>
<li>내부에서는 MutableLiveData의 값을 변경할 수 있도록 함</li>
</ol>
<pre><code>private val _currentScrambledWord = MutableLiveData&lt;String&gt;()

val currentScrambledWord: LiveData&lt;String&gt;
   get() = _currentScrambledWord

   private fun getNextWord() {
    ...
   } else {
       _currentScrambledWord.value = String(tempWord)
       ...
   }
}</code></pre><h4 id="5-livedata-객체에-관찰자-연결하기">5. LiveData 객체에 관찰자 연결하기</h4>
<p>viewModel의 변수에 Fragment 생명주기를 포함한 관잘차를 추가함으로서
 Fragment가 STARTED 또는 RESUME 상태일 때만 관찰자로부터 알림을 받게 된다.</p>
<pre><code>// Observe the scrambledCharArray LiveData, passing in the LifecycleOwner and the observer.
viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord -&gt;
   })

// Specify the fragment view as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = viewLifecycleOwner</code></pre><h4 id="6-점수-및-단어-수에-관찰자-연결하기">6. 점수 및 단어 수에 관찰자 연결하기</h4>
<h4 id="7-데이터-결합과-함께-livedata-사용하기">7. 데이터 결합과 함께 LiveData 사용하기</h4>
<h4 id="8-데이터-결합-변수-추가하기">8. 데이터 결합 변수 추가하기</h4>
<h4 id="9-결합-표현식-사용하기">9. 결합 표현식 사용하기</h4>
<p>코드 복습
android:text에는 String 을 넣을 수 있다.
android:text=&quot;@{user.firstName}/&gt;
android:text=&quot;@{gameViewModel.currentScrambledWord}&quot;/&gt;
Resource를 불러올 수 있다.
android:padding=&quot;@{@dimen/largePadding}&quot;
android:text=&quot;@{@string/example_resource(user.lastName)}&quot;
&lt; string name=&quot;example_resource&quot;&gt;Last Name%s&lt;/string </p>
<h4 id="10-talkback을-사용-설정한-상태에서-unscramble-앱-테스트하기-스킵">10. TalkBack을 사용 설정한 상태에서 Unscramble 앱 테스트하기 (스킵)</h4>
<h4 id="11-사용하지-않는-코드-삭제하기">11. 사용하지 않는 코드 삭제하기</h4>
<h4 id="12-솔루션-코드">12. 솔루션 코드</h4>
<h4 id="13-요약">13. 요약</h4>
<ul>
<li>LiveData는 데이터를 보유합니다. LiveData는 모든 데이터에 사용할 수 있는 래퍼입니다.</li>
<li>LiveData는 관찰 가능합니다. 즉, LiveData 객체에서 보유한 데이터가 변경되면 관찰자에 알림이 제공됩니다.</li>
<li>LiveData는 수명 주기를 인식합니다. LiveData에 관찰자를 연결하면 관찰자는 LifecycleOwner(일반적으로 활동 또는 프래그먼트)와 연결됩니다. LiveData는 STARTED 또는 RESUMED 같은 활성 수명 주기 상태인 관찰자만 업데이트합니다. LiveData 및 관찰에 관한 자세한 내용은 <a href="https://developer.android.com/topic/libraries/architecture/livedata.html?hl=ko#work_livedata">여기</a>에서 알아볼 수 있습니다.  (링크 공부 보류)</li>
<li>앱은 데이터 결합 및 결합 표현식을 사용하여 레이아웃에서 LiveData 변경사항을 수신할 수 있습니다.</li>
<li>결합 표현식은 레이아웃 내에서 레이아웃 속성을 참조하는 속성(attribute properties)(예: android:text)에서 작성됩니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CodeLab ViewModel 에 데이터 저장하기]]></title>
            <link>https://velog.io/@day_0893_/CodeLab-ViewModel-%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@day_0893_/CodeLab-ViewModel-%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 03 Sep 2023 20:43:13 GMT</pubDate>
            <description><![CDATA[<p><a href="https://developer.android.com/codelabs/basic-android-kotlin-training-viewmodel?hl=ko#0">CodeLab Url</a></p>
<h4 id="4-viewmodel-추가하기">4. ViewModel 추가하기</h4>
<ul>
<li><p>Kotlin 속성 위임: var &lt; proverty-name&gt;: &lt; property-type&gt; by &lt; delegate-class&gt;()
kotlin에는 var 속성에 자동으로 getter와 setter 함수가 생성됩니다.
val의 경우에는 getter만 생성됩니다.</p>
</li>
<li><p>아래와 같이 by를 사용하여 위임할경우 getter와 setter의 책임을 다른 클래스에 넘길 수 있습니다.(대리자 클래스)</p>
<pre><code>private val viewModel: GameViewModel by viewModels()</code></pre></li>
<li><p>아래와 같이 뷰 모델을 초기화할 기기의 구성이 변경되는 동안 앱이 viewModel 참조의 상태를 손실하게 되빈다.
예를 들어 기기를 회전하면 활동이 소멸된 후 다시 생성되고 초기상태의 새로운 뷰 모델 인스턴스가 다시 시작됩니다.
대신 속성 위임 적용 방식을 사용해 viewModel을 초기화하면 대리자 클래스는 첫 액세스시 자동으로 viewModel 객체를 만들고 이 값을 구성 변경 중에도 유지했다가 요청이 있을 때 반환합니다.</p>
<pre><code>private var viewModel: GameViewModel()</code></pre></li>
</ul>
<h4 id="5-viewmodel로-데이터-이동">5. ViewModel로 데이터 이동</h4>
<p>UI에 표시되어야 하는 내용은 ViewModel로 옮긴다
private var로 설정하여 외부에서 접근을 막는다.</p>
<pre><code>class GameViewModel : ViewModel() {

    private var score = 0
    private var currentWordCount = 0
    private var currentScrambledWord = &quot;test&quot;
...
}</code></pre><ul>
<li>지원속성 Backing properties
외부에서 읽은 수는 있지만 수정은 불가능하도록 만들어야할 때 사용하는 방식 <pre><code>//ex1
// Declare private mutable variable that can only be modified
// within the class it is declared.
private var _count = 0
</code></pre></li>
</ul>
<p>// Declare another public immutable field and override its getter method.
// Return the private property&#39;s value in the getter method.
// When count is accessed, the get() function is called and
// the value of _count is returned.
val count: Int
   get() = _count</p>
<p>//ex2
private var _table: Map&lt;String, Int&gt;? = null
public val table: Map&lt;String, Int&gt;
    get() {
        if (_table == null) {
            _table = HashMap() // Type parameters are inferred
        }
        return _table ?: throw AssertionError(&quot;Set to null by another thread&quot;)
    }</p>
<p>```</p>
<h4 id="6viewmodel의-수명주기">6.ViewModel의 수명주기</h4>
<p>viewModel의 수명주기는 Activity와 Fragment의 범위가 유지되는 동안 유지됩니다.
<img src="https://velog.velcdn.com/images/day_0893_/post/ee90d622-c910-49b8-94ed-b9be25b0e4cd/image.png" alt=""></p>
<ul>
<li>ANdroid 앱 아키텍처 가이드라인에서는 책임이 서로 다른 클래스를 분리하고 모델에서 UI를 만들도록 권장합니다.</li>
<li>UI 컨트롤러는 Activity 또는 Fragment 와 같은 UI 기반 클래스입니다. UI 컨트롤러에는 UI 및 운영체제 상호작용을 처리하는 로직만 포함해야합니다. UI에 표시할 데이터의 소스여서는 안 됩니다. UI 에 표시할 데이터와 모든 관련 로직은 ViewModel에 배치합니다.</li>
<li>ViewModel 클래스는 Ui 관련 데이터를 저장하고 관리합니다. 화면 회전과 같이 구성을 변경해도 데이터를 유지할 수 있습니다.</li>
<li>ViewModel은 권장되는 Android 아키텍처 구조입니다.</li>
</ul>
<h4 id="참고">참고</h4>
<ul>
<li>ViewModel 개요</li>
<li>앱 아키텍처 가이드</li>
<li>머터리얼:<ul>
<li>(<a href="https://m3.material.io/components">https://m3.material.io/components</a>)</li>
<li>(<a href="https://medium.com/over-engineering/hands-on-with-material-components-for-android-dialogs-75c6d726f83a">https://medium.com/over-engineering/hands-on-with-material-components-for-android-dialogs-75c6d726f83a</a>)</li>
</ul>
</li>
<li>지원 속성</li>
<li>Android 아키텍처 구성요소</li>
<li>Android 머티리얼 대화상자</li>
<li>속성 및 필드: getter, setter, const, lateinit</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jetpack UI 레이어 정보]]></title>
            <link>https://velog.io/@day_0893_/Jetpack-UI-%EB%A0%88%EC%9D%B4%EC%96%B4-%EC%A0%95%EB%B3%B4</link>
            <guid>https://velog.io/@day_0893_/Jetpack-UI-%EB%A0%88%EC%9D%B4%EC%96%B4-%EC%A0%95%EB%B3%B4</guid>
            <pubDate>Sun, 03 Sep 2023 10:14:12 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>ViewModel에 데이터 저장하기:
<a href="https://velog.io/write?id=cae7ba01-581d-4d85-a2bd-7b1cd965c343">VelogURL</a>
<a href="https://developer.android.com/codelabs/basic-android-kotlin-training-viewmodel?hl=ko#1">codeLab</a></p>
</li>
<li><p>ViewModel과 함께 LiveData 사용하기: 
<a href="https://velog.io/@day_0893_/CodeLab-ViewModel%EA%B3%BC-%ED%95%A8%EA%BB%98-LiveData-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">VelogURL</a>
<a href="https://developer.android.com/codelabs/basic-android-kotlin-training-livedata?hl=ko#0">codeLab</a></p>
</li>
</ul>
<p><a href="https://developer.android.com/topic/architecture?hl=ko">디벨로퍼URL</a></p>
<h4 id="ui-레이어">UI 레이어</h4>
<p>UI의 역할은 화면에 애플리케이션 데이터를 표시하고 상호 작용의 기본 지점으로도 가능하다는 것입니다. (상호 작용의 예: 버튼누르기, 네트워크 응답)
실제 데이터와 UI에 표시되는 정보가 다를 수 있습니다.(예: 두 데이터 소스를 조합하여 표시하거나 일부만 표시하는 경우)
<img src="https://velog.velcdn.com/images/day_0893_/post/7faa82da-a58a-4e85-a841-f64bb9fe2a16/image.png" alt="">
UI elements, State holders</p>
<ul>
<li>UI 레이어 아키텍처
UI라는 용어는 사용하는 API(JetpackCompose 또는 뷰) 와 관계 없이 데이터를 표시하는 Acitivty Fragment와 같은 UI 요소를 가리킵니다.
데이터 레이어의 역할은 데이터를 보유하고 관리하며 앱 데이터에 액세스할 권한을 제공하는 것이므로 UI 레이어에서 다음 단계를 실행합니다.</li>
</ul>
<ol>
<li>앱 데이터를 사용 하고 UI에 쉽게 렌더링 될 수 있는 데이터로 변환</li>
<li>UI 렌더링 가능 데이터를 사용하고 사용자에게 표시할 수 있는 요소로 변환</li>
<li>이렇게 조합된 UI 요소의 사용자 입력 이벤트를 사용하고 입력 이벤트의 결과를 필요에 따라 UI 데이터에 반영한다.</li>
</ol>
<ul>
<li>UI 상태 정의
NewsUiState UI 상태
isSignedIn: 로그인 상태
isPremium: 프리미엄 상태
newsItems: 뉴스 아이템 리스트 상태
userMessage: 뉴스 메시지 리스트</li>
</ul>
<p>NewsItemUiState 뉴스 아이템
title: 제목
body: 내용
bookmarked: 북마크 상태</p>
<pre><code>data class NewsUiState(
    val isSignedIn: Boolean = false,
    val isPremium: Boolean = false,
    val newsItems: List&lt;NewsItemUiState&gt; = listOf(),
    val userMessages: List&lt;Message&gt; = listOf()
)

data class NewsItemUiState(
    val title: String,
    val body: String,
    val bookmarked: Boolean = false,
    ...
)</code></pre><ul>
<li><p>불변성
위 예에서 UI 상태 정의는 변경할 수 없습니다.(val) 불변성의 주요 이점은 변경 불가능한 객체가 순간의 애플리케이션 상태를 보장한다는 점입니다. 덕분에 UI는 상태를 읽고 이에 따라 UI 요소를 업데이트 하는 한가지 역할에 집중할 수 있습니다. 따라서 UI 자체가 데이터의 유일한 소인 경우를 제외하고 UI에서 UI상태를 직접 수정해서는 안 됩니다. 
예를 들어 NewsItemUiState 객체에 있는 bookmarked 플래그가 Activity 클래스에서 업데이트 되면 이 플래그는 북마크된 기사 상태의 소스로서 데이터 레이어와 경합합니다.</p>
<ul>
<li>이 가이드의 이름 지정 규칙
이 가이드에서는 화면의 기능이나 묘사되는 화면의 부분에 따라 UI 상태 클래스의 이름을 지정합니다. 규칙은 다음과 같습니다.
기능+UiState
예를 들어 뉴스를 표시하는 화면의 상태는 NewsUiState 이고 뉴스 항목 목록에 있는 뉴스 항목의 상태는 NewsItemUiState 일 수 있습니다.</li>
</ul>
</li>
</ul>
<h4 id="단방향-데이터-흐름으로-상태-관리">단방향 데이터 흐름으로 상태 관리</h4>
<p>이전 섹션에서는 UI 상태가 UI 렌더링에 필요한 세부정보가 포함된 변경 불가능한 스냅샷임을 확인했씁니다. 하지만 데이터의 동적 특성에 따라 상태는 시간이 지나면서 변경될 수 있으며 이는 앱을 채우는 데 사용되는 기본 데이터를 수정하는 사용자 상호작용이나 기타 이벤트로 이벤트로 인해 발생하기도 합니다.</p>
<p>여기서 중재 요소가 각 이벤트에 적용할 로직을 정의하고 UI 상태를 만들기 위해 지원데이터 소스에 필요한 변환을 실행하는 상호작용을 처리하는 이점이 있을 수 있습니다. 상호작용과 이에 따른 로직이 UI 자체에 포함될 수 있지만 UI가 이름에서 알 수 있는 것 이상의 역할(예 데이터 소유자, 생성자, 변환자)을 담당하기 시작하면 빠르게 복잡해 질 수 있습니다. 궁극적으로 UI 에 주는 부담을 줄여야합니다. UI 상태가 매우 단순하지 않은 이상 UI 역할은 오직 UI 상태를 사용 및 표시하는 것 이어야합니다. -&gt; UDF 단방향 데이터 흐름</p>
<ul>
<li>상태 홀더
UI 상태를 생성하는 역할을 담당하고 생성 작업에 필요한 로직을 포함하는 클래스를 상태홀더라고 합니다. 상태 홀더의 크기는 하단의 앱바 같은 단일 위젯부터 전체화면이나 탐색 대상에 이르기까지 관리 대상UI 요서 범위에 따라 다양합니다.</li>
</ul>
<p>전체 화면이나 탐색 대상의 경우 일반적인 구현은 ViewModel의 인스턴스이지만 애플리케이션의 요구사항에 따라 간단한 클래스로도 충분할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/day_0893_/post/31c5f5db-d991-4abb-a1b9-1f678551acb4/image.png" alt=""> 
단방향 데이터 흐름(UDF)의 작동 방식을 보여주는 다이어그램
Unidiredical Data Flow</p>
<ul>
<li>ViewModel이 UI 에 사용될 상태를 보유하고 노출합니다. UI 상태는 ViewModel에 의해 변환된 애플리케이션 데이터 입니다.</li>
<li>UI 가 ViewModel에 사용자 이벤트를 알립니다.</li>
<li>ViewModel이 사용자 작업을 처리하고 상태를 업데이트 합니다.</li>
<li>업데이트된 상태가 렌더링할 UI에 다시 제공됩니다.</li>
<li>상태 변경을 야기하는 모든 이벤트에 위의 작업이 반복됩니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/day_0893_/post/f225d83d-8f77-4d1c-9181-d88cd9149530/image.png" alt="">
사용자의 기사 북마크 요청은 상태 변경을 야기할 수 있는 이벤트의 예입니다.
상태 생성자의 경우 UI 상태의 모든 필드를 채우고 UI가 완전히 렌더링 되는데 필요한 이벤트를 처리하기 위해 모든 필수 로직을 정의하는 역할은 ViewModel이 담당합니다.</p>
<p><img src="https://velog.velcdn.com/images/day_0893_/post/2e97d121-9d37-44ca-b1e2-74a38a56a923/image.png" alt=""> </p>
<ul>
<li><p>로직의 유형
기사 북마크는 앱에 가치를 부여하므로 비즈니스 로직입니다.</p>
<ul>
<li>비지니스 로직: 앱 데이터에 대한 제품 요구사항의 구현입니다. 일반적으로 비지니스 로직은 UI레이어에 배치되지 않고 도메인 또는 데이터 레이어에 배치됩니다.</li>
<li>UI 동작 로직 또는 UI 로직: 화면에서 상태 변경을 표시하는 방법입니다. 예를 들어 Android Resource를 사용하여 화면에 표시할 올바른 텍스트를 가져오거나, 사용자가 버튼을 클릭할 때 특정 화면으로 이동하거나 토스트메시지 또는 스택바를 사용하여 화면에 사용자 메시지를 표시합니다.</li>
</ul>
<p>특히 Context 같은 UI 유형의 경우 UI 로직은 ViewModel이 아닌 UI 에 있어야합니다. 테스트의 가능성을 높이고 문제 구분에 도움이 되도록 UI 로직을 다른 클래스에 위임하고자 하며 UI 가 복잡한 경우 간단한 클래스를 상태 홀더로 만들 수 있습니다. UI에서 생성된 간단한 클래스는 UI의 생명 주기를 따르기 따문에 Android SDK 종속 항목을 사용할 수 있습니다. ViewModel의 수명주기는 더 깁니다.</p>
</li>
</ul>
<ul>
<li>UDF 를 사용하는 이유 <ul>
<li>데이터 일관성: UI용 정보 소스가 하나입니다.</li>
<li>테스트 가능성: 상태 소스가 분리되므로 UI와 별개로 테스트할 수 있습니다.</li>
<li>유지 관리성: 상태 변경은 잘 정의된 패턴을 따릅니다. 즉 변경은 사용자 이벤트 및 데이터를 가져온 소스 모두의 영향을 받습니다.</li>
</ul>
</li>
</ul>
<h4 id="ui-상태-노출">UI 상태 노출</h4>
<p>UDF 를 사용하여 상태 생성 관리를 하기 떄문에 스트림으로 간주할 수 있습니다. LiveData 또는 StateFlow와 같이 관찰 가능한 데이터 홀더에 UI 상태를 노출해야합니다. </p>
<pre><code>class NewsViewModel(...) : ViewModel() {

    val uiState: StateFlow&lt;NewsUiState&gt; = …
}</code></pre><p>관찰 가능한 데이터 홀더로서의 LiveData에 관해서는 이 <a href="https://developer.android.com/codelabs/basic-android-kotlin-training-livedata?hl=ko#0">Codelab</a>에서 소개합니다. Kotlin 흐름에 관한 비슷한 소개는 Android의 Kotlin 흐름을 참고하세요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JetPack - 앱 아키텍처 정보]]></title>
            <link>https://velog.io/@day_0893_/JetPack-%EC%95%B1-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%A0%95%EB%B3%B4</link>
            <guid>https://velog.io/@day_0893_/JetPack-%EC%95%B1-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%A0%95%EB%B3%B4</guid>
            <pubDate>Sat, 02 Sep 2023 11:35:30 GMT</pubDate>
            <description><![CDATA[<p> <a href="https://developer.android.com/topic/architecture?hl=ko">디벨로퍼 URL</a></p>
<ul>
<li><p>모바일 앱 사용자 환경</p>
<ul>
<li>앱 구성요소는 Activity, Fragment, Service, ContentProvider, Broadcast Receiver 가 있다.</li>
<li>휴대기기 리소스가 제한되어있기 때문에 앱을 위한 공간을 확보하고 언제든지 일부 앱 프로세스가 종료 될 수 있다.</li>
<li>앱 구성 요소는 개별적이고 비순차적으로 실행될 수 있으며, 운영체제에 의해 앱이 소멸 될 수 있다. 이러한 이벤트는 직접 제어할 수 없기 때문에 앱 구성요소에 애플리케이션 데이터나 상태를 저장해서는 안 되며 앱 구성요소가 서로 종속 되면 안된다.</li>
</ul>
</li>
<li><p>일반 아키텍처 원칙 </p>
<ul>
<li>애프리케이션의 데이터와 상태를 저장하고 앱 구성요소를 사용할 수 없기 때문에 앱 아키텍처가 필요하다. 앱 아키텍처는 각 기능 간의 경계를 정의하여 앱을 견고하게 만든다.</li>
</ul>
</li>
<li><p>관심사 분리</p>
<ul>
<li>Activity와 Fragment와 같은 UI 기반 클래스는 UI 및 운영체제 상호작용을 처리하는 로직만 포함해야한다. 이렇게 코드를 가볍게하여 구성요소와 수명 주기와 관련된 많은 문제를 피하고 클래스 테스트 기능성을 개선할 수 있다.</li>
<li>Activity 및 Fragment 구현은 소유 대상이 아니고 Android OS와 앱 사이의 계약을 나타내도록 이어주는 클래스이다. OS에 의해 언제든지 소멸될 수 있기 때문에 이러한 클래스에 대한 의존성을 최소화 하는 것이 좋다.</li>
</ul>
</li>
<li><p>데이터 모델에서 UI 도출하기</p>
<ul>
<li>데이터 모델은 앱의 데이터를 나타내며 UI의 구성요소와 독립되어 있다. UI 및 앱의 구성요소 수명주기와 관련이 없다. 하지만 OS가 메모리에서 앱의 프로세스를 삭제하면 데이터 모델도 삭제된다.<ul>
<li>지속 모델이 이상적인 이유: Android OS 에서 앱을 제거해도 사용자 데이터가 삭제되지 않는다.</li>
<li>네트워크 연결이 취약하거나 연결되지 않아도 앱이 계속 작동한다.</li>
</ul>
</li>
</ul>
</li>
<li><p>단일 소스 저장소</p>
<ul>
<li>앱에서 새로운 데이터 유형을 정의할 때 데이터 유형에 단일 소스 저장소를 할당해야한다. (SSOT)
SSOT는 데이터 소유자 이며 SSOT만 데이터를 수정 변경할 수 있다.
SSOT는 이를 위해 불변 유형(val)을 사용하여 데이터를 노출하고 다른 유형이 호출할 수 있는 이벤트를 수신하거나 함수를 노출하여 데이터를 수정한다.</li>
<li>SSOT 패턴의 이점<ul>
<li>특정 유형 데이터의 모든 변경사항을 한곳으로 일원화한다.</li>
<li>다른 유형이 조작할 수 없도록 데이터를 보호한다.</li>
<li>데이터 변경사항을 더 쉽게 추적할 수 있도록한다. (버그 발견에 유용)
오프라인 중심의 애플리케이션의 데이터 정보소스는 주로 데이터베이스이다. 정보 소스가 ViewModel이거나 UI인 경우도 있다.</li>
</ul>
</li>
</ul>
</li>
<li><p>단방향 데이터 흐름</p>
<ul>
<li>단일 소스 저장 원칙은 Google 가이드에서 종종 단방향 데이터 흐름(UDF)패턴과 함께 사용 된다. UDF에서 상태는 한방향으로 흐르고, 데이터를 수정하는 이벤트는 반대 방향으로 흐른다.</li>
<li>앱에서 상태 또는 데이터는 일반적으로 계층 구조의 상위 범위 유형에서 하위 범위 유형으로 흐른다.
반대로 이벤트는 보통 하위 범위 유형에서 트리거되어 상응하는 데이터 유형의 SSOT(단일 소스 저장소)에 도달한다.
이 패턴은 데이터 일관성을 강화하고, 오류 발생 확률을 줄여주며 디버그를 쉽게한다.</li>
</ul>
</li>
<li><p>권장 앱 아키텍처</p>
<ul>
<li>화면에 애플리케이션 데이터를 표시하는 UI 레이어</li>
<li>앱의 비지니스 로직을 포함하고 애플리케이션 데이터를 노출하는 데이터레이어
<img src="https://velog.velcdn.com/images/day_0893_/post/2082a4d9-8cf5-4821-a6e9-f9bbcb33d3e1/image.png" alt="">화살표는 클래스 간의 종속성을 나타낸다</li>
</ul>
</li>
<li><p>최신 앱 아키텍처</p>
<ul>
<li>반응형 및 계층형 아키텍처</li>
<li>앱의 모든 레이어에서의 단방향 데이터 흐름 (UDF)</li>
<li>상태 홀더가 있는 UI 레이어로 UI의 복잡성 관리</li>
<li>코루틴 및 흐름</li>
<li>종속 항목 삽입 권장사항</li>
</ul>
</li>
<li><p>UI 레이어</p>
<ul>
<li>UI는 화면에 데이터를 표시한다. 사용자의 상호작용(버튼 클릭) 또는 외부 입력(네트워크 응답)으로 인해 데이터가 변할 때 마다 변경 사항을 반영하여 UI가 업데이트 되야한다.<ul>
<li>화면에 데이터를 렌더링하는 UI 요소 (View, Jetpack Compose 함수)</li>
<li>데이터를 보유하고 이를 UI에 노출하며 로직을 처리하는 상태 홀더(ViewModel 클래스)
<img src="https://velog.velcdn.com/images/day_0893_/post/cdb31c95-49f3-469e-b0a2-7b30422a796b/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
<li><p>데이터 레이어</p>
<ul>
<li>데이터 레이어에는 비지니스 로직이 포함되어있다. 비지니스 로직은 앱에 가치를 부여하는 요소로 데이터의 생성 저장 변경 방식을 결정하는 규칙으로 구성된다.</li>
<li>데이터 레이어는 0개 부터 여러개의 데이터 소스를 각각 포함할 수 있는 저장소로 구성된다. 예를 들어 영화관련 데이터에는 MoviesRepository 클래스, 결제관련 데이터에는 PaymentsRepository 클래스를 만들 수 있다.
<img src="https://velog.velcdn.com/images/day_0893_/post/92a04348-c219-40e6-9049-107f4bbc6c0c/image.png" alt=""></li>
<li>저장소 클래스에서 담당하는 작업<ul>
<li>앱의 나머지 부분에 데이터 노출</li>
<li>데이터 변경 사항을 한곳에 집중</li>
<li>여러 데이터 소스간의 충돌 해결</li>
<li>앱의 나머지 부분에서 데이터 소스 추상화</li>
<li>비지니스 로직 포함</li>
</ul>
</li>
</ul>
</li>
<li><p>도메인 레이어 (Optional Layer)</p>
<ul>
<li>복잡한 비지니스 로직이나 여러 ViewModel에서 재사용되는 간단한 비지니스 로직의 캡슐화를 담당한다. 예를들어 ViewModel에서 시간대를 사용하여 적절하게 화면에 메시지를 표시할 수 있는 GetTimeZoneUseCase 클래스가 있다.</li>
</ul>
</li>
<li><p>구성요소 간 종속 항목 관리
앱의 클래스는 올바른 동작을 위해 다른 클래스에 종속된다. 아래 패턴중 하나를 사용할 수 있다.</p>
<ul>
<li>종속 항목 주입(DI): 종속 항목 주입을 사용하면 클래스가 자신의 종속 항목을 구성할 필요 없이 종속 항목을 정의할 수 있다. 런타임 시 다른 클래스가 이 종속 항목을 제공해야한다.</li>
<li>서비스 로케이터: 서비스 로케이터 패턴은 클래스가 자신의 종속 항목을 구성하는 대신 종속 항목을 가져올 수 있는 레지스트리를 제공한다. </li>
</ul>
</li>
<li><p>일반 권장사항</p>
<ul>
<li>앱 구성요소에 데이터를 저장한다.: 활동 서비스 BroadcastReceiver와 같은 앱 진입점을 데이터 소스로 지정하지 않는다. 대신 그 진입점과 관련된 데이터 일부만 가져오도록 구성요소에 맞춰 조정한다. </li>
<li>Android 클래스의 종속 항목을 줄인다: 앱 구성요소는 Context 또는 Toast 같은 Android 프레임웤SDK API 를 사용하는 유일한 클래스여야한다.</li>
<li>앱의 다양한 모듈 간 책임이 잘 정의된 경계를 만든다.: </li>
<li>각 모듈에서 가능한 적게 노출한다.</li>
<li>다른 앱과 차별되도록 앱의 고유한 핵심에 초점을 맞춘다.</li>
<li>앱의 각 부분을 독립적으로 테스트 하는 방법을 고려한다.: 네트워크와 로컬 데이터베이스에서 데이터를 가져오기위한 API를 잘 정의하면 데이터 보존 모듈을 더 쉽게 테스트 할 수 있다.</li>
<li>유형은 동시 실행 정책을 담당한다.</li>
<li>가능한 관련성이 높은 최신 데이터를 보존한다.</li>
</ul>
</li>
</ul>
<ul>
<li>아키텍처의 이점<ul>
<li>앱의 전반적인 유지관리성, 품질, 견고성이 개선된다</li>
<li>앱을 확장할 수 있다. 코드 충돌이 최소화되어더 많은 팀이 동일한 코드베이스에 기여할 수 있다.</li>
<li>온보딩에 도움이된다.(코드의 일관성)</li>
<li>테스트가 쉽다.</li>
<li>버그를 체계적으로 조사할 수 있다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android 명명 규칙]]></title>
            <link>https://velog.io/@day_0893_/Android-%EB%AA%85%EB%AA%85-%EA%B7%9C%EC%B9%99</link>
            <guid>https://velog.io/@day_0893_/Android-%EB%AA%85%EB%AA%85-%EA%B7%9C%EC%B9%99</guid>
            <pubDate>Fri, 01 Sep 2023 03:12:35 GMT</pubDate>
            <description><![CDATA[<h4 id="실행코드-java-kotlin">실행코드 (Java, Kotlin)</h4>
<p>클래스: MainActivity
인터페이스: ClickListener
변수: mName
메소드: printName()
상수: MAX_NUM</p>
<h4 id="xml">XML</h4>
<p>&lt; What &gt;, &lt; Where &gt;, &lt; Description &gt;, &lt; Size &gt;</p>
<ul>
<li>What: 표현하고자 하는 컴포넌트를 표시합니다. (MainActivity -&gt; activity)</li>
<li>Where: 논리적으로 앱에서 무엇을 표현하는지 나타냅니다. (MainActivity -&gt; main)</li>
<li>Description: 어느 목적으로 사용되는지 나타냅니다. (title, name)</li>
<li>Size: 상세 정보 (50dp)
<img src="https://velog.velcdn.com/images/day_0893_/post/52de8cd1-bcf5-4821-a842-060cb1201b38/image.png" alt="">
<a href="chrome-extension://efaidnbmnnnibpcajpcglclefindmkaj/https://jeroenmols.com/img/blog/resourcenaming/resourcenaming_cheatsheet.pdf">출처</a></li>
</ul>
<p>layouts: what_where
ex) activity_main, view_menu</p>
<p>Strings: where_description
ex) main_title</p>
<p>Drawables: where_description_size
ex) main_userimage</p>
<p>Ids: what where description
ex) tv_menu_title</p>
<p>dimension: what_where_description_size
ex) keyline_all_text</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android 기본 제공 아이콘 사용]]></title>
            <link>https://velog.io/@day_0893_/Android-%EA%B8%B0%EB%B3%B8-%EC%A0%9C%EA%B3%B5-%EC%95%84%EC%9D%B4%EC%BD%98-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@day_0893_/Android-%EA%B8%B0%EB%B3%B8-%EC%A0%9C%EA%B3%B5-%EC%95%84%EC%9D%B4%EC%BD%98-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Fri, 01 Sep 2023 02:36:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/day_0893_/post/fe76e486-783a-4126-8ee9-daae5a62308d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/day_0893_/post/05637bfb-e18c-47c0-a58f-3dd41d72ccce/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JetPack 페이징 라이브러리]]></title>
            <link>https://velog.io/@day_0893_/JetPack-%ED%8E%98%EC%9D%B4%EC%A7%95-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC</link>
            <guid>https://velog.io/@day_0893_/JetPack-%ED%8E%98%EC%9D%B4%EC%A7%95-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC</guid>
            <pubDate>Sun, 27 Aug 2023 17:23:51 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@day_0893_/JetPack-%EB%AA%A9%EC%B0%A8">목차</a>
<a href="https://developer.android.com/topic/libraries/architecture/paging/v3-overview?hl=ko">안드로이드 디벨로퍼 URL</a></p>
<h4 id="paging-라이브러리-개요">Paging 라이브러리 개요</h4>
<p>네트워크나 로컬의 대규모의 데이터를 페이지로 표시할 수 있다</p>
<h4 id="페이징-라이브러리를-사용하여-얻을-수-있는-이점">페이징 라이브러리를 사용하여 얻을 수 있는 이점</h4>
<ul>
<li>Paging된 데이터의 메모리 내 캐싱. 이렇게 하면 앱이 Paging 데이터로 작업하는 동안 시스템 리소스를 효율적으로 사용할 수 있습니다.</li>
<li>요청 중복 삭제 기능이 기본 제공되므로 앱에서 네트워크 대역폭과 시스템 리소스를 효율적으로 사용할 수 있습니다.</li>
<li>사용자가 로드된 데이터의 끝까지 스크롤할 때 구성 가능한 RecyclerView 어댑터가 자동으로 데이터를 요청합니다.</li>
<li>Kotlin 코루틴 및 플로뿐만 아니라 LiveData 및 RxJava를 최고 수준으로 지원합니다.</li>
<li>새로고침 및 재시도 기능을 포함하여 오류 처리를 기본으로 지원합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JetPack - 수명 주기 인식]]></title>
            <link>https://velog.io/@day_0893_/JetPack-%EC%88%98%EB%AA%85-%EC%A3%BC%EA%B8%B0-%EC%9D%B8%EC%8B%9D-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C%EB%A1%9C-%EC%88%98%EB%AA%85-%EC%A3%BC%EA%B8%B0-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@day_0893_/JetPack-%EC%88%98%EB%AA%85-%EC%A3%BC%EA%B8%B0-%EC%9D%B8%EC%8B%9D-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C%EB%A1%9C-%EC%88%98%EB%AA%85-%EC%A3%BC%EA%B8%B0-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Sun, 27 Aug 2023 15:13:06 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@day_0893_/JetPack-%EB%AA%A9%EC%B0%A8">목차 이동</a></p>
<p><a href="https://developer.android.com/codelabs/android-lifecycles?index=..%2F..%2Findex#1">라이프 사이클 코드랩</a></p>
<h4 id="수명-주기-인식-구성요소로-수명-주기-처리">수명 주기 인식 구성요소로 수명 주기 처리</h4>
<h4 id="기존-코드-문제점">기존 코드 문제점</h4>
<ul>
<li>아래 코드의 onStart onStop를 확인해보면 onStart()의 리스너 실행에 지연이 있을 경우 onStop의 호출이 먼저실행되어 리스너가 종료되지 못하는 에러가 발생할 수있다.</li>
<li>Activity와 Fragment가 중지되기 전에 구송요소가 시작되는 보장이 없다.</li>
</ul>
<pre><code>
internal class MyLocationListener(
        private val context: Context,
        private val callback: (Location) -&gt; Unit
) {

    fun start() {
        // connect to system location service
    }

    fun stop() {
        // disconnect from system location service
    }
}

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this) { location -&gt;
            // update UI
        }
    }

    public override fun onStart() {
        super.onStart()
        myLocationListener.start()
        // manage other components that need to respond
        // to the activity lifecycle
    }

    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
        // manage other components that need to respond
        // to the activity lifecycle
    }
}</code></pre><p>androidx.lifecycle 패키지는 이러한 문제를 탄력적이고 단독적인 방법으로 처리하는 데 도움이 되는 클래스와 인터페이스를 제공한다.</p>
<h4 id="lifecycle">Lifecycle</h4>
<ul>
<li><p>Livfecycle은 Activity나 Fragment와 같은 구성요소의 수명 주기 상태 관련 정보를 포함하며 다른 객체가 이 상태를 관찰할 수 있게 하는 클래스 입니다.</p>
</li>
<li><p>Livfecycle은 Event, States를 사용하여 연결된 구성요소의 수명 주기 상태를 추적합니다.</p>
</li>
<li><p>Event: 프레임워크 및 Lifecycle 클래스에 전달되는 수명주기 이벤트</p>
</li>
<li><p>States: Lifecycle 객체가 추적한 구성요소의 현재 상태</p>
</li>
<li><p>사용법: DefaultLifecycleObserver 를 사용한다.</p>
<pre><code>class MyObserver : DefaultLifecycleObserver {
  override fun onResume(owner: LifecycleOwner) {
      connect()
  }

  override fun onPause(owner: LifecycleOwner) {
      disconnect()
  }
}
</code></pre></li>
</ul>
<p>myLifecycleOwner.getLifecycle().addObserver(MyObserver())</p>
<pre><code>#### LifecycleOwner
- LifecyclerOwner는 클래스에 Lifecycle 이 있음을 나타내는 단일 인터페이스이다. (getLifecycler() 매서드 하나가 존재)
- 전체 애플리케이션의 수명 주기를 관리해야될 경우 ProcessLifecyclerOwner을 참고한다.

- 아래 코드의 MyLocationListener는 DefaultLifecycleObserver를 구현하도록 하고 있다. 수명 주기 상태에 반응하는 로직이 Activity 대신 MyLocationListener에 선언, 개별 구성 요소가 자체로직을 저장하도록 설정하면 Activity와 Fragment 로직을 더 쉽게 관리할 수 있다.
</code></pre><p>class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener</p>
<pre><code>override fun onCreate(...) {
    myLocationListener = MyLocationListener(this, lifecycle) { location -&gt;
        // update UI
    }
    Util.checkUserStatus { result -&gt;
        if (result) {
            myLocationListener.enable()
        }
    }
}</code></pre><p>}</p>
<pre><code> 일반적인 사용 사례 에서는 Lifecycle 이 현재 정상 상태가 아닌 경우 콜백 호출을 피한다. 예를 들어 활동 상태가 저장된 후 콜백이 프래그먼트 트랜잭션을 실행하면 비정상 종료 트리거를 할 수 있으므로 콜백을 호출하지 않는것이 좋다.

이러한 사용 사례를 쉽게 만들 수 있도록 Lifecycle 클래스는 다른 객체가 현재 상태를 쿼리할 수 있도록 한다.</code></pre><p>internal class MyLocationListener(
        private val context: Context,
        private val lifecycle: Lifecycle,
        private val callback: (Location) -&gt; Unit
): DefaultLifecycleObserver {</p>
<pre><code>private var enabled = false

override fun onStart(owner: LifecycleOwner) {
    if (enabled) {
        // connect
    }
}

fun enable() {
    enabled = true
    if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
        // connect if not connected
    }
}

override fun onStop(owner: LifecycleOwner) {
    // disconnect if connected
}</code></pre><p>}</p>
<pre><code>#### 맞춤 LifecycleOwner 구현
LifecycleOwner 를 만들려는 맞춤 클래스가 있다면 LifecycleRegistry 클래스를 사용할 수 있지만 다음 코드 와같이 클래스에 이벤트를 전달해야한다.</code></pre><p>class MyActivity : Activity(), LifecycleOwner {</p>
<pre><code>private lateinit var lifecycleRegistry: LifecycleRegistry

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    lifecycleRegistry = LifecycleRegistry(this)
    lifecycleRegistry.markState(Lifecycle.State.CREATED)
}

public override fun onStart() {
    super.onStart()
    lifecycleRegistry.markState(Lifecycle.State.STARTED)
}

override fun getLifecycle(): Lifecycle {
    return lifecycleRegistry
}</code></pre><p>}</p>
<pre><code>#### 수명 주기 인식 구성요소의 권장사항
- UI 컨트롤러(Activity, Framgnet)를 가능한 가볍게 유지 (ViewModel을 사용하여 데이터를 확보하고 LiveData 객체를 관찰하여 변경사항을 뷰에 적용)
- UI 컨트롤러에서는 데이터 기반 UI 작성
- ViewModel 클래스에 데이터 로직을 배치한다. ViewModel은 UI 컨트롤러와 앱 나버지 부분간의 커넥터 역할을 한다. 단 네트워크에서 데이터를 가져오는것은 ViewModel의 책임이 아니다. 
- DataBinding을 사용하여 뷰와 UI 컨트롤러 사이의 인터페이스를 깔끔하게 유지한다. (Java Butter Knife)
- UI가 복잡하다면 UI 수정을 처리할 수 있는 presenter 클래스를 만든다.
- ViewModel 에서 View 또는 Activity 컨텍스트를 참조하지 않는다.ViewModel이 Acitivity 보다 오래 지속되면 GarbageCollector가 제대로 처리를 못하게된다.
- Coroutine을 사용하여 작업 및 비동기적 실행작업을 처리한다.

#### 수명주기 인식 구성요소의 사용 사례
- 사용자 위치 표시 앱: 대략적인 위치와 세부적인 위치 업데이트간 전환: 
  - 앱이 포그라운드 상태일 때 세부위치 표시
  - 앱이 백그라운드 상태일때 대략적인 위치 업데이트
  - LiveData를 사용하여 사용자 위치가 변경될 때마다 자동으로 UI 업데이트
- 동영상 버퍼링 중지와 시작
- 네트워크 연결 시작과 중지
- 애니메이션 드로어블 일시중지와 재개

#### 중지 이벤트 처리



#### 코드랩

LiveData, LifeCycle, ViewModel 
 - LiveData: 데이터 변화 관찰
 - Lifecycle: 메모리릭 방지
 - ViewModel: 데이터 관리



step1: 화면 회전시 값이 초기화 된다고 나와있음 (에뮬레이터 재현 안됨)
step2: ViewModel 사용시: Step1의 회전 에러가 고쳐진다함, 다른 앱 이용 중 복귀시 값이 보존 되지 못하는 에러 존재 (에뮬레이터 재현 안됨)

아직 감이 잡히지 않아 추후 진행 예정
(MyLocationListener)


</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JetPack 뷰 결합]]></title>
            <link>https://velog.io/@day_0893_/JetPack-%EB%B7%B0-%EA%B2%B0%ED%95%A9</link>
            <guid>https://velog.io/@day_0893_/JetPack-%EB%B7%B0-%EA%B2%B0%ED%95%A9</guid>
            <pubDate>Sat, 26 Aug 2023 18:20:50 GMT</pubDate>
            <description><![CDATA[<p>목차: <a href="https://velog.io/write?id=45950adf-c91a-4ccc-a426-f616f037f857">https://velog.io/write?id=45950adf-c91a-4ccc-a426-f616f037f857</a></p>
<h4 id="findviewbyid와의-차이점">findViewById와의 차이점</h4>
<p> Null 안전
 유형 안전</p>
<h4 id="데이터-결합과-비교">데이터 결합과 비교</h4>
<ul>
<li>더 빠른 컴파일<ul>
<li>사용 편의성</li>
<li>레이아웃 변수 또는 레이아웃 표현식 사용 불가</li>
<li>양방향 데이터 결합 지원 불가 </li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[WorkManager 특징]]></title>
            <link>https://velog.io/@day_0893_/WorkManager-%ED%8A%B9%EC%A7%95</link>
            <guid>https://velog.io/@day_0893_/WorkManager-%ED%8A%B9%EC%A7%95</guid>
            <pubDate>Sat, 26 Aug 2023 18:20:17 GMT</pubDate>
            <description><![CDATA[<p>목차: <a href="https://velog.io/write?id=45950adf-c91a-4ccc-a426-f616f037f857">https://velog.io/write?id=45950adf-c91a-4ccc-a426-f616f037f857</a></p>
<p>참고 URL: <a href="https://developer.android.com/topic/libraries/architecture/workmanager?hl=ko">https://developer.android.com/topic/libraries/architecture/workmanager?hl=ko</a></p>
<p>WorkManager: 백그라운드 처리에 권장되는 기본 API (앱이 다시 시작 되거나 시스템이 재부팅될 때 작업이 예약된 채로 남아있으면 그 작업이 유지 됩니다.)</p>
<ol>
<li>즉시 실행: OneTimeWorkRequest에서 setExpedited() 호출</li>
<li>장기 실행: WorkRequest, Worker에서 setForeground() 호출</li>
<li>지연 가능: PeriodicWorkRequest, Worker</li>
</ol>
<p>특징</p>
<ul>
<li>작업 제약 조건: 기기 ON 상태, 무제한 네트워크 상태, 배터리가 충분한 상태</li>
<li>강력한 예약관리: 예약작업은 SQLite 데이터베이스에 저장되어 기기를 재부팅해도 작업이 유지되고 다시 예약되도록 보장한다. (절전 기능, 잠자기모드 권장사항을 준수하기 때문에 배터리 소모를 걱정하지 않아도 된다.</li>
<li>신속한 처리 작업: </li>
<li>유연한 재시도 정책: 지수 백오프 정책을 비롯해 유연한 재시도 정책을 재공한다.</li>
<li>작업 채이닝: 순차실행 또는 동시실행이 제어 가능하다.</li>
<li>내장 스레딩 상호 운용성: 코루틴 RxJava와 원활하게 통합되며 자체 비동기 API를 연결할 수 있는 유연성이 제공된다.</li>
</ul>
<p>다른 API와의 관계
WorkManager: 백그라운드에서도 실행 가능한 지속적인 작업 
코루틴: 지속적이지 않은 모든 비동기 작업
AlarmManager: 알람에만 사용 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JetPack 데이터 결합]]></title>
            <link>https://velog.io/@day_0893_/JetPack-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B2%B0%ED%95%A9</link>
            <guid>https://velog.io/@day_0893_/JetPack-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B2%B0%ED%95%A9</guid>
            <pubDate>Sat, 26 Aug 2023 18:20:05 GMT</pubDate>
            <description><![CDATA[<p>목차: <a href="https://velog.io/write?id=45950adf-c91a-4ccc-a426-f616f037f857">https://velog.io/write?id=45950adf-c91a-4ccc-a426-f616f037f857</a></p>
<p>데이터 결합 라이브러리 공홈: <a href="https://developer.android.com/topic/libraries/data-binding?hl=ko">https://developer.android.com/topic/libraries/data-binding?hl=ko</a></p>
<h4 id="특징">특징</h4>
<ul>
<li><p>UI와 데이터 소스를 결합할 수 있는 지원 라이브러리</p>
</li>
<li><p>ViewBinding이 성능이 우수함, findViewById만 대체할경우 viewBinding을 사용함</p>
</li>
<li><p>레이아웃 및 결합 표현식
레이아웃에서 변수 및 include 를 사용할 수 있는 기능을 제공</p>
</li>
<li><p>식별 가능한 데이터 객체 작업
데이터가 변경될 때 UI 새로고침에 신경쓰지 않아도 된다.</p>
</li>
<li><p>생성된 바인딩 클래스</p>
</li>
<li><p>결합 어뎁터
레이아웃 표현식으로 속성 또는 리스너를 설정하는 결합 어뎁터가 있다.
setText() 메서드나 setOnclickListener() 메서드를 호출하여 리스너를 클릭 이벤트에 추가할 수 있다.</p>
</li>
<li><p>아키텍처 구성요소에 레이아웃 뷰 연결
테스트와 유지관리가 쉬운 앱 디자인에 사용할 수 있다. (아키텍처 구성요소를 데이터 결합 라이브러리와 함께 사용하여 UI 개발을 단순화 할 수 있다.)</p>
</li>
<li><p>양방향 데이터 바인딩
속성의 데이터 변경사항을 받는 동시에 속성의 사용자 업데이트를 수신 대기하는 기능을 지원한다.</p>
</li>
</ul>
<p><strong>코드랩</strong>: <a href="https://developer.android.com/codelabs/android-databinding#0">https://developer.android.com/codelabs/android-databinding#0</a></p>
<p>Transformations : LiveData를 위한 Class로 데이터가 변하면서 조건에 따라 다른 데이터도 변경 시킬 때 유용하게 사용함
=&gt; LiveData.map(), LiveData.switchMap() 으로 대체됨</p>
<pre><code>private val _likes = MutableLiveData(0)
val likes: LiveData&lt;Int&gt; = _likes;
val popularity: LiveData&lt;Popularity&gt; = likes.map {
        if (it &lt; 3) {
            Popularity.NORMAL
        } else if (it in 4..5) {
            Popularity.POPULAR
        } else {
            Popularity.STAR
        }
    }</code></pre><p>아래는  MutableLiveData.map API 이다.
MediatorLiveData: 여러개의 LiveData를 묶어서 Observe가 가능하다.</p>
<pre><code>fun &lt;X, Y&gt; LiveData&lt;X&gt;.map(
    transform: (@JvmSuppressWildcards X) -&gt; (@JvmSuppressWildcards Y)
): LiveData&lt;Y&gt; {
    val result = MediatorLiveData&lt;Y&gt;()
    result.addSource(this) { x -&gt; result.value = transform(x) }
    return result
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JetPack - 목차]]></title>
            <link>https://velog.io/@day_0893_/JetPack-%EB%AA%A9%EC%B0%A8</link>
            <guid>https://velog.io/@day_0893_/JetPack-%EB%AA%A9%EC%B0%A8</guid>
            <pubDate>Sat, 26 Aug 2023 18:19:34 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/TaeHeeHyeung/android_jetpack_tutorial">GitHub</a></p>
<p><a href="https://developer.android.com/topic/architecture?hl=ko">안드로이드 디벨로퍼 URL </a> </p>
<h4 id="--coroutine">- coroutine</h4>
<p><a href="https://developer.android.com/kotlin/coroutines?hl=ko">안드로이드 디벨로퍼 Corout</a> 
<a href="https://velog.io/@day_0893_/Android-Kotlin-Coroutine">벨로그</a></p>
<h4 id="--앱-아키텍처-가이드">- 앱 아키텍처 가이드</h4>
<ul>
<li>앱 아키텍처 정보: <a href="https://velog.io/@day_0893_/JetPack-%EC%95%B1-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%A0%95%EB%B3%B4">velog</a></li>
</ul>
<h4 id="--ui-레이어">- UI 레이어</h4>
<ul>
<li>UI 레이어 정보: <a href="https://velog.io/@day_0893_/Jetpack-UI-%EB%A0%88%EC%9D%B4%EC%96%B4-%EC%A0%95%EB%B3%B4">velog</a> </li>
<li>UI 이벤트 : <a href="https://velog.io/@day_0893_/JetPack-UI-%EC%9D%B4%EB%B2%A4%ED%8A%B8">velog</a> 작성중..</li>
</ul>
<h4 id="--아키텍처-구성요소">- 아키텍처 구성요소</h4>
<ul>
<li><p>UI 레이어 라이브러리</p>
<ul>
<li>뷰 결합: <a href="https://velog.io/@day_0893_/JetPack-%EB%B7%B0-%EA%B2%B0%ED%95%A9">velog</a></li>
<li>데이터 결합 라이브러리: <a href="https://velog.io/write?id=de17fcf3-2b1b-474e-bb34-d886decc3e4c">velog</a></li>
<li>수명 주기 인식 구성요소<ul>
<li>수명 주기 처리: <a href="https://velog.io/@day_0893_/JetPack-%EC%88%98%EB%AA%85-%EC%A3%BC%EA%B8%B0-%EC%9D%B8%EC%8B%9D-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C%EB%A1%9C-%EC%88%98%EB%AA%85-%EC%A3%BC%EA%B8%B0-%EC%B2%98%EB%A6%AC">velog</a></li>
<li>ViewModel</li>
</ul>
</li>
</ul>
</li>
<li><p>[페이징 라이브러리]: <a href="https://velog.io/@day_0893_/JetPack-%ED%8E%98%EC%9D%B4%EC%A7%95-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC">velog</a></p>
</li>
</ul>
<ul>
<li><a href="https://velog.io/@day_0893_/WorkManager-%ED%8A%B9%EC%A7%95">WorkManager</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>