<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>PONYO</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 08 Mar 2022 00:36:01 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>PONYO</title>
            <url>https://velog.velcdn.com/images/dev_jin/profile/c12b9db1-0ad0-4123-8036-aaa89476930e/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. PONYO. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_jin" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Kotlin Flows in practice]]></title>
            <link>https://velog.io/@dev_jin/%EC%A0%95%EB%A6%AC-Kotlin-Flows-in-practice</link>
            <guid>https://velog.io/@dev_jin/%EC%A0%95%EB%A6%AC-Kotlin-Flows-in-practice</guid>
            <pubDate>Tue, 08 Mar 2022 00:36:01 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.youtube.com/watch?v=fSB6_KE95bU">Kotlin Flows in practice</a>
위의 영상을 보고 정리한 글입니다.😊</p>
<p><img src="https://images.velog.io/images/dev_jin/post/1998ec99-3276-4745-a1c5-3b6b77efecf7/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%EC%A0%84%2012.13.22.png" alt=""></p>
<p>데이터는 한 방향으로 흐르게 하는 것이 좋은데, 이는 오류가 발생할 가능성이 줄고, 관리하기 쉽기 때문이다. 데이터를 한 방향으로 흐르게 하고 수도관 역할을 하는 인프라를 구현해서 데이터 스트림을 결합하고 변환하는 것이 좋다. 예를 들어 무언가 바뀌어서 수정이 필요한 경우 사용자가 로그아웃하면 관을 다시 설치할 수 있다. 이렇게 데이터 스트림을 결합하고 변환하려면 정교한 무언가가 필요한데, 그게 오늘 정리해볼 <strong>Flow</strong>이다!</p>
<p><img src="https://images.velog.io/images/dev_jin/post/46cfc14d-b154-4340-bb0a-b6e6a86835a7/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%EC%A0%84%2012.19.22.png" alt=""></p>
<p>flow는 사용자 데이터나 UI상태가 될 수 있다. 생산자는 데이터를 flow에 입력하고 
소비자는 flow에서 데이터를 수집한다. Android는 데이터 소스나 레포지토리가 
전형적인 앱 데이터 생산자이다. 최종적으로 화면에 데이터를 표시하는 UI가 소비자 역할을 한다.</p>
<hr>
<h2 id="creating-flows">Creating Flows</h2>
<p><img src="https://images.velog.io/images/dev_jin/post/8c6144f3-8bab-4258-8170-5f28731ab3dd/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%EC%A0%84%2012.22.52.png" alt=""></p>
<p>Flow를 생성하는 방법에 대해 알아보자!
데이터 소스에서 사용하는 라이브러리는 코루틴과 Flow에 이미 통합되어 있어서 대부분의 경우 우리가 직접 flow를 만들 필요가 없다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/7e44e5f0-b94b-40fe-8adb-902be87d769b/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%EC%A0%84%2012.27.56.png" alt=""></p>
<p>자주 사용하는 라이브러리인 DataStore, Retrofit, Room, WorkManager 등은 Flow를 사용하여 데이터를 제공하는 댐 역할을 하기때문에 개발자는 데이터 생성 방법을 몰라도 파이프에 연결하기만 하면 된다.   </p>
<p><img src="https://images.velog.io/images/dev_jin/post/9926dd13-1a50-47fb-bfe2-4ce92415e025/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%EC%A0%84%2012.31.11.png" alt=""></p>
<p>Room을 예시로 들자면, X타입의 Flow를 노출하여 데이터베이스의 변경 사항을 알 수 있고, 
Room 라이브러리가 생산자 역할을 맡아서 업데이트가 있을 때마다 쿼리 내용을 전송해준다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/7797a5c8-7bb6-471a-8f8c-6a373502b740/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%EC%A0%84%2012.36.31.png" alt=""></p>
<p>Flow를 직접 만들어야한다면? 여러가지 옵션이 있지만 Flow builder를 알아보자
<code>userMessagesDataSource</code>에서 수시로 앱에서 온 매세지를 확인하려고 한다고 생각해보자.
List형식의 Message를 flow로 노출할 수 있는데, 먼저 flow를 만들려면 flow builder을 사용해야한다. flow builder는 suspend 블록을 매개변수로 받기때문에 suspend함수를 호출할 수 있다. 이는 Flow가 코루틴의 컨텍스트에서 실행되기 때문이다. 
내부에서는 <code>while(true)</code> 루프로 로직을 주기적으로 반복한다. </p>
<p>먼저 API에서 메세지를 가져온 다음, <code>emit</code> suspend 함수로 flow에 결과를 추가한다.
그러면 컬렉터가 항목을 받을 때까지 코루틴을 중단하고, 마지막으로 일정 시간 코루틴을 중단한다.
위의 flow는 동일한 코루틴에서 연산을 순차로 실행한다. <code>while(true)</code> 루프로 인해 lateMessages를 계속 가져오고, 관찰자가 떠나거나 데이터 수집을 중단하면 멈추게된다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/a92fb542-f761-4137-bb34-d5dc35f350eb/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%EC%A0%84%2012.37.13.png" alt=""></p>
<p>flow builder에 전달된 suspend 블록은 생산자 블록이라고도한다.
Android에서 생산자와 소비자 간의 계층은 그 이후의 게층 요구 사항에 맞게 데이터 스트림을 수정할 수 있다. flow를 변환하려면 중간 연산자를 사용한다. </p>
<p> <img src="https://images.velog.io/images/dev_jin/post/d8f3809c-7515-487b-90dd-983a111fa363/image.png" alt=""></p>
<p><code>latestMessages</code> 스트림이 flow의 시작점이라고 한다면 <code>map</code> 연산자를 사용해서 데이터를 다른 유형으로 변환할 수 있다. </p>
<p><img src="https://images.velog.io/images/dev_jin/post/81d589c3-96b4-43ec-aa88-8b4c981c2ca5/image.png" alt=""></p>
<p>예를 들어 <code>map</code> 람다 내부의 데이터 소스에서 받은 원본 메세지를 <code>MessagesUiModel</code>로 변환하는데, 이는 앱의 이 계층에 대해서는 더욱 우수한 추상화를 제공한다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/7ee48200-1001-4aed-a9fe-12beba365448/image.png" alt=""></p>
<p>각 연산자는 기능에 따라 데이터를 전송하는 새로운 flow를 생성하는데, 스트림을 필터링하여 중요한 알림이 포함된 해당 메세지의 flow를 가져올 수 있다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/320b063c-97a3-440a-91f9-856c25eb5544/image.png" alt=""></p>
<p>스트림에서 발생하는 오류는 어떻게 처리할까?
<code>catch</code> 연산자는 업스트림 flow 항목을 처리하는 동안 발생할 만한 예외를 찾아낸다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/f835b123-4396-4736-95ab-f00bd959b3fd/image.png" alt=""></p>
<p><strong>Upstream flow</strong>란?
프로듀서 블록에서 생성한 flow이고, 현재 연산자 전에 이들을 호출한다. </p>
<p><img src="https://images.velog.io/images/dev_jin/post/79dc5248-82ca-409f-ba9d-badad0388057/image.png" alt=""></p>
<p><strong>Downstream flow</strong>란?
마찬가지로 현재 연산자 이후에 발생하는 모든 것을 의미한다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/aea5f17b-974b-4a25-a5c9-3617c2856807/image.png" alt=""></p>
<p><code>catch</code>는 필요하거나 새 값을 전송할 때 예외를 다시 발생시킬 수 있다. 예를 들어 위의 코드는 <code>IllegalArgumentExceptions</code>를 다시 발생시키지만, 다른 예외가 발생하면 빈 목록을 전달한다.</p>
<hr>
<h2 id="observing-flows">Observing Flows</h2>
<p><img src="https://images.velog.io/images/dev_jin/post/e67d6d9b-2585-4534-b726-29dd7c08422f/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%209.56.03.png" alt=""></p>
<p>지금까지 스트림이 어떻게 생성되고 수정할 수 있는지 살펴봤고, 이제 수집 방법을 알아보자.
일반적으로 flow수집은 화면 데이터를 표시하기 위해 <em>UI계층에서 일어난다.</em></p>
<p><img src="https://images.velog.io/images/dev_jin/post/8e956233-0921-48a0-a044-57e640ee23ba/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%209.59.46.png" alt=""> </p>
<p>판초가 최신 메세지가 표시되는 목록을 보려면 terminal 연산자를 사용해서 값을 수신해야한다.
이때, 스트림의 모든 값을 전송 즉시 가져오려면 <code>collect</code>를 사용하면 된다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/51544e7a-aca8-42ef-9e53-900eb449c2f9/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2010.06.51.png" alt=""></p>
<p><code>collect</code>는 새로운 값이 생길 때마다 호출되는 함수를 매개변수로 받는데, 이는 suspend 함수이므로
코루틴 내에서 실행해야한다.</p>
<p>terminal 연산자를 flow에 적용하면 필요에 따라 플로가 생성되고 값을 전송하기 시작한다.
반면, intermediate 연산자는 일련의 연산자만 설정하며 항목을 flow로 전송했을 때 
간격을 두고 연산자를 실행한다. </p>
<p><img src="https://images.velog.io/images/dev_jin/post/9a2634a3-4727-4b78-a88a-e4f966efecc0/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2010.12.38.png" alt=""></p>
<p><code>userMessages</code>에서 collect를 호출할 때마다 새로운 flow가 생성된다. 생산자 블록은 정해진 간격에 따라 API에서 메세지를 새로고침하기 시작한다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/779aec97-2d66-4602-8753-774e2bd6d43e/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2010.14.38.png" alt=""></p>
<p>위의 flow를 코루틴 용어로 <code>cold flow</code>라고하는데, 이는 필요에 따라 생성되고 관찰되는 중에만 데이터를 전송하기 때문이다.</p>
<hr>
<h2 id="flows-in-android-ui">Flows in Android UI</h2>
<p><img src="https://images.velog.io/images/dev_jin/post/771ffd4b-d32e-480e-8a2e-487a85a58384/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2010.17.25.png" alt=""></p>
<p>이제 <strong>Android UI에서 최적으로 flow를 수집하는 방법</strong>에 대해 알아볼 것이다.
고려해야 할 점은 크게 두 가지이다.</p>
<blockquote>
<ol>
<li>앱이 백그라운드에 있을 때 리소스를 낭비하지 않아야 함.</li>
<li>구성 변경(Configuration Change)</li>
</ol>
</blockquote>
<p><img src="https://images.velog.io/images/dev_jin/post/29d7d335-a093-4e0c-853d-853ad380cd3f/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2010.19.43.png" alt=""></p>
<p><code>MessageActivity</code>에서 화면에 메세지 목록을 표시해야한다고 생각해보자.</p>
<p>그렇다면 flow에서 얼마나 오래 수집해야 할까?
UI가 적절히 동작하고, 화면에 UI가 표시되지 않을 때는 flow에서 수집을 중단해야한다.
<del><em>(판초가 이를 닦거나 낮잠을 잘때는 수도꼭지를 잠가야하는거처럼!)</em></del>
UI도 마찬가지로, 화면에 정보를 표시하지 않을 때는 플로에서 수집해서는 안된다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/1e83be59-b67e-4294-94d6-eaa913820cde/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2010.27.21.png" alt=""></p>
<p>여기에는 여러 옵션이 있는데, 위의 모든 옵션이 UI 수명 주기를 인식한다. 수명 주기 코루틴별 API나 LiveData를 사용할 수 있다. 예를 들어 <code>repeatOnLifecycle</code>과 <code>flowWithLifecycle</code> 등이 있다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/91edfc5f-4846-4239-94e7-f1d153be5876/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2010.30.15.png" alt=""></p>
<p><code>asLiveData</code> flow 연산자는 flow를 LiveData로 변환해서 UI가 화면에 표시되는 동안에만 항목을 관찰하고, UI에서는 평소처럼 LiveData를 소비하면된다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/80b3652e-b40b-4499-b101-7bc814719b98/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2010.51.07.png" alt=""></p>
<p>UI 계층에서 flow를 수집할 때 <code>repeatOnLifecycle</code>을 사용하는 게 좋은데, 이는 <code>Lifecycle.State</code>를 매개변수로 받는 suspend 함수이다. 이 API는 수명 주기를 인식하며 수명 주기가 해당 상태에 도달하면 
블록을 전달할 새 코루틴이 자동으로 시작되고, 수명 주기가 상태 아래로 떨어지면 진행 중인 코루틴이 취소된다. 블록 안쪽은 코루틴 컨텍스트이므로 <code>collect</code>를 호출할 수 있다.</p>
<p><code>repaetOnLifecycle</code>은 suspend 함수이므로 코루틴에서 호출해야한다.  액티비티 안에 있기때문에 lifecycleScope로 시작할 수 있다.</p>
<p>수명 주기가 <code>onCreate</code> 될때 이 함수를 호출하는 것이 좋고, 수명주기가 <code>onDestroy</code>될 때까지 실행을 다시 시작하지 않는다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/fbdde4c8-2f3a-4196-96af-b8aa93ccc74d/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2010.55.39.png" alt=""></p>
<p>여러 flow에서 수집을해야 할 경우
<code>repaetOnLifecycle</code> 블록에서 <code>launch</code>를 사용해 여러 코루틴을 생성해야한다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/2aa67fb1-f437-421f-bd3f-a34785ae1d14/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2010.57.07.png" alt=""></p>
<p><code>flowWithLifecycle</code> 연산자는 수집할 flow가 하나 뿐일때 <code>repaetOnLifecycle</code> 대신 사용할 수 있다.
이 API는 수명주기가 대상 상태에서 들어가고 나갈 때 항목을 전송하고 기본 생산자를 취소한다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/6ec197ea-ecd6-4f09-ac16-86faa7a839e2/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2011.00.04.png" alt=""></p>
<p>시각적으로 작동원리를 확인해보자!
액티비티의 수명 주기를 생성 시점부터 살펴보면 사용자가 홈 버튼을 누르면 백그라운드로 전송되고,
액티비티에서 onStop 신호를 받은 다음, 다시 onStart가 호출되었을 때 다시 앱을 연다.</p>
<p>STARTED 상태로 <code>repaetOnLifecycle</code>를 호출하면 UI가 화면에 표시되는 동안 flow 전송을 처리하고, 앱이 백그라운드로 이동하면 수집이 취소된다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/ee9d5d14-30a3-4ea0-9111-646af67f3c86/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2011.05.14.png" alt=""></p>
<p><code>repaetOnLifecycle</code>, <code>flowWithLifecycle</code>은 <code>lifecycle-runtime-ktx:2.4.0</code> 라이브러리에 새로 추가된 API이다. 이는 새로운 API이므로 Android UI에서 다른 방식으로 flow를 수집할 수도 있다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/417ce6f3-ca30-442d-ae64-cbefcd36d029/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2011.08.43.png" alt=""></p>
<p>예를 들면 <code>lifecycleScope</code>에서 시작한 코루틴에서 바로 수집할 수도 있지만, 이는 앱이 백그라운드에 있을 때에도 flow에서 수집하고 UI요소를 업데이트를 하기 때문에 이런 방식의 flow 수집은 위험할 수 있다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/031c6572-1769-4b3a-85a0-dc517f5f59d2/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2011.20.47.png" alt=""></p>
<p>다른 예시로 <code>LifecycleCoroutineScope.launchWhen X</code> API도 비슷한 문제가 있다.
<del><em>(판초가 물을 낭비하면 안되듯이)</em></del>
항목이 화면에 표시되지 않을 때는 우리도 flow에서 수집해서는 안된다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/ea0acc35-e85e-4d89-a178-87ece8e9d954/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2011.22.38.png" alt=""></p>
<p><code>lifecycleScope.launch</code>에서 직접 수집하는 경우 액티비티가 백그라운드에 있을 때도 계속 flow 업데이트를 받는다. 이는 낭비일 뿐만 아니라 위험하기도 한데, 예를 들어 앱이 백그라운드일 때 다이얼로그를 표시하면 충돌이 일어난다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/a8385263-70f8-4fca-8b79-4dca998dee29/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2011.28.18.png" alt=""></p>
<p>위의 문제를 해결하려면 <code>onStart</code>에서 수동으로 수집을 시작하고, <code>onStop</code>에서 수집을 중단해야하지만,
<code>repeatOnLifecycle</code>을 사용하면 작성했던 상용구 코드(보일러플레이트)를 제거할 수 있다.</p>
<p><code>launchWhenStarted</code>를 대안으로 고려할 경우, 앱이 백그라운드에 있을 때 flow 수집을 중단하기 때문에 <code>lifecycleScope.launch</code>보다 나은 편이다. 하지만 이 방법은 flow 생산자를 계속 활성화시켜서 화면에 표시되지 않을 아이템으로 메모리를 채울 수 있는 아이템으로 백그라운드에서 계속 전달할 수도 있다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/7b2d5e56-10dc-4be3-978f-49d2f5177487/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-07%20%EC%98%A4%ED%9B%84%2011.33.44.png" alt=""></p>
<p>UI에서 flow 생산자의 구현 방법을 알 수 없으므로 <code>repeatOnLifecycle</code>이나 <code>flowWithLifecycle</code>을 안전하게 사용하는게 좋다. </p>
<hr>
<p>이제 앱에서 <strong>구성 변경</strong>이 일어날 경우 몇 가지 요령을 알아보자.</p>
<p>flow를 뷰에 노출하면 수명주기가 서로 다른 두 요소 사이에 데이터를 전달해야 한다는 걸 
고려해야한다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/70632ffc-8121-4e81-9538-da797fee025e/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.19.15.png" alt=""></p>
<p>기기가 회전되었거나 구성 변경을 수신하면 모든 액티비티를 다시 시작하지만 <code>ViewModel</code>은 그렇지 않다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/64259b9e-1327-4d6a-9736-4ce1f5b1661b/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.22.38.png" alt=""></p>
<p><code>ViewModel</code>에서 모든 flow를 노출하는 건 아니다. 예를 들면 위의 <code>cold flow</code>인데, <code>cold flow</code>는 처음으로 수집될 때마다 다시 시작하기 때문에 리포지토리는 한 번 회전 후 다시 호출될 것 이다.
이 때문에 우리에게는 일종의 <strong>버퍼</strong>가 필요하다. 재생성 횟수와 상관없이 데이터를 보관하고 있다가 여러 컬렉터 사이에 공유하면 된다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/b5904897-f935-4d23-85c4-786b325fef4c/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.25.22.png" alt=""></p>
<p><code>StateFlow</code>가 바로 이런 목적으로 생성되었다. StateFlow는 물로 비유하면 물탱크이다. 
수집이 없더라도 데이터를 보관할 수 있다. 일회성 수집이 아닐 수 있으므로 액티비티나 프래그먼트와 함께 사용하는게 안전하다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/3b88da2e-a924-4ef3-9376-8b9ba2196133/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.28.06.png" alt=""></p>
<p>예를 들어 위의 코루틴에서처럼 <code>StateFlow</code>의 여러 버전을 사용하고 필요할 때마다 값을 업데이트할 수 있다. 하지만 위의 방식이 반응형이라고 하긴 어렵다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/61987910-076a-4127-9cf2-e5714b80aa35/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.30.55.png" alt=""></p>
<p><code>flow</code> -&gt; <code>StateFlow</code>
이렇게 변경하면 <code>StateFlow</code>가 업스트림 flow에서 모든 업데이트를 받아 최신값을 저장한다. 컬렉터가 없거나 많을 수 있으므로 ViewModel에 사용하기 좋다. 여러 유형의 flow가 있지만 <code>StateFlow</code>를 매우 정확하게 최적화할 수 있으므로 이 방식을 권장한다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/7a544087-8231-46a1-9da6-7b104d01c7d6/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.34.03.png" alt=""></p>
<p>flow를 StateFlow로 변환할때 <code>stateIn</code> 연산자를 사용할 수 있다.
이는 세 가지 매개변수를 받는다.</p>
<ul>
<li><code>initialValue</code>: StateFlow에 항상 값이 있어야 하기 때문</li>
<li><code>scope</code>: 코루틴 범위는 공유가 시작되는 시점을 제어하는데 
 여기에 viewModelScope를 사용할 수 있다.</li>
<li><code>started</code>: 흥미로운 부분인데, <code>WhileSubscribed(5000)</code>이 
 무슨 의미인지 이어서 두 가지 시나리오를 살펴보자.</li>
</ul>
<p><img src="https://images.velog.io/images/dev_jin/post/70607df5-c646-4f54-874b-ec7af1d5c13f/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.45.30.png" alt=""></p>
<p>첫 번째 시나리오
flow의 컬렉터인 액티비티가 일정 시간 파괴되었다가 다시 생성시키는 회전 시나리오이다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/4a44298a-0dc3-429c-aa90-3ee78f3c6220/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.47.15.png" alt=""></p>
<p>두 번째 시나리오
홈으로 이동해서 앱을 백그라운드로 보내는 것 </p>
<p><img src="https://images.velog.io/images/dev_jin/post/c2b84eb9-0dac-4c4c-a0cb-f328dba4ca3d/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.48.19.png" alt=""></p>
<p>회전 시나리오에서는 최대한 빠르게 전환하려면 flow를 다시 시작해서는 안된다.
그러나 홈으로 이동하는 두 번째 시나리오일 경우에는 배터리와 다른 리소스를 아끼기 위해 모든 flow를 중단해야한다.</p>
<p>그러면 어떤 시나리오인지 어떻게 탐지할까?
-&gt; 정답은 <strong>시간 초과</strong>를 사용하는 것이다.</p>
<p><code>StateFlow</code>의 수집이 중단되었을 때 모든 업스트림 flow를 즉시 중단하는 것은 아니다.
오히려 약 5초정도 잠시 기다린다. 시간 초과 전에 flow를 수집하면 업스트림 flow가 취소되지 않는다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/6bc19612-07be-48c9-bc8a-6d118f0dd074/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.52.45.png" alt=""></p>
<p><code>WhileSubscribed(5000)</code>은 바로 그런 일을 하는 파라미터이다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/60c50673-bf45-46a9-856e-d8024a408da5/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.54.08.png" alt=""> </p>
<p>위의 표에는 앱이 백그라운드에 갔을 때의 반응을 시각적으로 볼 수 있다. 홈 버튼을 누르기 전에 뷰가 업데이트를 수신하고 <code>StateFlow</code>는 정상적으로 업스트림 flow를 생성한다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/2ca1bbf1-52d6-48d5-b888-c67d9a836316/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.56.11.png" alt=""></p>
<p>이제 뷰가 중단되면 수집이 즉시 종료된다. 그러나 <code>StateFlow</code>는 구성 방법으로 인해
업스트림 flow를 중단하는데 5초가 걸리고, 제한시간이 지나면 업스트림 flow가 취소된다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/e7a9c99f-9c52-4020-a57a-0d49d53c302d/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%201.58.40.png" alt=""></p>
<p>사용자가 앱을 다시 열 경우 업스트림 flow가 자동으로 다시 시작된다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/6b3d3f31-2c77-4259-a406-b90996efa016/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%202.00.11.png" alt=""></p>
<p>그러나 회전 시나리오에서 뷰는 5초 이내로 잠시 중단된다. 따라서 <code>StateFlow</code>는 절대 복원되지 않고
모든 업스트림 flow를 활성 상태로 유지하며 아무 일도 없었던 것처럼 사용자에게 회전 인스턴스를 보낸다. </p>
<p>다시말해서 <code>StateFlow</code>를 사용하여 ViewModel에서 flow를 노출하거나 <code>asLiveData</code>를 사용해서
이와 동일한 작업을 실행하는게 좋다.</p>
<hr>
<h2 id="testing-flows">Testing Flows</h2>
<p><img src="https://images.velog.io/images/dev_jin/post/2933e9d3-55cd-4487-a529-eb66a46054f5/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%202.05.13.png" alt=""></p>
<p>flow 테스트는 데이터 스트림이라 까다로울 수 있어서 몇 가지 요령을 살펴보도록 하자.
두 가지 시나리오가 있는데</p>
<p><img src="https://images.velog.io/images/dev_jin/post/50ce70cd-bd14-40b9-adc7-7af82256ce5d/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%202.08.03.png" alt=""></p>
<p>첫 번째는 테스트 대상이 무엇이든 <code>UnitUnderTest</code>가 flow를 받는 시나리오이다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/028369a3-77b7-458a-aeca-25efbf205a0b/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%202.10.05.png" alt=""></p>
<p>이 경우에는 종속성을 가상 생산자로 교체하여 테스트하는 게 편리한데,
예를 들어 이 가상 리포지토리를 프로그래밍하여 각 테스트 사례에 필요한 걸 전송할 수 있다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/8047983f-00a6-44ee-9a32-0121b6787927/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%202.19.33.png" alt=""></p>
<p>간단한 <code>cold flow</code>의 예를 들자면, 테스트 자체는 테스트 아래 주제의 결과에 대한 assertion을 만든다.
이는 flow나 다른게 될 수도 있다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/f33c58fd-994d-4d95-a302-51618b718ae0/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%202.14.38.png" alt=""></p>
<p>두 번째는 <code>UnitUnderTest</code>가 flow를 노출하고 이 값이나 값 스트림을 인증하고 싶다면 
여러가지 방법으로 수집할 수 있다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/52abf648-f4ab-4a07-ae11-8e4b13234ca5/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%202.21.25.png" alt=""></p>
<p>flow에서 <code>first()</code> 메서드를 호출하면 첫 항목을 수신하고 수집을 중단할 때 까지 계속 수집한다. </p>
<p><img src="https://images.velog.io/images/dev_jin/post/17dbde64-a452-41d5-89b2-d3d0aa026ad0/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%202.23.09.png" alt=""></p>
<p>또한 <code>take(5)</code>와 같은 연산자로 <code>toList</code> terminal 연산자를 호출해 메세지를 5개만 수집할 수도 있는 유용한 방법이다.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/e952c977-3478-46ce-9b94-34aa5b3a1aa9/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-03-08%20%EC%98%A4%EC%A0%84%202.25.49.png" alt=""></p>
<p>더 많은 자료는 위 링크를 확인해주세요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] Retrofit2↔REST API ]]></title>
            <link>https://velog.io/@dev_jin/Android-Retrofit2REST-API</link>
            <guid>https://velog.io/@dev_jin/Android-Retrofit2REST-API</guid>
            <pubDate>Mon, 21 Feb 2022 09:42:22 GMT</pubDate>
            <description><![CDATA[<h2 id="rest">REST</h2>
<p><strong>REST(REpresentational State Transfer)</strong>란 자원의 표현에 의한 상태 전달을 뜻함.
REST는 네트워크 상에서 client와 server 사이의 통신 방식 중 하나로 프로토콜이나 표준이 아닌 아키텍처 스타일이다.</p>
<h2 id="rest-api">REST API</h2>
<p><strong>API(Application Programming Interface)</strong>는 애플리케이션 소프트웨어를 구축하고 통합하는 정의 및 프로토콜 세트
API는 정보 제공자와 정보 사용자 간의 약속으로, 소비자에게 필요한 콘텐츠(호출)와 생산자에게 필요한 콘텐츠(응답)을 구성한다.</p>
<p><strong>REST API</strong>란?
HTTP 요청을 보낼 때 어떤 URI에 어떤 메소드를 사용할지에 대한 약속
REST API를 통해 요청이 수행될때 REST API는 리소스 상태에 대한 표현을 요청자에게 하이퍼텍스트 전송 프로토콜(HTTP)을 통해 전송함. 
이러한 요청을 수신하면 REST용으로 설계된 API(RESTful API 또는 RESTful 웹 서비스)가 HTML, XML, 일반 텍스트, JSON과 같은 다양한 형식으로 메시지를 반환할 수 있음.</p>
<blockquote>
<p><code>JSON형식의 데이터 구조</code></p>
</blockquote>
<pre><code class="language-kotlin">{
&quot;name&quot; : &quot;JINA&quot;,
&quot;age&quot;  : 23
}</code></pre>
<ul>
<li>중괄호 {} -&gt; 객체</li>
<li>대괄호 [] -&gt; 배열</li>
<li>내부에서는 <code>key:value</code>로 각 속성 값을 정의<ul>
<li>key와 value는 콜론(:)에 의해 구분</li>
<li>각각의 속성은 쉼표(,)로 구분됨.</li>
</ul>
</li>
</ul>
<p>JSON(JavaScript Object Notation)은 프로그래밍 언어에 구애받지 않고 사람과 기계 모두가 읽을 수 있기 때문에 가장 일반적으로 사용되는 파일 형식이다.</p>
<h3 id="rest-아키텍처-가이드라인">REST 아키텍처 가이드라인</h3>
<ol>
<li>client-server</li>
<li>stateless</li>
<li>cache</li>
<li><strong>uniform interface</strong></li>
<li>layered system</li>
<li>code-on-demand (optional)
<a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm">자세한 내용</a></li>
</ol>
<h3 id="restful-api">RESTful API</h3>
<p>RESTful API는 위의 REST의 가이드라인을 잘 준수하는 API를 뜻한다. </p>
<hr>
<h2 id="retrofit2↔rest-api">Retrofit2↔rest API</h2>
<p><strong>Retrofit2</strong>는 Android및 java에서 쓸 수 있는 REST 클라이언트이다.</p>
<p>Retrofit2는 JSON 구조에 대해 정의해야 하는 POJO(Plain Old Java Object)를 사용하여 JSON 응답을 자동으로 직렬화하는데,(파싱 필요없음)
JSON을 직렬화하려면 먼저 Gson 으로 변환하는 컨버터가 필요함.
일반적으로 JSON의 경우 GSON을 사용하지만 XML이나 기타 프로토콜을 사용하면 그에 맞는 converter을 추가할 수 있음.</p>
<blockquote>
<ul>
<li><strong>직렬화</strong>: 객체를 직렬화하여 전송 가능한 형태로 만드는 것, 객체들의 데이터를 연속적인 데이터로 변형하여 Stream을 통해 데이터를 읽도록 해준다. </li>
</ul>
</blockquote>
<ul>
<li><strong>역직렬화</strong>: 직렬화된 파일 등을 역으로 직렬화해서 다시 객체의 형태로 만드는 것, Stream 데이터를 읽어서 객체의 형태로 복원한다.</li>
</ul>
<p>Retrofit2는 Rest API에서 사용하는 CURD 기능을 모두 제공한다. 
POST(create), PUT(update), GET(read), DELETE(delete) 등을 annotation (@)을 
활용하여 손쉽게 통신 코드를 작성할 수 있다.</p>
<p>Retrofit2는 기본적으로 OkHttp를 네트워킹 계층으로 활용하며 그 위에 구축된다.
OkHttp는 HTTP를 더 간편하고 효율적으로 쓸 수 있도록 도와주는 클라이언트 라이브러리임.</p>
<hr>
<h2 id="retrofit2">Retrofit2</h2>
<ol>
<li>DTO (POJO)작성 - &#39;Data Transfer Object&#39;, &#39;Plain Old Java Object&#39; 형태의 모델(Model) / JSON 타입변환에 사용</li>
<li>interface 정의</li>
<li>Retrofit2 인스턴스 생성</li>
</ol>
<h2 id="간단하게-사용해보기">간단하게 사용해보기</h2>
<p>retrofit2 관련 라이브러리 추가 및 manifest에 인터넷 권한 추가
🔽
<code>interface 작성</code></p>
<pre><code class="language-kotlin">interface LoginService {
    @FormUrlEncoded
    @POST(&quot;/app_login/&quot;)
    fun requsetLogin(
        @Field(&quot;userid&quot;) userid:String,
        @Field(&quot;userpw&quot;) userpw:String

    ) : retrofit2.Call&lt;LoginResponse&gt;
}</code></pre>
<ul>
<li>API 인터페이스를 생성 후 어노테이션을 이용하여 HTTP Method인 POST, GET, PUT, DELETE를 설정</li>
<li>Call은 응답이 왔을때 Callback으로 불려질 타입으로 LoginService를 통하여 HTTP 요청을 원격 웹서버로 보낼 수 있음.</li>
<li>만약 헤더를 모든 요청마다 추가해야하면 한다면 OkHttp interceptor를 사용하기</li>
</ul>
<p><code>DTO</code></p>
<pre><code class="language-kotlin">class LoginResponse(val code: String, val msg: String) {
}</code></pre>
<ul>
<li>Rest API의 데이터 구조에 맞게 생성</li>
<li>JSON 데이터의 속성명과 변수명 + 타입(ex String,Int,Boolean)이 일치 해야함.</li>
<li><code>@SerializedName</code>을 사용하면 변수명과 값의 명이 다르거나 순서가 달라도 알아서 맞춰서 들어감.</li>
</ul>
<p><code>Retrofit.Build를 통해 Retrofit 인스턴스 생성</code></p>
<ul>
<li>baseUrl, Converter, Client 설정</li>
<li>converter은 여러개 추가 가능</li>
<li>addConverterFactory에는 GsonConverter를 추가하여 JSON 형식을 DTO Class 형식으로 자동변환해줌.<pre><code class="language-kotlin">var retrofit = Retrofit.Builder()
          .baseUrl(&quot;http://&quot;)
          .addConverterFactory(GsonConverterFactory.create()).build()</code></pre>
<code>Interface 객체 구현</code><pre><code class="language-kotlin">var loginService: LoginService = retrofit.create(LoginService::class.java)</code></pre>
</li>
</ul>
<p><code>통신작업 실행</code></p>
<pre><code class="language-kotlin">LoginService.requsetLogin(id, pw).enqueue(object: Callback&lt;LoginResponse&gt;{
                override fun onFailure(call: Call&lt;LoginResponse&gt;, t: Throwable) {
                    //실패할 경우


                }

                override fun onResponse(call: Call&lt;LoginResponse&gt;, response: Response&lt;LoginResponse&gt;) {
                    //정상응답이 올 경우

                }
                }</code></pre>
<ul>
<li>비동기 enqueue 작업으로 실행, 통신종료 후 이벤트 처리를 위해 Callback 등록</li>
<li>onResponse, onFailure 메인스레드에서 처리</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jetpack Compose개념]]></title>
            <link>https://velog.io/@dev_jin/Androidkotlin-Jetpack-Composer%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@dev_jin/Androidkotlin-Jetpack-Composer%EA%B0%9C%EB%85%90</guid>
            <pubDate>Tue, 25 Jan 2022 17:14:01 GMT</pubDate>
            <description><![CDATA[<p><a href="https://youtu.be/7Mf2175h3RQ">What&#39;s new in Jetpack Compose</a>
위의 영상을 보고 정리한 글입니다.😃</p>
<h2 id="compose란">Compose란?</h2>
<ul>
<li><p>시대가 변하면서 기기 성능의 향상과 더불어 앱에 대한 기대가 높아지면서 최신 아키텍쳐을 기반으로 코틀린을 활용하는 선언적 UI도구 키트인 Jetpack Compose가 나오게 되었다. </p>
</li>
<li><p>XML 레이아웃을 수정하거나 Layout Editor를 사용하는 대신 Jetpack Compose 함수를 호출하여 원하는 요소를 말하면 Compose 컴파일러에서 나머지 작업을 완료할 수 있다.</p>
</li>
</ul>
<h3 id="compose의-주요-개념-2가지"><strong>compose의 주요 개념 2가지</strong></h3>
<h4 id="1-declarative">1. <em>Declarative</em></h4>
<p><img src="https://images.velog.io/images/dev_jin/post/6e1bd335-8b3f-49dc-aa41-cbe7cde1c7e8/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-25%20%EC%98%A4%ED%9B%84%2011.04.11.png" alt="">
기존의 xml을 사용하는 방식은 데이터가 바뀌면 UI도 업데이트 해줘야 했음. 
ex)<del>데이터베이스 혹은 네트워크가 호출될때, 사용자와의 인터랙션이 끝나면 새로운 정보로 UI를 업데이트해서 데이터를 동기화해야했음.</del></p>
<p><img src="https://images.velog.io/images/dev_jin/post/b5169b85-cf80-469b-a3e2-b3bb5f2a3d90/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-25%20%EC%98%A4%ED%9B%84%2010.35.39.png" alt=""></p>
<p><img src="https://images.velog.io/images/dev_jin/post/df1568ce-3d0a-4eca-aecf-9845dc5af345/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-25%20%EC%98%A4%ED%9B%84%2010.36.21.png" alt=""></p>
<p>compose를 사용하면 <strong>상태를 바로 UI로 변환</strong>함. UI는 한 번 생성하면 변경이 불가능하기 때문에 compose는 위의 사진처럼 앱 상태가 바뀌면 UI전체를 다시 생성함.
(변경되지 않았다면 다시 생성하지 않음.) </p>
<p>코드는 특정 상태에 대한 UI형태를 설명할 뿐이지 어떻게 생성하는지는 설명하지 않음. 
🤔그럼 대체 어떻게 상태를 UI로 변환하는걸까???
<img src="https://images.velog.io/images/dev_jin/post/c38b073f-ebbc-40d8-8021-4c91a8214cf0/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-25%20%EC%98%A4%ED%9B%84%2011.26.16.png" alt="">-&gt; 데이터를 매개변수로 받아서 UI를 전달하기 때문에 UI가 동기화 상태에서 벗어나지 않음.</p>
<p>상태, 메세지 목록이 바뀌었을때 위의 함수를 실행하면 새로운 UI가 생성됨. 
이를 <strong>리컴포징</strong>이라고 함.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/37951a2d-3a61-42ba-b37b-69c8e41a9ee5/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-25%20%EC%98%A4%ED%9B%84%2011.25.43.png" alt="">
🙄메세지 목록은 어떻게 바뀌는지? 
-&gt; 콜 스택을 처리하는 동안 ViewModel이 메세지의 liveData를 보여줌. 
-&gt; 데이터를 관찰함. -&gt; messages 필드를 읽는 컴포저블은 새 데이터가 업데이트 될때마다 리컴포저블됨.</p>
<p>이 과정을 직접 관찰할 필요없이 Compose 컴파일러는 어느 컴포저블이 상태를 읽는지 추적하고 상태가 바뀌면 자동으로 다시 실행함.(입력이 변경된 컴포저블만 다시 실행함.)</p>
<p>단, 각 컴포저블은 변경할 수 없고 컴포저블을 참조하거나 내용을 업데이트 할 수 없음. 
<strong>정보를 입력할때는 모두 매개변수로 컴포저블에 전달해야함.</strong></p>
<p>하지만 위의 내용이 컴포저블이 고정돼 있다는 뜻은 아님!!
예시를 들자면
<img src="https://images.velog.io/images/dev_jin/post/21271b54-e2ab-40f1-bc64-545e0769a128/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-25%20%EC%98%A4%ED%9B%84%2011.51.07.png" alt="">
view 방식에서는 체크박스를 누르면 체크박스에 체크가 생겼었음. 하지만 컴포저블에서는 체크박스를 눌러도 체크가 생기지 않음. 왜냐하면 체크박스의 상태는 상수로 전달했기 때문임. 체크가 생기도록 하려면 checked를 false에서 true로 바꿔줘야함.
<img src="https://images.velog.io/images/dev_jin/post/e6013a98-a657-443f-8e17-70413c77e8ce/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-25%20%EC%98%A4%ED%9B%84%2011.58.54.png" alt=""></p>
<p>체크박스를 클릭하면 onCheckChange 이벤트를 제공하는데, 이 콜백에서 체크 선택여부를 결정하는 변수를 바꿀 수 있음. 이렇게 하면 체크박스를 눌렀을 때 체크가 생김.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/511aef00-8a16-4dac-b76c-ddd7b904ac1d/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%2012.03.26.png" alt="">
위의 콜백처럼 상태를 바꾸지 않으면 체크박스를 눌러도 아무런 변화가 없다는 것을 이해하는게 중요함. 이 내용이 바로 선언적 UI의 핵심 개념임. 이렇게 하면 만약 검증을 실행해서 유효할 때만 디스플레이를 업데이트하고 싶을 경우 코드로 완전히 통제하기 때문에 검증이 실패한 시점에 다시 돌아와서 변경을 취소할 필요가 없음.</p>
<h4 id="요소는-전달되는-매개변수가-완전히-통제함">요소는 전달되는 매개변수가 완전히 통제함.</h4>
<p>단일 진실 공급원를 제공하는데, 이는 동기화 해야할 상태를 없애는 것을 의미.
<img src="https://images.velog.io/images/dev_jin/post/d57fca03-148d-43b2-8b59-7b7ab2e25aba/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%2012.17.41.png" alt="">
🧐만약 입력된 데이터가 바뀌어서 리컴포지션을 호출하더라도 유지하고 싶은 변수가 있다면?
-&gt; <code>remember</code>을 사용하면 이전의 값을 기억할 수 있고, 기억한 값을 통해 재할당을 방지하거나 상태를 고정할 수 있음.</p>
<p>위의 내용을 더 구체적인 예시로 보자면
<img src="https://images.velog.io/images/dev_jin/post/16d2e0fb-c9e4-42a1-a1d0-2cc73957a355/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%2012.18.06.png" alt="">
state와 update lamda를 컴포저블의 매개변수로 전달하고 단일 진실 공급원으로 로직을 쌓을 수 있음.</p>
<blockquote>
</blockquote>
<p><em><strong>선언적 UI의 핵심</strong></em></p>
<ul>
<li>특정 상태의 UI를 완전히 설명함.</li>
<li>상태가 바뀌면 프레임워크에서 UI업데이트를 처리함.</li>
</ul>
<p><img src="https://images.velog.io/images/dev_jin/post/bced4292-be2b-4239-93a1-2dc4e1667792/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-25%20%EC%98%A4%ED%9B%84%2010.44.56.png" alt="">
Compose는 여러가지 어플리케이션 아키텍쳐와 호환되지만 <strong>단방향 데이터 플로우를 따르는 아키텍처와 잘맞음.</strong></p>
<p>viewModel이 화면 상태의 단일 스트림을 노출 -&gt; Compose UI에서 관찰 -&gt; 각 구성요소의 매개변수로 전달 -&gt; 각 구성요소는 필요한 상태만 수신하므로 데이터를 바꿀 때만 업데이트 하면됨.
<img src="https://images.velog.io/images/dev_jin/post/65fa77bc-09f3-4568-a8f0-1df564d2db7b/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%2012.48.17.png" alt="">
ViewState 객체의 단일 스트림을 생성하면 상태 변경을 한 곳에서 처리하는데 도움이 됨.(전체적인 화면 상태를 추론하고 오류를 낮추기 쉬움.) 이 패턴을 사용하면 입력에 따라 완전히 제어되기 때문에 간단하게 컴포저블을 테스트 할 수 있음.</p>
<hr>
<h4 id="2-ui-toolkit">2. <em>UI Toolkit</em></h4>
<p>composable은 다양한 UI구성요소 도구키트를 제공함. Jetpack Compose는 머터리얼 디자인 구성요소와 테마시스템을 구현하고, 어플리케이션을 어셈블하는 데 필요한 구성요소도 제공함.
<img src="https://images.velog.io/images/dev_jin/post/a4c12c80-97dd-4013-9af8-1b8237c30a19/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%2012.57.25.png" alt="">
Buttons, Cards, FABs, AppBars등이 해당하는데, 모든 구성요소는 기본적으로 material 스타일링을 따르고 
<img src="https://images.velog.io/images/dev_jin/post/bf326cde-dfbc-4275-97cc-43855de3aa75/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%2012.59.53.png" alt="">
material테마를 구현하기 때문에 모든 구성요소를 브랜드에 맞게 원하는대로 커스텀할 수 있음.(색, 도형, 서체 스타일 등 지정가능)
<img src="https://images.velog.io/images/dev_jin/post/a3920859-2ebb-474e-86ea-2691a273b2ac/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%201.03.06.png" alt="">
Compose는 간단하지만 강력한 새로운 레이아웃 시스템을 제공하는데 행과 열을 기반으로 하는 가로와 세로 선형 레이아웃과 비슷하다고 보면 됨. View방식과 달리 Compose레이아웃 모델은 여러 척도로 전달할 수 없어서 중첩된 레이아웃에 적합함.
<img src="https://images.velog.io/images/dev_jin/post/dd628d92-0b31-48fb-86a6-c645c8257cdb/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%201.08.51.png" alt="">
척도와 배치를 직접 설정해서 레이아웃을 만들고 싶다면 위처럼 함수를 구현하면 됨.
<img src="https://images.velog.io/images/dev_jin/post/13ac61d0-bb40-4724-b051-799a374195f8/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%201.08.06.png" alt="">
Compose에서 크게 개선된 기능은 애니메이션 시스템임. 훨씬 간단하게 사용할 수 있고 보다 효과적이고 간단하게 UI에 모션을 줄 수 있음.</p>
<hr>
<h4 id="compose-장점"><strong>Compose 장점</strong></h4>
<ul>
<li><p>접근성
Compose는 UI에 병렬트리를 생성하는 시멘틱 시스템을 기반으로 접근성 서비스에 더 많은 정보를 제공하거나 UI요소를 매칭해서 사용하는데 도움이 됨.</p>
</li>
<li><p>테스트
Compose는 테스트 기능을 극대화하는 전용 테스트 아티팩트를 제공하고 독립적으로 컴포저블을 테스트하는 간편한 API를 제공함. 테스트 규칙에서 clock이 노출되고 그 후 UI가 업데이트되고 이를 제어하기 위한 API가 제공됨.</p>
</li>
</ul>
<p><img src="https://images.velog.io/images/dev_jin/post/9313478f-7e78-4f3d-a85e-861f229cb5de/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%201.27.27.png" alt="">
이는 애니메이션 코드를 테스트할 때도 테스트를 완전히 통제할 수 있음.
<img src="https://images.velog.io/images/dev_jin/post/9853adb5-3bb0-4140-af0b-de6116b1950a/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%201.34.07.png" alt="">
Compose는 코틀린으로만 개발됐고, 우수한 언어 기능을 활용해 강력,간결,직관적인 API를 구축할 수 있음. 
<img src="https://images.velog.io/images/dev_jin/post/384a54f4-0bc4-4aa7-aa68-f7e880271f07/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%201.31.13.png" alt="">
코루틴을 사용하면 간단한 비동기식 API를 작성할 수 있음.(제스처, 스크롤, 애니메이션 등)
제스처를 애니메이션으로 핸드오프하는 것처럼 비동기식 이벤트를 결합한 코드를 간결하게 작성할 수 있음. 구조적 동시성을 통해 이런 취소와 정리를 제공함.
<img src="https://images.velog.io/images/dev_jin/post/53e0eefe-e4ad-4094-98e8-7349555c5790/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%201.42.51.png" alt="">
코틀린은 툴링으로 구성된 강력한 에코시스템이 있는데, UI구성요소를 새로운 함수로 추출해서 간단하게 재사용할 수 있음. 
<img src="https://images.velog.io/images/dev_jin/post/3bc6d367-2c62-4a11-bc76-8d129d772d03/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%201.45.16.png" alt="">
Compose는 앱이 다크모드에 반응하도록 간편하게 테마를 설정할 수 있음.
시스템이 다크모드인지 체크-&gt;색상표 전환
Compose는 모든 테마가 런타임에서 실행되기 때문에 기본 테마 외에 동적테마도 손쉽게 지원함.
<img src="https://images.velog.io/images/dev_jin/post/1860a52f-4a0c-4a60-b059-1edaa4435d00/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-25%20%EC%98%A4%ED%9B%84%2010.51.35.png" alt="">
<strong>Compose는 필요에따라 점진적으로 도입할 수 있음.</strong>
화면의 작은 요소부터 UI의 큰 부분이나 화면 전체를 교체하면 됨. 
<img src="https://images.velog.io/images/dev_jin/post/c669de10-d3bc-485e-b731-3ec55e8c6f95/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%201.51.07.png" alt="">
Views에 Compose를 포함할 수 있고 Compose에서 Views를 호스팅할 수도 있음.
(Compose로 구축되지 않은 컨텐츠를 표시할 때 유용)
<img src="https://images.velog.io/images/dev_jin/post/1b3d4c1c-c2c8-440b-8455-e72714b60e7f/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-25%20%EC%98%A4%ED%9B%84%2010.53.10.png" alt="">
View와의 상호운용성 외에도 다른 주요 라이브러리와의 통합을 제공함. 
material또는 Appcompat XML테마를 Compose로 변환할때는 어댑터를 제공함.
<img src="https://images.velog.io/images/dev_jin/post/33b51aec-6b68-4af3-9ad0-1215ea11072f/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-01-26%20%EC%98%A4%EC%A0%84%201.58.54.png" alt="">
<code>@Preview</code>를 통해 구성요소와 함께 레이아웃을 미리 볼 수 있음. 또한 독립적이고 반복적으로 개선할 수 있고, 미리 구현한 애니메이션을 바로 확인할 수 있음.</p>
<blockquote>
</blockquote>
<h4 id="🎯용어정리">🎯용어정리</h4>
<p><strong>단일 진실 공급원(single source of truth, SSOT)</strong></p>
<ul>
<li>정보 모형과 관련된 데이터 스키마를 모든 데이터 요소를 한 곳에서만 제어 또는 편집하도록 조직하는 관례를 이른다. 데이터 요소로의 가능한 연결(관계 스키마 내의 다른 영역이나 원거리의 연방 데이터베이스에서도 올 수 있음)은 모두 참조로만 이루어진다. </li>
<li>데이터가 위치한 다른 모든 곳들은 단지 으뜸되는 &quot;진실 공급원&quot;의 위치를 참조하기만 하므로, 으뜸되는 위치의 데이터 요소를 갱신하면 수정 사항 반영을 빠뜨릴 데이터의 사본이 존재할 가능성 없이 시스템 전체에 전파되게 된다.</li>
<li><em>데이터 스트림(Data Stream)*</em></li>
<li>데이터의 흐름</li>
<li>작은 데이터 조각들이 하나의 줄기를 이루며, 순서대로 물 흐르듯이 전송되는 데이터 열(列)</li>
</ul>
<p><a href="https://ko.wikipedia.org/wiki/%EB%8B%A8%EC%9D%BC_%EC%A7%84%EC%8B%A4_%EA%B3%B5%EA%B8%89%EC%9B%90">단일 진실 공급원 위키피디아</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/kotlin] RecyclerView ListAdapter + Diffutil]]></title>
            <link>https://velog.io/@dev_jin/Androidkotlin-RecyclerView-ListAdapter</link>
            <guid>https://velog.io/@dev_jin/Androidkotlin-RecyclerView-ListAdapter</guid>
            <pubDate>Wed, 24 Nov 2021 08:12:13 GMT</pubDate>
            <description><![CDATA[<h2 id="listadapter란">ListAdapter란?</h2>
<p>기본적으로 RecyclerView를 사용하기 위해서는  Adapter, LayoutManager, ViewHolder가 필요한데 Adapter는 데이터 리스트를 실제 눈으로 볼 수 있게 item으로 변환하는 중간다리 역할을 한다.</p>
<p>기존에 <code>notifyDataSetChanged()</code>를 통해 데이터를 업데이트하면 조금만 바뀌어도 나머지 데이터도 모두 업데이트되는 불필요함이 있었는데 ListAdapter를 사용하게되면 백그라운드 스레드에서 <strong><code>DiffUtil</code></strong>를 통해 불필요한 교체 비용을 줄일 수 있다.</p>
<blockquote>
</blockquote>
<h3 id="diffutil"><strong><code>DiffUtil</code></strong></h3>
<p>RecyclerView의 성능을 개선할 수 있게 해주는 유틸리티 클래스로 현제 데이터 리스트와 교체될 리스트를 비교해서 무엇이 바뀌었는지 비교해서 바뀐 부분의 데이터만 바꿔준다.</p>
<blockquote>
</blockquote>
<h3 id="asynclistdiffer"><strong><code>AsyncListDiffer</code></strong></h3>
<p>DiffUtil을 더 편리하게 사용할 수 있게 해주는 클래스다. 백그라운드 스레드에서 처리하도록 지원해주며 AsyncListDiffer의 submitlist 메서드를 통해 이 모든과정이 처리된다.</p>
<h4 id="binding--listadapter--asynclistdiffer-사용방법">binding + ListAdapter + AsyncListDiffer 사용방법</h4>
<p><img src="https://images.velog.io/images/dev_jin/post/d14defc4-534e-4325-8e84-703b0d3fa650/adapter2.PNG" alt=""></p>
<ul>
<li><p>아이템을 비교하기 위한 DiffUtil.ItemCallback을 구현한다. </p>
</li>
<li><p><code>areItemsTheSame()</code>: oldItem과 newItem이 동일한지 확인하는데 item을 구분할 수 있는 값을 사용한다.</p>
</li>
<li><p><code>areContentsTheSame()</code>: <code>areItemsTheSame()</code>이 true일때 호출되며, oldItem과 newitem이 같은 데이터를 포함하는지 확인하는데 데이터 클래스의 모든 필드를 비교한다. 두 아이템 사이에 차이점이 있는경우 <code>DiffUtil</code>에 업데이트 되었다고 알려준다.</p>
</li>
<li><p>Adapter 내부에 <code>getItemCount()</code>, <code>submitList()</code>등 원하는 메소드를 사용한다.</p>
</li>
<li><p><code>getItemCount()</code>: protected method로 클래스 내부에서 사용하고 ListAdapter + Diffutil에서는 리스트의 아이템을 가져오는데 사용된다.</p>
</li>
<li><p><code>submitList()</code>: 일반 어댑터의 add(), notifyDataSetChanged() 대신 사용하고 리스트를 변경할 때 사용한다.</p>
</li>
</ul>
<p><img src="https://images.velog.io/images/dev_jin/post/08068f3e-fc03-4527-a29d-72fb2fbf4de2/differ3.PNG" alt=""></p>
<p><a href="https://developer.android.com/codelabs/kotlin-android-training-diffutil-databinding#3">참고사이트1</a>
<a href="https://developer.android.com/reference/androidx/recyclerview/widget/ListAdapter#protected-constructors_1">참고사이트2</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/Kotlin] Room]]></title>
            <link>https://velog.io/@dev_jin/AndroidKotlin-Room</link>
            <guid>https://velog.io/@dev_jin/AndroidKotlin-Room</guid>
            <pubDate>Tue, 16 Nov 2021 09:41:52 GMT</pubDate>
            <description><![CDATA[<h2 id="🎪room">🎪Room</h2>
<p>Room은 <strong>로컬 데이터베이스에 데이터를 저장할때</strong> 사용한다. 기기가 네트워크에 액세스할 수 없을때 오프라인 상태일때도 사용자가 여전히 콘텐츠를 탐색할 수 있으며 기기가 다시 온라인 상태가 되면 콘텐츠 변경사항이 서버에 동기화된다. Room은 위와 같은 문제를 자동으로 처리하기 때문에 SQLite 대신 Room을 사용할 것을 권장하고있다.</p>
<h2 id="room-구조">Room 구조</h2>
<p>📌<strong><em>Room의 구성요소 3가지</em></strong></p>
<ol>
<li><strong>Room Database</strong>: 데이터베이스를 생성하고 관리하는 데이터베이스 객체 만들기 위한 추상 클래스 </li>
<li><strong>DAO</strong>: Data Access Object의 약자로 Dao를 통해 쿼리문을 사용해 데이터베이스에 접근하는 인터페이스이다. </li>
<li><strong>Entities</strong>: 데이터베이스 내의 테이블 </li>
</ol>
<h2 id="room-사용방법">Room 사용방법</h2>
<p>Entity</p>
<ul>
<li>Entitiy에는 하나 이상의 기본키(<code>@PrimaryKey</code>)를 설정해야한다.</li>
<li>열로 사용할 변수 설정은 <code>@CcolumnInfo</code> 어노테이션을 적어주면 된다. 기본적으로 변수 명은 열 이름이 되지만 별도로 열 이름을 설정하고 싶다면 위의 코드처럼 name 속성을 주면된다.</li>
</ul>
<hr>
<p>DAO</p>
<p><img src="https://images.velog.io/images/dev_jin/post/03ce42b3-934f-4a0e-9149-bfceaf2cb6ee/dao.PNG" alt=""></p>
<ul>
<li><p>DAO로 정의하기 위해선 <code>@Dao</code>라는 어노테이션이 필요하다. </p>
</li>
<li><p><code>@Insert</code>를 붙이면 테이블에 데이터 삽입, <code>@Update</code>를 붙이면 테이블의 데이터 수정, <code>@Delete</code>를 붙이면 테이블의 데이터 삭제이다.</p>
</li>
<li><p>이 외에 다른 기능을 하는 메서드를 만들고 싶다면 
<code>@Query</code> 어노테이션을 붙이고 그 안에 어떤 동작을 할 건지 sql 문법으로 작성한다.</p>
</li>
</ul>
<hr>
<p>Database
<img src="https://images.velog.io/images/dev_jin/post/f2629702-74b9-41be-be04-25fe8f898824/database.PNG" alt=""></p>
<ul>
<li>@Database 어노테이션이 필요하다.</li>
<li>클래스는 추상 클래스로 작성되어야 한다.</li>
<li>RoomDatabase()를 상속해야하며 매개 변수가 없는 추상 메서드를 포함해야한다. 반환값은 DAO이다.</li>
<li>version은 앱을 업데이트하다가 entity의 구조를 변경해야 하는 일이 생겼을 때 이전 구조와 현재 구조를 구분해주는 역할을 한다. 만약 구조가 바뀌었는데 버전이 같다면 에러가 뜨며 디버깅이 되지 않는다. 처음 데이터베이스를 생성하는 상황이라면 그냥 1을 넣어주면 된다.</li>
</ul>
<hr>
<p>코드 내에서 사용하기
<img src="https://images.velog.io/images/dev_jin/post/b57b20ac-b3de-4de9-9c0d-d65c45622dd6/%EC%BD%94%EB%93%9C.PNG" alt=""></p>
<p>코드에서사용할때 Room.databaseBuilder를 사용하여 onCreate 내부에 선언해주면 된다.</p>
<p>🙆‍♀️<em><strong>싱글톤 패턴을 사용한 경우</strong>(공식문서에서 권장하는 방법)</em></p>
<pre><code class="language-kotlin">val db = AppDatabase.getInstance(applicationContext)</code></pre>
<p>🙅‍♀️<strong>싱글톤 패턴을 사용안한 경우</strong></p>
<pre><code class="language-kotlin">    val db = Room.databaseBuilder(
                applicationContext,
                AppDatabase::class.java, &quot;database-name&quot;
            ).build()
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 딥링크 ]]></title>
            <link>https://velog.io/@dev_jin/Android-%EB%94%A5%EB%A7%81%ED%81%AC</link>
            <guid>https://velog.io/@dev_jin/Android-%EB%94%A5%EB%A7%81%ED%81%AC</guid>
            <pubDate>Thu, 21 Oct 2021 09:23:47 GMT</pubDate>
            <description><![CDATA[<h2 id="🔗딥-링크란">🔗딥 링크란?</h2>
<ul>
<li>딥 링크는 홈페이지가 아닌 홈페이지 내의 특정 화면에 한 번에 도달할 수 있는 링크를 말한다. 링크는 모바일과 웹에서 모두 사용되는 개념이다. 예시를 들자면 <a href="https://velog.io/">https://velog.io/</a> 벨로그 자체 링크가 아닌  나의 벨로그(특정화면)으로 이동하기 위한 링크 <a href="https://velog.io/@dev_jin">https://velog.io/@dev_jin</a> 를 말하는 것 이다. 마찬가지로, <strong>모바일 앱 딥링킹</strong>은 사용자가 웹 페이지 또는 다른 앱에서 URL을 클릭하면 앱을 실행하고 특정 페이지를 여는 기술이다.</li>
</ul>
<h2 id="딥링크가-필요한-이유">딥링크가 필요한 이유</h2>
<p>sns에서 광고로 뜬 상품을 클릭하면 앱 스토어로 연결되어 앱을 깔아야만 상품을 볼 수 있도록 딥링크를 사용하거나, 또는 휴면 계정으로 전환된 회원들에게만 다시 앱을 사용하게끔 프로모션을 진행한다면 프로모션에 대한 검색이나 쿠폰 코드를 직접 입력할 필요없이 앱 내에서 한 번의 클릭으로 프로모션 페이지로 이동하도록 딥링크를 사용할 수 있을 것이다.</p>
<p>이렇듯 모바일 앱 딥링크는 사용자 경험을 원활하게 하여 사용자가 앱을 설치하거나 계속 사용하도록 하는 역할이다.</p>
<h2 id="다양한-유형의-링크">다양한 유형의 링크</h2>
<p><img src="https://images.velog.io/images/dev_jin/post/03b6a699-26f5-4354-bd1b-4b929940b743/deeplink.svg" alt="">
Android 앱에서 만들 수 있는 다양한 유형의 링크(딥 링크, 웹 링크, Android 앱 링크)등 여러가지 방식이 있다. 위의 그림은 링크 간의 관계를 보여준다.</p>
<p>오늘은 딥 링크에 대해 알아볼 것이다. <strong>딥링크</strong>는 사용자를 앱의 특정 부분으로 직접 연결하는 모든 체계의 URI이다.  딥링크를 생성하려면 Intent를 추가하여 사용자를 앱에서 특정 활동으로 유도한다.</p>
<h2 id="앱-콘텐츠에-대한-딥-링크-생성">앱 콘텐츠에 대한 딥 링크 생성</h2>
<blockquote>
</blockquote>
<h4 id="🧩앱-콘텐츠에-대한-딥-링크-생성-순서"><strong>🧩앱 콘텐츠에 대한 딥 링크 생성 순서</strong></h4>
<p><em>1. 수신 링크에 대한 인텐트 필터 추가</em>
<em>2. 들어오는 인텐트에서 데이터 읽기</em>
<em>3. 딥 링크 테스트</em></p>
<h3 id="1-수신-링크에-대한-인텐트-필터-추가">1. 수신 링크에 대한 인텐트 필터 추가</h3>
<p>앱 컨텐츠에 대한 링크를 만들려면 매니페스트에 다음 요소와 속성 값이 <strong>인텐트 필터</strong>를 추가한다. 
<img src="https://images.velog.io/images/dev_jin/post/dc1b488a-e925-444a-97ea-3142136d4ae2/deep1.PNG" alt=""></p>
<ul>
<li><p><code>&lt;action&gt;</code>태그 안에 <code>ACTION_VIEW</code>로 구글에 검색했을 때 인텐트 필터에 도달할 수 있도록 인텐트 작업을 지정한다.</p>
</li>
<li><p>하나 이상의 <code>&lt;data&gt;</code>태그를 추가한다. 각 태그는 활동으로 확인되는 URI형식을 나타낸다. <code>&lt;data&gt;</code>태그안에 최소한 <code>android:scheme</code> 속성이 포함되어야 한다. <code>android:scheme</code> 속성은 URI의 스키마 부분으로 URI를 지정하기 위한 최소한의 필수 속성이다. <code>scheme</code>속성을 설정하지 않으면 다른 URI 속성이 의미가 없다. 스키마는 뒤에 콜론을 쓰지 않고 지정한다. 예를 들어 http이다.(http: 아님) <code>android:host</code> 속성은 URI 권한의 호스트 부분이다. 이 속성도 <code>scheme</code> 속성이 지정되어 있지 않으면 의미가 없다. 속성을 추가하여 활동이 수락하는 URI 유형을 더욱 세분화할 수 있다. 예를 들어, 유사한 URI를 수락하지만, 경로 이름에 따라 달라지는 여러 활동이 있을 경우 android:path 속성 또는 속성의 pathPattern이나 pathPrefix 변형을 사용하여 시스템이 다양한 URI 경로에 대해 열어야 하는 활동을 구분할 수 있다. <code>android:pathPrefix</code> 속성은 URI에서 슬래시(/)로 시작해야하는 경로 부분이다. <code>pathPrefix</code> 속성은 인텐트 객체의 경로 중 일부분만 일치하는 일부 경로를 지정한다. </p>
</li>
<li><p><code>&lt;category&gt;</code> 태그 안에 <code>BROWSABLE</code>속성은 웹브라우저에서 인텐트 필터에 접근하려면 필요하다. 이 카테고리가 없는 경우 브라우저에서 링크를 클릭해도 앱으로 확인되지 않는다. <code>DEFAULT</code>속성이 있어야 앱이 암시적 인텐트에 응답할 수 있다. 이 카테고리가 없는면 인텐트에서 앱 구성요소 이름을 지정한 경우에만 시작할 수 있다.</p>
</li>
</ul>
<p>위의 코드에는 두개의 인텐트 필터가 있는데 차이점은 <code>&lt;data&gt;</code> 태그안의 요소이다. 물론 동일한 인텐트 필터 안에 여러 <code>&lt;data&gt;</code>요소를 포함할 수 있지만 고유한 URI(예시: <code>scheme</code>및 <code>host</code>의 특정 조합)을 선언하려는 경우 별도의 인텐트 필터를 만드는게 중요하다. 왜냐하면 동일한 인텐트 빌터의 여러 <code>&lt;data&gt;</code>요소는 결합한 속성의 모든 변형을 고려하여 실제로 병합되기 때문이다. 예를 들어 다음 코드를 봐보자.</p>
<p><img src="https://images.velog.io/images/dev_jin/post/526fe5aa-bba0-48a2-813e-8ed947654ea4/deep2.PNG" alt=""></p>
<p><code>https://www.example.com</code> 및 <code>app://open.my.app</code>만 지원하는 것 같지만, 실제로는 이 두 가지 외에 <code>app://www.example.com</code> 및 <code>https://open.my.app</code>도 지원한다.</p>
<p>위와 같이 활동 컨텐츠의 URI가 포함된 인텐트 필터를 앱 매니페스트에 추가하면 Android는 런타임 시 앱과 일치하는 URI가 있는 모든 <code>Intent</code>를 전달할 수 있다.</p>
<h3 id="2-수신되는-인텐트에서-데이터-읽기">2. 수신되는 인텐트에서 데이터 읽기</h3>
<p>시스템이 인텐트 필터를 통해 활동을 시작하면 <code>Intent</code>에서 제공하는 데이터를 사용하여 렌더링해야하는 대상을 결정할 수 있다. <code>getData()</code> 및 <code>getAction()</code> 메서드를 호출하여 수신 <code>Intent</code>와 연결된 데이터와 작업을 가져온다. 액티비티의 생명주기 동안에는 이러한 메서드를 언제든 호출할 수 있지만, 일반적으로 <code>onCreate()</code>나 <code>onStart()</code>와 같은 초기 콜백에서 호출해야한다. 다음 코드는 <code>Intent</code>에서 데이터를 가져오는 방법을 보여준다.
<img src="https://images.velog.io/images/dev_jin/post/a6a3d3fe-17c1-4964-9840-d158a39cd955/deep3.PNG" alt="">
앱에 대한 사용자의 경험을 향상시키려면 고려할 사항이 있다.</p>
<ul>
<li><p>딥링크는 요청메세지, 전면 페이지, 로그인 없이 사용자를 곧바로 컨텐츠에 연결해야한다. 그러므로 사용자가 이전에 어플리케이션을 실행한 적이 없더라도 앱 컨텐츠를 볼 수 있도록 만들어야한다.</p>
</li>
<li><p>딥 링크를 통해 앱에 들어왔을 때 뒤로 가기 등의 이동 설계가 필요하다.</p>
</li>
</ul>
<h3 id="3-딥-링크-테스트하기">3. 딥 링크 테스트하기</h3>
<p>Android 디버그 브리지를 활동 관리자(am) 도구와 함께 사용하여 딥 링크용으로 지정한 인텐트 필터 URI가 올바른 앱 활동으로 확인되는지 테스트 할 수 있다. 기기나 에뮬레이터에 관해 adb명령어를 실행할 수 있다.
adb를 사용해 인텐트 필터 URI를 테스트하는 일반적인 코드를 봐보자.
<img src="https://images.velog.io/images/dev_jin/post/ee12ff77-c783-4f45-92b4-d067702d6ccd/deep4.PNG" alt="">
예를 들면 아래의 명령어는 지정된 URI와 연결된 타겟의 앱 액티비티를 본다. 
<img src="https://images.velog.io/images/dev_jin/post/bcdf3f43-5bdd-4612-a366-9d0bf9035f4c/deep5.PNG" alt="">
1번에서 설정한 매니페스트 선언과 인텐트 핸들러는 앱과 웹사이트 간의 연결 및 수신 링크로 해야 할 작업을 정의한다. 그러나 시스템에서 앱이 URI집합의 기본 핸들러로 취급되도록 하려면 이 연결을 인증하도록 시스템에 요청해야한다. </p>
<p><a href="https://www.adjust.com/ko/blog/dive-into-deeplinking/">참고자료1</a>
<a href="https://developer.android.com/training/app-links/deep-linking">참고자료2</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/안드로이드] Thread/Handler/Looper]]></title>
            <link>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-ThreadHandlerLooper-5v6by33k</link>
            <guid>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-ThreadHandlerLooper-5v6by33k</guid>
            <pubDate>Mon, 27 Sep 2021 06:04:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/dev_jin/post/0f3e9971-fc25-4ec8-8513-02982d77a714/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C.png" alt=""></p>
<blockquote>
</blockquote>
<h3 id="✍키워드-정리"><strong>✍키워드 정리</strong></h3>
<ul>
<li><strong>프로세스</strong> : 실행 중인 프로그램 <em>ex)  <strong>실행중인</strong> 인스타그램</em></li>
<li>프로세스는 (다수의) 작업 단위 집합</li>
<li><strong>스레드(Thread)</strong> : 하나의 프로세스 상의 독립적인 실행 흐름 <em>ex) 좋아요 기능, 스토리 공유 기능 등</em></li>
<li>하나의 프로세스에는 메인스레드만 있거나 메인스레드 + 서브스레드로 구성</li>
<li>백그라운드 스레드는 UI요소를 직접 접근 못함</li>
<li>백그라운드 스레드와 메인스레드를 연결해 주는 것은 <strong>핸들러</strong></li>
</ul>
<h2 id="thread">Thread</h2>
<p>안드로이드는 기본적으로 하나의 메인 스레드로 작동한다. 안드로이드의 스레드는 ① 메인 스레드와 ② 백그라운드 스레드로 나눌 수 있다. 메인스레드는 1개만 존재하고, 백그라운드 스레드는 여러 개가 존재할 수 있다.</p>
<h3 id="1-메인-스레드ui-스레드">1. 메인 스레드(UI 스레드)</h3>
<ul>
<li>UI조작은 메인 스레드만 가능하다.</li>
<li>안드로이드 UI 구성 요소와 상호작용하고, UI 이벤트를 사용자에게 응답하는 스레드이다.</li>
<li>UI 이벤트 및 작업에 대해 오래 걸리면 안드로이드 시스템은 ANR(어플리케이션 응답 없음) 오류가 생긴다.</li>
</ul>
<h3 id="2-백그라운드-스레드">2. 백그라운드 스레드</h3>
<ul>
<li>백그라운드 스레드는 UI조작을 할 수 없다.</li>
<li>백그라운드 스레드에서는 처리 시간을 미리 계산할 수 없는 작업의 처리가 권장된다.</li>
<li>백그라운드 스레드는 Thread 클래스를 상속받거나 Runnable 인터페이스를 구현해 생성할 수 있다.</li>
</ul>
<hr>
<p><em>-&gt; 이러한 멀티 스레드 환경에서 동기화 이슈를 차단하고 스레드간의 통신을 위해 사용되는 것이 핸들러와 루퍼이다.</em></p>
<h2 id="handler">Handler</h2>
<blockquote>
<p><strong>핸들러(Handler)</strong> : 받은 메세지를 처리하고 스레드간의 메세지를 전달하는 작업을 수행함</p>
</blockquote>
<p>핸들러를 생성하면 핸들러를 생성한 스레드와 루퍼에 연결된다. 핸들러에는 postDelayed와 같은 메세지의 순서나 보내는 시간을 조작하는 함수를 가지고 있어서 스케줄링을 할 수 있다는 장점이 있다.</p>
<h2 id="looper">Looper</h2>
<blockquote>
<p><strong>루퍼(Looper)</strong> : 같은 작업을 계속 수행함. 메세지큐에서 차례대로 메세지와 runnable 객체를 꺼내서 핸들러에게 전달함.</p>
</blockquote>
<p>하나의 스레드에는 오직 하나의 Looper 를 가지며, Looper 는 오직 하나의 스레드를 담당한다. Looper는 스레드간의 신호이므로 handler에 보낸다고 해서 곧바로 처리되는 것이 아니다. 동시다발적으로 메세지가 발생할 수 있기 때문에 메세지 큐(Message Queue)에 쌓았다가 처리한다. 큐(Queue)에 들어있는 내용을 하나씩 꺼내 처리하는 것이 루퍼이다. 메인 쓰레드가 looper를 가지고 있으며 무한 루프를 돌며 큐의 내용을 처리한다.</p>
<h2 id="사용-예제">사용 예제</h2>
<p><strong>안드로이드에서는 별도 스레드의 결과를 메인 스레드에서 처리하므로 Main Looper를 명시하여 Handler 생성한다.</strong></p>
<pre><code class="language-kotlin">var handler: Handler? = null
val thread = Thread {  
    handler = Handler(Looper.getMainLooper())
}
thread.start()</code></pre>
<hr>
<p><strong>postDelayed를 사용하여 2초후 작업할 수 있도록 스케줄링하는 예시</strong></p>
<pre><code class="language-kotlin">lateinit var runnable: Runnable
var handler: Handler? = null

handler = Handler(Looper.getMainLooper())
        runnable = Runnable {
           //UI작업
        }
        handler!!.postDelayed(runnable, 2000)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/안드로이드] LiveData를 알아보자]]></title>
            <link>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9CLiveData%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9CLiveData%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Fri, 17 Sep 2021 06:36:31 GMT</pubDate>
            <description><![CDATA[<h1 id="👩💻livedata란">👩‍💻LiveData란?</h1>
<blockquote>
<p>LiveData는 관찰 가능한 데이터 홀더 클래스이다. 관찰 가능한 일반 클래스와 달리 LiveData는 수명 주기를 인식한다. 즉, 활동, 프래그먼트, 서비스 등 다른 앱 구성요소의 수명 주기를 고려한다. 수명 주기 인식을 통해 LiveData는 활동 수명 주기 상태에 있는 앱 구성요소 관찰자만 업데이트한다.</p>
</blockquote>
<h2 id="livedata사용-이점">LiveData사용 이점</h2>
<p><strong>1. UI와 데이터 상태의 일치 보장</strong></p>
<ul>
<li>Observer 패턴을 따름</li>
<li>LiveData는 기본 데이터가 변경될 때 <code>Observer</code>객체에 알려 UI를 업데이트 할 수 있음. 그러므로 앱 데이터가 변경될 때마다 관찰자가 대신 UI를 업데이트하므로 우리가 직접 업데이트 할 필요가 없음.</li>
</ul>
<p><strong>2. 메모리 누수 없음</strong></p>
<ul>
<li>관찰자는 <code>Lifecycle</code> 객체에 결합되어 있어서 연결된 <strong>라이프사이클이 끝나면 자동으로 삭제됨</strong></li>
</ul>
<p><strong>3. 중지된 활동으로 인한 비정상 종료 없음</strong></p>
<ul>
<li>활동이 백 스택에 있을 때를 비롯하여 관찰자의 수명 주기가 비활성 상태에 있으면 관찰자는 어떤 LiveData 이벤트도 받지 않음</li>
</ul>
<p><strong>4. 수명주기를 수동으로 처리하지 않음</strong></p>
<ul>
<li>UI구성요소는 관련 데이터를 관찰하기만 할 뿐 관찰을 중지하거나 다시 시작하지 않음.</li>
</ul>
<hr>
<h2 id="livedata--mutablelivedata">LiveData / MutableLiveData</h2>
<ul>
<li><strong>LiveData</strong>는 Get만 가능하여 읽기 전용으로 데이터를 변경할 수 없음</li>
<li><strong>MutableLiveData</strong>는 GET/SET이 모두 가능하여 데이터의 변경과 읽기 둘다 가능</li>
</ul>
<h2 id="setvalue--postvalue"><code>setValue()</code> / <code>postValue()</code></h2>
<ul>
<li><code>setValue()</code>는 MainThread가 보장될 경우 활용할 수 있음(MainThread 에서의 데이터 변경)</li>
<li><code>postValue()</code>는 MainThread가 아닌 IO 스케줄러 활용시 활용할 수 있음(MainThread 로 데이터 전달)</li>
<li><blockquote>
<p> 두 방식 모두 UI로 값을 전달하기 위해 만들어져 있음. 데이터의 처리가 IO 스케줄러 상에서 발생해야하는 경우 Thread, Coroutines 등을 활용하여 처리해야함.</p>
</blockquote>
</li>
</ul>
<h2 id="livedata-기본사용법">LiveData 기본사용법</h2>
<blockquote>
<ol>
<li>종속성 추가</li>
<li>LiveData를 사용할 곳에서 LiveData 정의하고 초기화</li>
<li>LiveData에 Observer 달기</li>
</ol>
</blockquote>
<pre><code class="language-kotlin">dependencies {
    ...
    implementation &#39;androidx.appcompat:appcompat:1.1.0&#39;
    ...
}</code></pre>
<hr>
<p><code>mainActivity</code></p>
<pre><code class="language-kotlin">var pageNumber = MutableLiveData&lt;String&gt;()
// LiveData의 value의 변경을 감지하고 호출함
pageNumber.observe(this, Observer {
    // it로 넘어오는 param은 LiveData의 value
    binding.numTxt.text = it
})

//liveText의 value를 변경
pageNumber.value = &quot;${lastPageNum}&quot;</code></pre>
<hr>
<h2 id="livedata--databinding-사용법">LiveData + DataBinding 사용법</h2>
<blockquote>
<ol>
<li>종속성 추가</li>
<li>App 수준의 gradle에 databinding 추가</li>
<li>xml 최상위 Layout <code>&lt;layout&gt;&lt;/layout&gt;</code> 으로 감싸주기</li>
<li><code>&lt;variable/&gt;</code> 추가</li>
<li>liveData를 이용해 바꿀 텍스트에 activity의 객체인 pageNumber 추가</li>
</ol>
</blockquote>
<pre><code class="language-kotlin">dependencies {
    ...
    implementation &#39;androidx.appcompat:appcompat:1.1.0&#39;
    ...
}</code></pre>
<hr>
<p><code>build.gradle(:app)</code></p>
<pre><code class="language-kotlin"> dataBinding {
        enabled = true
    }</code></pre>
<hr>
<p><code>activity_main.xml</code></p>
<pre><code class="language-kotlin">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;layout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;&gt;

    &lt;data&gt;

        &lt;import type=&quot;android.view.View&quot; /&gt;

        &lt;variable
            name=&quot;activity&quot;
            type=&quot;com.example.MainActivity&quot; /&gt;

    &lt;/data&gt;

    &lt;androidx.constraintlayout.widget.ConstraintLayout
        android:id=&quot;@+id/backgroundLayout&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;&gt;

        &lt;androidx.appcompat.widget.AppCompatTextView
            android:id=&quot;@+id/pageTxtView&quot;
            app:layout_constraintStart_toStartOf=&quot;parent&quot;
            app:layout_constraintTop_toTopOf=&quot;parent&quot;
            android:layout_marginStart=&quot;@dimen/dp_48&quot;
            android:layout_marginTop=&quot;@dimen/dp_44&quot;
            android:textSize=&quot;@dimen/sp_32&quot;
            android:textColor=&quot;@color/black&quot;
            tools:text=&quot;2/34&quot;
            android:text=&quot;@{activity.pageNumber}&quot;
            android:includeFontPadding=&quot;false&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;/&gt;

    &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
&lt;/layout&gt;</code></pre>
<hr>
<p><code>mainActivity</code>
<strong><em>databinding이랑 같이 쓰면 LiveData의 value의 변경을 감지하고 호출 할 필요없음</em></strong></p>
<pre><code class="language-kotlin">var pageNumber = MutableLiveData&lt;String&gt;()

//liveText의 value를 변경
pageNumber.value = &quot;${lastPageNum}&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/안드로이드] 코루틴의 기초]]></title>
            <link>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%9D%98-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%9D%98-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Tue, 14 Sep 2021 05:56:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/dev_jin/post/c50a1700-dc0e-4085-b452-41201b079e69/%EC%BD%94%ED%8B%80%EB%A6%B0.png" alt=""></p>
<h2 id="📌코루틴coroutine이란">📌코루틴(Coroutine)이란?</h2>
<blockquote>
<p><em>코루틴은 코루틴이 시작된 스레드를 중단하지 않으면서 비동기적으로 실행되는 코드</em></p>
</blockquote>
<p> <strong>1. 협력형 멀티태스킹</strong></p>
<ul>
<li>co(함께, 동시에)+Routine(특정한 일을 실행하기 위한 일련의 명령)</li>
<li>코루틴 함수는 언제든지 중간에 나갈 수 있고 언제든지 나갔던 부분으로 들어올 수 있음</li>
<li>return이나 }을 만나지 않아도 언제든지 탈출 가능</li>
<li>suspend로 선언된 함수를 만난면 코루틴 밖으로 나감.</li>
</ul>
<p> <strong>2. 동시성(시분할)프로그래밍 지원</strong></p>
<ul>
<li>함수를 중간에 빠져나왔다가, 다른 함수에 진입하고, 다시 빠져나온 부분부터 다시 시작하는 특성</li>
<li>사용자 입장에서는 위의 과정이 너무 빨라서 동시에 하는 것처럼 느껴짐</li>
<li>병렬성과는 다른 개념</li>
</ul>
<p> <strong>3. 비동기 처리를 쉽게 도와줌</strong></p>
<h2 id="코루틴coroutine장점">코루틴(Coroutine)장점</h2>
<p><strong>1. 경량</strong></p>
<ul>
<li>코루틴을 실행 중인 스레드를 차단하지 않는 정지를 지원하므로 단일 스레드에서 많은 코루틴을 실행할 수 있음.</li>
</ul>
<p><strong>2. 메모리 누수 감소</strong></p>
<ul>
<li>구조화된 동시 실행을 사용하여 범위 내에서 작업을 실행</li>
</ul>
<p><strong>3. 처리 도중 취소 가능</strong></p>
<ul>
<li>실행 중인 코루틴 계층 구조를 통해 자동으로 취소 가능</li>
</ul>
<h2 id="코루틴-사용방법">코루틴 사용방법</h2>
<blockquote>
</blockquote>
<h4 id="👀먼저-코루틴을-사용하기-위해-알아야-할-키워드를-알아보자"><em>👀먼저 코루틴을 사용하기 위해 알아야 할 키워드를 알아보자</em></h4>
<ul>
<li>CoroutineScope</li>
<li>CoroutineContext</li>
<li>Dispatcher</li>
<li>launch</li>
</ul>
<p><strong>CoroutineScope</strong> :코루틴의 범위, 코루틴 블록을 묶음으로 제어할 수 있는 단위이다.
<strong>CoroutineContext</strong> :코루틴을 어떻게 처리할지에 대한 여러가지 정보의 집합이다.
<strong>Dispatcher</strong> :Dispatcher는 CoroutineContext의 주요 요소이다.
CoroutineContext를 상속받아 어떤 스레드를 이용해서 어떻게 동작할 것인지 미리 정의해둔다.</p>
<ul>
<li>Dispatchers.Main : 안드로이드의 경우 UI 스레드를 사용</li>
</ul>
<p><strong>launch</strong> :코루틴을 만들고 함수 본문의 실행을 해당하는 디스패처에 전달하는 함수</p>
<blockquote>
<p><em><strong>🍕사용방법</strong></em></p>
</blockquote>
<ol>
<li>사용할 Dispatcher을 결정</li>
<li>Dispatcher을 이용해서 CoroutineScope 만들기</li>
<li>CoroutineScope의 launch또는 async에 수행할 코드 블록을 넘기기</li>
</ol>
<pre><code class="language-kotlin">CoroutineScope(Dispatchers.Main).launch {
                        //작업
                        }</code></pre>
<p>launch 와 async 는 CoroutineScope 의 확장함수 이며, 넘겨 받은 코드 블록으로 코루틴을 만들고 실행해주는 코루틴 빌더이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/안드로이드] Gradle Scripts에 대한 정리
]]></title>
            <link>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Gradle-Scripts%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Gradle-Scripts%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 07 Sep 2021 09:37:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/dev_jin/post/99732ac0-8768-4fb9-af14-323c9aab3579/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C2.png" alt=""></p>
<h2 id="📌gradle-scripts란">📌Gradle Scripts란?</h2>
<blockquote>
<p><em>안드로이드 프로젝트를 생성하면 Gradle Scripts 안에 build.gradle, settings.gradle 등의 
파일이 자동으로 만들어지고, 이 파일들을 통해 안드로이드 프로젝트의 빌드 정보, 라이브러리 등을 관리한다.</em></p>
</blockquote>
<p>Gradle Scripts는 총 7개의 파일로 구성되어 있는데
<code>build.gradle (Project:)</code>과 <code>build.gradle (Module: .app)</code> 2가지를 중점적으로 알아보자!</p>
<p><img src="https://images.velog.io/images/dev_jin/post/84384874-86c6-4567-a0e9-c3d4ef10d731/gradle1.PNG" alt=""></p>
<hr>
<h3 id="1-buildgradle-project-프로젝트">1. build.gradle (Project: 프로젝트)</h3>
<ul>
<li><p>프로젝트 수준의 그레이들 설정 파일 :프로젝트의 모든 모듈에 적용되는 빌드 구성을 정의한다.
<img src="https://images.velog.io/images/dev_jin/post/83a6867c-4d48-411e-b08a-3541c63c4eb9/gradle2.PNG" alt=""></p>
</li>
<li><p><em>(1) buildscript*</em>  : 최상위 빌드 파일은 <code>buildscript</code>블록을 사용하여 프로젝트의 모든 모듈에 공통되는 Gradle 저장소와 종속 항목을 정의함.</p>
<ul>
<li>respositories : 외부 저장소 설정. google()이 기본으로 설정된다.</li>
<li>dependencies(의존성:라이브러리): gradle 플러그인 버전 설정</li>
<li><code>ext</code>: 여러 모듈이 포함된 프로젝트일 경우 프로젝트 수준에서 특정 속성을 정의한 후 모든 모듈에서 공유하는 것이 더 유용하다.</li>
</ul>
</li>
<li><p><em>(2) allprojects*</em></p>
<ul>
<li>새 프로젝트의 경우 Android Studio는 기본적으로 JCenter 및 Google의 Maven 저장소를 포함한다.</li>
<li>respositories : 위의 buildscript &gt; respositories와 동일한 외부저장소가 설정된다.</li>
</ul>
</li>
</ul>
<p>  <strong>(3) task</strong>
    - 프로젝트 전체적으로 공통으로 사용할 작업을 정의한다.
    - clean(type: Delete): 기본으로 추가된 공통작업으로, 빌드시 생성되는 build디렉터리들을 삭제한다.</p>
<hr>
<h3 id="2-buildgradle-module-프로젝트app">2. build.gradle (Module: 프로젝트.app)</h3>
<ul>
<li><p>모듈 수준의 그레이들 설정 파일로, 각 모듈마다 생성된다.</p>
</li>
<li><p>모듈의 종류는 app모듈, 웨어러블 모듈, 안드로이드TV 모듈 등이 있다.</p>
</li>
<li><p>보통 phone&amp;tablet 프로젝트를 생성하기 때문에, 기본으로 app 모듈 수준의 빌드설정/라이브러리 정보가 저장된다.
<img src="https://images.velog.io/images/dev_jin/post/0060cc40-4416-499c-af71-cb2a0cadeed8/gradle3.PNG" alt=""></p>
<p><strong>(1) plugins</strong></p>
<ul>
<li>안드로이드 개발을 위한 플러그인 설정 영역으로, &#39;com.android.application&#39;이 기본으로 지정된다.</li>
</ul>
<p><strong>(2) android</strong></p>
<ul>
<li>compileSdkVersion은 grdle이 앱을 컴파일하는데 사용해야하는 안드로이드 API레벨을 지정한다.
즉,  앱이 API 레벨과 같거나 낮은 API 기능을 사용 가능하다는 것을 의미한다. </li>
<li>buildToolsVersion은 SDK 빌드 도구, 명령줄, 유틸리티 및 컴파일러의 버전을 지정한다.</li>
</ul>
<p><strong>(3) dependencies</strong></p>
<ul>
<li>외부 라이브러리를 설정한다.</li>
<li><code>build.gradle</code> 파일의 <code>dependencies</code> 블록에 implementation과 같은 종속 항목 구성을 지정<ul>
<li>예시) implementation files(&#39;libs/foo.jar&#39;, &#39;libs/bar.jar&#39;)</li>
<li>예시) implementation &#39;com.example.android:app-magic:12.3&#39;</li>
<li>예시) implementation group: &#39;com.example.android&#39;, name: &#39;app-magic&#39;, version: &#39;12.3&#39;</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>  <a href="https://developer.android.com/studio/build?hl=ko#kts">👩‍💻참고사이트</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/kotlin] 
리사이클러뷰 TabLayout, Filterable 사용법]]></title>
            <link>https://velog.io/@dev_jin/Androidkotlin-%EB%A6%AC%EC%82%AC%EC%9D%B4%ED%81%B4%EB%9F%AC%EB%B7%B0-TabLayout-Filterable-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@dev_jin/Androidkotlin-%EB%A6%AC%EC%82%AC%EC%9D%B4%ED%81%B4%EB%9F%AC%EB%B7%B0-TabLayout-Filterable-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Thu, 02 Sep 2021 02:44:09 GMT</pubDate>
            <description><![CDATA[<h1 id="✍tablayout-filterable-사용법">✍TabLayout, Filterable 사용법</h1>
<blockquote>
<p><strong>_Tablayout을 사용해서 원하는 키워드가 포함된 리스트만 볼 수 있도록 리사이클러뷰를 필터링해보자!! _</strong></p>
</blockquote>
<hr>
<h2 id="tablayout-구성">TabLayout 구성</h2>
<p><img src="https://images.velog.io/images/dev_jin/post/dfbfa805-f875-460e-b11d-9cee629c52a6/tab.PNG" alt="">
<strong>1. TabLayout안에 원하는 tabItem을 배치한다(ex: All, LEARNING, GAME)
2. tabItem의 text, icon 색상은 tabLayout에서 변경 가능하다.</strong></p>
<pre><code class="language-kotlin">&lt;com.google.android.material.tabs.TabLayout
                android:id=&quot;@+id/tabLayout&quot;
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:background=&quot;@color/black&quot;
                app:layout_scrollFlags=&quot;scroll|enterAlways&quot;
                app:tabIconTint=&quot;@color/white&quot;
                app:tabIndicatorColor=&quot;@color/white&quot;
                app:tabSelectedTextColor=&quot;#B0FFFF&quot;
                app:tabTextColor=&quot;@color/white&quot;&gt;

                &lt;com.google.android.material.tabs.TabItem
                    android:id=&quot;@+id/unFilteredList&quot;
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:text=&quot;ALL&quot;
                    android:icon=&quot;@drawable/ic_baseline_person_24&quot;/&gt;

                &lt;com.google.android.material.tabs.TabItem
                    android:id=&quot;@+id/filteredList1&quot;
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:text=&quot;LEARNING&quot;
                    android:icon=&quot;@drawable/ic_baseline_psychology_24&quot;/&gt;

                &lt;com.google.android.material.tabs.TabItem
                    android:id=&quot;@+id/filteredList2&quot;
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:icon=&quot;@drawable/ic_baseline_videogame_asset_24&quot;
                    android:text=&quot;GAME&quot;/&gt;
            &lt;/com.google.android.material.tabs.TabLayout&gt;</code></pre>
<h2 id="activity">Activity</h2>
<p><strong>1. addOnTabSelectedListener()메서드를 이용해서 모든 탭의 동작을 처리할 수 있다</strong>
<strong>2. onTabSelected()에서 각각의 tab을 when()으로 tab의 파라미터인 position을 구별한다.
(왼쪽부터 0, 1, 2 순서)</strong>
<strong>3. 리사이클러뷰 어댑터의 filter() 메서드를 사용하여 필터링 하고싶은 텍스트를 넣어준다.
(전체를 다 보여주고 싶다면 &quot; &quot;을 넣어준다)</strong> </p>
<p><code>ContentsListActivity.kt</code></p>
<pre><code class="language-kotlin">binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab?) {
                Log.e(&quot;TAG&quot;, &quot;${tab!!.position}&quot;)
                when(tab.position){
                    0 -&gt;{
                        mListAdapter.filter.filter(&quot;&quot;)
                    }
                    1 -&gt; {
                        mListAdapter.filter.filter(&quot;LEARNING&quot;)
                    }
                    2 -&gt; {
                        mListAdapter.filter.filter(&quot;GAME&quot;)
                    }
                }
            }

            override fun onTabUnselected(tab: TabLayout.Tab?) {
                tab!!.view.setBackgroundColor(Color.TRANSPARENT)
            }

            override fun onTabReselected(tab: TabLayout.Tab?) {
            }
        })</code></pre>
<h2 id="recyclerviewadapter">RecyclerViewAdapter</h2>
<h3 id="구현방법">구현방법</h3>
<p>*<em>1. 자신의 리사이클러뷰 어댑터에 Filterable을 상속받는다.
2. 액티비티에서 받아온 텍스트(필터링하고 싶은 단어)를 아이템의 텍스트와 비교해서 일치하면 filteringList에 item을 넣어준다. (나는 itme.type과 비교했다.)   *</em>
3. 받아온 String 값이 공백(&quot; &quot;)이면 필터링이 안된 전체 리스트를(unfilterdList)를 보여준다.</p>
<p><code>RecyclerViewAdapter.kt</code></p>
<pre><code class="language-kotlin">class RecyclerViewAdapter(items: ArrayList&lt;UnitsDto?&gt;?) : RecyclerView.Adapter&lt;RecyclerView.ViewHolder&gt;(), Filterable
{

    private var context:Context? = null
    private var unFilteredList = items
    private var filteredList = items


    override fun getFilter(): Filter {
        return object : Filter() {
            override fun performFiltering(constraint: CharSequence?): FilterResults {
                val charString = constraint.toString()
                filteredList = if (charString.isEmpty()) {
                    unFilteredList
                } else {
                    val filteringList = ArrayList&lt;UnitsDto?&gt;()
                    for (item in unFilteredList!!) {
                        if (item!!.type == charString) filteringList.add(item)
                    }
                    filteringList
                }
                val filterResults = FilterResults()
                filterResults.values = filteredList
                return filterResults
            }

            override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
                filteredList = results?.values as ArrayList&lt;UnitsDto?&gt;?
                notifyDataSetChanged()
            }
        }

    }
    `
    `
    `//이하 생략



}</code></pre>
<hr>
<h2 id="결과물">결과물</h2>
<blockquote>
<p><em><strong>LEARNING, GAME 2가지 키워드가 포함된 리스트만 볼 수 있는 리사이클러뷰 완성!</strong></em></p>
</blockquote>
<p><img src="https://images.velog.io/images/dev_jin/post/f891fd07-36f2-4046-b18e-946b06aa7157/all.PNG" alt=""></p>
<p><img src="https://images.velog.io/images/dev_jin/post/28ca7cb1-e52c-4372-acca-a4936ed09397/tab3.PNG" alt=""></p>
<p><img src="https://images.velog.io/images/dev_jin/post/501e7882-5669-4a71-ad38-3689e04c8039/game.PNG" alt=""></p>
<p><a href="https://velog.io/@dev_jin/AndroidKotlinInfiniteendless-Scrolling%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4">👩‍💻리사이클러뷰 무한스크롤 구현해보기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/Kotlin] Infinite/endless Scrolling(무한 스크롤) 페이징]]></title>
            <link>https://velog.io/@dev_jin/AndroidKotlinInfiniteendless-Scrolling%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4</link>
            <guid>https://velog.io/@dev_jin/AndroidKotlinInfiniteendless-Scrolling%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4</guid>
            <pubDate>Wed, 01 Sep 2021 08:21:46 GMT</pubDate>
            <description><![CDATA[<h2 id="recyclerview-무한-스크롤">RecyclerView 무한 스크롤</h2>
<h4 id="무한-스크롤이란"><strong>무한 스크롤이란?</strong></h4>
<ul>
<li>리스트를 밑으로 스크롤 했을 때 progressBar가 보였다가, 
새로운 리스트가 추가되는 것을 말함.</li>
</ul>
<hr>
<blockquote>
<h2 id="📌구현방법">📌구현방법</h2>
</blockquote>
<ol>
<li>RecyclerView 생성</li>
<li>Scroll이 끝에 닿는 것을 감지하여 닿았을 경우 데이터에 NULL 추가 후에 Adapter에 알림</li>
<li>일정 시간이 지나면, NULL 제거 후 새로운 데이터 추가하고 Adapter에 알림.</li>
</ol>
<h2 id="👀전체코드">👀전체코드</h2>
<p><code>ContentsListActivity.kt</code></p>
<pre><code class="language-kotlin">class ContentsListActivity : AppCompatActivity() {
    private lateinit var binding: ActivityContentsListBinding
    private var dtoList :ArrayList&lt;UnitsDto&gt; = ArrayList()
    private var items: ArrayList&lt;UnitsDto?&gt; = ArrayList()
    private lateinit var mMapLayoutManager: LinearLayoutManager
    private lateinit var mListAdapter: RecyclerViewAdapter
    private lateinit var mRecyclerView: RecyclerView
    val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
    private var isLoading =false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityContentsListBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.apply {
            lifecycleOwner = this@ContentsListActivity
            activity = this@ContentsListActivity
        }

        setData()
        initAdapter()
        initScrollListener()
    }

    private fun setData(){
        dtoList= intent.getSerializableExtra(EXTRA_TITLE) as ArrayList&lt;UnitsDto&gt;
        for (i in 0 until 10){
            items.add(dtoList[i])
        }
    }
//1. RecyclerView 생성
    private fun initAdapter(){
        mListAdapter = RecyclerViewAdapter(items)
        mMapLayoutManager = LinearLayoutManager(this)
        binding.recyclerView.adapter = mListAdapter

    }
     // 2. scroll이 끝에 닿으면 데이터에 null 추가 및 Adapter에 알림
    private fun initScrollListener(){
        binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)

                if(!isLoading){
                    if ((recyclerView.layoutManager as LinearLayoutManager?)!!.findLastCompletelyVisibleItemPosition() == items.size - 1){
                        Log.e(&quot;true&quot;, &quot;True&quot;)
                        moreItems()
                        isLoading =  true
                    }

                }
            }
        })
    }

//3. null 제거 후 새로운 데이터 추가 및 Adapter에 알림
    fun moreItems(){
        mRecyclerView = binding.recyclerView

        val runnable = Runnable {
            items.add(null)
            mListAdapter.notifyItemInserted(items.size -1)
        }
        mRecyclerView.post(runnable)

        CoroutineScope(mainDispatcher).launch {
            delay(2000)
            val runnable2=  Runnable{
                items.removeAt(items.size - 1)
                val scrollPosition = items.size
                mListAdapter.notifyItemRemoved(scrollPosition)
                var currentSize = scrollPosition
                var nextLimit = currentSize+10
                Log.e(&quot;hello&quot;, &quot;${nextLimit}&quot;)
                if (currentSize &lt; dtoList.size-10){
                    while (currentSize-1&lt;nextLimit){
                        items.add(dtoList[currentSize])
                        currentSize++
                    }
                }else{
                    while (currentSize!=dtoList.size){
                        items.add(dtoList[currentSize])
                        currentSize++
                    }
                }
                mListAdapter.updateItem(items)
                isLoading = false
        }
            runnable2.run()
        }
    }
}</code></pre>
<hr>
<p><code>RecyclerViewAdapter.kt</code></p>
<pre><code class="language-kotlin">class RecyclerViewAdapter(items: ArrayList&lt;UnitsDto?&gt;?) : RecyclerView.Adapter&lt;RecyclerView.ViewHolder&gt;(), Filterable
{
    companion object {
        private const val TYPE_ITEM = 0
        private const val TYPE_LOADING = 1
    }
    private var context:Context? = null
    private var unFilteredList = items
    private var filteredList = items
    override fun getItemCount(): Int {
        return if (filteredList == null){
            0
        }else{
            filteredList?.size!!
        }

    }

    override fun getItemViewType(position: Int): Int {
        return when (filteredList?.get(position)) {
            null -&gt; TYPE_LOADING
            else -&gt; TYPE_ITEM

        }
    }

    fun updateItem(list:ArrayList&lt;UnitsDto?&gt;?){
        this.filteredList = list
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        context = parent.context
        if (viewType == TYPE_ITEM){
            val binding = RecycleritemLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return ItemViewHolder(binding)
        }else{
            val binding = ItemLoadingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return LoadingViewHolder(binding)
        }


    }
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is ItemViewHolder){
            val item = filteredList?.get(position)
            val itemHolder = holder as ItemViewHolder
            itemHolder.bind(item!!)
        }else if (holder is LoadingViewHolder){

        }
    }
    // 아이템뷰에 프로그레스바가 들어가는 경우
    inner class LoadingViewHolder(var binding: ItemLoadingBinding) :
        RecyclerView.ViewHolder(binding.root) {

    }

    inner class ItemViewHolder(var binding: RecycleritemLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: UnitsDto){
            binding.nameTxtView.text = &quot;item&quot;
            binding.typeTxtView.text = item.type
            if (item.progress == 100){
                binding.progressTxtView.setTextColor(Color.parseColor(&quot;#B0FFFF&quot;))
            }
            binding.progressTxtView.text = (&quot;${item.progress}% 완료&quot;)


            Glide.with(context!!)
                .load(item.thumbnail)
                .apply(RequestOptions.bitmapTransform(RoundedCorners(15)))
                .thumbnail(0.5f)
                .into(binding.imgView)
            binding.imgView.clipToOutline = true

        }

    }
}</code></pre>
<p><a href="https://yujin-dev.tistory.com/35">참고사이트1</a>,  
<a href="https://todaycode.tistory.com/12">참고사이트2</a></p>
<hr>
<p><a href="https://velog.io/@dev_jin/Androidkotlin-%EB%A6%AC%EC%82%AC%EC%9D%B4%ED%81%B4%EB%9F%AC%EB%B7%B0-TabLayout-Filterable-%EC%82%AC%EC%9A%A9%EB%B2%95">👩‍💻TabLayout, 리사이클러뷰 필터링 구현해보기</a>
<img src="https://images.velog.io/images/dev_jin/post/3eaa2863-4e4b-4000-9610-e5286aa159a9/tab.PNG" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/kotlin] Cannot call this method in a scroll callback
]]></title>
            <link>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Cannot-call-this-method-in-a-scroll-callback</link>
            <guid>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Cannot-call-this-method-in-a-scroll-callback</guid>
            <pubDate>Tue, 31 Aug 2021 09:38:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/dev_jin/post/460c424b-8a8f-470f-8ea2-462f208f14b6/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C2.png" alt=""></p>
<h2 id="🚨cannot-call-this-method-in-a-scroll-callback">🚨Cannot call this method in a scroll callback</h2>
<hr>
<pre><code class="language-kotlin">W/RecyclerView: Cannot call this method in a scroll callback. Scroll callbacks mightbe run during a measure &amp; layout pass where you cannot change theRecyclerView data. Any method call that might change the structureof the RecyclerView or the adapter contents should be postponed tothe next frame. 
java.lang.IllegalStateException:  androidx.recyclerview.widget.RecyclerView{bcfafdd VFED..... ......ID 0,0-340,640 #7f0a0146 app:id/recyclerView}, adapter:com.example.getcontents.adapter.RecyclerViewAdapter@f16752, layout:androidx.recyclerview.widget.LinearLayoutManager@fb02a23, context:com.example.getcontents.activity.ContentsListActivity@7fec6f9</code></pre>
<p>스크롤 콜백에서 이 메서드를 호출할 수 없습니다. 스크롤 콜백은 RecyclerView 데이터를 변경할 수 없는 측정 및 레이아웃 단계에서 실행될 수 있습니다. RecyclerView 또는 어댑터 내용의 구조를 변경할 수 있는 모든 메서드 호출은 다음 프레임으로 연기되어야 함.</p>
<h3 id="📌오류원인">📌오류원인</h3>
<ul>
<li>리사이클러뷰를 스크롤 하는 동안 notify 관련 함수(notifyDataSetChanged, notifyItemChanged 등)를 사용했기 때문</li>
</ul>
<h3 id="🙌해결방법">🙌해결방법</h3>
<pre><code class="language-kotlin">val runnable = Runnable {
            items.add(null)
            mListAdapter.notifyItemInserted(items.size -1)
        }
        mRecyclerView.post(runnable)</code></pre>
<p><strong><a href="https://medium.com/@shahbaz8x/cannot-call-this-method-in-a-scroll-callback-9beaf9abc2db">참고사이트</a></strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android/안드로이드] Manifest란?]]></title>
            <link>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Manifest%EB%9E%80</link>
            <guid>https://velog.io/@dev_jin/Android%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Manifest%EB%9E%80</guid>
            <pubDate>Sun, 29 Aug 2021 08:55:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/dev_jin/post/8c95dde2-a7ce-4fe2-8a90-9371eed26ff4/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C.png" alt=""></p>
<h2 id="📌manifest란">📌manifest란?</h2>
<p>매니페스트 파일은 Android 빌드 도구, Android 운영체제 및 Google Play에 앱에 관한 필수 정보를 설명한다. 가장 상위 폴더에 <code>AndroidManifest.xml</code>이 있어야하며, 이 정보는 앱이 실행되기 전에 시스템이 알아야 할 내용을 정의하고 있다.</p>
<hr>
<h3 id="package-name">Package Name</h3>
<p>Manifest 파일의 루트 요소<code>&lt;manifest&gt;&lt;/manifest&gt;</code>에는 해당 앱의 패키지 네임이 반드시 기재되어야 한다. “com.example.myapplication” 이라는 패키지를 만들었다면 아래와 같은 Manifest 파일이 생성된다.</p>
<pre><code class="language-kotlin">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=“com.example.myapplication”
    android:versionCode=&quot;1&quot;
    android:versionName=&quot;1.0&quot; &gt;
    ...
&lt;/manifest&gt;</code></pre>
<p>이는 우리가 앱을 빌드하여 APK 를 추출하는 과정에서 Android Build Tool 에 의해 다음 2가지 목적으로 사용된다.</p>
<ul>
<li>App Resource 에 접근하는데 사용되는 R 클래스의 네임스페이스로 적용
위 예에서는 com.example.myapplication.R 클래스가 생성됨.</li>
<li>매니페스트 파일 내에서 선언된 상대경로에 적용
예를 들어 <code>&lt;activity android:name=&quot;.MainActivity&gt;</code> 라고 선언했다면 이는 &quot;com.example.myapplication.MainActivity&quot; 를 의미함.</li>
</ul>
<pre><code class="language-kotlin">&lt;manifest package=&quot;com.example.myapplication&quot; ... &gt;
    &lt;application ... &gt;
        &lt;activity android:name=&quot;.MainActivity&quot; ... &gt;
            ...
        &lt;/activity&gt;
    &lt;/application&gt;
&lt;/manifest&gt;</code></pre>
<h3 id="app-components">App Components</h3>
<p>안드로이드에는 4대 컴포넌트가 있는데, 앱에서 이 컴포넌트들을 사용하기 위해서는 Manifest 파일에 해당하는 XML 요소를 선언해줘야 한다.</p>
<ul>
<li><strong>〈acvitivy〉</strong> : Activity의 각 하위 클래스</li>
<li><strong>〈service〉</strong> : Service의 각 하위 클래스</li>
<li><strong>〈receiver〉</strong> : Broadcast Receive의 각 하위 클래스</li>
<li><strong>〈provider〉</strong> : Content Provider의 각 하위 클래스</li>
</ul>
<h3 id="permissions">Permissions</h3>
<p>Android 앱은 민감한 사용자 데이터 또는 특정 시스템 기능에 액세스하기 위한 권한을 요청해야 한다. 예를 들어 인터넷 권한을 사용하는 앱은 매니페스트에 다음과 같은 줄이 있어야함.</p>
<pre><code class="language-kotlin">&lt;manifest ... &gt;
    &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;
    ...
&lt;/manifest&gt;</code></pre>
]]></description>
        </item>
    </channel>
</rss>