<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>엄코딩의 개발 일지</title>
    <link>https://eso0609.tistory.com/</link>
    <description>엄선오의 프로그래밍 블로그</description>
    <language>ko</language>
    <pubDate>Thu, 9 Apr 2026 18:21:43 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>엄코딩</managingEditor>
    <image>
      <title>엄코딩의 개발 일지</title>
      <url>https://tistory1.daumcdn.net/tistory/2385471/attach/05a49f6718f749a3b98d0b80e24bc7f2</url>
      <link>https://eso0609.tistory.com</link>
    </image>
    <item>
      <title>WorkManager</title>
      <link>https://eso0609.tistory.com/98</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Migrating from Firebase JobDispatcher to WorkManager&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2021-05-11 오전 8.54.56.png&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;417&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QkkDY/btq4HZYd5SZ/h2jdP3X97UjYUikoSVIKnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QkkDY/btq4HZYd5SZ/h2jdP3X97UjYUikoSVIKnK/img.png&quot; data-alt=&quot;WorkManager 동작 원리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QkkDY/btq4HZYd5SZ/h2jdP3X97UjYUikoSVIKnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQkkDY%2Fbtq4HZYd5SZ%2Fh2jdP3X97UjYUikoSVIKnK%2Fimg.png&quot; data-filename=&quot;스크린샷 2021-05-11 오전 8.54.56.png&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;417&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;WorkManager 동작 원리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Note: &lt;b&gt;&lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;If your app targets Android 10 (API level 29) or above, your FirebaseJobDispatcher and GcmNetworkManager API calls will no longer work on devices running Android Marshmallow (6.0) and above&lt;/span&gt;&lt;/b&gt;&lt;span data-token-index=&quot;2&quot; data-reactroot=&quot;&quot;&gt;.&lt;/span&gt; Follow the migration guides for FirebaseJobDispatcher and GcmNetworkManager for guidance on migrating. Also, see the Unifying Background Task Scheduling on Android announcement for more information regarding their deprecation.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/workmanager&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;developer.android.com/topic/libraries/architecture/workmanager&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1620799908793&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;WorkManager로 작업 예약 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;WorkManager로 작업 예약&amp;nbsp;&amp;nbsp;Android Jetpack의 일부 WorkManager는 지연 가능한 비동기 작업을 쉽게 예약할 수 있는 API로, 지연 가능한 비동기 작업은 앱이 종료되거나 기기가 다시 시작되더라도 실행될 것&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/topic/libraries/architecture/workmanager&quot; data-og-url=&quot;https://developer.android.com/topic/libraries/architecture/workmanager?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cXr62f/hyKbtQn2w2/7kh6MxoFGNxZnCL9AmSII1/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676,https://scrap.kakaocdn.net/dn/czIhEP/hyKblri7fN/LScWfbRWFarVkbfJqdoKwk/img.png?width=1704&amp;amp;height=798&amp;amp;face=0_0_1704_798&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/workmanager&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/topic/libraries/architecture/workmanager&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cXr62f/hyKbtQn2w2/7kh6MxoFGNxZnCL9AmSII1/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676,https://scrap.kakaocdn.net/dn/czIhEP/hyKblri7fN/LScWfbRWFarVkbfJqdoKwk/img.png?width=1704&amp;amp;height=798&amp;amp;face=0_0_1704_798');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;WorkManager로 작업 예약 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;WorkManager로 작업 예약&amp;nbsp;&amp;nbsp;Android Jetpack의 일부 WorkManager는 지연 가능한 비동기 작업을 쉽게 예약할 수 있는 API로, 지연 가능한 비동기 작업은 앱이 종료되거나 기기가 다시 시작되더라도 실행될 것&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결론은 targetSDK 30일 경우에 FirebaseJobDispatcher, GcmNetworkManager을 사용중이라면 WorkManager로 마이그레이션해야한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마이그레이션 방법은 간단하다. 기존에 JobService를 사용하였다면 ListenableWorker로 변경해주면된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드 변경은 google docs를 보고 따라하면 충분하지만, 내부 동작이나 클래스들을 조금 더 살펴볼 필요가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;WorkManager 특징&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;1. Job을 스케줄링 할 수 있다.&lt;/p&gt;
&lt;p&gt;2. Job의 상태를 관찰 할 수 있다.&lt;/p&gt;
&lt;p&gt;3. 제약조건을 두어, &lt;span style=&quot;color: #333333;&quot;&gt;해당 제약조건을 만족해야 Job이 동작할 수 있도록 설계할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;4. Job들을 원하는 순서대로 실행되도록 chain할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;Robust Scheduling&lt;/b&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;작업은 내부적으로 관리되는 SQLite 데이터베이스에 저장&lt;/b&gt;, WorkManager는 이 작업이 지속되고 기기 재부팅시 일정이 재조정되도록 한다.&lt;/li&gt;
&lt;li&gt;절전 기능, &lt;a href=&quot;https://developer.android.com/training/monitoring-device-state/doze-standby&quot;&gt;Doze 모드&lt;/a&gt; 정책을 따라서 시행된다. ( 이전 도즈모드일 경우 FCM, AlarmManager 사용 )&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주요 Class&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;WorkManager : 실행할 Job들을 enqueue하는 역할과 작업을 chain하는 역할을 담당한다.&lt;/p&gt;
&lt;p&gt;Worker : 백그라운드에서 동작하고, Result를 반환하는 doWork 메소드를 가진다.&lt;/p&gt;
&lt;p&gt;ListenableWorker : 메인스레드에서 동작하고, ListenableFuture&amp;lt;Result&amp;gt;를 반환하는 startWork 메소드를 가진다.&lt;/p&gt;
&lt;p&gt;WorkRequest : Worker에 태그를 추가하거나, 특정 제약조건을 설정할지 명시한다.&lt;/p&gt;
&lt;p&gt;Data : Worker들은 Data를 통해 데이터를 주고 받을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Result&lt;/b&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Result.success() : 작업이 성공적으로 완료되었음을 의미한다.&lt;/li&gt;
&lt;li&gt;Result.failure() : 작업이 실패로 끝났고, 다시 시작하지 않아도 됨을 의미한다.&lt;/li&gt;
&lt;li&gt;Result.retry() : 작업이 실패로 끝났고, 다시 시작해야 함을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Cancel&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;WorkManager 에서 취소 하고자 하는 작업이 이미 완료 된 작업이라면 취소 메서드는 아무 기능도 하지 않는다. 아직 실행 전 큐에 담긴 상태라면 실행하지 않고 취소 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예제 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1620801126770&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WorkManager.getInstance(...)
&amp;nbsp; &amp;nbsp; .beginWith(listOf(workA,workB))
&amp;nbsp; &amp;nbsp; .then(workC)
&amp;nbsp; &amp;nbsp; .enqueue()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같은 코드를 작성한 경우 workA와 workB는 동시에 실행된다. (순차적이지 않다.) 이후 workC가 실행된다.&lt;/p&gt;
&lt;p&gt;(workA와 workB가 성공적으로 완료되어야만 workC가 동작한다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선 beginWith에 존재하는 작업들이 완료가 되면 세팅되어있던 InputMerger를 통해 데이터가 생성된다.&lt;/p&gt;
&lt;p&gt;InputMerger는 추상화 클래스이고 이를 구현한 OverwritingInputMerger, ArrayCreatingInputMerger가 존재한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이름에서 느껴지듯이 전자는 결과를 overwrite하고, 후자는 배열로 합쳐준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HI0OI/btq4Hv4eoeI/wXrjJlghBP6E3XHHe1Vcs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HI0OI/btq4Hv4eoeI/wXrjJlghBP6E3XHHe1Vcs0/img.png&quot; data-alt=&quot;OverwritingInputMerger&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HI0OI/btq4Hv4eoeI/wXrjJlghBP6E3XHHe1Vcs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHI0OI%2Fbtq4Hv4eoeI%2FwXrjJlghBP6E3XHHe1Vcs0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OverwritingInputMerger&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lCOl6/btq4INJSEb0/Jvoynk5jZTxBr7Y4qMHgmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lCOl6/btq4INJSEb0/Jvoynk5jZTxBr7Y4qMHgmK/img.png&quot; data-alt=&quot;ArrayCreatingInputMerger&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lCOl6/btq4INJSEb0/Jvoynk5jZTxBr7Y4qMHgmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlCOl6%2Fbtq4INJSEb0%2FJvoynk5jZTxBr7Y4qMHgmK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ArrayCreatingInputMerger&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1620801241389&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var firstReq: OneTimeWorkRequest =OneTimeWorkRequestBuilder&amp;lt;FirstWorker&amp;gt;()
        .addTag(TAG)
        .setInputMerger(ArrayCreatingInputMerger::class.java)
        .build()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;InputMerger를 세팅하는 방법은 WorkerRequest를 생성할 때 위와 같은 코드를 작성하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;WorkQuery&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1620801682169&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val workQuery = WorkQuery.Builder
//                        .fromUniqueWorkNames(listOf(TAG))
                        .fromIds(listOf(firstReq.id, secondReq.id, thirdReq.id, fourthReq.id))
                        .addStates(listOf(WorkInfo.State.SUCCEEDED, WorkInfo.State.CANCELLED))
                        .build()

                val workInfos: ListenableFuture&amp;lt;List&amp;lt;WorkInfo&amp;gt;&amp;gt; = workManager.getWorkInfos(workQuery)


                for (currentWorker in workInfos.get()) {
                    Log.e(&quot;Seonoh&quot;, &quot;Worker : &quot; + currentWorker.tags)
                    Log.e(&quot;Seonoh&quot;, &quot;State : &quot; + currentWorker.state.name)
                }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WorkQuery는 일종의 query를 통해서 작업 상태를 관찰할 수 있다.&lt;/p&gt;
&lt;p&gt;처음에는 주석으로 처리된 uniqueWorkName값을 사용해서 쿼리를 진행했는데, WorkManager가 내부적으로는 sqlite를 사용하여 내부에 저장되어서 이전 작업 목록들도 나오게 되어서 id 값을 통해 진행하게되었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;getLiveData&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1620801827060&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;workManager.getWorkInfoByIdLiveData(fourthReq.id)
            .observe(this, androidx.lifecycle.Observer{
binding.run{
stateTv.text=it.state.name
                    val progressValue =it.outputData.getInt(&quot;Progress&quot;, 0);
                    if (progressPercent &amp;lt; progressValue)
                        progressTv.text= progressValue.toString()

                    workerTitleTv.text=it.tags.toString()

                    if (it.outputData.getStringArray(&quot;finishedWorker&quot;) != null) {
                        for (result init.outputData.getStringArray(&quot;finishedWorker&quot;)!!) {
                            resultText += result

                        }
                    }
                    resultTv.text= resultText
}
            })

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;getWorkInfoByIdLiveData함수등을 사용하여 작업을 observe하여 변화값에 대하여 ui처리가 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ListenableWorker&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;마지막으로 살펴볼 부분은 ListenableWorker이다. 구글에서는 왠만하면 Worker를 이용하기를 권장하고 있다. 하지만 콜백기반의 비동기 API를 사용하는 경우 ListenableWorker를 사용해야한다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/workmanager/advanced/listenableworker?hl=ko#kotlin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;developer.android.com/topic/libraries/architecture/workmanager/advanced/listenableworker?hl=ko#kotlin&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1620802281323&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ListenableWorker의 스레딩 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;특정 상황에서는 맞춤설정 스레딩 전략을 제공해야 합니다. 예를 들어 콜백 기반의 비동기 작업을 처리해야 하는 경우입니다. 이때는 차단 방식으로 작업을 할 수 없기 때문에 Worker를 사용할 수&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/topic/libraries/architecture/workmanager/advanced/listenableworker?hl=ko#kotlin&quot; data-og-url=&quot;https://developer.android.com/topic/libraries/architecture/workmanager/advanced/listenableworker?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cmhrAR/hyKblZaXE4/swmFo05YSldQMhjDTqOre0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/workmanager/advanced/listenableworker?hl=ko#kotlin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/topic/libraries/architecture/workmanager/advanced/listenableworker?hl=ko#kotlin&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cmhrAR/hyKblZaXE4/swmFo05YSldQMhjDTqOre0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;ListenableWorker의 스레딩 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;특정 상황에서는 맞춤설정 스레딩 전략을 제공해야 합니다. 예를 들어 콜백 기반의 비동기 작업을 처리해야 하는 경우입니다. 이때는 차단 방식으로 작업을 할 수 없기 때문에 Worker를 사용할 수&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;우선 지금까지 학습 내용을 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1620802087058&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CallbackWorker(
        context: Context,
        params: WorkerParameters
) : ListenableWorker(context, params) {
    override fun startWork(): ListenableFuture&amp;lt;Result&amp;gt; {
        return CallbackToFutureAdapter.getFuture { completer -&amp;gt;
            val callback = object : Callback {
                var successes = 0

                override fun onFailure(call: Call, e: IOException) {
                    completer.setException(e)
                }

                override fun onResponse(call: Call, response: Response) {
                    successes++
                    if (successes == 100) {
                        completer.set(Result.success())
                    }
                }
            }

            repeat(100) {
                downloadAsynchronously(&quot;https://example.com&quot;, callback)
            }

            callback
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;getFuture() 함수에서 내부적으로 동작하는 기능이 많다.&lt;/p&gt;
&lt;pre id=&quot;code_1620802347779&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static &amp;lt;T&amp;gt; ListenableFuture&amp;lt;T&amp;gt; getFuture(@NonNull Resolver&amp;lt;T&amp;gt; callback) {
    Completer&amp;lt;T&amp;gt; completer = new Completer&amp;lt;&amp;gt;();
    SafeFuture&amp;lt;T&amp;gt; safeFuture = new SafeFuture&amp;lt;&amp;gt;(completer);
    completer.future = safeFuture;
    // Set something as the tag, so that we can hopefully identify the call site from the
    // toString()
    // of the future. Retaining the instance could potentially cause a leak (if it's an inner
    // class)
    // and it's probably a lambda anyway so retaining the class provides just as much
    // information.
    completer.tag = callback.getClass();
    // Start timeout before invoking the callback
    final Object tag;
    try {
        tag = callback.attachCompleter(completer);
        if (tag != null) {
            completer.tag = tag;
        }
    } catch (Exception e) {
        safeFuture.setException(e);
    }
    return safeFuture;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;디버그를 통해 내부를 조금만 더 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Resolver&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Resolver은 callback object를 만들고, 트리거하는데 요구되는 작업을 시작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Object attachCompleter(@NonNull Completer&amp;lt;T&amp;gt; completer) throws Exception&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;콜백 객체를 생성하고, 이를 트리거 하는 작업을 설정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;결국 콜백오는 시점에 completer를 통해 set(RESULT)를 하는것이다.&lt;/p&gt;</description>
      <category>Android/WorkManager</category>
      <category>android</category>
      <category>future</category>
      <category>WORKER</category>
      <category>workmanager</category>
      <category>안드로이드</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/98</guid>
      <comments>https://eso0609.tistory.com/98#entry98comment</comments>
      <pubDate>Wed, 12 May 2021 17:08:50 +0900</pubDate>
    </item>
    <item>
      <title>React Native Version Upgrade</title>
      <link>https://eso0609.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우선 이번 프로젝트의 react native version은 0.61.5이고, 0.63.4 버전으로 업그레이드할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업데이트하게 된 계기는... 0.61.5 버전에서 ios 이미지들이 보이지 않는 이슈가 존재했는데, 대안으로 &lt;a href=&quot;https://www.npmjs.com/package/react-native-fix-image&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;react-native-fix-image&lt;/a&gt;를 사용했다. 하지만 해당 버그가 해결된 버전이 나온 터라 언제까지 이 라이브러리에 의존할 수 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 들어서 이 프로젝트들을 하나씩 버전을 올리고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벌써 3번째...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에 두번째 모두 다음 사이트를 참고했다.&amp;nbsp;&lt;a href=&quot;https://react-native-community.github.io/upgrade-helper/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;react native upgrade helper&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1608022547913&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Upgrade React Native applications&quot; data-og-description=&quot;&quot; data-og-host=&quot;react-native-community.github.io&quot; data-og-source-url=&quot;https://react-native-community.github.io/upgrade-helper/&quot; data-og-url=&quot;https://react-native-community.github.io/upgrade-helper/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://react-native-community.github.io/upgrade-helper/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react-native-community.github.io/upgrade-helper/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Upgrade React Native applications&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;react-native-community.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 보니 이 부분까지 바꿔줘야 하는 건가?라는 생각이 많이 들었다. 하지만 그 부분을 하나하나 테스트해보지 못하는 게 현실...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번에는 다른 방법이 없을까 생각하면서 자료를 찾다가 cli를 통한 버전 업그레이드 방법이 생각났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 이전에 사용하지 않았던 이유는 뭔지 모를 불안감 때문이었다. )&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://reactnative.dev/docs/upgrading&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;reactnative.dev/docs/upgrading&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1608022661578&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Upgrading to new React Native versions &amp;middot; React Native&quot; data-og-description=&quot;Upgrading to new versions of React Native will give you access to more APIs, views, developer tools and other goodies. Upgrading requires a small amount of effort, but we try to make it straightforward for you.&quot; data-og-host=&quot;reactnative.dev&quot; data-og-source-url=&quot;https://reactnative.dev/docs/upgrading&quot; data-og-url=&quot;https://reactnative.dev/docs/upgrading&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/brEkky/hyIATo3bvd/2MnFnZUnAp1DId9UkobElK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bsvyTd/hyIAFYCXny/nUbzm3KYC9qlTxxcFW2Wq0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://reactnative.dev/docs/upgrading&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://reactnative.dev/docs/upgrading&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/brEkky/hyIATo3bvd/2MnFnZUnAp1DId9UkobElK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bsvyTd/hyIAFYCXny/nUbzm3KYC9qlTxxcFW2Wq0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Upgrading to new React Native versions &amp;middot; React Native&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Upgrading to new versions of React Native will give you access to more APIs, views, developer tools and other goodies. Upgrading requires a small amount of effort, but we try to make it straightforward for you.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;reactnative.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. react, react-native 버전 업그레이드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째로 package.json에 있는 react, react-native 버전을 올리고자 하는 버전으로 설정해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1608024641244&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add react-native@{{REACT_NATIVE_UPGRADE_VERSION}}
yarn add react@{{REACT_UPGRADE_VERSION}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인의 경우 기존 react와 react-native 버전은 아래와 같았다.&lt;/p&gt;
&lt;pre id=&quot;code_1608022969021&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;react&quot;: &quot;16.9.0&quot;,
&quot;react-native&quot;: &quot;0.61.5&quot;,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업그레이드할 버전은 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1608023022458&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;react&quot;: &quot;16.13.1&quot;,
&quot;react-native&quot;: &quot;0.63.4&quot;,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. npx react-native upgrade&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째로 진행한 부분은 &lt;i&gt;npx react-native upgrade &lt;/i&gt;명령어를 통해 변경된 사항을 적용시키는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 수행함으로써 새로운 파일이 있으면 생성되고, 동일한 파일은 건너뛰게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 기존 프로젝트와 업데이트할 버전의 템플릿이 충돌되는 경우 git conflict와 같이 파일을 유지하거나 덮어쓸 수 있게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. pod install 오류 해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정상 여기까지 하고 실행하면 될 것 같아서, 큰 기대를 하고 실행을 해보았다. 역시나 pod install에서 오류가 났었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-15 오후 6.11.07.png&quot; data-origin-width=&quot;2774&quot; data-origin-height=&quot;516&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjf3qT/btqQgyNt9BN/GGBFEkZDZjdGShNbm7RfQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjf3qT/btqQgyNt9BN/GGBFEkZDZjdGShNbm7RfQK/img.png&quot; data-alt=&quot;jscallinvoker error&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjf3qT/btqQgyNt9BN/GGBFEkZDZjdGShNbm7RfQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjf3qT%2FbtqQgyNt9BN%2FGGBFEkZDZjdGShNbm7RfQK%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-15 오후 6.11.07.png&quot; data-origin-width=&quot;2774&quot; data-origin-height=&quot;516&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;jscallinvoker error&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 오류는 jscallinvoker가 해당 버전에서 duplicate 되면서 찾을 수 없다는 에러였는데 PodFile에서 간단하게 해결할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 pod 'ReactCommon/jscallinvoker', :path =&amp;gt; &quot;../node_modules/react-native/ReactCommon&quot; 구문을 다음과 같이 경로를 재지정해주었다. pod 'ReactCommon/jscallinvoker', :path =&amp;gt; &quot;../node_modules/react-native/ReactCommon&quot;&lt;/p&gt;
&lt;pre id=&quot;code_1608023625747&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pod 'ReactCommon/jscallinvoker', :path =&amp;gt; &quot;../node_modules/react-native/ReactCommon&quot; (X)

pod 'React-callinvoker', :path =&amp;gt; &quot;../node_modules/react-native/ReactCommon/callinvoker&quot; (O)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. retry...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 오류를 해결하고 다시 pod install을 진행해보았더니 무수히 많은 에러와 함께 실행되지 않았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 이전 버전 업데이트할 당시에 많이 봐왔던 에러들이었다. Podfile-lock과 관련된 에러였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json, package-lock.json, Podfile 등을 지우는 스크립트를 지정하여 사용했었는데 Podfile-lock은 빠져있었다. package-lock이나 Podfile.lock과 관련돼서 공부가 더 필요하겠지만 우선 간단히 정리했던 내용이 있으니 &lt;a href=&quot;https://eso0609.tistory.com/96&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 참고하면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 진행하면 ios에서는 잘 실행되었다. 물론 위에서 언급했던 react-native-fix-image를 이제는 더이상 사용하지 않아도 이미지 이슈는 해결되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Android error ( code-push error )&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이번에는 안드로이드에서 오류가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 code-push를 사용하고 있었는데 관련 오류였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;What went wrong: Could not determine the dependencies of task ':app:compileDebugJavaWithJavac'.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Could not resolve all task dependencies for configuration ':app:debugCompileClasspath'. Could not resolve project :react-native-code-push. Required by: project :app Unable to find a matching configuration of project :react-native-code-push:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;None of the consumable configurations have attributes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-native-code-push의 config 매칭을 할 수 없다... 는 에러인데 마찬가지로 이전 노가다로 버전을 올렸을 때 겪었던 이슈였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;android 디렉토리에서 settings.gradle에 다음 구문을 추가하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1608024320679&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;include ':app', ':react-native-code-push' 
project(':react-native-code-push').projectDir =file('../node_modules/react-native-code-push/android/app')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 위 문제가 아니라 버전을 올렸는데 갑자기 code-push 관련 오류가 보이는 것 같다면 &lt;a href=&quot;https://github.com/microsoft/react-native-code-push&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 확인해보면 code-push버전과 react-native-version이 매칭 되는지 확인해보길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJVgGf/btqQlJHng6H/aWR6AKxk5F0l2aRJVFvkGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJVgGf/btqQlJHng6H/aWR6AKxk5F0l2aRJVFvkGk/img.png&quot; data-alt=&quot;react-native-code-push version info&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJVgGf/btqQlJHng6H/aWR6AKxk5F0l2aRJVFvkGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJVgGf%2FbtqQlJHng6H%2FaWR6AKxk5F0l2aRJVFvkGk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;react-native-code-push version info&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번째 업그레이드는 비교적 수월하게 진행한 것 같다. 이전에 정말 엄청나게 삽질하면서 했던 일들이 정말 당황스러울 수 있는 상황에서도 침착함을 유지해줄 수 있었던 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 react-native version 편하게 올리자.&lt;/p&gt;</description>
      <category>ReactNative/React Native Version Upgrade</category>
      <category>0.63.4</category>
      <category>callinvoker</category>
      <category>jscallinvoker</category>
      <category>react native code push version</category>
      <category>react native version</category>
      <category>react native version upgrade</category>
      <category>react-native-fix-image</category>
      <category>version upgrade</category>
      <category>리액트네이티브</category>
      <category>리액트네이티브 버전 업그레이드</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/97</guid>
      <comments>https://eso0609.tistory.com/97#entry97comment</comments>
      <pubDate>Wed, 16 Dec 2020 11:31:01 +0900</pubDate>
    </item>
    <item>
      <title>pod Install &amp;amp; pod update 무슨 차이가 있을까?</title>
      <link>https://eso0609.tistory.com/96</link>
      <description>&lt;p&gt;React Native로 android / ios 환경을 개발하면서 아무 생각 없이 pod install &amp;amp; pod update를 남발하고 있었다.&lt;/p&gt;
&lt;p&gt;그러던중 최근에 xcode 12.0.1 ARC Semantic Issue Group 관련 이슈를 겪었다.&lt;/p&gt;
&lt;p&gt;요약하자면 프로젝트에서 Weibo SDK를 사용하고 있는데 Weibo SDK 함수를 불러오지 못하는 문제였다.&lt;/p&gt;
&lt;p&gt;하지만, 협업하고 있는 개발자분 작업환경에서는 잘 동작했다.... 그래서 clean-install, rm -rf ./node_modules, npm install, pod install, pod update를 한참동안이나 남발한 것 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결국에는 이게 문제가 아니었음을 알게된건 한참동안의 삽질 후였다...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;pod install&lt;/h3&gt;
&lt;p&gt;React Native를 시작하면서 ios 작업환경을 처음 겪게 되었고, xcode를 사용하게 되었다. ( 내 개발 인생에 ios는 없을 것만 같았다. )&lt;/p&gt;
&lt;p&gt;그리고 개발을 하면서 Library를 사용하게되었고 자연스럽게 Podfile의 존재 이유를 알게되었다.&lt;/p&gt;
&lt;p&gt;내가 쉽게 이해한 바로는 Podfile을 통해 Library를 사용할 수 있다. Podfile에 라이브러리 정보를 입력하고, 아래 명령어를 입력하면 해당 라이브러리를 다운로드 받게된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1602666129621&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pod install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;pod install을 사용하면 이를 Podfile.lock에서 기록해두는데 이 부분에서 중요한점은 해당 파일에 열거된 lib들에 대해서는 &lt;i&gt;&lt;b&gt;지정된 버전만 다운로드 하게된다는 점 &lt;/b&gt;&lt;/i&gt;이다. ( 이 부분에 있어서 pod update와 차이가 난다. )&amp;nbsp;&lt;/p&gt;
&lt;p&gt;** 더 자세하게 알고 싶다면 &lt;a href=&quot;https://guides.cocoapods.org/using/pod-install-vs-update.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;COCOAPODS&lt;/a&gt; 공식 링크를 확인해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 Pod update는 어떻게 다를까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;pod update&lt;/h3&gt;
&lt;pre id=&quot;code_1602666535908&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pod update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;해당 명령어를 사용하게되면 Podfile.lock을 참조하지 않고 최신 버전으로 업데이트하게 된다. 즉, pod update 명령을 실행하면 모든 pod들 library version들을 최신 버전으로 업데이트를 하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;** 더 자세하게 알고 싶다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://guides.cocoapods.org/using/pod-install-vs-update.html&quot;&gt;COCOAPODS&lt;/a&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;공식 링크를 확인해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p&gt;결국 삽질의 이유는 버전차이였다. 단순하게 package.json에 리스트된 것들이 전부일 줄 알았고, Podfile에 있는 것들만 install면 될 줄 알았다. 하지만 PodFile.lock의 중요성을 알게 되었고, 평소 협업시 PodFile.lock은 공유하지 않았다.&lt;/p&gt;
&lt;p&gt;협업하시는 개발자분에게 PodFile.lock를 공유드렸고, 단번에 해결되었다...&amp;nbsp;&lt;/p&gt;
&lt;p&gt;정말 단순한 문제로 발생한 반나절 삽질이었는데, 역시 삽질을 통해 배워서인지 뿌듯하고, 다시는 이런 삽질을 안할것만 같은 자신감이 든다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ReactNative/Bug fix</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/96</guid>
      <comments>https://eso0609.tistory.com/96#entry96comment</comments>
      <pubDate>Wed, 14 Oct 2020 18:19:29 +0900</pubDate>
    </item>
    <item>
      <title>Apple Login Email 이슈</title>
      <link>https://eso0609.tistory.com/95</link>
      <description>&lt;p&gt;React Native 환경에서 개발하고, Apple Login 기능이 필요한 상황이라면 &lt;a href=&quot;https://github.com/invertase/react-native-apple-authentication&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RN Apple Login Library&lt;/a&gt;를 사용하는 것이 상당히 편하다는 것을 느낄 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최근에 Apple Login 작업을 진행하면서 겪었던 이슈와 해결 방법에 대해서 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 상황&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일단 마케팅 용도로 이메일 정보가 필요한 상황에서 문제는 시작되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선 애플로그인시 나타나는 화면을 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcuiyI/btqFknwtxDD/NKAkQ5IA96CwQLko495f41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcuiyI/btqFknwtxDD/NKAkQ5IA96CwQLko495f41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcuiyI/btqFknwtxDD/NKAkQ5IA96CwQLko495f41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcuiyI%2FbtqFknwtxDD%2FNKAkQ5IA96CwQLko495f41%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위 이미지처럼 애플로그인을 진행하면 나의 이메일 공유하기, 나의 이메일 가리기 두가지 항목이 존재한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이메일을 공유한다면 유저의 실제 이메일 정보 값을 받아올 수 있었고, 가린다면 나름 암호화된 메일 주소를 받게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지는 순조로웠다. 하지만.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;애플 로그인은 보안상 문제로 처음 로그인시에만 email값을 주게된다. 그 이후로는 null로 온다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 프로젝트의 로직상 애플 로그인을 진행하고, 추가로 입력해야 하는 부분까지 진행해야 회원가입이 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 고민이 되었다. 이메일 정보를 로컬에 저장하는게 좋을지, 서버에서 저장해두는게 좋을지.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;로컬에 저장한다면 문제점이 생긴다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앱을 삭제했을때. 구체적으로 말해보자면, 앱을 켜고, 애플로그인을 하고, 추가입력화면으로 진입했을 때, 추가 입력을 하지 않고 앱을 끈 경우 로컬에 이메일을 저장했다면 문제없다. 하지만 앱을 삭제한다면? 문제는 여기서부터 생겨났다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;애플은 보안상( 또 보안상 ) Apple Login을 단 한번이라도 진행한 앱을 저장하고있다. ( 애플폰에서 )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이를 확인해볼 수 있는 방법은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;설정 -&amp;gt; 자신의 Apple 프로필 클릭 -&amp;gt; 암호 및 보안 -&amp;gt; 내 Apple ID를 사용하는 앱&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ObeVT/btqFmtvqQQ5/fGwSD2koWD7QOaxlYGVF2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ObeVT/btqFmtvqQQ5/fGwSD2koWD7QOaxlYGVF2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ObeVT/btqFmtvqQQ5/fGwSD2koWD7QOaxlYGVF2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FObeVT%2FbtqFmtvqQQ5%2FfGwSD2koWD7QOaxlYGVF2k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같은 경로로 진입해본다면 Apple ID를 사용했던 앱들을 볼 수 있다. 이는 &lt;b&gt;앱을 지워도 이 목록은 지워지지 않는다. 사용자가 직접 지워야 지워진다...만약 사용자가 직접 지운다면 Email 값을 받을 수 있지만....너무 좋겠지만...&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결국 애플로그인을 한번이라도 했다면 email 정보는 저장해두어야 한다. 그렇지 않으면 null값 만을 받을 수 있기 때문에 해당 정보를 사용하기가 어렵다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 검색 또 검색을 한 결과 해당 결과값을 디코딩하면 이메일 정보를 확인할 수 있다는 정보를 알게되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;바로 &lt;a href=&quot;https://jwt.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;jwt&lt;/a&gt;에 접속해서 진행해보았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결과값으로 받은 identityToken을 넣어보니 email 정보를 얻을 수 있었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l0C8n/btqFk7l4IFf/YZ6SmSQWCGgkJSjldb5UI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l0C8n/btqFk7l4IFf/YZ6SmSQWCGgkJSjldb5UI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l0C8n/btqFk7l4IFf/YZ6SmSQWCGgkJSjldb5UI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl0C8n%2FbtqFk7l4IFf%2FYZ6SmSQWCGgkJSjldb5UI1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;( 최대한 가리면서 어떤 유형의 값인지를 보여주고 싶다보니.. 이미지가 좀 난해한 점 이해부탁드립니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;토큰속에 이메일 정보가 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;서버측에서 이 로직을 진행하게 되면 해결!!! ( 기존 서버에서 이미 해당 로직으로 작업하고 있어서 문제될 부분이 없었다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ReactNative/Apple Login</category>
      <category>Apple Login</category>
      <category>apple login email null</category>
      <category>apple login get email</category>
      <category>applelogin</category>
      <category>react native</category>
      <category>react native apple login</category>
      <category>rn apple login</category>
      <category>rn apple login email</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/95</guid>
      <comments>https://eso0609.tistory.com/95#entry95comment</comments>
      <pubDate>Fri, 3 Jul 2020 16:35:26 +0900</pubDate>
    </item>
    <item>
      <title>Apple Login</title>
      <link>https://eso0609.tistory.com/94</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;i&gt;&lt;b&gt;갑자기 왜? Apple Login을 사용해야 하는가&lt;/b&gt;&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;바야흐로...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2019년 09월 12일 애플 뉴스에 따르면 신규앱은 당일부터, 기존의 앱과 업데이트는 2020년 4월 중으로 해당 가이드라인 ( Apple 로그인 관련 )을 따라야 한다고 기재되어 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;실제 본인이 참여한 프로젝트에서 애플 로그인 관련해서 2020년 7월 2일 심사에서 거부를 당했다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실제 IOS 앱 심사를 경험한지는 얼마되지 않았지만, 리젝 사유는 매번 천차만별인 것 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결론은, 앱에서 사용자의 기본 계정을 설정 또는 인증하기 위해 타사 또는 소셜 로그인 서비스 ( facebook, google, wechat 등 )을 사용하는 앱은 Apple 로그인 역시 제공해야 한다. ( 자세한 사항은 &lt;a href=&quot;https://developer.apple.com/kr/app-store/review/guidelines/#sign-in-with-apple&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크(4.8)&lt;/a&gt;&amp;nbsp;참조. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Setting Apple Login&lt;/h2&gt;
&lt;p&gt;이 포스팅은 애플 개발자로 등록이 되어있거나, 팀에 속해있을 경우를 전제로 두고 작업을 진행한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본인은 React Native환경에서 다음과 같은 오류를 보게되어 이 작업을 시작했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593759974680&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;com.apple.AuthenticationServices.AuthorizationError 오류 1000&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Setting Identifiers ( App ID 등록 )&lt;/h3&gt;
&lt;p&gt;우선 애플 개발자 사이트&amp;nbsp;&lt;a href=&quot;https://developer.apple.com/&quot;&gt;https://developer.apple.com/&lt;/a&gt; 접속한다.&lt;/p&gt;
&lt;p&gt;사이트에 접속해서 우측 상단 &lt;b&gt;Account&lt;/b&gt;에 접속하면 좌측 네비게이션 바에서 &lt;b&gt;Certificates, Ids &amp;amp; Profiles&lt;/b&gt;&amp;nbsp;로 접속한다.&lt;/p&gt;
&lt;p&gt;다시 좌측에서 &lt;b&gt;Identifiers&lt;/b&gt; 클릭.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kC9c7/btqFk77gZhQ/CcL7E4DCLszUNsuFqzwNQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kC9c7/btqFk77gZhQ/CcL7E4DCLszUNsuFqzwNQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kC9c7/btqFk77gZhQ/CcL7E4DCLszUNsuFqzwNQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkC9c7%2FbtqFk77gZhQ%2FCcL7E4DCLszUNsuFqzwNQK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같은 이미지가 보이는데 +를 클릭하여 App ID를 등록한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qqahi/btqFk7MWNvs/VbyDyfZ7VvaTkFas8810c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qqahi/btqFk7MWNvs/VbyDyfZ7VvaTkFas8810c0/img.png&quot; data-alt=&quot;App IDs 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qqahi/btqFk7MWNvs/VbyDyfZ7VvaTkFas8810c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQqahi%2FbtqFk7MWNvs%2FVbyDyfZ7VvaTkFas8810c0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;App IDs 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wXACR/btqFkJshC7E/ijsKMI1Tuw3k2gNFDDSbx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wXACR/btqFkJshC7E/ijsKMI1Tuw3k2gNFDDSbx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wXACR/btqFkJshC7E/ijsKMI1Tuw3k2gNFDDSbx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwXACR%2FbtqFkJshC7E%2FijsKMI1Tuw3k2gNFDDSbx0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위 항목들을 채우고 아래로 스크롤하여 Sign In with Apple을 클릭한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czA76Q/btqFkTaiEjK/GMj3Ty0ugWyY6aCCFiBgOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czA76Q/btqFkTaiEjK/GMj3Ty0ugWyY6aCCFiBgOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czA76Q/btqFkTaiEjK/GMj3Ty0ugWyY6aCCFiBgOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczA76Q%2FbtqFkTaiEjK%2FGMj3Ty0ugWyY6aCCFiBgOK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;선택했다면 Edit을 누르고 나머지 설정을 진행한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQ54tC/btqFnhOQs28/RO5Jehx2Mug7nO2ERBsXZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQ54tC/btqFnhOQs28/RO5Jehx2Mug7nO2ERBsXZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQ54tC/btqFnhOQs28/RO5Jehx2Mug7nO2ERBsXZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQ54tC%2FbtqFnhOQs28%2FRO5Jehx2Mug7nO2ERBsXZK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Enable as a primary App ID를 선택했다면 저장하고 다음 단계를 진행하면 된다. ( App ID 등록 끝 )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Key 생성&lt;/h3&gt;
&lt;p&gt;Identifier와 같이 + 버튼을 누른다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/905W1/btqFmuAXlJV/n1qlnEJzcmltSntZ2TojXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/905W1/btqFmuAXlJV/n1qlnEJzcmltSntZ2TojXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/905W1/btqFmuAXlJV/n1qlnEJzcmltSntZ2TojXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F905W1%2FbtqFmuAXlJV%2Fn1qlnEJzcmltSntZ2TojXk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Sing in with Apple을 선택하고 Configure을 클릭한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 해당 App ID를 선택한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 진행하면 키를 다운 받을 수 있는 화면을 볼 수 있다. 키는 다운로드해서 잘 보관하면 된다. 이페이지를 나가면 재다운로드 할 수 없다고 하여 일단 다운로드! ( 아직 써보지는 않았음. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 진행하면 App에서는 문제없이 Apple Login 기능을 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본인은 React Native에서 애플 로그인 기능 작업을 진행했고,&lt;a href=&quot;https://github.com/invertase/react-native-apple-authentication&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; RN 애플 로그인 라이브러리&lt;/a&gt;를 사용했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고사항 : &lt;a href=&quot;https://spiralmoon.tistory.com/entry/Apple-%EC%95%A0%ED%94%8C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-Sign-In-with-Apple&quot;&gt;https://spiralmoon.tistory.com/entry/Apple-%EC%95%A0%ED%94%8C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-Sign-In-with-Apple&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>이슈/Apple 연동 로그인</category>
      <category>applelogin</category>
      <category>애플로그인필수</category>
      <category>애플연동로그인</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/94</guid>
      <comments>https://eso0609.tistory.com/94#entry94comment</comments>
      <pubDate>Fri, 3 Jul 2020 15:47:45 +0900</pubDate>
    </item>
    <item>
      <title>React Native Hook</title>
      <link>https://eso0609.tistory.com/93</link>
      <description>&lt;p&gt;React Native Hook을 사용하게 된 가장 큰 이유는 Hook을 학습하게 된 가장 큰 이유는 Class를 쓰지 않고 function에서 state를 관리할 수 있기 때문이다. 이렇게 된다면 함수형 컴포넌트를 사용하면서, '&lt;i&gt;&lt;b&gt;독립적으로 state를 관리할 수 있게 되고, 테스트가 편해질 것' &lt;/b&gt;&lt;/i&gt;이라고 생각했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.&amp;nbsp; 함수형 컴포넌트 상태 관리 ( useState )&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Hook을 사용하기 전에는 클래스단에서 state를 선언하고 가감연산 부분에서 setState를 해주었을 것이다. 하지만 Hook을 사용하게 된다면 이제는 &lt;i&gt;&lt;b&gt;함수형 컴포넌트에서도 state를 쉽게 관리&lt;/b&gt;&lt;/i&gt;할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1591319579881&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Counter(): JSX.Element {

    const [count, setCount] = useState(0);
    
    const onCountUp = () =&amp;gt; {
      setCount(count + 1);
    };
    
    const onCountDown = () =&amp;gt; {
      setCount(count - 1);
    };
    
    return (
      &amp;lt;View style={styles.container}&amp;gt;
        &amp;lt;View style={styles.touchArea}&amp;gt;
          &amp;lt;TouchableOpacity style={styles.upContainer} onPress={onCountUp}&amp;gt;
            &amp;lt;CustomText style={styles.title}&amp;gt;UP&amp;lt;/CustomText&amp;gt;
          &amp;lt;/TouchableOpacity&amp;gt;
          &amp;lt;TouchableOpacity style={styles.downContainer} onPress={onCountDown}&amp;gt;
            &amp;lt;CustomText style={styles.title}&amp;gt;DOWN&amp;lt;/CustomText&amp;gt;
          &amp;lt;/TouchableOpacity&amp;gt;
        &amp;lt;/View&amp;gt;

        &amp;lt;View style={styles.countContainer}&amp;gt;
          &amp;lt;CustomText style={styles.title}&amp;gt;{count}&amp;lt;/CustomText&amp;gt;
        &amp;lt;/View&amp;gt;
      &amp;lt;/View&amp;gt;
    );
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드는 Hook을 설명할 때 많이 사용되는 가감연산만을 구현하고 있다. useState는 현재 state값과 state를 업데이트 할 수 있는 함수를 반환하고 초기값을 받는다. 즉, 클래스로 작성했을때에는 아래 코드 느낌이었을 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1591320091075&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;this.state = { count : 0 }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1591320153604&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; setCount(value : number){
    this.setState(
      count : value
    )
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 위 절차를 줄여주고, 함수내에서 state를 쉽게 관리할 수 있다는 점이 Hook의 장점이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 함수형 컴포넌트 라이프 사이클 ( useEffect )&lt;/h2&gt;
&lt;p&gt;useEffect는 컴포넌트가 렌더링 될 때마다 특정 작업을 실행할 수 있도록하는 Hook이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1591335526702&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; useEffect(() =&amp;gt; {
      showLog('렌더링');
      showLog('count : ' + count);
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드는 함수형 컴포넌트가 렌더링 될 때 실행되는 코드다. 만약 마운트 될 때만 실행하고 싶다면 useEffect 함수의 2번째 인자로 비어있는 배열을 넣어주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1591335547100&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; useEffect(() =&amp;gt; {
      showLog('렌더링 완료.');
      showLog('count : ' + count);
    }, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드는 마운트 됬을 때만 동작하게된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 특정 값이 변할때만 실행하고 싶은 코드가 있다면 아래 코드처럼 특정 값을 배열에 넣어주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1591323168438&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; useEffect(() =&amp;gt; {
      showLog('렌더링 완료.');
      showLog('count : ' + count);
    }, [count]);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;useEffect함수는 기본적으로 렌더링 된 후에 실행되고, 두번째 파라미터에 따라 실행 조건이 변한다.&lt;/p&gt;
&lt;p&gt;만약 컴포넌트가 언마운트 되기 전, 업데이트 되기 직전에 특정 작업을 수행하고 싶다면 아래 코드처럼 작성하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1591323343966&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; useEffect(() =&amp;gt; {
      showLog('렌더링 완료.');
      showLog('count : ' + count);
      return () =&amp;gt; {
        console.log('cleanup');
      };
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결론 &lt;u&gt;&lt;i&gt;&lt;b&gt;진작 Hook을 사용했어야 했다.&lt;/b&gt;&lt;/i&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고자료 : &lt;a href=&quot;https://velog.io/@velopert/react-hooks&quot;&gt;https://velog.io/@velopert/react-hooks&lt;/a&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ReactNative/React Native Hooks</category>
      <category>componentlifecycle</category>
      <category>Hook</category>
      <category>lifecycle</category>
      <category>reactnative</category>
      <category>TypeScript</category>
      <category>useEffect</category>
      <category>useState</category>
      <category>함수형컴포넌트</category>
      <category>함수형컴포넌트라이프사이클</category>
      <category>함수형컴포넌트상태관리</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/93</guid>
      <comments>https://eso0609.tistory.com/93#entry93comment</comments>
      <pubDate>Fri, 5 Jun 2020 11:17:49 +0900</pubDate>
    </item>
    <item>
      <title>SDK location not found</title>
      <link>https://eso0609.tistory.com/92</link>
      <description>&lt;p&gt;협업을 진행하며 다음과 같은 에러 문구를 보게되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;SDK&amp;nbsp;location&amp;nbsp;not&amp;nbsp;found.&amp;nbsp;Define&amp;nbsp;location&amp;nbsp;with&amp;nbsp;an&amp;nbsp;ANDROID_SDK_ROOT&amp;nbsp;environment&amp;nbsp;variable&amp;nbsp;or&amp;nbsp;by&amp;nbsp;setting&amp;nbsp;the&amp;nbsp;&lt;a href=&quot;sdk.dir&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;sdk.dir&lt;/a&gt; path in your project's local properties file at '~/android/local.properties'.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문구 그대로 SDK 위치를 찾을 수 없는 것이 문제의 원인이었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;React Native에서는 다음과 같은 방법으로 이 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. React Native 프로젝트에서 android 디렉토리로 접근한다.&lt;/p&gt;
&lt;p&gt;2. android 디렉토리에서 local.properties 파일을 생성한다.&lt;/p&gt;
&lt;p&gt;3. 아래 코드를 입력한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;For windows users&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;sdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dir&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;C\:\\&lt;/span&gt;&lt;span&gt;Users&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;UserName&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;AppData&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;Local&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;Android&lt;/span&gt;&lt;span&gt;\\sdk&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;For Mac users&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;sdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dir &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/Users/&lt;/span&gt;&lt;span&gt;USERNAME&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;Library&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;Android&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;sdk&lt;/span&gt;&lt;/p&gt;</description>
      <category>ReactNative/Bug fix</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/92</guid>
      <comments>https://eso0609.tistory.com/92#entry92comment</comments>
      <pubDate>Tue, 17 Mar 2020 11:51:32 +0900</pubDate>
    </item>
    <item>
      <title>ESLint, Prettier 함께 사용하기.</title>
      <link>https://eso0609.tistory.com/91</link>
      <description>&lt;p&gt;VSCode를 이용하여 react-native 프로젝트를 진행하던 시기, 회사에서는 commit - create pull request - review - merge .. 의 과정으로 협업을 진행하고 있었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;commit과 review 과정을 겪으면서 개발자마다 코딩 스타일이 다르고, 이를 구두 또는 일반 문서로 합일화 하는 것은 어렵다고 생각했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 코딩 스타일을 하나의 스타일로 규제할 수는 없을까 라는 생각을 시초로 EsLint, Prettier에 접근하게 되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;스타트업에서의 기술부채가 늘어가는 것은 당연한 것이라고 생각하고 있고, 기술부채를 꾸준히 지워가는게 중요하다고 생각한다. 그래서 ESLint를 적용해야겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ESLint&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ESLint is designed to be completely configurable, meaning you can turn off every rule and run only with basic syntax validation, or mix and match the bundled rules and your custom rules to make ESLint perfect for your project.&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://eslint.org/docs/user-guide/getting-started&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ESLint&lt;/a&gt;는 쉽게 말해서 javascript에서 코딩 컨벤션과 에러를 체크해주는 프로그램이다. 독립적으로 또는 IDE 플러그인으로도 존재한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prettier&lt;/h3&gt;
&lt;p&gt;&lt;s&gt;프리티어....? 아마존 잠깐 했을때 프리티어...?&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;An opinionated code formatter&lt;/li&gt;
&lt;li&gt;Supports many language&lt;/li&gt;
&lt;li&gt;Integrates wirh most editors&lt;/li&gt;
&lt;li&gt;Has few options&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;음... 한마디로 많은 언어를 지원해주는 코드 포매터다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ESLint, Prettier 같이 사용하는 이유&lt;/h3&gt;
&lt;p&gt;처음에 들었던 생각은 'ESLint, Prettier 둘 중에 하나만 사용하면 되는 걸.. 왜 두가지를 함께 사용해야할까? ' 였다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;각각의 doc와 구글링을 통해서 내린 결론은 &lt;b&gt;서로의 부족한점을 매꿔주기 위해서 함께 사용하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;ESLint는 code quality, Prettier은 code style위주라고 생각하면된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들어서 ESLint는 사용하지 않는 변수, 전역변수의 사용 등을 알려준다면 Prettier은 한줄의 최대길이,&amp;nbsp; &lt;span style=&quot;background-color: #f3c000;&quot;&gt;' &lt;/span&gt;과 &lt;span style=&quot;background-color: #f89009;&quot;&gt;&quot;&lt;/span&gt; 등을 신경써준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ESLint 모듈 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1576654769379&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm install eslint --save-dev&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prettier 모듈 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1576654840086&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install prettier --save-dev --save-exact&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;eslint-plugin-react-native 모듈 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1576654983943&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install eslint-plugin-react-native --save-dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;react-native 개발시 사용하면 좋다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;eslint-config-prettier 모듈 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1576655004947&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install eslint-config-prettier --save-dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;eslint와 prettier를 함께 사용하는 경우 불필요하거나 충돌 위험이 있는 eslint 설정을 해제하는 모듈이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VS Code 세팅&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3mys6/btqAz2hJzKk/HkgG5ZUm3p4WrRknmpZRT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3mys6/btqAz2hJzKk/HkgG5ZUm3p4WrRknmpZRT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3mys6/btqAz2hJzKk/HkgG5ZUm3p4WrRknmpZRT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3mys6%2FbtqAz2hJzKk%2FHkgG5ZUm3p4WrRknmpZRT0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ClPp0/btqAyF8ATBS/3ZNcDgk8wKgNX4981ONOP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ClPp0/btqAyF8ATBS/3ZNcDgk8wKgNX4981ONOP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ClPp0/btqAyF8ATBS/3ZNcDgk8wKgNX4981ONOP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FClPp0%2FbtqAyF8ATBS%2F3ZNcDgk8wKgNX4981ONOP0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;설치가 완료되면 setting창에 들어가서 오른쪽 상단 아이콘을 통해 settings.json 접근한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&quot;editor.formatOnSave&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;javascript.format.enable&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt; &lt;/span&gt;&lt;s&gt;&lt;span&gt;&quot;prettier.eslintIntegration&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true ( 포스팅 시간 기준 2019.12.18 deprecate되어서 삭제 )&lt;/span&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드를 settings.json 에 추가하면 설정파일만 생성해주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설정파일 생성&lt;/h3&gt;
&lt;p&gt;프로젝트의 루트레벨에서 .eslintrc.json을 생성한 후에, 아래 코드를 작성하면된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1576661439073&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;extends&quot;: [
    &quot;eslint:recommended&quot;,
    &quot;plugin:prettier/recommended&quot;,
    &quot;plugin:react/recommended&quot;,
    &quot;prettier&quot;,
    &quot;prettier/react&quot;
  ],
  &quot;plugins&quot;: [&quot;prettier&quot;, &quot;react&quot;],
  &quot;parserOptions&quot;: {
    &quot;ecmaVersion&quot;: 6,
    &quot;sourceType&quot;: &quot;module&quot;,
    &quot;ecmaFeatures&quot;: {
      &quot;jsx&quot;: true
    }
  },
  &quot;env&quot;: {
    &quot;es6&quot;: true,
    &quot;browser&quot;: true,
    &quot;node&quot;: true
  },
  &quot;rules&quot;: {
    &quot;prettier/prettier&quot;: [
      &quot;error&quot;,
      {},
      {
        &quot;usePrettierrc&quot;: false
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드를 적용하게 되면, Prettier의 규칙을 ESLint의 규칙으로 사용하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 아래 코드는 Prettier의 규칙 설정을 무시하겠다는 뜻이다.&lt;/p&gt;
&lt;pre id=&quot;code_1576661554927&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;usePrettierrc&quot;: false&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코딩 스타일을 사용자화 하고 싶은 경우에 ESLint 규칙은 rules레벨에서, prettier 규칙은 rules~~prettier/prettier레벨에서 변경하면 된다.&lt;/p&gt;</description>
      <category>ReactNative/ESlint, Prettier</category>
      <category>coderule</category>
      <category>codingconvention</category>
      <category>eslint</category>
      <category>JavaScript</category>
      <category>linter</category>
      <category>prettier</category>
      <category>린터</category>
      <category>코드규칙</category>
      <category>코딩컨벤션</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/91</guid>
      <comments>https://eso0609.tistory.com/91#entry91comment</comments>
      <pubDate>Wed, 18 Dec 2019 18:35:27 +0900</pubDate>
    </item>
    <item>
      <title>React Native Navigation undefined is not an object error</title>
      <link>https://eso0609.tistory.com/90</link>
      <description>&lt;p&gt;React Native( 이하 RN ) 개발을 시작하면서 처음 난관에 부딫쳤던 에러가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;개발해야 하는 피쳐는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;rootView안에 FlatList에서 사용하는 CustomView가 존재한다. 이 CustomView에 TouchableOpacity를 씌우고 onPress시에&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;detail 화면으로 이동해야한다. 이전까지 너무 쉬웠던 navigate였기에 방심했던걸까..&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선 에러는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;KakaoTalk_Image_2019-11-20-10-49-03.jpeg&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/befCQx/btqzSOE0JHX/s2hEP168pIbdhAbnqxDP70/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/befCQx/btqzSOE0JHX/s2hEP168pIbdhAbnqxDP70/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/befCQx/btqzSOE0JHX/s2hEP168pIbdhAbnqxDP70/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbefCQx%2FbtqzSOE0JHX%2Fs2hEP168pIbdhAbnqxDP70%2Fimg.jpg&quot; data-filename=&quot;KakaoTalk_Image_2019-11-20-10-49-03.jpeg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;undefined is not an object... stackoverflow를 열심히 뒤져 보아도 모두 같은 내용일 뿐 해결되지 않았다. ( 너무 기본이라서 나오지 않았던 걸까...? )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;constructor에 bind하는 방법이며, rootView에서 navigation을 넘겨주는 방법이며... 다 내가 찾던 방법이 아니었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 &lt;a href=&quot;https://reactnavigation.org/docs/en/navigation-prop.html#docsNav%5D&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ReactNative Document&lt;/a&gt;를 차근차근 읽어보던중 해법을 찾았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&lt;b&gt;this.props.navigation&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span&gt;It's important to highlight the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;navigation&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;prop is&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;not&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;passed in to&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;all&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;components; only&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;screen&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;components receive this prop automatically! React Navigation doesn't do anything magic here. For example, if you were to define a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;MyBackButton&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;component and render it as a child of a screen component, you would not be able to access the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;navigation&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;prop on it. If, however, you wish to access the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;navigation&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;prop in any of your components, you may use the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://reactnavigation.org/docs/en/with-navigation.html&quot;&gt;withNavigation&lt;/a&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;HOC.&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- 루트를 설정할 때 연결했었던 Screen에는 자동으로 navigation props가 전달된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- 직접적으로 설정되어 있지 않은 경우 react-navigation이 제공해주는 HOC 컴포넌트를 인자로 호출해서 직접 주입해줘야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1574215271437&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { withNavigation } from 'react-navigation';

class SubComponent extends React.Component {
	...
}

export default withNavigation(SubComponent);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;진행하고 있던 프로젝트에서 FlatList의 ItemView를 클릭시 DetailView로 이동했어야 했는데, 해당 ItemView는 SubComponent였지만 위 코드처럼 HOC 컴포넌트를 인자로 호출해서 직접 주입하지 않았다.&lt;/p&gt;
&lt;p&gt;그래서 에러가 발생했었고, 위 코드처럼 HOC 컴포넌트를 인자로 호출해서 직접 주입해주니 WORKING !!&lt;/p&gt;</description>
      <category>ReactNative/React-Native Navigation</category>
      <category>reactnative</category>
      <category>reactnativeerror</category>
      <category>reactnativenavigation</category>
      <category>undefinedisnotanobject</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/90</guid>
      <comments>https://eso0609.tistory.com/90#entry90comment</comments>
      <pubDate>Wed, 20 Nov 2019 11:06:49 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 앱 예약 게시</title>
      <link>https://eso0609.tistory.com/89</link>
      <description>&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size26&quot;&gt;사내에서 예약 게시기능을 처음으로 사용해보면서 이게 될까 라는 의문을 많이 가졌습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size26&quot;&gt;' 이렇게만 하면 된다고..? ' 라는 생각 이 글 보고 지워버리셨으면 좋겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size26&quot;&gt;우선 예약 게시 기능을 이용하기 전에 알아 두어야할 중요한 사실은 예약 게시 기능은 업데이트에만 사용할 수 있고, &lt;b&gt;앱을 처음 게시할 때는 사용할 수 없는 기능&lt;/b&gt;입니다. 또한 인앱 상품 페이지를 변경하거나, 가격을 업데이트하거나 '이번 버전의 새로운 기능' 섹션에 출시 노트를 추가한 경우 변경사항이 즉시 게시됩니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size26&quot;&gt;보다 자세한 사항은 &lt;a href=&quot;https://support.google.com/googleplay/android-developer#topic=3450769&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Play Console 고객센터&lt;/a&gt;에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Google Play Console 설정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xZFnb/btqzK0ELOF9/wJSHLYXeFenGASF51GYksk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xZFnb/btqzK0ELOF9/wJSHLYXeFenGASF51GYksk/img.png&quot; data-alt=&quot;Google Play Console&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xZFnb/btqzK0ELOF9/wJSHLYXeFenGASF51GYksk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxZFnb%2FbtqzK0ELOF9%2FwJSHLYXeFenGASF51GYksk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Google Play Console&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;Google Play Console -&amp;gt; 앱정보 -&amp;gt; 스토어 등록정보 및 가격 및 배포 등 이 섹션에 있는 &lt;b&gt;여러 페이지에서 예약 게시 설정을 할 수 있습니다. &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&lt;b&gt;해당 섹션내 여러 페이지에 들어가셔서 하단에 보시면 예약 게시 토글 버튼이 있습니다. 이를 활성화 시켜 주시면 됩니다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V2ype/btqzK1cCmSu/SPNZt3QlUuNe7Ekj9KlKnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V2ype/btqzK1cCmSu/SPNZt3QlUuNe7Ekj9KlKnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V2ype/btqzK1cCmSu/SPNZt3QlUuNe7Ekj9KlKnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV2ype%2FbtqzK1cCmSu%2FSPNZt3QlUuNe7Ekj9KlKnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱 업로드&lt;/h2&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;예약 게시를 활성화 시킨 다음에는, &lt;b&gt;평상시처럼 앱 업데이트를 해주면됩니다.&lt;/b&gt; Google Play Console에 들어가서 앱 버전에서 업로드하고 노트적고 등 평상시처럼!!!&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhN75W/btqzH1lgPZf/pLgEguTZKRhpBHfGhGZ6GK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhN75W/btqzH1lgPZf/pLgEguTZKRhpBHfGhGZ6GK/img.png&quot; data-alt=&quot;예약 게시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhN75W/btqzH1lgPZf/pLgEguTZKRhpBHfGhGZ6GK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhN75W%2FbtqzH1lgPZf%2FpLgEguTZKRhpBHfGhGZ6GK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예약 게시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;업데이트를 진행하시면서 느끼셨겠지만 페이지 상단에 이전에는 보이지 않았던 HeaderView가 보입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6PXXo/btqzIfXWaaw/PHIaEkzPb0JnvIoDgHWEz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6PXXo/btqzIfXWaaw/PHIaEkzPb0JnvIoDgHWEz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6PXXo/btqzIfXWaaw/PHIaEkzPb0JnvIoDgHWEz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6PXXo%2FbtqzIfXWaaw%2FPHIaEkzPb0JnvIoDgHWEz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size26&quot;&gt;변경 로그 보기를 클릭하면 위와 같은 화면을 보실 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size26&quot;&gt;저의 경우 2시간정도 지나고나서 예약 게시 출시 가능 알람을 받았습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size26&quot;&gt;그리고나서 Google Play Console 프로덕션 트렉에서 아래와 같은 화면을 볼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VtlDo/btqzJd6aADo/aab28Fmfk2nWAdNm6qmQBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VtlDo/btqzJd6aADo/aab28Fmfk2nWAdNm6qmQBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VtlDo/btqzJd6aADo/aab28Fmfk2nWAdNm6qmQBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVtlDo%2FbtqzJd6aADo%2Faab28Fmfk2nWAdNm6qmQBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;앱 업데이트 검토가 끝나기전에는 비활성화 되어있던 게시 버튼이 활성화 되어있고, 클릭하고 싶게? 변했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tY1s2/btqzHVr5jGc/QW7TABUpcS1AYm4Z4eGc20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tY1s2/btqzHVr5jGc/QW7TABUpcS1AYm4Z4eGc20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tY1s2/btqzHVr5jGc/QW7TABUpcS1AYm4Z4eGc20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtY1s2%2FbtqzHVr5jGc%2FQW7TABUpcS1AYm4Z4eGc20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 원하는 시간에 클릭을 해주면 예약 게시 끝입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;읽어주셔서 감사합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android/google playstore android app 예약 게시</category>
      <category>GooglePlayConsole예약게시</category>
      <category>안드로이드앱예약게시</category>
      <category>앱예약업로드</category>
      <category>예약게시</category>
      <category>플레이스토어예약게시</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/89</guid>
      <comments>https://eso0609.tistory.com/89#entry89comment</comments>
      <pubDate>Wed, 13 Nov 2019 14:42:21 +0900</pubDate>
    </item>
    <item>
      <title>React-Native Navigation ( 화면 전환 ) 시작하기</title>
      <link>https://eso0609.tistory.com/88</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Navigation&lt;/h3&gt;
&lt;p&gt;React-Native에서 stack navigator는 앱이 화면간에 전환 및 탐색 기록을 관리 할 수 있는 방법을 제공한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앱에서 stack navigator를 하나만 사용하는 경우 웹 브라우저가 탐색 상태를 처리하는 방식과 개념적으로 유사하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사용자가 앱과 상호 작용할 때 앱이 탐색 stack에서 항목을 push 및 pop하여 사용자에게 다른 화면이 표시된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;React-Native stack navigator와 웹 브라우저의 차이점은 React Navigation의 stack navigator는 스택의 경로 사이를 탐색 할 때 Android, IOS에서 예상되는 제스처 및 애니메이션을 제공한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 설정&lt;/h3&gt;
&lt;p&gt;1. 우선 expo-cli를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1569306820046&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i -g expo-cli&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2. expo에서 react-navigation, react-native-gesture-handler, react-native-reanimated, react-native-screens, react-navigation-stack 를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1569306900885&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;expo install
	 react-navigation
	 react-native-gesture-handler 
	 react-native-reanimated 
 	 react-native-screens
     react-navigation-stack&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3. App,js 작성&lt;/p&gt;
&lt;pre id=&quot;code_1569306978001&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import { View, Text } from 'react-native';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';

class HomeScreen extends React.Component {
  render() {
    return (
      &amp;lt;View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}&amp;gt;
        &amp;lt;Text&amp;gt;Seonoh Home Screen&amp;lt;/Text&amp;gt;
      &amp;lt;/View&amp;gt;
    );
  }
}

const AppNavigator = createStackNavigator({
  Home: {
    screen: HomeScreen,
  },
});

export default createAppContainer(AppNavigator);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드를 실행한 결과는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;197&quot; height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OsS3v/btqywWjSfD1/esNPnRHeNGMzFQpKC5WnR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OsS3v/btqywWjSfD1/esNPnRHeNGMzFQpKC5WnR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OsS3v/btqywWjSfD1/esNPnRHeNGMzFQpKC5WnR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOsS3v%2FbtqywWjSfD1%2FesNPnRHeNGMzFQpKC5WnR1%2Fimg.png&quot; width=&quot;197&quot; height=&quot;416&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;하지만 텍스트만 띄우는걸 하려고한게 아니다!!!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우리의 목적은 화면전환!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;App.js를 수정해야한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;App.js에서 Button을 추가하고 onPress function을 정의한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 navigator에 DetailsScreen 추가하고 초기 화면을 Home으로 지정해주는 작업을 하면된다.&lt;/p&gt;
&lt;pre id=&quot;code_1569307584625&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import { View, Text, Button } from 'react-native';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';

class HomeScreen extends React.Component {
  render() {
    return (
      &amp;lt;View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}&amp;gt;
        &amp;lt;Text&amp;gt;Seonoh Home Screen&amp;lt;/Text&amp;gt;
        &amp;lt;Button
          title = 'Go detail screen'
          onPress = {()=&amp;gt;this.props.navigation.navigate('Details')}/&amp;gt;
      &amp;lt;/View&amp;gt;
    );
  }
}

class DetailsScreen extends React.Component {
  render() {
    return (
      &amp;lt;View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}&amp;gt;
        &amp;lt;Text&amp;gt;Seonoh Detail Screen&amp;lt;/Text&amp;gt;
        &amp;lt;Button
          title = 'Go Home screen'
          onPress = {()=&amp;gt;this.props.navigation.navigate('Home')}/&amp;gt;
      &amp;lt;/View&amp;gt;
    );
  }
}

const AppNavigator = createStackNavigator(
  {
    Home: HomeScreen,
    Details: DetailsScreen
  },
  {
    initialRouteName: 'Home',
  }
);

export default createAppContainer(AppNavigator);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; 위 코드에서 중요한 부분은 this.props.navigation.navigate('Home') 코드이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드에서는 navigate함수를 이용했는데, 외에도 push함수가 있다. 네이밍에서 느껴지듯이 navigate함수는 찾아가는 느낌이고, push는 추가해주는 느낌이다. 어떻게 다를지 감이 온다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;navigate함수를 사용해서 Home화면에서 ~navigate('Home')하면 어떻게 될까? ( 화면이 그대로 유지된다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 push를 한다면? ( 'Home' ) 화면이 하나 더 뜬다. 그리고 goBack()함수를 이용한다면 stack에서 하나씩 pop되면서 제거된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행결과&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/channel/3354303/cliplink/402330428&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/GYnoM/hyCYmDlrNt/5FK0xj6BISBPG6lOhnBB5k/img.png?width=404&amp;amp;height=854&amp;amp;face=0_0_404_854,https://scrap.kakaocdn.net/dn/EKtMv/hyCYmi3fPs/WgHN0CMCBORYG6wz6zuvC1/img.jpg?width=640&amp;amp;height=360&amp;amp;face=0_0_640_360&quot; data-video-play-service=&quot;daum_tistory&quot; data-video-width=&quot;404&quot; data-video-height=&quot;854&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/402330428?service=daum_tistory&quot; width=&quot;404&quot; height=&quot;854&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ReactNative/React-Native Navigation</category>
      <category>react navigation</category>
      <category>React-native</category>
      <category>react-native navigation</category>
      <category>react-native 화면전환</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/88</guid>
      <comments>https://eso0609.tistory.com/88#entry88comment</comments>
      <pubDate>Tue, 24 Sep 2019 15:51:22 +0900</pubDate>
    </item>
    <item>
      <title>TypeScript 적용하기</title>
      <link>https://eso0609.tistory.com/87</link>
      <description>&lt;p&gt;포스팅을 시작하기 전 저의 경우 회사내에서 앞으로 코드리뷰 과정을 진행하기 위해 기존 보기 힘들었던 자바스크립트 코드에 TypeScript를 적용하기로 했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기존 안드로이드 개발자라서 타입 명시를 할 수 없었던(?)&amp;nbsp; 자바스크립트가 상당히 어색했는데, 이번 기회에 TypeScript를 바로 적용해보기로 했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;TypeScript를 적용한 이유를 설명드리자면, 안드로이드 개발시 혼자 코드를 작성하더라도 타입명시를 꼭 하는 편입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;혼자 개발하는 경우에도 작성하다가 이 변수의 타입이 뭐였더라 찾는 경우가 많았습니다. 하물며 대규모 인원이 투입된 프로젝트라면... 더 심하겠죠. 그래서 적용해보기로 했습니다. ( 정말 쉽습니다. 해보세요! )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;적용하기전에 TypeScript '너무 생소한데... 써도되나..?' '언제 없어질지 모르는데..' 생각이 들어서 검색해봤습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 이미지는 2019.9.23 기준입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zZv2C/btqytVlvAu7/wL7vLSChGSXs9KVqyj7ig1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zZv2C/btqytVlvAu7/wL7vLSChGSXs9KVqyj7ig1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zZv2C/btqytVlvAu7/wL7vLSChGSXs9KVqyj7ig1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzZv2C%2FbtqytVlvAu7%2FwL7vLSChGSXs9KVqyj7ig1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TypeScript란?&lt;/h3&gt;
&lt;p&gt;TypeScript는 Microsoft에 의해 개발/관리되고 있는 오픈소스 프로그래밍 언어입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어플리케이션 규모가 커지면서 분업화되고, 모듈별 서로다른 팀에서 개발을 진행하다보면 자바스크립트가 불편한 경우가 많습니다.&lt;/p&gt;
&lt;p&gt;( 실제로 명시되어 있는 타입이 아니다보니 작성한지 꾀나 오래된 코드의 경우 기대값과 다른 값들을 넣을때가 많았습니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;안드로이드 개발자 입장에서 TypeScript는 정말 당연한 것이었습니다. ( 당연한게 없이 개발하다니... 자스 개발자분들 존경을 표합니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;TypeScript는 JavaScript + 타입 끝!!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TypeScript 설치&lt;/h3&gt;
&lt;p&gt;TypeScript를 설치하는 방법은 크게 두가지입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. NPM을 통해 (Node.js 패키지 매니저)&lt;/p&gt;
&lt;p&gt;2. TypeScript의 Visual Studio 플러그인 설치&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;npm install -g -typescript 명령어를 통해 TypeScript를 설치합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;.ts 파일 생성&lt;/h3&gt;
&lt;p&gt;단지 확장자만 .ts 일뿐 코드는 완전 JavaScript입니다. 저는 test.ts 파일을 만들고 다음 코드를 작성했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1569220950039&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function greeter(name : string){
  return 'Hello '+name
}

greeter(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;작성하면서 보셨겠지만 greeter(1) 1아래에 빨간줄이 뜨는 것을 확인하실 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qWYdi/btqywwdJPky/XeEntKbeBJ0xBCTWnOQcHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qWYdi/btqywwdJPky/XeEntKbeBJ0xBCTWnOQcHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qWYdi/btqywwdJPky/XeEntKbeBJ0xBCTWnOQcHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqWYdi%2FbtqywwdJPky%2FXeEntKbeBJ0xBCTWnOQcHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;드디어 자바스크립트에서도!!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1569221342670&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function greeter(name : string) {
    return 'Hello ' + name;
}
greeter(&quot;seonoh&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같은 코드를 test.ts 파일에 작성하고 tsc test.ts 명령어를 입력하면 test.js파일이 생기는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고자료 : &lt;a href=&quot;https://typescript-kr.github.io/pages/tutorials/TypeScript%20in%205%20minutes.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TypeScript 공식문서&lt;/a&gt;&lt;/p&gt;</description>
      <category>ReactNative/TypeScript</category>
      <category>TypeScript</category>
      <category>타입스크립트</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/87</guid>
      <comments>https://eso0609.tistory.com/87#entry87comment</comments>
      <pubDate>Mon, 23 Sep 2019 16:03:49 +0900</pubDate>
    </item>
    <item>
      <title>플렉스박스는 새로운 레이아웃 표준이다</title>
      <link>https://eso0609.tistory.com/86</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;플렉스박스( flex box )란?&lt;/h3&gt;
&lt;p&gt;CSS에 유연한 박스 레이아웃 모델이 소개되기 전에는 레이아웃을 만들 때 에러를 유발할 것 같은 해킹스러운 다양한 방법이 존재했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;플렉스 박스는 레이아웃이 동작하도록 만들기 위해 일반적으로 제공해야 하는 많은 프로퍼티를 추상화하여 이를 개선했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;플렉스 박스는 이름에서 볼 수 있듯이 유연한 박스 모델이다. 컨테이너 역할을 하는 박스가 있으며 박스 내에는 자식 요소들이 있다.&lt;/p&gt;
&lt;p&gt;( 이 부분만 놓고 보자면 안드로이드 상에서 LinearLayout이 생각났는데 선형 레이아웃보다는 flex라는 이름에서 더 유연할 것이라는 생각을 해볼 수 있다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b537Xr/btqyuEchX6H/1mOR4jSBlRVasbhPWK1EV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b537Xr/btqyuEchX6H/1mOR4jSBlRVasbhPWK1EV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b537Xr/btqyuEchX6H/1mOR4jSBlRVasbhPWK1EV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb537Xr%2FbtqyuEchX6H%2F1mOR4jSBlRVasbhPWK1EV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmVhz8/btqytsDDdNu/kPBag3Fq142Ik6YjgGo4a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmVhz8/btqytsDDdNu/kPBag3Fq142Ik6YjgGo4a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmVhz8/btqytsDDdNu/kPBag3Fq142Ik6YjgGo4a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmVhz8%2FbtqytsDDdNu%2FkPBag3Fq142Ik6YjgGo4a1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;플렉스박스 개념에 대한 상세정보는 &lt;a href=&quot;https://css-tricks.com/snippets/css/a-guide-to-flexbox/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 통해 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;플렉스 박스 styles.js 정의&lt;/h3&gt;
&lt;p&gt;간단한 예제를 통해 플렉스박스를 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;create-react-native-app my-app을 톨해 생성된 패키지에 styles.js를 생성한 뒤 다음 코드를 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1569213789658&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { StyleSheet } from 'react-native'

const styles = StyleSheet.create({
    container : {
        //플렉스박스 레이아웃 모델 활성화
        flex : 1,
        //간격을 다른 자식을 참고해 정의한다.
        justifyContent : 'space-around',
        //자식을 컨테이너의 중앙에 맞춘다.
        alignItems : 'center',
        backgroundColor : 'ghostwhite',
    },
    box : {
        width : 100,
        height : 100,
        justifyContent : 'center',
        alignItems : 'center',
        backgroundColor : 'lightgray',
    },
    boxText : {
        color : 'darkslategray',
        fontWeight : 'bold',
    },
});

export default styles;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;플렉스박스 렌더링&lt;/h3&gt;
&lt;p&gt;App.js에서 다음 작업을 진행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1569213849869&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import{
  Text,
  View
} from 'react-native';
import styles from './styles'

const StyleSheets = () =&amp;gt; (
  &amp;lt;View style = {styles.container}&amp;gt;
    &amp;lt;View style = {styles.box}&amp;gt;        
      &amp;lt;Text style = {styles.boxText}&amp;gt;
        I'm in a box
      &amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;
  &amp;lt;/View&amp;gt;
);       

export default StyleSheets&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;267&quot; height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ofhJv/btqyssYpLMl/2KwPhW5QhMQFpeXCzDJ4KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ofhJv/btqyssYpLMl/2KwPhW5QhMQFpeXCzDJ4KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ofhJv/btqyssYpLMl/2KwPhW5QhMQFpeXCzDJ4KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FofhJv%2FbtqyssYpLMl%2F2KwPhW5QhMQFpeXCzDJ4KK%2Fimg.png&quot; width=&quot;267&quot; height=&quot;564&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이 정도는 아직 감이 오질 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번에는 단순히 세 열을 갖는 레이아웃을 구성해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;styles.js 수정&lt;/h3&gt;
&lt;p&gt;styles에서는 플렉스박스에게 자식을 어느 방향으로 그릴건지에 대한 설정, 간격을 다른 자식을 참고해 정의하는 설정, width 값을 변경하는 작업을 진행합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1569214222452&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { StyleSheet } from 'react-native'

const styles = StyleSheet.create({
    container : {
        //플렉스박스 레이아웃 모델 활성화
        flex : 1,
        //플렉스박스에게 자식을 위에서 아래로 (vertical)로 그리도록 요청.
        flexDirection : 'column',
        //간격을 다른 자식을 참고해 정의한다.
        justifyContent : 'space-around',
        //자식을 컨테이너의 중앙에 맞춘다.
        alignItems : 'center',
        backgroundColor : 'ghostwhite',
    },
    box : {
        width : 300,
        height : 100,
        justifyContent : 'center',
        alignItems : 'center',
        backgroundColor : 'lightgray',
    },
    boxText : {
        color : 'darkslategray',
        fontWeight : 'bold',
    },
});

export default styles;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;flex, flexDirection 프로퍼티는 행 레이아웃이 위에서 아래로 배치되도록 설정하는 프로퍼티이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 alignItems, justifyContent 프로퍼티는 자식 요소를 컨테이너의 가운데 맞추고 주변에 공간을 각각 추가한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;App.js&lt;/h3&gt;
&lt;p&gt;App.js에서는 3개의 뷰를 그려주면된다.&lt;/p&gt;
&lt;pre id=&quot;code_1569214265660&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';

import{
  Text,
  View
} from 'react-native';
import styles from './styles'

const ThreeColumnLayout = () =&amp;gt; (
  &amp;lt;View style = {styles.container}&amp;gt;
    &amp;lt;View style = {styles.box}&amp;gt;
      &amp;lt;Text style = {styles.boxText}&amp;gt;
        #1
      &amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;

    &amp;lt;View style = {styles.box}&amp;gt;
      &amp;lt;Text style = {styles.boxText}&amp;gt;
        #2
      &amp;lt;/Text&amp;gt;                                                                                                                                               
    &amp;lt;/View&amp;gt;

    &amp;lt;View style = {styles.box}&amp;gt;
      &amp;lt;Text style = {styles.boxText}&amp;gt;
        #3
      &amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;
  &amp;lt;/View&amp;gt;
)

export default ThreeColumnLayout&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;285&quot; height=&quot;601&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFTLqa/btqyuFWBmhL/tlhr3nbkl9ZnuKYlN8WgDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFTLqa/btqyuFWBmhL/tlhr3nbkl9ZnuKYlN8WgDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFTLqa/btqyuFWBmhL/tlhr3nbkl9ZnuKYlN8WgDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFTLqa%2FbtqyuFWBmhL%2Ftlhr3nbkl9ZnuKYlN8WgDk%2Fimg.png&quot; width=&quot;285&quot; height=&quot;601&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;플렉스를 이용해서 안드로이드의 recyclerview를 구현한 느낌이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 recylerview grid기능도 있지 않을까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;styles.js&lt;/h3&gt;
&lt;p&gt;styles에서는 Platform.select라는 기능을 추가했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;눈으로 보기에도 ios, android 플랫폼에 따라서 각각 프로퍼티를 세팅해줄 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1569214746396&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Platform,StyleSheet,StatusBar } from 'react-native'

const styles = StyleSheet.create({
    container : {
        //플렉스박스 레이아웃 모델 활성화
        flex : 1,
        //플렉스박스에게 자식을 위에서 아래로 (vertical)로 그리도록 요청.
        flexDirection : 'column',
        //간격을 다른 자식을 참고해 정의한다.
        justifyContent : 'space-around',
        //자식을 컨테이너의 중앙에 맞춘다.
        alignItems : 'center',
        backgroundColor : 'ghostwhite',
        ...Platform.select({
            ios : { paddingTop : 20 },
            android : { paddingTop:StatusBar.currentHeight}
        })
    },
    box : {
        width : 100,
        height : 100,
        justifyContent : 'center',
        alignItems : 'center',
        backgroundColor : 'lightgray',
        borderWidth : 1,
        borderStyle : 'dashed',
        borderColor : 'darkslategray',
        margin : 10
    },
    boxText : {
        color : 'darkslategray',
        fontWeight : 'bold',
    },
});

export default styles;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고자료 : 리액트&amp;amp;리액트 네이티브 통합교과서&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ReactNative/플렉스박스</category>
      <category>Flexbox</category>
      <category>react</category>
      <category>React-native</category>
      <category>리액트네이티브</category>
      <category>플렉스박스</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/86</guid>
      <comments>https://eso0609.tistory.com/86#entry86comment</comments>
      <pubDate>Mon, 23 Sep 2019 15:23:40 +0900</pubDate>
    </item>
    <item>
      <title>컴포넌트 프로퍼티와 상태 파헤치기</title>
      <link>https://eso0609.tistory.com/85</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴포넌트 상태란?&lt;/h3&gt;
&lt;p&gt;컴포넌트 상태에 대한 설명은 다음 질문으로 부터 시작된다.&lt;/p&gt;
&lt;p&gt;JSX에서 자바스크립트 컬렉션을 &amp;lt;li&amp;gt;요소에 매핑하는 &amp;lt;ul&amp;gt; 태그를 선언했다면 이 컬렉션은 어떻게 채워질까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;상태는 리액트의 동적인 부분이다. 이는 컴포넌트의 초기 상태를 선언하고 시간이 지남에 따라 계속 변한다는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;초기 상태를 가진 컴포넌트가 빈 배열을 렌더링한다고 가정했을 때, 나중에 이 배열이 채워질 수 있다. 이를 &lt;b&gt;상태 변화&lt;/b&gt;라고 하며 컴포넌트의 상태가 변할 때마다 &lt;b&gt;자동으로 렌더링&lt;/b&gt;된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴포넌트 프로퍼티란?&lt;/h3&gt;
&lt;p&gt;프로퍼티는 데이터를 컴포넌트로 전달하기 위해 사용된다. 새로운 상태가 인수 값인 메서드를 호출하는 대신, 오직 컴포넌트가 렌더링될 때만 전달된다. 즉 JSX 요소에 프로퍼티 값을 전달한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉, 프로퍼티는 JSX 컨텍스트에서 XML 형식으로 호출되기 때문에 어트리뷰트(attributes)라 불린다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;프로퍼티는 컴포넌트의 초기 렌더링 후에 변경되지 않는 부분이 상태와 다르다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴포넌트 상태 설정&lt;/h3&gt;
&lt;p&gt;지난번 포스팅을 토대로 react app을 만든후 module.js라는 파일을 만들고 아래와 같은 코드를 작성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1568948366768&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { Component } from 'react';

export default class MyComponent extends Component{
    state = {
        heading : 'React Awesomesauce ( Busy )',
        content : 'Loading...'
    }

    render(){
        const { heading, content } = this.state;

        return (
            &amp;lt;main&amp;gt;
                &amp;lt;h1&amp;gt;{heading}&amp;lt;/h1&amp;gt;
                &amp;lt;p&amp;gt;{content}&amp;lt;/p&amp;gt;
            &amp;lt;/main&amp;gt;
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드는 초기 상태를 가진 컴포넌트이며 JSX는 heading, content 2개의 상태 프로퍼티에 의존하고 있다.&lt;/p&gt;
&lt;p&gt;이러한 초깃값 설정은 렌더링 시 예기치 못한 일을 방지해준다.&lt;/p&gt;
&lt;p&gt;위 코드 실행 결과 출력되는 화면은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlcmK0/btqyqSBwsMW/MxkBmQi49BGIVKQqiH4ka1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlcmK0/btqyqSBwsMW/MxkBmQi49BGIVKQqiH4ka1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlcmK0/btqyqSBwsMW/MxkBmQi49BGIVKQqiH4ka1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlcmK0%2FbtqyqSBwsMW%2FMxkBmQi49BGIVKQqiH4ka1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 컴포넌트를 렌더링하고 상태 값 변경으로 재렌더링하는 코드를 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1568948500416&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import MyComponent from './module';

const myComponent = ReactDOM.render(
    (&amp;lt;MyComponent/&amp;gt;),
    document.getElementById('root')
);

setTimeout(()=&amp;gt; {
    myComponent.setState(
        {
            heading: 'React Awesomesauce',
            content: 'DONE!',
        }
    );
},3000);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;컴포넌트는 일단 기본 상태로 렌더링된다.&amp;nbsp; 그리고 setTimeout() 호출을 통해서 3초 후에 2개의 상태 프로퍼티 값을 변경한다.&lt;/p&gt;
&lt;p&gt;그리고 UI에 자동 반영된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ztC5U/btqyrFPwc3Y/gDMuLi4yJyE5030fnfT3yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ztC5U/btqyrFPwc3Y/gDMuLi4yJyE5030fnfT3yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ztC5U/btqyrFPwc3Y/gDMuLi4yJyE5030fnfT3yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FztC5U%2FbtqyrFPwc3Y%2FgDMuLi4yJyE5030fnfT3yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴포넌트 상태 병합&lt;/h3&gt;
&lt;p&gt;리액트 컴포넌트의 상태를 설정할 때 setState()에 전달한 객체와 컴포넌트의 상태값을 병합한다.&lt;/p&gt;
&lt;p&gt;이를 통해 상태의 나머지 부분을 그대로 남겨두고 컴포넌트 상태의 일부만을 설정할 수 있기 때문에 매우 유용하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1568954875116&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { Component } from 'react';

export default class MyComponent extends Component{
    state = {
        first : 'loading...',
        second : 'loading...',
        third : 'loading...',
        fourth : 'loading...',
        doneMessage : 'finished!'
    }

    render(){
        const { state } = this;

        return(
            &amp;lt;ul&amp;gt;
                {Object.keys(state)
                .filter(key =&amp;gt; key !== 'doneMessage')
                .map(key =&amp;gt; (
                    &amp;lt;li key = {key}&amp;gt;
                        &amp;lt;strong&amp;gt;{key}: &amp;lt;/strong&amp;gt;
                        {state[key]}
                    &amp;lt;/li&amp;gt;
                ))}
            &amp;lt;/ul&amp;gt;
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드를 통해서 컴포넌트는 key값이 doneMessage인 값을 제외하고 상태의 키와 값을 렌더링한다.&lt;/p&gt;
&lt;pre id=&quot;code_1568954941335&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import MyComponent from './module';

const myComponent = ReactDOM.render(
    (&amp;lt;MyComponent/&amp;gt;),
    document.getElementById('root')
);

setTimeout(()=&amp;gt; {
    myComponent.setState(
        {
            first : 'done!'
        }
    );
},1000);

setTimeout(()=&amp;gt; {
    myComponent.setState(
        {
            second : 'done!'
        }
    );
},2000);

setTimeout(()=&amp;gt; {
    myComponent.setState(
        {
            third : 'done!'
        }
    );
},3000);

setTimeout(()=&amp;gt; {
    myComponent.setState(
        {
            fourth : myComponent.state.doneMessage
        }
    );
},4000);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위코드를 통해 1,2,3,4초 마다 갱신되는 UI를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;위코드에서 fourth 값은 처음 3개와 달라보인다. 새로운 객체를 기존 상태로 병합하는 대신, 함수를 전달할 수 있다.&lt;/p&gt;
&lt;p&gt;이 함수는 컴포넌트의 현재 상태 값을 인수로 가진다. 현재 상태 값을 기반으로 상태를 변경할 경우에 유용하게 쓰일 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baEFZH/btqynRkgODX/6QFiKswvAcgkkyA5tBw7TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baEFZH/btqynRkgODX/6QFiKswvAcgkkyA5tBw7TK/img.png&quot; width=&quot;331&quot; height=&quot;121&quot; style=&quot;width: 56.1641%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baEFZH/btqynRkgODX/6QFiKswvAcgkkyA5tBw7TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaEFZH%2FbtqynRkgODX%2F6QFiKswvAcgkkyA5tBw7TK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;&quot; height=&quot;&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XqZoT/btqynRq25dm/qwz2bV9fkkCZ5IOKAeouT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XqZoT/btqynRq25dm/qwz2bV9fkkCZ5IOKAeouT1/img.png&quot; style=&quot;width: 41.5103%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XqZoT/btqynRq25dm/qwz2bV9fkkCZ5IOKAeouT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXqZoT%2FbtqynRq25dm%2Fqwz2bV9fkkCZ5IOKAeouT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;&quot; height=&quot;&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고자료 : 리액트&amp;amp;리액트 네이티브 통합 교과서&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ReactNative/컴포넌트 프로퍼티와 상태</category>
      <category>react</category>
      <category>react상태</category>
      <category>react시작하기</category>
      <category>react입문</category>
      <category>react컴포넌트상태</category>
      <category>react컴포넌트프로퍼티</category>
      <category>react프로퍼티</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/85</guid>
      <comments>https://eso0609.tistory.com/85#entry85comment</comments>
      <pubDate>Fri, 20 Sep 2019 13:55:54 +0900</pubDate>
    </item>
    <item>
      <title>난생처음 React를 접하다.</title>
      <link>https://eso0609.tistory.com/84</link>
      <description>&lt;p&gt;안녕하세요. 안드로이드 개발 2년차가 되어가는 엄선오라고합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번에 처음으로 리엑트 공부를 시작하게되었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;매번 Java,Kotlin 언어로만 프로그래밍하다가 새로운 언어와 플랫폼으로 개발하다보니 정말 기본적인 부분부터 막막했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 이렇게 포스팅을 하게 되었습니다. ( 정말 처음 시작하는 입문자 분들을 위한 포스팅입니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;환경 설정&lt;/h3&gt;
&lt;p&gt;우선 &lt;a href=&quot;https://nodejs.org/en/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NodeJs&lt;/a&gt;를 최신버전으로 세팅했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 사용하시는 IDE 저의 경우에는 Visual Sturio Code를 열고, 새로운 WorkPlace를 설정해주었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;React 시작하기&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 터미널을 열고 다음 명령어를 입력합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1568876220181&quot; class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm install -g create-react-app
$ create-react-app my-app&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGQPr8/btqyogiYrKO/xwbnNWz5tkOjDDLtlwlrm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGQPr8/btqyogiYrKO/xwbnNWz5tkOjDDLtlwlrm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGQPr8/btqyogiYrKO/xwbnNWz5tkOjDDLtlwlrm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGQPr8%2FbtqyogiYrKO%2FxwbnNWz5tkOjDDLtlwlrm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9xvBN/btqyoOT33nW/f0FFdVRAdECGjs7Rsp3UUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9xvBN/btqyoOT33nW/f0FFdVRAdECGjs7Rsp3UUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9xvBN/btqyoOT33nW/f0FFdVRAdECGjs7Rsp3UUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9xvBN%2FbtqyoOT33nW%2Ff0FFdVRAdECGjs7Rsp3UUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 위 이미지처럼 파일들이 생성됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;ls 명령어를 통해 현위치에 속한 파일을 보면 my-app이 생긴것을 볼 수 있는데요, my-app으로 이동하고나서 &lt;u&gt;&lt;i&gt;npm start&lt;/i&gt;&lt;/u&gt; 명령어를 통해서&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;react를 실행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAF99J/btqymT9SBu7/tQVBkFgWDuAxR8Av14sB0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAF99J/btqymT9SBu7/tQVBkFgWDuAxR8Av14sB0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAF99J/btqymT9SBu7/tQVBkFgWDuAxR8Av14sB0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAF99J%2FbtqymT9SBu7%2FtQVBkFgWDuAxR8Av14sB0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;벌써 두근두근 거리네요 ㅎㅎ&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자 이제 프로그래밍의 시작 &quot;Hello World&quot;를 출력해볼 차례네요.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;src 하위 파일중에 index.js에 들어가셔서 다음 작업을 해줍니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기존 코드 제거 (&amp;nbsp;&lt;span&gt;ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById('root')); )&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;아래 코드 추가&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1568876651400&quot; class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ReactDOM.render(
    &amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;,
    document.getElementById('root')
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;render() 함수는 JSX 마크업을 렌더링하고, 결과 콘텐트를 DOM 노드에 배치한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;리엑트 객체는 여기서 사용되지 않고 트랜스파일된 JSX 소스에서 사용된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드는 JSX 마크업을 렌더링하며, 자바스크립트와 혼합된 XML 문법을 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 브라우저에 도달하기 전에 트랜스파일러에 의해 배치된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 작업을 마친 뒤에 저장을 누르는 순간!!!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;252&quot; height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ek7o8w/btqypywsyi8/HnG95PGyFy4uziQWsYUA2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ek7o8w/btqypywsyi8/HnG95PGyFy4uziQWsYUA2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ek7o8w/btqypywsyi8/HnG95PGyFy4uziQWsYUA2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fek7o8w%2Fbtqypywsyi8%2FHnG95PGyFy4uziQWsYUA2K%2Fimg.png&quot; width=&quot;252&quot; height=&quot;205&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;실시간으로 반영되는 것을 볼 수 있습니다. ( 우와... )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제부터 React 공부 재미있게 할 수 있을 것 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고자료 : 리액트&amp;amp;리액트 네이티브 통합교과서&lt;/p&gt;</description>
      <category>ReactNative/React 시작하기</category>
      <category>createreaceapp</category>
      <category>react</category>
      <category>react시작하기</category>
      <category>react입문</category>
      <category>react환경설정</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/84</guid>
      <comments>https://eso0609.tistory.com/84#entry84comment</comments>
      <pubDate>Thu, 19 Sep 2019 16:11:26 +0900</pubDate>
    </item>
    <item>
      <title>[코틀린] 지연 계산(lazy) 컬렉션 연산을 통한 속도 개선</title>
      <link>https://eso0609.tistory.com/83</link>
      <description>&lt;p style=&quot;text-align: left;&quot;&gt;이번 포스팅에서는 시퀀스(sequence) 연산을 통한 속도 개선에 대해 정리해보겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;본글은 &lt;a href=&quot;https://www.google.com/search?q=kotlin+in+action&amp;amp;oq=kotlin+in+action&amp;amp;aqs=chrome..69i57j0l3j69i61l2.2879j0j7&amp;amp;sourceid=chrome&amp;amp;ie=UTF-8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kotlin In Action&lt;/a&gt;을 보며 학습 내용을 정리한 글입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;지연 개발 (lazy) 컬렉션 연산&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;map이나 flilter 같은 함수는 결과 컬렉션을 즉시 생성합니다. &lt;span style=&quot;color: #fcaf6f;&quot;&gt;&lt;i&gt;&lt;b&gt;이는 컬렉션 함수를 연쇄하면 매 단계마다 계산 중간 결과를 새로운 컬렉션에 임시로 담는것입니다.&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;시퀀스(sequence)를 사용하면 중간 임시 컬렉션을 사용하지 않고도 컬렉션 연산을 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1565065212099&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; 	val p1 = Person(28,&quot;seonoh&quot;)
    val p2 = Person(28,&quot;bitna&quot;)
    val p3 = Person(26,&quot;seongyu&quot;)
    val p4 = Person(3,&quot;daon&quot;)

    val pList = listOf(p1,p2,p3,p4)

    println(pList.map(Person::name).filter { it.startsWith(&quot;s&quot;) })&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;위와 같은 코드에서 map 결과에 대한 리스트와 filter 결과에 대한 리스트까지 총 2개의 리스트가 반환됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;map과 filter는 리스트를 반환합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1565065343318&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Returns a list containing the results of applying the given [transform] function
 * to each element in the original collection.
 */
public inline fun &amp;lt;T, R&amp;gt; Iterable&amp;lt;T&amp;gt;.map(transform: (T) -&amp;gt; R): List&amp;lt;R&amp;gt; {
    return mapTo(ArrayList&amp;lt;R&amp;gt;(collectionSizeOrDefault(10)), transform)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1565065359830&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Returns a list containing only elements matching the given [predicate].
 */
public inline fun &amp;lt;T&amp;gt; Iterable&amp;lt;T&amp;gt;.filter(predicate: (T) -&amp;gt; Boolean): List&amp;lt;T&amp;gt; {
    return filterTo(ArrayList&amp;lt;T&amp;gt;(), predicate)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;원본 리스트에 원소가 2개밖에 없다면 리스트가 2개 더 생겨도 큰 문제가 되지는 않지만, 원소가 많아질수록 훨씬 더 효율이 떨어지게됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이를 해결할 방법으로 각 연산이 컬렉션을 직접 사용하는 대신 시퀀스를 사용하게 만들어야 합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1565065545924&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pList.asSequence() // 원본 컬렉션을 시퀀스로 변환한다.
        .map { Person::name }
        .filter { it.name.startsWith(&quot;s&quot;) }
        .toList() // 결과 시퀀스를 다시 리스트로 변환한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드는 중간 결과를 저장하는 컬렉션이 생기지 않기 때문에 원소가 많은 경우 성능이 좋아집니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코틀린 시쿠컨스는 Sequence 인터페이스에서 시작합니다. 이 인터페이스는 단지 한번에 하나씩 열거될 수 있는 원소의 시퀀스를 표현합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Sequence 안에는 iterator라는 단 하나의 메소드가 존재하는데 이 메소드를 통해 시퀀스로부터 원소 값을 얻을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;span style=&quot;color: #ff9a85;&quot;&gt;&lt;b&gt;Sequence 인터페이스의 강점은 그 인터페이스 위에 구현된 연산이 계산을 수행하는 방법때문에 생깁니다.&lt;/b&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시퀀스의 원소는 필요할 때 계산됩니다. 따라서 중간 처리 결과를 저장하지 않고도 연산을 연쇄적으로 적용해서 효율적으로 계산을 수행할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;왜 시퀀스를 다시 컬렉션으로 되돌려야 할까?&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;컬렉션보다 시퀀스가 훨씬 낫다면 그냥 시퀀스를 쓰는 편이 낫지 않을까? 생각할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본인도 마찬가지로 책을 보면서 그런 생각을 했습니다. 하지만 자연스럽게 생각이 나더군요.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;개발을 하다보면 원소하나하나에 인덱스를 사용해 접근하는 등 다른 API 메소드가 필요하다면 시퀀스를 리스트로 변환해야 한다는 것을...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;시퀀스 연산 실행 순서&lt;/h3&gt;
&lt;p&gt;시퀀스에 대한 연산은 중간연산과 최종연산으로 나뉩니다. 중간 연산은 다른 시퀀스를 반환합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 시퀀스는 최초 시퀀스의 연산을 반환하는 방법을 알고 있고, 최종 연산은 결과를 반환합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1565066225933&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pList.asSequence() // sequence
        .map { Person::name } // 중간 연산
        .filter { it.name.startsWith(&quot;s&quot;) } // 중간 연산
        .toList() // 최종 연산&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;결과는 최초 컬렉션에 대해 변환을 적용한 시퀀스로부터 일련의 계산을 수행해 얻을 수 있는 컬렉션이나 원소, 숫자 또는 객체입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1565066283535&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; listOf(1,2,3,4)
        .asSequence()
        .map { print(&quot;map($it)  &quot;);it*it }
        .filter { print(&quot;filter($it)  &quot;); it%2 == 0 }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드를 실행해보면 아무 내용도 출력되지 않습니다. 왜그럴까요?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;sequence 중간 연산은 항상 지연 계산됩니다. 위 코드에서 map과 filter 변환이&amp;nbsp; 늦춰져서 결과를 얻을 필요가 있을 때 ( 최종 연산이 호출되는 경우) 적용됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 코드와 같이 toList() (최종연산) 을 호출하게되면 늦춰졌던 모든 계산이 수행됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1565066648345&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;listOf(1,2,3,4)
        .asSequence()
        .map { print(&quot;map($it)  &quot;);it*it }
        .filter { print(&quot;filter($it)  &quot;); it%2 == 0 }
        .toList()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과 :&amp;nbsp;map(1)&amp;nbsp;&amp;nbsp;filter(1)&amp;nbsp;&amp;nbsp;map(2)&amp;nbsp;&amp;nbsp;filter(4)&amp;nbsp;&amp;nbsp;map(3)&amp;nbsp;&amp;nbsp;filter(9)&amp;nbsp;&amp;nbsp;map(4)&amp;nbsp;&amp;nbsp;filter(16)&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;직접 연산을 구현한다면 map 함수를 각 원소에 대해 먼저 수행해서 새 시퀀스를 얻고, 그 시퀀스에 대해 다시 filter를 수행할 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;컬렉션에 대한 map과 filter는 그런 방식으로 작동합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 시퀀스에 대한 map과 filter는 그렇지 않습니다. 시퀀스의 경우 모든 연산은 각 원소에 대해 순차적으로 적용됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉, 첫번째 원소가 (변환된 다음에 걸러지면섴0 처리되고, 다시 두번째 원소가 처리되며 모든 원소가 이렇게 처리됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 원소에 연산을 차례대로 적용하다가 결과가 얻어지면 그 이후의 원소에 대해서는 변환이 이뤄지지 않을 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 예를 통해 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1565067025606&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; println(listOf(1,2,3,4).map { print(&quot;access $it  &quot;);it*it }.find { print(&quot;result $it  &quot;); it&amp;gt;3 })&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드 같은 경우 map의 결과값들인 1,4,9,16 을 먼저 구하고, 1은 3보다 작으니 해당하지 않으며 4는 3보다 크니 조건에 해당되며 결과값이 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1565067134433&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;access 1  access 2  access 3  access 4  result 1  result 4  4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;원소 1,2,3,4 모두 접근하고 있는 것을 확인 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이와는 다르게 sequence는 어떨까요?&lt;/p&gt;
&lt;pre id=&quot;code_1565067185409&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;println(listOf(1,2,3,4)
	.asSequence()
    .map { print(&quot;access $it  &quot;);it*it }
    .find { print(&quot;result $it  &quot;); it&amp;gt;3 })&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시퀀스를 사용하면 지연 계산으로 인해 원소 중 일부의 계산은 이루어지지 않습니다. 말 그대로 시퀀스 (순차적)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1565067280662&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;access 1  result 1  access 2  result 4  4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 컬렉션에 대해 수행하는 연산의 순서도 성능에 영향을 끼칩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;map을 먼저 한 경우에는 모든 원소가 변환된 후에 filter처리를 하지만, filter를 먼저 하게되면 불필요한 원소를 제외시키고 map 처리를 하게됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 이미지가 이해에 큰 도움이 되어 똑같이 그려봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QjNgx/btqxfEyZmR3/Eo9V3Rk8K6X3zkpG0gyMdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QjNgx/btqxfEyZmR3/Eo9V3Rk8K6X3zkpG0gyMdk/img.png&quot; data-alt=&quot;map 연산 후 filter&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QjNgx/btqxfEyZmR3/Eo9V3Rk8K6X3zkpG0gyMdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQjNgx%2FbtqxfEyZmR3%2FEo9V3Rk8K6X3zkpG0gyMdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;map 연산 후 filter&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xMHc1/btqxhLwS7od/uQw0msScSGWRxyXPaIowJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xMHc1/btqxhLwS7od/uQw0msScSGWRxyXPaIowJk/img.png&quot; data-alt=&quot;filter 연산 후 map&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xMHc1/btqxhLwS7od/uQw0msScSGWRxyXPaIowJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxMHc1%2FbtqxhLwS7od%2FuQw0msScSGWRxyXPaIowJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;filter 연산 후 map&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고 자료 : Kotlin In Action,&amp;nbsp;&lt;a href=&quot;https://kotlinlang.org/&quot;&gt;https://kotlinlang.org/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Kotlin/kotlin 시퀀스(sequence)</category>
      <category>Kotlin</category>
      <category>kotlin lazy</category>
      <category>kotlin sequence</category>
      <category>lazy</category>
      <category>Sequence</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/83</guid>
      <comments>https://eso0609.tistory.com/83#entry83comment</comments>
      <pubDate>Tue, 6 Aug 2019 14:37:38 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 Kotlin Coroutines runBlocking, coroutineScope 차이</title>
      <link>https://eso0609.tistory.com/82</link>
      <description>&lt;p&gt;사내 업무량을 어느 정도 마치고, 오랜만에 다시 코루틴 학습을 시작했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;역시 복습하니 다시 모르는게 보였습니다. 그래서 정리의 필요성을 느끼고 포스팅을 시작했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코루틴에 대해서 처음부터 학습하실 분들은 저의 이전 &lt;a href=&quot;https://eso0609.tistory.com/73&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;포스팅들&lt;/a&gt;이나 &lt;a href=&quot;https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;구글 Document&lt;/a&gt;를 보시는 것을 추천드립니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564115261249&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//CoroutineScope는 runBlocking 내부에서만 사용 가능
    //runBlocking, coroutineScope 차이점
    //coroutineScope에서는 자식 스레드가 완료될 때 까지 현재 스레드를 block 하지 않는다.
    //runBlocking에서는 자식 스레드가 완료될 때 까지 현재 스레드를 block 한다.
    fun main() = runBlocking { // this: CoroutineScope
        //자식 스레드가 완료될 때 까지 현재 스레들 block 한다.

        //자식 스레드가 완료될 때 까지 현재 스레드를 block한다고 하는데
        //왜 아래 coroutineScope에 &quot;Task from coroutine scope&quot;가 먼저 실행 되는건지 궁금합니다.
        launch {
            delay(200L)
            Log.e(&quot;main&quot;,&quot;Task from runBlocking&quot;)
        }

        coroutineScope { // Creates a coroutine scope
            //자식 스레드
            launch {
                delay(500L)
                Log.e(&quot;main&quot;,&quot;Task from nested launch&quot;)
            }
            //현재 스레드
            delay(100L)
            Log.e(&quot;main&quot;,&quot;Task from coroutine scope&quot;)
        }
        Log.e(&quot;main&quot;,&quot;Coroutine scope is over&quot;)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;정리해보니 위와 같았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;주석을 열심히 달아보고, 혼자 질문도 해봅니다 ㅎㅎ( 사실 한참 고민하다가 오픈채팅방에 자문을 구했습니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GES6L/btqw1TDb4JI/O3WJwKq25gjwX6DvFsgVmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GES6L/btqw1TDb4JI/O3WJwKq25gjwX6DvFsgVmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GES6L/btqw1TDb4JI/O3WJwKq25gjwX6DvFsgVmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGES6L%2Fbtqw1TDb4JI%2FO3WJwKq25gjwX6DvFsgVmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;질문하려고 주석을 정리하다보니 저의 질문에 의심을 품게 되었습니다. ( 사실 제가 틀에 박힌 생각을 하고 있지는 않았나 싶어서... )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;항상 느끼는 거지만, 질문을 하려고 정리하다보면 문득 정답이 스쳐간다는...&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;답변해주신 태환님께 정말 감사했습니다! ( 문제가 될 시 삭제하겠습니다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;runBlocking은 자식 스레드가 완료될 때 까지 현재 스레드를 block하는 것이고, coroutineScope는 자식 스레드가 완료될 때 까지 현재 스레드를 block 하지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&quot;launch는 그냥 스레드를 돌려두는 것이고, coroutineScope 자체가 async/await 할때까지 기다린다&quot;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 문장까지 들었을 때는 사실 아직도 헷갈리고 있었는데,&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&quot;coroutineScope 자체가 async/await이고, coroutineScope 안에서 다시 launch 하면 그냥 또 돌아간다&quot; 라는 말을 듣고 이해할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 저에게 좀 더 이해하기 쉽도록 예제 코드를 작성해봤습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1564117779590&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Job을 등록할 수 있도록 초기화
    // CoroutineScope의 동작을 제어할 객체
    // 안드로이드 상에서는 Lifecycle을 활용할 수 있도록 도와준다.
    lateinit var job : Job


    // coroutine의 스레드를 어떠한 형태로 사용할지 지정할 수 있다.
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job



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

        job = Job()

        runBlocking {
            delay(1000L)
            Log.e(&quot;coroutineScope&quot;,&quot;STEP 1&quot;)
        }

        Log.e(&quot;coroutineScope&quot;,&quot;STEP 2&quot;)

        CoroutineScope(coroutineContext).launch{
            delay(1000L)
            Log.e(&quot;coroutineScope&quot;,&quot;STEP 4&quot;)
        }


        Log.e(&quot;coroutineScope&quot;,&quot;STEP 3&quot;)

    }

    // 작업 중이던 모든 job, children을 종료 처리
    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행순서는 STEP1 -&amp;gt; STEP2 -&amp;gt; STEP3 -&amp;gt; STEP4&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 생각대로 흘러가는 것 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고로 위에서 쓰인 &lt;b&gt;&lt;i&gt;Dispatchers&lt;/i&gt;&lt;/b&gt;는 코루틴을 실행할 때 어떤 스레드를 사용할 것인지 결정하기 위해 사용됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Dispachers.Main은 안드로이드에서는 Main 스레드 즉, UI 스레드를 위해서만 사용됩니다. 또한 suspend functions, UI 프레임워크 연산, 최근에는 LiveData update를 위해 사용됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이외에도 Dispatchers.IO, Dispatchers.Defaule가 존재합니다. 자세한 내용은 &lt;a href=&quot;https://developer.android.com/kotlin/coroutines&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 통해 확인해보시면 좋을 것 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고자료&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://thdev.tech/coroutines/2019/04/08/Init-Coroutines-Job/&quot;&gt;https://thdev.tech/coroutines/2019/04/08/Init-Coroutines-Job/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1564123998990&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a href=&quot;https://thdev.tech/coroutines/2019/04/08/Init-Coroutines-Job/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-original-url=&quot;https://thdev.tech/coroutines/2019/04/08/Init-Coroutines-Job/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iqz1n/hyCaap6l5X/peGUP0r8YjNq8UYQVuIhY0/img.jpg?width=1000&amp;amp;height=500&amp;amp;face=0_0_1000_500,https://scrap.kakaocdn.net/dn/bI05ZC/hyB8ReBxjK/JA4vU0KYRUz1MemvVTVbMK/img.png?width=606&amp;amp;height=358&amp;amp;face=0_0_606_358');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Kotlin Coroutines의 Job 동작을 알아보자&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;개인 광고 영역 Kotlin Coroutines을 컨트롤하기 위한 Job을 제공해준다. 이 Job은 N 개의 coroutines의 동작을 제어할 수도 있으며, 하나의 coroutines 동작을 제어할 수도 있다. 먼저 Job이 어떤 것인지 알아보고, exception 발생 케이스를 함께 알아보겠다. Job Coroutines의 Job은 결국 coroutines의 상태를 가지고 있는데, 아래와 같은 6가지 상태를 포함하고 있으며, active/complet&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;thdev.tech&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.android.com/kotlin/coroutines&quot;&gt;https://developer.android.com/kotlin/coroutines&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1564124025141&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a href=&quot;https://developer.android.com/kotlin/coroutines&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-original-url=&quot;https://developer.android.com/kotlin/coroutines&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b7A6Lo/hyB8IWiuWg/bMOD8ICLljFcuUYxBZaiZK/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Improve app performance with Kotlin coroutines &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;A coroutine is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously. Coroutines were added to Kotlin in version 1.3 and are based on established concepts from other languages. On Android, coroutines help to&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android/Coroutine</category>
      <category>android</category>
      <category>Coroutine</category>
      <category>coroutine runblocking</category>
      <category>coroutinescope</category>
      <category>안드로이드</category>
      <category>코루틴</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/82</guid>
      <comments>https://eso0609.tistory.com/82#entry82comment</comments>
      <pubDate>Fri, 26 Jul 2019 16:06:29 +0900</pubDate>
    </item>
    <item>
      <title>Android 8.0 Only fullscreen opaque activities can request orientation error</title>
      <link>https://eso0609.tistory.com/81</link>
      <description>&lt;h3&gt;문제 상황&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어제 기분 좋게 7마켓에 모두 빌드를 올리고, 오전에 모든 마켓에서 release 되었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RSOrv/btqwQWHjlKF/U8P5pGTnCgiZRcsVoyKbkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RSOrv/btqwQWHjlKF/U8P5pGTnCgiZRcsVoyKbkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RSOrv/btqwQWHjlKF/U8P5pGTnCgiZRcsVoyKbkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRSOrv%2FbtqwQWHjlKF%2FU8P5pGTnCgiZRcsVoyKbkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Fabric에 처음 보는 에러가 날라왔습니다.(긴장..)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;&lt;b&gt;에러 문구&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;&lt;b&gt;Only&amp;nbsp;fullscreen&amp;nbsp;opaque&amp;nbsp;activities&amp;nbsp;can&amp;nbsp;request&amp;nbsp;orientation&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;color: #333333;&quot;&gt;해결 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;문제 상황을 인지하고, 구글링을 시도했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;제일 먼저 눈에 띄었던 &lt;a href=&quot;https://gun0912.tistory.com/79&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;박상권님의 블로그&lt;/a&gt;를 보며 이해할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;( 상권님의 에러 대응 방법도 배우게되었습니다. 감사합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;에러는 투명한 Activity를 만들기 위해 style에서 사용한&amp;nbsp;&lt;b&gt;windowIsTranslucent&lt;/b&gt; 때문에 발생합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;저도 안드로이드 8.0 ( Oreo ) API 26 SDK에서 코드를 봤습니다. ( 사실 이렇게 구글 SDK 코드를 보는 건 처음이었습니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1563513027832&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (getApplicationInfo().targetSdkVersion &amp;gt; O &amp;amp;&amp;amp; mActivityInfo.isFixedOrientation()) {
            final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
            final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
            ta.recycle();
            if (isTranslucentOrFloating) {
                throw new IllegalStateException(
                        &quot;Only fullscreen opaque activities can request orientation&quot;);
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;정말 이런 코드가 있었습니다. 제가 정말 맞게 공부하고 있었는가 의문이 들었습니다. 이렇게 명료하게 에러 이유를 볼 수 있었다니...!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 문제의 해결방법으로는 orientation을 설정하지 않는 방법, style v26 따로 두는 방법, sdk버전이 안드로이드 오레오일 경우 예외처리, 그리고 코드상으로 orientation을 설정하고 이에 try/catch로 감싸는 방법이 있었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;저는 BaseActivity에 아래와 같은 코드를 추가했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1563513714291&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; try{
            //API 8.0 Only fullscreen opaque activities can request orientation 안드로이드 이슈.
            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
        }catch (e : IllegalStateException){

        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 AndroidManifest에 있는 모든 orientation을 지워줬습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행 결과 잘 작동합니다.&lt;/p&gt;</description>
      <category>Android</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/81</guid>
      <comments>https://eso0609.tistory.com/81#entry81comment</comments>
      <pubDate>Fri, 19 Jul 2019 15:01:18 +0900</pubDate>
    </item>
    <item>
      <title>Lorg/apache/http/params/BasicHttpParam error</title>
      <link>https://eso0609.tistory.com/80</link>
      <description>&lt;h3&gt;문제 상황&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;프로젝트에서 SNS 연동 로그인을 사용중입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그중에서도 Weibo 연동 로그인 버튼 클릭시 나타난 에러입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;에러가 발생한 기기는 Android 9, API 28 이었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;&lt;b&gt;에러 문구&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;&lt;b&gt;-&amp;gt; NoClassDefFoundError:&amp;nbsp;Failed&amp;nbsp;resolution&amp;nbsp;of:&amp;nbsp;Lorg/apache/http/params/BasicHttpParam&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;해결방법&lt;/h3&gt;
&lt;p&gt;Android 6.0에서는 Apache HTTP 클라이언트에 대한 지원이 제거되었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Android 9 부터는 이 라이브러리가 bootclasspath에서 제거되고 기본적으로 앱에서 사용할 수 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉 기본적으로 제공하고 있던 기능이었는데 Android 9에서는 제공하지 않기 때문에 에러가 발생했다는 걸 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 문제를 해결하기 위해서는 다음과 같은 코드를 AndroidMainfest &amp;lt;Application&amp;gt; 영역에 추가해주어야 합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1563511588817&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;uses-library android:name=&quot;org.apache.http.legacy&quot; android:required=&quot;false&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Android/Android 9.0 변경 사항 및 이슈</category>
      <category>Android Pie</category>
      <category>android9.0</category>
      <category>android9.0이슈</category>
      <category>android파이</category>
      <category>BasicHttpParam에러</category>
      <category>Lorg/apache/http/params/BasicHttpParam</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/80</guid>
      <comments>https://eso0609.tistory.com/80#entry80comment</comments>
      <pubDate>Fri, 19 Jul 2019 13:48:03 +0900</pubDate>
    </item>
    <item>
      <title>Android Mockito를 사용한 유닛 테스트 코드 작성</title>
      <link>https://eso0609.tistory.com/79</link>
      <description>&lt;h2&gt;Mockito를 이용한 UI 테스트 코드&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Mockito는 유닛 테스트를위한 Java mocking framework 입니다. Mockito를 사용하여 쉽게 Mock Object를 만들어 관리할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Mock에 대한 개념은 아래 링크에서 자세하게 설명하고 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@SlackBeck/mock-object%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-85159754b2ac&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Mock Object&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1562897781720&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a href=&quot;https://medium.com/@SlackBeck/mock-object%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-85159754b2ac&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-original-url=&quot;https://medium.com/@SlackBeck/mock-object%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-85159754b2ac&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cclMV1/hyBYtqbAij/ixTLrrmygOldSvHQ1V1UBk/img.jpg?width=437&amp;amp;height=250&amp;amp;face=0_0_437_250,https://scrap.kakaocdn.net/dn/c9UPYv/hyBWDBfBYp/DgrskVS3qvx2RfxIwYxHck/img.png?width=60&amp;amp;height=23&amp;amp;face=0_0_60_23,https://scrap.kakaocdn.net/dn/tDNpw/hyBYoJbNUw/8jUpFRnRXoz2FprKtvm460/img.png?width=60&amp;amp;height=22&amp;amp;face=0_0_60_22');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Mock Object란 무엇인가? - Yoo Young-mo - Medium&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;다른 누군가로부터 휴대 전화 서비스(CellphoneService) 기능을 제공 받아 이를 사용한 휴대 전화 문자 발신기(CellphoneMmsSender)를 프로그래밍 한다고 생각해 보자.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2&gt;이 포스팅의 목적&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;제가 제일 처음 안드로이드 유닛 테스트 예제 코드를 작성하는 것 부터 이번 포스팅까지,&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;테스트를 접해보지 못하신분들, 도무지 어떻게 시작해야할지 모르는 분들에게 유익한 포스팅이 되었으면 좋겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;저도 구글링하면서 테스트를 실제 프로젝트에 도입해보기 위해 공부하며 정리한 포스팅인만큼, 충분히 쉽게 이해하실 수 있을겁니다!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 시작했으면 끝을 봐야겠죠!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;build.gradle&lt;/h2&gt;
&lt;pre id=&quot;code_1562897990495&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId &quot;com.example.mockitotest&quot;
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName &quot;1.0&quot;
        testInstrumentationRunner &quot;android.support.test.runner.AndroidJUnitRunner&quot;
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation&quot;org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version&quot;
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'

    testImplementation 'junit:junit:4.12'
    testImplementation 'org.mockito:mockito-core:2.28.2'

    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Person.kt&lt;/h3&gt;
&lt;pre id=&quot;code_1562898044093&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; data class Person(var name : String, var age : Int)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;MockTest.kt&lt;/h3&gt;
&lt;pre id=&quot;code_1562898071657&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import org.junit.Test
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock

class MockTest{
    @Test
    fun test(){

        val p : Person = mock(Person::class.java)
        assertTrue(p != null)

        `when`(p.name).thenReturn(&quot;Seonoh&quot;)
        `when`(p.age).thenReturn(28)

        assertEquals(p.name,&quot;Seonoh&quot;)
        assertEquals(p.age,28)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코틀린에서는 when 구문이 존재해서인지 `when`으로 표기됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해당 구문을 통해서 stubbing을 제공합니다. 즉, 여기서는 thenReturn(&quot;Seonoh&quot;)를 통해서 Person의 name에 &quot;Seonoh&quot;를&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 stubbing란 무엇일까 많이 들어봤지만 정확한 뜻은 무엇일까요?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 링크를 통해서 차근차근 이해해보면 좋을 것 같습니다. ( 저는 '정해진 질문에 대해 사전에 준비한 답' 이 와닿았습니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@SlackBeck/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8A%A4%ED%85%81-test-stub-%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-ff9c8840c1b0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;테스트 스텁&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;실행결과&lt;/h3&gt;
&lt;pre id=&quot;code_1562898151498&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;org.mockito.exceptions.base.MockitoException: 
Cannot mock/spy class com.example.mockitotest.Person
Mockito cannot mock/spy because :
 - final class&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;황당짤.gif&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JfRKs/btqwIK6ILhX/ZPP04NCukk5bqLzwmAYpe1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JfRKs/btqwIK6ILhX/ZPP04NCukk5bqLzwmAYpe1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JfRKs/btqwIK6ILhX/ZPP04NCukk5bqLzwmAYpe1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/JfRKs/btqwIK6ILhX/ZPP04NCukk5bqLzwmAYpe1/img.gif&quot; data-filename=&quot;황당짤.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;???&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행하자마자 겪은 에러입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 에러의 이유는 Mockito mock에서는 일반적으로 인스턴스를 만들기 위해 요청 클래스들을 확장합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 코틀린의 모든 클래스들은 default로 final 클래스로 정의됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 Mockito 라이브러리에서는 해당 클래스들을 확장할 수가 없는 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;해결방법&lt;/h3&gt;
&lt;p&gt;&lt;u&gt;&lt;i&gt;open&lt;/i&gt;&lt;/u&gt;을 사용하여 해결할 수도 있지만 개발자의 디자인 아키텍쳐와 맞지 않을 수 있기 때문에 dependency를 설정해주면됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;&lt;i&gt;testImplementation 'org.mockito:mockito-inline:2.8.47'&lt;/i&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android/Android Testing</category>
      <category>android mockito</category>
      <category>android unit test</category>
      <category>Mock</category>
      <category>mock object</category>
      <category>mockito</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/79</guid>
      <comments>https://eso0609.tistory.com/79#entry79comment</comments>
      <pubDate>Fri, 12 Jul 2019 14:43:34 +0900</pubDate>
    </item>
    <item>
      <title>Android Espresso를 사용한 UI 테스트 예제 코드 작성</title>
      <link>https://eso0609.tistory.com/78</link>
      <description>&lt;h2&gt;UI 테스트 코드를 작성하게된 이유&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;유닛테스트와 마찬가지로 수동으로 테스트하게 되면, 인력 소모와 테스트의 효율성, 안정성을 보장하기 어렵습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 최근에는 모바일앱이 점점 거대해지고, 복잡해지면서 각각의 기능을 분리하여 개발하게 되고,&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;모듈화된 feature들을 merge하는 경우가 많습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이에 효율적인 테스트 코드 작성이 가능해진다면 복잡한 앱이라도 더 쉬운 테스트, 효율적인 테스트가 가능할 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;이 포스팅의 목적&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번 포스팅은 이전 포스팅인 유닛테스트 (&amp;nbsp;&lt;a href=&quot;https://eso0609.tistory.com/77&quot;&gt;https://eso0609.tistory.com/77&lt;/a&gt;&amp;nbsp;) 와 다르게 UI 테스트를 목적으로 작성했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;구글링을 통해서 학습하고, 정리한 내용은 Kotlin으로 작성해 보았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예제 코드는 간단하게 이름, 아이디, 비밀번호를 입력하고, 하단 textView에 입력된 정보가 맞게 들어갔는지 확인합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 버튼을 클릭하여 입력된 정보를 Toast 메세지로 뿌려줍니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MxDRT/btqwGOhBCjM/aWND7pwriaMKBHlqIRWao1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MxDRT/btqwGOhBCjM/aWND7pwriaMKBHlqIRWao1/img.png&quot; data-alt=&quot;프로젝트 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MxDRT/btqwGOhBCjM/aWND7pwriaMKBHlqIRWao1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMxDRT%2FbtqwGOhBCjM%2FaWND7pwriaMKBHlqIRWao1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프로젝트 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;UI 테스트를 위해 src/androidTest에 프로젝트를 생성해줍니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;build.gradle&lt;/h3&gt;
&lt;pre id=&quot;code_1562825554076&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId &quot;com.example.espressotest&quot;
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName &quot;1.0&quot;
        testInstrumentationRunner &quot;android.support.test.runner.AndroidJUnitRunner&quot;
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation&quot;org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version&quot;
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'

    testImplementation 'junit:junit:4.12'

    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    androidTestImplementation 'com.android.support.test:rules:1.0.2'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;activity_main.xml&lt;/h3&gt;
&lt;pre id=&quot;code_1562825590929&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
    &amp;lt;LinearLayout
        android:orientation=&quot;vertical&quot;
        android:padding=&quot;16dp&quot;
        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;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        tools:context=&quot;.MainActivity&quot;&amp;gt;

    &amp;lt;LinearLayout
            android:weightSum=&quot;4&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:orientation=&quot;horizontal&quot;&amp;gt;

        &amp;lt;TextView
                android:id=&quot;@+id/name_tv&quot;
                android:layout_weight=&quot;1&quot;
                android:textSize=&quot;16sp&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:text=&quot;NAME&quot;/&amp;gt;
        &amp;lt;EditText
                android:id=&quot;@+id/name_et&quot;
                android:layout_weight=&quot;3&quot;
                android:layout_marginLeft=&quot;8dp&quot;
                android:textSize=&quot;16sp&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:hint=&quot;Input name ..&quot;/&amp;gt;

    &amp;lt;/LinearLayout&amp;gt;

    &amp;lt;LinearLayout
            android:layout_marginTop=&quot;16dp&quot;
            android:weightSum=&quot;4&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:orientation=&quot;horizontal&quot;&amp;gt;

        &amp;lt;TextView
                android:id=&quot;@+id/id_tv&quot;
                android:layout_weight=&quot;1&quot;
                android:textSize=&quot;16sp&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:text=&quot;ID&quot;/&amp;gt;
        &amp;lt;EditText
                android:id=&quot;@+id/id_et&quot;
                android:layout_weight=&quot;3&quot;
                android:layout_marginLeft=&quot;8dp&quot;
                android:textSize=&quot;16sp&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:hint=&quot;Input id ..&quot;/&amp;gt;

    &amp;lt;/LinearLayout&amp;gt;

    &amp;lt;LinearLayout
            android:layout_marginTop=&quot;16dp&quot;
            android:weightSum=&quot;4&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:orientation=&quot;horizontal&quot;&amp;gt;

        &amp;lt;TextView
                android:id=&quot;@+id/pwd_tv&quot;
                android:layout_weight=&quot;1&quot;
                android:textSize=&quot;16sp&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:text=&quot;PWD&quot;/&amp;gt;
        &amp;lt;EditText
                android:id=&quot;@+id/pwd_et&quot;
                android:layout_weight=&quot;3&quot;
                android:layout_marginLeft=&quot;8dp&quot;
                android:textSize=&quot;16sp&quot;
                android:layout_width=&quot;0dp&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:hint=&quot;Input password ..&quot;/&amp;gt;

    &amp;lt;/LinearLayout&amp;gt;

    &amp;lt;Button
            android:id=&quot;@+id/done_btn&quot;
            android:text=&quot;DONE&quot;
            android:layout_marginTop=&quot;16dp&quot;
            android:layout_gravity=&quot;center&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;/&amp;gt;

    &amp;lt;LinearLayout
            android:weightSum=&quot;3&quot;
            android:layout_marginTop=&quot;16dp&quot;
            android:id=&quot;@+id/board_linear&quot;
            android:orientation=&quot;vertical&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;match_parent&quot;&amp;gt;

        &amp;lt;TextView
                android:id=&quot;@+id/name_board_tv&quot;
                android:textSize=&quot;16sp&quot;
                android:textStyle=&quot;bold&quot;
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:ellipsize=&quot;end&quot;/&amp;gt;
        &amp;lt;TextView
                android:layout_marginTop=&quot;4dp&quot;
                android:id=&quot;@+id/id_board_tv&quot;
                android:textSize=&quot;16sp&quot;
                android:textStyle=&quot;bold&quot;
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:ellipsize=&quot;end&quot;/&amp;gt;
        &amp;lt;TextView
                android:layout_marginTop=&quot;4dp&quot;
                android:id=&quot;@+id/pwd_board_tv&quot;
                android:textSize=&quot;16sp&quot;
                android:textStyle=&quot;bold&quot;
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:ellipsize=&quot;end&quot;/&amp;gt;


    &amp;lt;/LinearLayout&amp;gt;

&amp;lt;/LinearLayout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;MainActivity.class&lt;/h3&gt;
&lt;pre id=&quot;code_1562825847947&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

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

        name_et.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(p0: Editable?) {

            }

            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

            }

            override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                name_board_tv.text = &quot;NAME : $p0&quot;
            }
        })

        id_et.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(p0: Editable?) {

            }

            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

            }

            override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                id_board_tv.text = &quot;ID : $p0&quot;
            }
        })

        pwd_et.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(p0: Editable?) {

            }

            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {

            }

            override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                pwd_board_tv.text = &quot;PWD : $p0&quot;
            }
        })

        done_btn.setOnClickListener {
            Toast.makeText(
                this,
                &quot;이름 : ${name_board_tv.text}\nID : ${id_board_tv.text}\nPWD : ${pwd_board_tv.text}&quot;,
                Toast.LENGTH_LONG
            ).show()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;EspressoTest.class&lt;/h3&gt;
&lt;pre id=&quot;code_1562825998195&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.action.ViewActions.*
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.matcher.ViewMatchers.withId
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.support.test.filters.LargeTest
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@LargeTest
class EspressoTest{

@get:Rule
var myActivityActivityTestRule: ActivityTestRule&amp;lt;MainActivity&amp;gt; = ActivityTestRule(MainActivity::class.java)


        @Test
        fun startTest(){

                //editText에 값 입력후 키보드 닫음.
                onView(withId(R.id.name_et)).perform(typeText(&quot;qwertyui&quot;))
                onView(withId(R.id.id_et)).perform(typeText(&quot;ace69&quot;))
                onView(withId(R.id.pwd_et)).perform(typeText(&quot;123456&quot;), closeSoftKeyboard())


                //textView 값이 Hello Wordl! 인지 확인
                onView(withId(R.id.name_board_tv)).check(matches(withText(&quot;NAME : qwertyui&quot;)))
                onView(withId(R.id.id_board_tv)).check(matches(withText(&quot;ID : ace69&quot;)))
                onView(withId(R.id.pwd_board_tv)).check(matches(withText(&quot;PWD : 123456&quot;)))

                //Button 클릭
                onView(withId(R.id.done_btn)).perform(click())
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;실행&lt;/h3&gt;
&lt;p&gt;이번 Espresso UI테스트의 실행은 안드로이드 단말기 또는 에뮬레이터로 실행하게됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;EspressoTest 우클릭 Run ~ 하시면됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;실행결과&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/channel/3354303/cliplink/400206476&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/0ZMJq/hyBYpAUGUW/txwCTK5pZeu7nnktZKH8Q0/img.png?width=404&amp;amp;height=854&amp;amp;face=0_0_404_854,https://scrap.kakaocdn.net/dn/c0Td1C/hyBYkfiIJx/blkvzPP2xoATyWKDtZRRl1/img.jpg?width=640&amp;amp;height=360&amp;amp;face=0_0_640_360&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/400206476?service=kakao_tv&quot; width=&quot;404&quot; height=&quot;854&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;처음에 샤오미 단말기로 테스트해보았는데, 계속해서 timeout에러가 났습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 부분에 대해서는 더 학습해보고 포스팅 해볼 예정입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;( 이번 테스트 기기는 갤럭시 s10+였습니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고 자료&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;-&amp;nbsp;&lt;a href=&quot;https://developer.android.com/training/testing/espresso&quot;&gt;https://developer.android.com/training/testing/espresso&lt;/a&gt;&lt;/p&gt;</description>
      <category>Android/Android Testing</category>
      <category>android espresso test</category>
      <category>android ui test</category>
      <category>espresso</category>
      <category>espresso ui test</category>
      <category>ui test</category>
      <category>안드로이드 ui테스트</category>
      <category>안드로이드 테스트</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/78</guid>
      <comments>https://eso0609.tistory.com/78#entry78comment</comments>
      <pubDate>Thu, 11 Jul 2019 15:33:49 +0900</pubDate>
    </item>
    <item>
      <title>Android JUnit을 사용한 로컬 유닛 테스트 예제 코드 작성</title>
      <link>https://eso0609.tistory.com/77</link>
      <description>&lt;h3&gt;유닛테스트 코드를 작성한 이유&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앱을 개발하다보면 코드가 예상대로 실행이 되는지 테스트가 필요합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 매번 수동으로 테스트하게 되면 인력 소모도 심하고, 수동으로 하다보니 매번 정확하지 않은 테스트가 수행될 수 있어 안정성이 떨어질 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 자동화 테스트를 진행하게 된다면 인력 소모를 줄일 수 있고, 보다 정교하고 안정적인 테스트를 진행할 수 있다고 생각했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;이 포스팅의 목적&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번 포스팅은 로컬 JVM 에서 실행할 수 있는 유닛 테스트 코드 작성입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;구글링한 자료를 바탕으로 Kotlin 언어로 작성해 보았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlo64v/btqwDIIUpoZ/bRH7uFwcsOXWFBwl8NNCR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlo64v/btqwDIIUpoZ/bRH7uFwcsOXWFBwl8NNCR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlo64v/btqwDIIUpoZ/bRH7uFwcsOXWFBwl8NNCR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdlo64v%2FbtqwDIIUpoZ%2FbRH7uFwcsOXWFBwl8NNCR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;우선 위 이미지에서 테스트 환경 프로젝트 구조를 볼 수 있습니다. ( 샘플 예제 코드와는 관련이 없습니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;~androidTest/java/com.example.androidtest/ExampleInstrumentedTest는 실제 안드로이드 기기를 통한 테스트 방법이고,&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;~test/java/com.example.androidtest/ExampleUnitTest가 유닛테스트, 즉&amp;nbsp; 이번 포스팅에서 다루게 될&amp;nbsp;&amp;nbsp;로컬 JVM에서 실행할 수 있는 테스트 방법입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;build.gradle&lt;/h3&gt;
&lt;pre id=&quot;code_1562740613893&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId &quot;com.example.androidtest&quot;
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName &quot;1.0&quot;
        testInstrumentationRunner &quot;android.support.test.runner.AndroidJUnitRunner&quot;
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation&quot;org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version&quot;
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'

    // android test implementation
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;values/strings&lt;/h3&gt;
&lt;pre id=&quot;code_1562741326259&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;resources&amp;gt;
    &amp;lt;string name=&quot;app_name&quot;&amp;gt;AndroidTest&amp;lt;/string&amp;gt;
    &amp;lt;string name=&quot;coffee_price&quot;&amp;gt;Coffee: $%.1f&amp;lt;/string&amp;gt;
    &amp;lt;string name=&quot;total_price&quot;&amp;gt;Total price: $%.1f&amp;lt;/string&amp;gt;
    &amp;lt;string name=&quot;increment_label&quot;&amp;gt;+&amp;lt;/string&amp;gt;
    &amp;lt;string name=&quot;decrement_label&quot;&amp;gt;-&amp;lt;/string&amp;gt;
    &amp;lt;string name=&quot;default_coffee_count&quot;&amp;gt;0&amp;lt;/string&amp;gt;
&amp;lt;/resources&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;acitivity_main.xml&lt;/h3&gt;
&lt;pre id=&quot;code_1562741367652&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;LinearLayout
        xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:orientation=&quot;vertical&quot;&amp;gt;
    &amp;lt;TextView
            android:id=&quot;@+id/coffee_price&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:textSize=&quot;20sp&quot;
            android:text=&quot;@string/coffee_price&quot;/&amp;gt;
    &amp;lt;LinearLayout
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginTop=&quot;12dp&quot;
            android:layout_marginBottom=&quot;12dp&quot;
            android:gravity=&quot;center_vertical&quot;&amp;gt;
        &amp;lt;Button
                android:id=&quot;@+id/coffee_decrement&quot;
                android:layout_width=&quot;wrap_content&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:background=&quot;@android:color/transparent&quot;
                android:text=&quot;@string/decrement_label&quot;/&amp;gt;
        &amp;lt;TextView
                android:id=&quot;@+id/coffee_count&quot;
                android:layout_width=&quot;wrap_content&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_marginLeft=&quot;16dp&quot;
                android:layout_marginRight=&quot;16dp&quot;
                android:textSize=&quot;32sp&quot;
                android:text=&quot;@string/default_coffee_count&quot;/&amp;gt;
        &amp;lt;Button
                android:id=&quot;@+id/coffee_increment&quot;
                android:layout_width=&quot;wrap_content&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:background=&quot;@android:color/transparent&quot;
                android:text=&quot;@string/increment_label&quot;/&amp;gt;
    &amp;lt;/LinearLayout&amp;gt;
    &amp;lt;TextView
            android:id=&quot;@+id/total_price&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:textSize=&quot;20sp&quot;
            android:text=&quot;@string/total_price&quot;/&amp;gt;
&amp;lt;/LinearLayout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;MainActivity.class&lt;/h3&gt;
&lt;pre id=&quot;code_1562741433649&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.PersistableBundle
import android.util.Log
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    val COFFEE_COUNT = &quot;coffee_count&quot;
    val DEFAULT_COFFEE_PRICE = 5.0f

    private lateinit var mOrder : CoffeeOrder

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

        mOrder = CoffeeOrder(DEFAULT_COFFEE_PRICE)

        coffee_price.text = String.format(getString(R.string.coffee_price),DEFAULT_COFFEE_PRICE)
        total_price.text = String.format(getString(R.string.total_price),0.0f)

        coffee_increment.setOnClickListener {
            mOrder.incrementCoffeeCount()
            updateCoffeeCount()
            updateTotalPrice()
        }

        coffee_decrement.setOnClickListener {
            mOrder.decrementCoffeeCount()
            updateCoffeeCount()
            updateTotalPrice()
        }
    }


    override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
        outState!!.putInt(COFFEE_COUNT,mOrder.mCoffeeCount)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
        super.onRestoreInstanceState(savedInstanceState)
        if(savedInstanceState != null){
            mOrder.mCoffeeCount = savedInstanceState.getInt(COFFEE_COUNT)
            updateCoffeeCount()
            updateTotalPrice()
        }
    }

    private fun updateCoffeeCount(){
        coffee_count.text = &quot;&quot;+mOrder.mCoffeeCount

    }

    private fun updateTotalPrice(){
        total_price.text = String.format(getString(R.string.total_price),mOrder.mTotalPrice)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;CoffeeOrder.class&lt;/h3&gt;
&lt;pre id=&quot;code_1562741502125&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CoffeeOrder(coffeePrice : Float) {
    var mCoffeePrice = coffeePrice
    var mCoffeeCount =  0
        get() = if(field &amp;lt; 0) 0 else field

    var mTotalPrice = 0f

    fun incrementCoffeeCount(){
        mCoffeeCount++
        calculateTotalPrice()
    }

    fun decrementCoffeeCount(){
        if( mCoffeeCount &amp;gt; 0){
            mCoffeeCount--
            calculateTotalPrice()
        }
    }

    fun calculateTotalPrice(){
        mTotalPrice = mCoffeePrice * mCoffeeCount
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;CoffeeOrderTest.class&lt;/h3&gt;
&lt;pre id=&quot;code_1562741566685&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNotNull
import org.junit.Before
import org.junit.Test

class CoffeeOrderTest {
    private val PRICE_TEST = 5.0f
    private lateinit var mOrder : CoffeeOrder

    @Before
    fun setUp(){
        mOrder = CoffeeOrder(PRICE_TEST)
    }

    @Test
    fun orderIsNotNull(){
        assertNotNull(mOrder)
    }
    @Test
    fun orderDecrement(){
        mOrder.decrementCoffeeCount()
        assertEquals(0, mOrder.mCoffeeCount)

        mOrder.mCoffeeCount = 25
        mOrder.decrementCoffeeCount()
        assertEquals(24,mOrder.mCoffeeCount)
    }


    @Test
    fun orderIncrement(){
        mOrder.incrementCoffeeCount()
        assertEquals(1,mOrder.mCoffeeCount)

        mOrder.initCoffeeCount(25)
        mOrder.incrementCoffeeCount()
        assertEquals(26,mOrder.mCoffeeCount)

    }

    @Test
    fun orderTotalPrice(){
        assertEquals(0.0f, mOrder.mTotalPrice)

        mOrder.initCoffeeCount(25)
        assertEquals(PRICE_TEST*25, mOrder.mTotalPrice)
    }

    @Test
    fun orderSetCoffeeCount(){
        mOrder.initCoffeeCount(-1)
        assertEquals(0, mOrder.mCoffeeCount)

        mOrder.initCoffeeCount(25)
        assertEquals(25, mOrder.mCoffeeCount)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;실행&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/44yNA/btqwFApV6cz/6DMhKh79u5vQPYTIPIpkO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/44yNA/btqwFApV6cz/6DMhKh79u5vQPYTIPIpkO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/44yNA/btqwFApV6cz/6DMhKh79u5vQPYTIPIpkO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F44yNA%2FbtqwFApV6cz%2F6DMhKh79u5vQPYTIPIpkO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;실행 결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYjeFT/btqwHc9sGXH/8Ggf9IkHuDqTH91kCKtVs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYjeFT/btqwHc9sGXH/8Ggf9IkHuDqTH91kCKtVs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYjeFT/btqwHc9sGXH/8Ggf9IkHuDqTH91kCKtVs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYjeFT%2FbtqwHc9sGXH%2F8Ggf9IkHuDqTH91kCKtVs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;만약 기대값과 다른 결과가 나온다면?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAzpyD/btqwFPtCKN7/AcFUTDYieJ3VUUt07aHK3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAzpyD/btqwFPtCKN7/AcFUTDYieJ3VUUt07aHK3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAzpyD/btqwFPtCKN7/AcFUTDYieJ3VUUt07aHK3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAzpyD%2FbtqwFPtCKN7%2FAcFUTDYieJ3VUUt07aHK3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위 이미지에서 볼 수 있듯이 코드 왼쪽에 빨간 아이콘이 보입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;( 해당 테스트코드가 실패했을 경우 빨간색. 성공했다면 초록색이 보일겁니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;각각의 @Test 어노테이션이 붙은 코드 블록에서 아이콘을 확인할 수 있습니다. 즉, 각 메소드마다 테스트를 해볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 이미지에서 기대값이 -1인데 실재 값이 0으로 나왔을 때 아래와 같은 에러 코드를 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csIqlh/btqwFQzgIzB/SxicQiHpbMDIJJ4pzhK1L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csIqlh/btqwFQzgIzB/SxicQiHpbMDIJJ4pzhK1L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csIqlh/btqwFQzgIzB/SxicQiHpbMDIJJ4pzhK1L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsIqlh%2FbtqwFQzgIzB%2FSxicQiHpbMDIJJ4pzhK1L0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이것저것 테스트해보고 있는데 유닛테스트를 더 자세하게 학습하고 싶어지네요!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;읽어주셔서 감사합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- 참고 자료&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://alexzh.com/2016/03/24/android-testing-unit-testing/&quot;&gt;https://alexzh.com/2016/03/24/android-testing-unit-testing/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Android/Android Testing</category>
      <category>android junit4</category>
      <category>android test code example</category>
      <category>android unit test</category>
      <category>JUnit</category>
      <category>안드로이드 유닛테스트</category>
      <category>안드로이드테스트코드</category>
      <category>유닛테스트</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/77</guid>
      <comments>https://eso0609.tistory.com/77#entry77comment</comments>
      <pubDate>Wed, 10 Jul 2019 16:12:14 +0900</pubDate>
    </item>
    <item>
      <title>Android testing codelab ModuleComponentIdentifierImpl error</title>
      <link>https://eso0609.tistory.com/76</link>
      <description>&lt;p&gt;안드로이드 유닛 테스트 학습을 위해&amp;nbsp;&lt;a href=&quot;https://codelabs.developers.google.com/codelabs/android-testing/#4&quot;&gt;https://codelabs.developers.google.com/codelabs/android-testing/#4&lt;/a&gt;&amp;nbsp;에서 샘플 프로젝트를 다운받았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 안드로이드 스튜디오에서 실행한 결과...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시작하자마&amp;nbsp;Failed to notify build listener 에러가 떴습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자세한 로그&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Cause: org.jetbrains.plugins.gradle.tooling.util.ModuleComponentIdentifierImpl.getModuleIdentifier()Lorg/gradle/api/artifacts/ModuleIdentifie&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;열심히 구글링을 해보았는데, 모든 답변들이 gradle 버전 문제였습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 gradle plugin 버전을 올렸습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;( 올리는 김에 안스 버전도 함께 업데이트해줬어요. 기존 안드로이드 스튜디오 버전은 3.2.1이 었습니다. )&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-07-10 오전 11.55.52.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PwBa5/btqwDtroNmz/VeYv1gEwSDXAKNipsRZM0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PwBa5/btqwDtroNmz/VeYv1gEwSDXAKNipsRZM0K/img.png&quot; data-alt=&quot;안드로이드 스튜디오 버전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PwBa5/btqwDtroNmz/VeYv1gEwSDXAKNipsRZM0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPwBa5%2FbtqwDtroNmz%2FVeYv1gEwSDXAKNipsRZM0K%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-07-10 오전 11.55.52.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;안드로이드 스튜디오 버전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2019-07-10 오전 11.55.27.png&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRoJuD/btqwDtkKVGD/wHAuDFbxkiQI0pK029ZmB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRoJuD/btqwDtkKVGD/wHAuDFbxkiQI0pK029ZmB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRoJuD/btqwDtkKVGD/wHAuDFbxkiQI0pK029ZmB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRoJuD%2FbtqwDtkKVGD%2FwHAuDFbxkiQI0pK029ZmB0%2Fimg.png&quot; data-filename=&quot;스크린샷 2019-07-10 오전 11.55.27.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;버전 업데이트 후 실행해보니 정상적으로 실행됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android/Android Testing</category>
      <category>android test code</category>
      <category>android test codelab</category>
      <category>gradle plugin</category>
      <category>gradle update</category>
      <category>ModuleComponentIdentifierImpl</category>
      <category>ModuleComponentIdentifierImpl error</category>
      <category>안드로이드gradle</category>
      <category>안드로이드스튜디오</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/76</guid>
      <comments>https://eso0609.tistory.com/76#entry76comment</comments>
      <pubDate>Wed, 10 Jul 2019 13:21:38 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 Kotlin Coroutines 정리 Part 3</title>
      <link>https://eso0609.tistory.com/75</link>
      <description>&lt;h3&gt;Channels ( experimental )&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지연된 값은 코루틴간에 단일 값을 전송하는 편리한 방법을 제공합니다. &lt;i&gt;Channels&lt;/i&gt;는 값의 흐름을 전송하는 방법을 제공합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554180238358&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Channels are an experimental feature of kotlinx.coroutines. 
Their API is expected to evolve in the upcoming updates of the kotlinx.coroutines library with potentially breaking changes.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Channel basics&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Channel&amp;nbsp;&lt;/i&gt;은 개념적으로 &lt;i&gt;BlockingQueue&amp;nbsp;&lt;/i&gt;와 매우 유사합니다. 한가지 중요한 차이점은 blocking &lt;i&gt;put&lt;/i&gt; 연산 대신에 suspending &lt;i&gt;send&lt;/i&gt;, 그리고 blocking &lt;i&gt;take&lt;/i&gt; 연산 대신에 suspending &lt;i&gt;receive&lt;/i&gt;를 갖습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554180466731&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    val channel = Channel&amp;lt;Int&amp;gt;()
    launch {
        // this might be heavy CPU-consuming computation or async logic, we'll just send five squares
        for (x in 1..5) channel.send(x * x)
    }
    // here we print five received integers:
    repeat(5) { println(channel.receive()) }
    println(&quot;Done!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554180483560&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1
4
9
16
25
Done!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Closing and iteration over channels&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;queue와 다르게 채널은 더 이상 요소가 없다는 것은 나타낼 수 있습니다. receiver 측에서 for루프를 사용하여 채널로부터 요소들을 &lt;span style=&quot;color: #333333;&quot;&gt;편리하게&amp;nbsp;&lt;/span&gt;받을 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;개념적으로, close 는 특별한 close 토큰을 채널에 보내는 것과 같습니다. close 토큰이 수신되는 즉시 반복이 중지되므로 이전에 전송된 모든 요소가 수신된다고 보장할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554180822431&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    val channel = Channel&amp;lt;Int&amp;gt;()
    launch {
        for (x in 1..5) channel.send(x * x)
        channel.close() // we're done sending
    }
    // here we print received values using `for` loop (until the channel is closed)
    for (y in channel) println(y)
    println(&quot;Done!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554180840792&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1
4
9
16
25
Done!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Building channel producers&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코루틴이 일련의 요소를 생산하는 것은 매우 일반적입니다. 이는 동기 코드에서 종종 발견할 수 있는 &lt;i&gt;producer - consumer&lt;/i&gt; 코드의 일부분입니다. &lt;i&gt;producer&amp;nbsp;&lt;/i&gt;를 매개 변수로 사용하는 함수로 추상화 할 수 있지만, 결과가 함수에서 return 되어야 한다는 일반적 상식에 어긋납니다.&lt;/p&gt;
&lt;p&gt;편리한 코루틴 빌더 &lt;i&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;produce&lt;/a&gt;&amp;nbsp;&lt;/i&gt;를 통해서 &lt;i&gt;producer&lt;/i&gt; 측에서 이를 쉽게할 수 있습니다. 그리고 &lt;i&gt;consumer&lt;/i&gt; 측 for loop를 대신할 확장 함수 &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/consume-each.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;i&gt;consumeEach&lt;/i&gt;&lt;/a&gt; 가 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554181764418&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun CoroutineScope.produceSquares(): ReceiveChannel&amp;lt;Int&amp;gt; = produce {
    for (x in 1..5) send(x * x)
}

fun main() = runBlocking {
    val squares = produceSquares()
    squares.consumeEach { println(it) }
    println(&quot;Done!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554181786222&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1
4
9
16
25
Done!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Pipelines&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;파이프라인은 하나의 코루틴이 무한의 값 스트림을 생성하는 패턴입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554181856773&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun CoroutineScope.produceNumbers() = produce&amp;lt;Int&amp;gt; {
    var x = 1
    while (true) send(x++) // infinite stream of integers starting from 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다른 코루틴 또는 코루틴들에서는 이 스트림을 소비하고, 일부 처리를 수행합니다. 그리고 다른 결과를 생산합니다. 아래 예에서 숫자는 단지 제곱입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554182128150&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun CoroutineScope.square(numbers: ReceiveChannel&amp;lt;Int&amp;gt;): ReceiveChannel&amp;lt;Int&amp;gt; = produce {
    for (x in numbers) send(x * x)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;main code는 전체 파이프라인을 연결하고 시작합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554182162475&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    val numbers = produceNumbers() // produces integers from 1 and on
    val squares = square(numbers) // squares integers
    for (i in 1..5) println(squares.receive()) // print first five
    println(&quot;Done!&quot;) // we are done
    coroutineContext.cancelChildren() // cancel children coroutines
}

fun CoroutineScope.produceNumbers() = produce&amp;lt;Int&amp;gt; {
    var x = 1
    while (true) send(x++) // infinite stream of integers starting from 1
}

fun CoroutineScope.square(numbers: ReceiveChannel&amp;lt;Int&amp;gt;): ReceiveChannel&amp;lt;Int&amp;gt; = produce {
    for (x in numbers) send(x * x)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554182272121&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1
4
9
16
25
Done!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코루틴을 만드는 모든 함수들은 CoroutineScope의 확장으로 정의 되므로, 구조화된 동시성에 의존하여, 어플리케이션에 느린 전역 코루틴이 없는지 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Prime numbers with pipeline&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코루틴의 파이프라인을 사용하여 소수를 생성하는 예를 설명해 보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554184344099&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun CoroutineScope.numbersFrom(start: Int) = produce&amp;lt;Int&amp;gt; {
    var x = start
    while (true) send(x++) // infinite stream of integers from start
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음으로 파이프라인에 들어오는 스트림을 필터링하여 소수면 &lt;i&gt;send&lt;/i&gt; 합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554184479080&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun CoroutineScope.filter(numbers: ReceiveChannel&amp;lt;Int&amp;gt;, prime: Int) = produce&amp;lt;Int&amp;gt; {
    for (x in numbers) if (x % prime != 0) send(x)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 2에서 숫자의 흐름을 시작하고, 현재 채널로부터 소수를 가져옵니다. 그리고 각 소수에 대해 파이프라인 단계를 시작하여 새로운 파이프라인을 만듭니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554184575214&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;numbersFrom(2) -&amp;gt; filter(2) -&amp;gt; filter(3) -&amp;gt; filter(5) -&amp;gt; filter(7) ... &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음 예제는 메인스레드의 context에서 전체 파이프라인을 실행하는 10개의 소수를 출력합니다.&lt;/p&gt;
&lt;p&gt;모든 코루틴은 메인 runBlocking 코루틴의 범위에서 시작되므로, 모든 코루틴의 목록을 명시할 필요는 없습니다.&lt;/p&gt;
&lt;p&gt;10개의 소수를 출력하고 cancelChildren 확장 함수를 사용하여 모든 children 코루틴을 취소합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554184981461&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    var cur = numbersFrom(2)
    for (i in 1..10) {
        val prime = cur.receive()
        println(prime)
        cur = filter(cur, prime)
    }
    coroutineContext.cancelChildren() // cancel all children to let main finish    
}

fun CoroutineScope.numbersFrom(start: Int) = produce&amp;lt;Int&amp;gt; {
    var x = start
    while (true) send(x++) // infinite stream of integers from start
}

fun CoroutineScope.filter(numbers: ReceiveChannel&amp;lt;Int&amp;gt;, prime: Int) = produce&amp;lt;Int&amp;gt; {
    for (x in numbers) if (x % prime != 0) send(x)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554184997943&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2
3
5
7
11
13
17
19
23
29
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코루틴 빌더의 &lt;a href=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/iterator.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;i&gt;iterator&amp;nbsp;&lt;/i&gt;&lt;/a&gt;를 사용하여 동일한 파이프라인을 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;produce를 iterator로 send를 yield로 receive를 next로 ReceiveChannel을 Iterator로 대체해보세요. 그리고 Coroutine scope를 제거해보세요. 더이상 runBlocking가 필요하지 않을 것입니다. 그러나, 채널을 사용하는 파이프라인의 이점은 &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Dispatchers.Default&lt;/a&gt; context에서 여러 CPU 코어를 사용할 수 있다는 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어쩃든, 위처럼 소수를 찾는 방법은 극단적인 케이스로, 실용적이지 못합니다. 실제 파이프라인은 다른 suspending invocations ( 원격 서비스에 대한 비동기 호출 ) 을 포함하지만, 파이프라인은 비동기적인 produce와는 다르게 임의의 suspension을 허용하지 않기 때문에 sequence / iterator을 사용하여 만들 수 없습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Fan-out&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여러개의 코루틴은 동일한 채널에서 수신할 수 있으며 상호간에 작업을 분배할 수 있습니다. 주기적으로 정수를 생성하는 생산자 코루틴을 예로 들어보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554188349772&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun CoroutineScope.produceNumbers() = produce&amp;lt;Int&amp;gt; {
    var x = 1 // start from 1
    while (true) {
        send(x++) // produce next
        delay(100) // wait 0.1s
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 여러개의 프로세서 코루틴을 가질 수 있습니다. 이 예에서는 &lt;i&gt;id&lt;/i&gt;와 숫자만을 출력합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554188416114&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun CoroutineScope.launchProcessor(id: Int, channel: ReceiveChannel&amp;lt;Int&amp;gt;) = launch {
    for (msg in channel) {
        println(&quot;Processor #$id received $msg&quot;)
    }    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;5개의 프로세서를 &lt;i&gt;launch&lt;/i&gt; 하고, 약 1초동안 작동시켜보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554188505476&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val producer = produceNumbers()
repeat(5) { launchProcessor(it, producer) }
delay(950)
producer.cancel() // cancel producer coroutine and thus kill them all&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554188540667&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Processor #2 received 1
Processor #4 received 2
Processor #0 received 3
Processor #1 received 4
Processor #3 received 5
Processor #2 received 6
Processor #4 received 7
Processor #0 received 8
Processor #1 received 9
Processor #3 received 10&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과를 보면 알 수 있듯이, 비록 프로세서 아이디는 다를 수 있으나 &lt;i&gt;number&lt;/i&gt; 값은 위와 동일할 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;producer&lt;/i&gt; 코루틴을 취소하면 채널이 닫히고 결국 프로세서 코루틴이 수행하는 채널에서 종료될 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;consumeEach&lt;/i&gt; 와는 다르게 &lt;i&gt;for&lt;/i&gt; 루프 패턴은 다수의 코루틴에서 안전합니다. 만약 하나의 프로세서 코루틴이 실패하면, 다른 코루틴은 여전히 채널을 처리합니다. 반면에, &lt;i&gt;consumeEach&lt;/i&gt; 로 작성된 프로세서는 정상 또는 비정상 완료시 항상 채널을 &lt;i&gt;consumes&amp;nbsp;&lt;/i&gt;( 취소 ) 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Fan-in&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여러개의 코루틴이 동일한 채널로 전송할 수 있습니다. 예를 들어, 문자열 채널과, 특정 &lt;i&gt;delay&lt;/i&gt; 를 지정하여 문자열을 채널에 반복적으로 보내는 함수를 보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554189316431&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    val channel = Channel&amp;lt;String&amp;gt;()
    launch { sendString(channel, &quot;foo&quot;, 200L) }
    launch { sendString(channel, &quot;BAR!&quot;, 500L) }
    repeat(6) { // receive first six
        println(channel.receive())
    }
    coroutineContext.cancelChildren() // cancel all children to let main finish
}

suspend fun sendString(channel: SendChannel&amp;lt;String&amp;gt;, s: String, time: Long) {
    while (true) {
        delay(time)
        channel.send(s)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554189329954&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;foo
foo
BAR!
foo
foo
BAR!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Buffered channels&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지금까지 보았던 채널에는 버퍼가 없었습니다. 버퍼링되지 않은 채널은 발신자와 수신자가 서로 만날 때 값을 전송합니다.&lt;/p&gt;
&lt;p&gt;send 가 먼저 호출되면 receive 가 호출 될 때까지 일시 중단되고, receive가 먼저 호출되면 send 가 호출 될 때까지 일시 중단됩니다.&lt;/p&gt;
&lt;p&gt;Channel() factory function 그리고 produce 빌더는 선택적으로 버퍼의 크기를 구체화하는 용량을 파라미터로 갖습니다.&lt;/p&gt;
&lt;p&gt;버퍼는 발신자에게 일시 중단되기 전까지( BlockingQueue와 유사하게 버퍼가 꽉차기 전까지 ) 다수의 요소를 보내는 것을 허락합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554189685850&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    val channel = Channel&amp;lt;Int&amp;gt;(4) // create buffered channel
    val sender = launch { // launch sender coroutine
        repeat(10) {
            println(&quot;Sending $it&quot;) // print before sending each element
            channel.send(it) // will suspend when buffer is full
        }
    }
    // don't receive anything... just wait....
    delay(1000)
    sender.cancel() // cancel sender coroutine    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554189701737&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Sending 0
Sending 1
Sending 2
Sending 3
Sending 4&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;처음 요소 4개까지는 버퍼에 추가되고 다섯번째 요소를 보내려고 할 때 일시 중단됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Channels are fair&lt;/h3&gt;
&lt;p&gt;여러 코루틴에서 호출 한 순서와 관련하여 채널에 작업을 보내고 받는 것은 공정합니다. 그 순서는 First-In First-Out 입니다.&lt;/p&gt;
&lt;p&gt;즉 호출할 첫번째 코루틴 receive 요소를 가져옵니다. 다음 예제에서는 두개의 코루틴 &quot;ping&quot; &quot;pong&quot; 는 공유 채널인 table 에서 ball 객체를 수신합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554189984838&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Ball(var hits: Int)

fun main() = runBlocking {
    val table = Channel&amp;lt;Ball&amp;gt;() // a shared table
    launch { player(&quot;ping&quot;, table) }
    launch { player(&quot;pong&quot;, table) }
    table.send(Ball(0)) // serve the ball
    delay(1000) // delay 1 second
    coroutineContext.cancelChildren() // game over, cancel them
}

suspend fun player(name: String, table: Channel&amp;lt;Ball&amp;gt;) {
    for (ball in table) { // receive the ball in a loop
        ball.hits++
        println(&quot;$name $ball&quot;)
        delay(300) // wait a bit
        table.send(ball) // send the ball back
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554189998689&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ping Ball(hits=1)
pong Ball(hits=2)
ping Ball(hits=3)
pong Ball(hits=4)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;때떄로 채널은 실행 프로그램의 특성으로 불공정한 실행을 처리할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Ticker channels&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Ticker channel&lt;/i&gt;은 채널에서 마지막으로 소비된 이후 지연이 될 때마다 생성되는 특별한 &lt;i&gt;rendezvous&lt;/i&gt; 채널 입니다.&lt;/p&gt;
&lt;p&gt;쓸데없는 독립형으로 보일 수 있지만, 복잡한 시간 기반 &lt;i&gt;produce&lt;/i&gt; 파이프라인 그리고 &lt;i&gt;windowing&lt;/i&gt; , 시간 종속 처리를 수행하는 연산을 만드는데 유용합니다. &lt;i&gt;Ticker channel&amp;nbsp;&lt;/i&gt;은 &lt;i&gt;select&lt;/i&gt; 에서 사용될 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이러한 채널을 만들기 위해서는 f&lt;i&gt;actory method &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/ticker.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tciker&lt;/a&gt;&lt;/i&gt; 을 사용해야합니다. 더이상 필요 요소가 없다는 것을 나타내려면 &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/cancel.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;i&gt;ReceiveChannel.cancel&lt;/i&gt;&lt;/a&gt; 메소드를 사용하면 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554191359499&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt; {
    val tickerChannel = ticker(delayMillis = 100, initialDelayMillis = 0) // create ticker channel
    var nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
    println(&quot;Initial element is available immediately: $nextElement&quot;) // initial delay hasn't passed yet

    nextElement = withTimeoutOrNull(50) { tickerChannel.receive() } // all subsequent elements has 100ms delay
    println(&quot;Next element is not ready in 50 ms: $nextElement&quot;)

    nextElement = withTimeoutOrNull(60) { tickerChannel.receive() }
    println(&quot;Next element is ready in 100 ms: $nextElement&quot;)

    // Emulate large consumption delays
    println(&quot;Consumer pauses for 150ms&quot;)
    delay(150)
    // Next element is available immediately
    nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
    println(&quot;Next element is available immediately after large consumer delay: $nextElement&quot;)
    // Note that the pause between `receive` calls is taken into account and next element arrives faster
    nextElement = withTimeoutOrNull(60) { tickerChannel.receive() } 
    println(&quot;Next element is ready in 50ms after consumer pause in 150ms: $nextElement&quot;)

    tickerChannel.cancel() // indicate that no more elements are needed
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554191375733&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Initial element is available immediately: kotlin.Unit
Next element is not ready in 50 ms: null
Next element is ready in 100 ms: kotlin.Unit
Consumer pauses for 150ms
Next element is available immediately after large consumer delay: kotlin.Unit
Next element is ready in 50ms after consumer pause in 150ms: kotlin.Unit&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android/Coroutine</category>
      <category>android</category>
      <category>coroutines</category>
      <category>Kotlin</category>
      <category>안드로이드</category>
      <category>코루틴</category>
      <category>코틀린</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/75</guid>
      <comments>https://eso0609.tistory.com/75#entry75comment</comments>
      <pubDate>Tue, 2 Apr 2019 16:52:41 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 Kotlin Coroutines 정리 Part 2</title>
      <link>https://eso0609.tistory.com/74</link>
      <description>&lt;h3&gt;Cancelling coroutine execution&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;장기적으로 실행되는 어플리케이션에서 백그라운드 코루틴 제어권을 얻는 것이 필요합니다.&lt;/p&gt;
&lt;p&gt;예를 들어서 유저가 코루틴을 시작한 페이지를 닫은 경우, 결과가 더이상 필요하지 않은 경우, 해당 작업을 취소할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;launch&lt;/i&gt; 함수는 코루틴 실행을 취소할 수 있는 &lt;i&gt;job&amp;nbsp;&lt;/i&gt;을 반환합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554169963902&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val job = launch {
    repeat(1000) { i -&amp;gt;
            println(&quot;I'm sleeping $i ...&quot;)
        delay(500L)
    }
}
delay(1300L) // delay a bit
println(&quot;main: I'm tired of waiting!&quot;)
job.cancel() // cancels the job
job.join() // waits for job's completion 
println(&quot;main: Now I can quit.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554170101875&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;메인 호출 &lt;i&gt;job&lt;/i&gt;.&lt;i&gt;cancle&lt;/i&gt; 이 발생하면, 코루틴이 취소되었기 때문에 어떠한 출력도 볼 수 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;i&gt;job&lt;/i&gt; &lt;i&gt;extension&lt;/i&gt; 함수로&amp;nbsp; &lt;i&gt;cancel&lt;/i&gt;과 &lt;i&gt;join&lt;/i&gt;을 결합한&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;i&gt;cancelAndJoin&lt;/i&gt; 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;box&quot;&gt;
&lt;h1 id=&quot;cancelandjoin&quot;&gt;cancelAndJoin&lt;/h1&gt;
&lt;p&gt;&lt;span&gt;suspend&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;fun&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html&quot;&gt;&lt;span&gt;Job&lt;/span&gt;&lt;/a&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cancelAndJoin&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/index.html&quot;&gt;&lt;span&gt;Unit&lt;/span&gt;&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-core/common/src/Job.kt#L474&quot;&gt;(source)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Cancels the job and suspends invoking coroutine until the cancelled job is complete.&lt;/p&gt;
&lt;p&gt;This suspending function is cancellable and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;always&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;checks for the cancellation of invoking coroutine&amp;rsquo;s Job. If the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html&quot;&gt;Job&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;of the invoking coroutine is cancelled or completed when this suspending function is invoked or while it is suspended, this function throws&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html&quot;&gt;CancellationException&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In particular, it means that a parent coroutine invoking&lt;span&gt;&amp;nbsp;&lt;/span&gt;cancelAndJoin&lt;span&gt;&amp;nbsp;&lt;/span&gt;on a child coroutine that was started using&lt;span&gt;&amp;nbsp;&lt;/span&gt;launch(coroutineContext) { ... }&lt;span&gt;&amp;nbsp;&lt;/span&gt;builder throws&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html&quot;&gt;CancellationException&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;if the child had crashed, unless a non-standard&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html&quot;&gt;CoroutineExceptionHandler&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;is installed in the context.&lt;/p&gt;
&lt;p&gt;This is a shortcut for the invocation of&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html&quot;&gt;cancel&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;followed by&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html&quot;&gt;join&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Cancellation is cooperative&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코루틴 취소는 협조적입니다. 코루틴 코드는 취소가능하도록 협조되어야 합니다. kotlinx.coroutines의 모든 suspending function은 취소가능합니다. 코루틴의 취소를 확인하고, 취소되었을 때 &lt;i&gt;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CancelliationException&lt;/a&gt;&amp;nbsp;&lt;/i&gt;을 발생시킵니다.&lt;/p&gt;
&lt;p&gt;하지만, 코루틴이 복잡한 환경에서 작동하고, 취소를 확인하지 않으면 다음 예제처럼 취소할 수 없습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554170746824&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
    var nextPrintTime = startTime
    var i = 0
    while (i &amp;lt; 5) { // computation loop, just wastes CPU
        // print a message twice a second
        if (System.currentTimeMillis() &amp;gt;= nextPrintTime) {
            println(&quot;I'm sleeping ${i++} ...&quot;)
            nextPrintTime += 500L
        }
    }
}
delay(1300L) // delay a bit
println(&quot;main: I'm tired of waiting!&quot;)
job.cancelAndJoin() // cancels the job and waits for its completion
println(&quot;main: Now I can quit.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554172325909&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm sleeping 3 ...
I'm sleeping 4 ...
main: Now I can quit.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3&gt;Making computation code cancellable&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;계산 코드를 취소할 수 있게 만드는 방법은 두가지가 있습니다. 첫번째 방법은, 주기적으로 취소를 체크하는 &lt;i&gt;suspending function&lt;/i&gt; 를 발생시키는 것입니다.&amp;nbsp;&lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html&quot;&gt;&lt;i&gt;yield()&lt;/i&gt;&amp;nbsp;&lt;/a&gt;&amp;nbsp;함수는 목적을 위한 좋은 선택입니다.&amp;nbsp; 다른 한가지 방법은 명시적으로 취소 상태를 체크하는 것입니다.&lt;/p&gt;
&lt;p&gt;아래 코드는 후자의 접근 방식으로 접근한 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554172126442&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (isActive) { // cancellable computation loop
            // print a message twice a second
            if (System.currentTimeMillis() &amp;gt;= nextPrintTime) {
                println(&quot;I'm sleeping ${i++} ...&quot;)
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println(&quot;main: I'm tired of waiting!&quot;)
    job.cancelAndJoin() // cancels the job and waits for its completion
    println(&quot;main: Now I can quit.&quot;)    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드는 이 전 코드에서 while ( i &amp;lt; 5 ) 를 while ( &lt;a href=&quot;https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/is-active.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;i&gt;isActive&lt;/i&gt;&lt;/a&gt; ) 로 변경한 것입니다.&lt;/p&gt;
&lt;p&gt;안드로이드에서 isActive 설명은 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554172208378&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;* This property is a shortcut for `coroutineContext.isActive` in the scope when
 * [CoroutineScope] is available.
 * See [coroutineContext][kotlin.coroutines.coroutineContext],
 * [isActive][kotlinx.coroutines.isActive] and [Job.isActive].
 */
@Suppress(&quot;EXTENSION_SHADOWED_BY_MEMBER&quot;)
public val CoroutineScope.isActive: Boolean
    get() = coroutineContext[Job]?.isActive ?: true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554172281627&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;while ( i &amp;lt; 5 ) 를 사용했을 때와는 다르게 1300L 만큼 delay 된 후에 main이 실행된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Closing resources with finally&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;취소 가능한 suspending functions 는 일반적인 방법으로 처리할 수 있는 CancellationException을 throw 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들어서 try { ... } finally { .. } 표현과 Kotlin use function 은 코루틴이 취소되었을 때 종료 작업을 실행합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554172706743&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i -&amp;gt;
                    println(&quot;I'm sleeping $i ...&quot;)
                delay(500L)
            }
        } finally {
            println(&quot;I'm running finally&quot;)
        }
    }
    delay(1300L) // delay a bit
    println(&quot;main: I'm tired of waiting!&quot;)
    job.cancelAndJoin() // cancels the job and waits for its completion
    println(&quot;main: Now I can quit.&quot;)    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554172827436&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm running finally
main: Now I can quit.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Run non-cancellable block&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;finally 블록에서 suspending function을 호출하려고 시도하면 실행되고 있는 코루틴이 취소되므로 CancellationException 이 발생합니다.&lt;/p&gt;
&lt;p&gt;일반적으로 모든 문제를 해결하는 closing 연산들 ( 파일 닫기, job 취소, 또는 다른 종류의 통신 채널 닫기 등 ) 은 일반적으로 non-blocking 이고 suspending functions를 포함하지 않기 때문에 문제가 되지 않습니다. 그러나, 취소된 코루틴에서 delay 또는 중단해야 하는 경우 충돌하는 코드를 withContext 함수와 NonCancellable context를 사용한 withContext(NonCancellable) { ... } 를 사용하여 해당 코드를 감쌀 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554178349764&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i -&amp;gt;
                    println(&quot;I'm sleeping $i ...&quot;)
                delay(500L)
            }
        } finally {
            withContext(NonCancellable) {
                println(&quot;I'm running finally&quot;)
                delay(1000L)
                println(&quot;And I've just delayed for 1 sec because I'm non-cancellable&quot;)
            }
        }
    }
    delay(1300L) // delay a bit
    println(&quot;main: I'm tired of waiting!&quot;)
    job.cancelAndJoin() // cancels the job and waits for its completion
    println(&quot;main: Now I can quit.&quot;)    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554178384797&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
main: I'm tired of waiting!
I'm running finally
And I've just delayed for 1 sec because I'm non-cancellable
main: Now I can quit.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Timeout&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행중인 코루틴을 취소해야하는 가장 명백한 이유는 실행시간이 초과한 경우입니다. 해당 코루틴에 대한 참조를 수동적으로 추적하고, delay 또는 일시 정지된 후에 참조를 통해 추적하여 취소할 수 있지만 whilteTimeout 기능을 사용할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554178589305&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    withTimeout(1300L) {
        repeat(1000) { i -&amp;gt;
                println(&quot;I'm sleeping $i ...&quot;)
            delay(500L)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554178627016&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Exception in thread &quot;main&quot; kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
 at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException (Timeout.kt:122) 
 at kotlinx.coroutines.TimeoutCoroutine.run (Timeout.kt:88) 
 at kotlinx.coroutines.EventLoopBase$DelayedRunnableTask.run (EventLoop.kt:316) 
 at kotlinx.coroutines.EventLoopBase.processNextEvent (EventLoop.kt:123) 
 at kotlinx.coroutines.DefaultExecutor.run (DefaultExecutor.kt:61) 
 at java.lang.Thread.run (Thread.java:745) &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;withTimeout에 의해 발생되는 TimeoutCancellationException은 CancellationException의 subclass 입니다.&lt;/p&gt;
&lt;p&gt;이전 콘솔에서는 보지 못했는데, 그 이유는 취소된 코루틴 내부 CancellationException은 코루틴 완성에 정상적인 이유로 고려되었기 때문입니다. 그러나 예제에서는 withTimeout를 main 내부에서 사용했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;취소는 단지 예외이기 때문에, 모든 리소스들은 정상적으로 닫힙니다. 만약 구체적으로 모든 종류의 타임아웃 또는 withTimeout 과 유사한 withTimeoutOrNull을 사용하여,&lt;/p&gt;
&lt;p&gt;null 또는 timeout return해야 하는 추가적인 action이 필요한 경우에 try { ... } catch (e: TimeoutCancellationException&amp;nbsp; { ... } 을 사용하여 코드를 감쌀 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554179151130&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    val result = withTimeoutOrNull(1300L) {
        repeat(1000) { i -&amp;gt;
                println(&quot;I'm sleeping $i ...&quot;)
            delay(500L)
        }
        &quot;Done&quot; // will get cancelled before it produces this result
    }
    println(&quot;Result is $result&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554179165587&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Result is null&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android/Coroutine</category>
      <category>android</category>
      <category>Coroutine</category>
      <category>coroutines</category>
      <category>Kotlin</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/74</guid>
      <comments>https://eso0609.tistory.com/74#entry74comment</comments>
      <pubDate>Tue, 2 Apr 2019 13:27:21 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 Kotlin Coroutines 정리 Part 1</title>
      <link>https://eso0609.tistory.com/73</link>
      <description>&lt;h3&gt;Coroutin GlobalScope&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554097637087&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; GlobalScope.launch { // launch new coroutine in background and continue
            delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
            Log.e(&quot;coroutine&quot;,&quot;World!&quot;) // print after delay
        }
        Log.e(&quot;coroutine&quot;,&quot;Hello,&quot;)
        Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1554097753684&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hello,
World!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본질적으로 코루틴은 light-weight threads 입니다.&amp;nbsp; 코루틴은 CoroutineScope의 context에서 시작된 Coroutine builder로 시작됩니다.&lt;/p&gt;
&lt;p&gt;여기서 우리는 GlobalScope에서 새로운 코루틴을 시작합니다. 즉, 새로운 코루틴의 생명주기는 Application의 생명주기로 제한됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554098329822&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GlobalScope.launch { ... } with thread { ... } and delay(...) with Thread.sleep(...). Try it.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;GlobalScope.launch 를 thread로 바꾸어 보면 아래와 같은 에러 문구를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554179612673&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Suspend function 'delay' should be called only from a coroutine or another suspend function&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;delay()는 스레드를 차단하지 않으며, 코루틴에서만 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;hr&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3&gt;runBlocking { ... }&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 코드는 runBlocking 코루틴 빌더를 사용하여 blocking에 대해 명시적으로 알아보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1554100369342&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() { 
    GlobalScope.launch { // launch new coroutine in background and continue
        delay(1000L)
        println(&quot;World!&quot;)
    }
    println(&quot;Hello,&quot;) // main thread continues here immediately
    runBlocking {     // but this expression blocks the main thread
        delay(2000L)  // ... while we delay for 2 seconds to keep JVM alive
    } 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;처음 위 코드를 접했을 때 무슨말인지 몰랐습니다. 그래서 로그를 추가하여 순서를 파악해 보았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554100767961&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GlobalScope.launch { // launch new coroutine in background and continue
            delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
            Log.e(&quot;sequence&quot;,&quot;4&quot;) // print after delay
        }
        
        Log.e(&quot;sequence&quot;,&quot;1&quot;)

        runBlocking {     // but this expression blocks the main thread
            delay(4000L)  // ... while we delay for 2 seconds to keep JVM alive
            Log.e(&quot;sequence&quot;,&quot;3&quot;)

        }
        
        Log.e(&quot;sequence&quot;,&quot;2&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어떻게 출력될까요?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출력 순서는 1 4 3 2 입니다. 즉, GlobalScople.~에서 delay는 메인스레드를 막지 않고, runBlocking은 메인스레드를 막고 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554102207136&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

fun main() = runBlocking&amp;lt;Unit&amp;gt; { // start main coroutine
    GlobalScope.launch { // launch new coroutine in background and continue
        delay(1000L)
        println(&quot;World!&quot;)
    }
    println(&quot;Hello,&quot;) // main coroutine continues here immediately
    delay(2000L)      // delaying for 2 seconds to keep JVM alive
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드를 통해서 runBlocking을 사용하여 main 함수를 더&amp;nbsp;관용적인 방법으로 다시 작성할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554102364560&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; runBlocking&amp;lt;Unit&amp;gt; { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드는 최상위 메인 코루틴을 시작하기 위해 사용되는 어댑터로서의 역할을 하게됩니다.&lt;/p&gt;
&lt;p&gt;Kotlin의 Unit 즉, 올바른 형식의 main 함수가 반환되어야하기 떄문에 반환 형식을 명시적으로 Unit으로 지정합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;join()&lt;/h3&gt;
&lt;p&gt;다른 코루틴이 작동하는 동안 delay하는 것은 좋지 않은 접근 방법입니다.&lt;/p&gt;
&lt;p&gt;백그라운드 작업이 완료될때까지 join()을 사용하여 명시적으로 기다려야합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554103382112&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    val job = GlobalScope.launch { // launch new coroutine and keep a reference to its Job
        delay(1000L)
        println(&quot;World!&quot;)
    }
    println(&quot;Hello,&quot;)
    job.join() // wait until child coroutine completes    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;hr&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3&gt;구조화된 동시성&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코루틴의 실용적인 사용을 위해 여전히 요구되는 사항이 있습니다. 우리가 GlobalScope.launch 사용할 때 최상위 레벨의 코루틴을 생성합니다. 물론 light-weigh 이지만 실행되는 동안 일부 메모리 자원을 여전히 소모합니다. 새로 시작된 코루틴에 대한 참조를 유지하는 것을 잊어버리면 여전히 실행됩니다. 코루틴의 코드가 멈추는 경우 ( 예를 들어 너무 오랜 시간동안 delay되는 경우 ), 너무 많은 코루틴을 실행하여 메모리가 부족하면 어떻게 될까요?&lt;/p&gt;
&lt;p&gt;즉, 실행된 모든 코루틴에 대한 참조를 수동으로 유지하고 이들을 join() 해야 하는 것은 오류가 발생하기 쉽습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해결방법은 GlobalScope에서 coroutines를 시작하는 대신 일반적으로 Thread( thread는 항상 전역 )와 마찬가지로, 수행중인 작업의 특정 범위에서 동시 루틴을 시작할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;runBlocking를 포함한 모든 코루틴 빌더는 CoroutineScope 인스턴스를 추가합니다. 외부 코루틴에서는 범위 내에서 시작된 모든 코루틴이 완료 될 때까지 코루틴을 완료하지 않기 때문에 명시적으로 처리하지 않고도 ( join() ) 이 범위에서 코루틴을 시작할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 코드는 위의 코드를 join() 사용하지 않고 더 간단하게 작성한 코드입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554104175207&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking { // this: CoroutineScope
    launch { // launch new coroutine in the scope of runBlocking
        delay(1000L)
        println(&quot;World!&quot;)
    }
    println(&quot;Hello,&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Scope builder&lt;/h3&gt;
&lt;p&gt;다른 빌더가 제공하는 코루틴 Scope 외에도 coroutineScope 빌더를 사용하여 자신의 범위를 선언할 수 있습니다.&lt;/p&gt;
&lt;p&gt;새로운 코루틴 범위를 작성하고 시작한 모든 자식이 완료될 때까지 완료되지 않습니다.&lt;/p&gt;
&lt;p&gt;runBlocking 와 coroutineScope의 주요 차이점은 runBlocking는 모든 자식이 완료될 때까지 기다리는 동안 현재 스레드를 차단하지 않고,&lt;/p&gt;
&lt;p&gt;coroutineScope는 모든 자식이 완료될 때 까지 기다리지 않고 현재 스레드를 차단합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1554106006154&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking { // this: CoroutineScope
    launch { 
        delay(200L)
        println(&quot;Task from runBlocking&quot;)
    }
    
    coroutineScope { // Creates a new coroutine scope
        launch {
            delay(500L) 
            println(&quot;Task from nested launch&quot;)
        }
    
        delay(100L)
        println(&quot;Task from coroutine scope&quot;) // This line will be printed before nested launch
    }
    
    println(&quot;Coroutine scope is over&quot;) // This line is not printed until nested launch completes
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;출력 결과&lt;/p&gt;
&lt;pre id=&quot;code_1554106019991&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;hr&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Extract function refactoring&lt;/h3&gt;
&lt;p&gt;launch { ... } 코드 블록을 별도의 함수로 추출해 봅시다. 이 코드에서 &quot;Extract function&quot; 리펙토링을 실행하면 suspend 함수로 작성할 수 있습니다. 이것이 첫번쨰 suspend 기능입니다. suspending functions는 코루틴에서 사용할 수 있지만 추가적으로 동시에 다른 suspend 함수 ( delay 등 )을 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1554106749690&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking {
    launch { doWorld() }
    println(&quot;Hello,&quot;)
}

// this is your first suspending function
suspend fun doWorld() {
    delay(1000L)
    println(&quot;World!&quot;)
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Android/Coroutine</category>
      <category>android</category>
      <category>coroutines</category>
      <category>Kotlin</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/73</guid>
      <comments>https://eso0609.tistory.com/73#entry73comment</comments>
      <pubDate>Tue, 2 Apr 2019 10:30:20 +0900</pubDate>
    </item>
    <item>
      <title>Glide 라이브러리 사용시 유의사항</title>
      <link>https://eso0609.tistory.com/72</link>
      <description>&lt;p&gt;Fabric에서 오류를 확인하던 도중에&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Glide&amp;nbsp; 관련 You cannot start a load for a destroyed activity 에러를 확인했다.&lt;/p&gt;&lt;p&gt;에러 로그는 다음과 같다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Fatal Exception: java.lang.IllegalArgumentException: &lt;b&gt;You cannot start a load for a &lt;/b&gt;destroyed activity&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at com.bumptech.glide.manager.RequestManagerRetriever.b(SourceFile:302)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at com.bumptech.glide.manager.RequestManagerRetriever.a(SourceFile:127)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at com.bumptech.glide.Glide.a(SourceFile:632)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at com.likealocal.wenwo.dev.wenwo_android.utils.glide.GlideApp.a(SourceFile:84)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at com.likealocal.wenwo.dev.wenwo_android.ui.begin.SplashActivity.onAdvertisementSuccess(SourceFile:383)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at com.likealocal.wenwo.dev.wenwo_android.http.protocol.AdvertisementRequest$send$1.accept(SourceFile:43)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at com.likealocal.wenwo.dev.wenwo_android.http.protocol.AdvertisementRequest$send$1.accept(SourceFile:23)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at io.reactivex.internal.observers.ConsumerSingleObserver.a(SourceFile:61)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at io.reactivex.internal.operators.single.SingleObserveOn$ObserveOnSingleObserver.run(SourceFile:81)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(SourceFile:109)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at android.os.Handler.handleCallback(Handler.java:790)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at android.os.Handler.dispatchMessage(Handler.java:99)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at android.os.Looper.loop(Looper.java:164)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at android.app.ActivityThread.main(ActivityThread.java:7002)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at java.lang.reflect.Method.invoke(Method.java)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:441)&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1408)&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;해당 에러 내용은 액티비티가 destroy되고 load를 시작할 수 없다는 내용인데,&lt;/p&gt;&lt;p&gt;해결 방법은 무엇일까?&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;a href=&quot;https://github.com/bumptech/glide/issues/803&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;https://github.com/bumptech/glide/issues/803&lt;/a&gt; Glide 깃헙에도 해당 에러를 가지고 얘기가 많았다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;하지만 위 링크에서는 내가 원하는 정확한 해답을 찾을 수 없었다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;구글링을 통해 문제 상황을 검색하던 도중 내가 원하던 답을 찾을 수 있었다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/31964737/glide-image-loading-with-application-context/32887693#32887693&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;https://stackoverflow.com/questions/31964737/glide-image-loading-with-application-context/32887693#32887693&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;&lt;p&gt;문제 상황 인식&lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;해당 액티비티가 종료되고 나서 Glide load를 진행할 수 없다.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;&lt;p&gt;해결 방법&lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;1. Fragment의 경우를 생각해보면 이해하기 쉽다.&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;프레그먼트의 onCreateView 메소드에서 Glide.with~를 사용하는 경우,&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;사용자가 Back key를 눌렀을 경우 어떻게 될까?&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;해당 이미지가 5MB인 경우 with(getActivity.getApplicatonContext())를 사용하는경우&amp;nbsp;5MB의 데이터가 모두 다운로드되어 디코딩되고 캐싱되고, 해당 ImageView에 세팅된다. 그리고 이것은 Glide의 garbage collect된다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;2. with((Fragment)this) 를 사용하면 Glide는 Fragment의 생명주기를 구독하고, Fragment가 stop되자마자 해결되지 않은 모든 request를 멈추어야 한다. 그리고 destroy될 때 보류중인 요청은 모두 삭제된다. 즉, 이미지 다운로드가 중단될 것이고, 종료된 프레그먼트에서 더이상 리소스가 사용되지 않을 것이다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;3.with(getActivity())를 사용하면 Glide는 Activity의 생명주기를 구독하고, 위(Fragment)와 같은 일이 일어날 것이다. 하지만 Activity의 활동이 중지되거나 소멸된 경우에만 발생할 것이다.&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;4. 가장 좋은 방법은 사용 가능한 request들의 완료를 피하기 위해 가장 가까운 context/fragment를 사용하는 것이다. 또한, load를 수동으로 중지하는 방법도 있다. Glide.clear(ImageView|Target)&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;마지막으로 중요한 점은,&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;with(this)가 가능한 경우 이 방법을 사용하고, Adapter의 경우 RequestManger를 인자로 받는 것을 추천.&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Android/[Android] Error solution</category>
      <category>Glide</category>
      <category>glide참고사항</category>
      <category>You cannot start a load for a destroyed activity</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/72</guid>
      <comments>https://eso0609.tistory.com/72#entry72comment</comments>
      <pubDate>Mon, 28 Jan 2019 12:44:41 +0900</pubDate>
    </item>
    <item>
      <title>이미지 가로세로 비율 유지하기 adjustBounds</title>
      <link>https://eso0609.tistory.com/71</link>
      <description>&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;&lt;h1&gt;이미지 가로세로 비율 유지&lt;/h1&gt;&lt;/blockquote&gt;&lt;h2&gt;문제 상황&lt;/h2&gt;&lt;div&gt;&lt;b&gt;Glide를 사용하여 ImageView에 해당 url을 넣어주었는데, 이미지뷰 크기가 url에 해당하는 실제 이미지크기보다 크게 설정되는 상황.&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;프로젝트를 작업중에 이미지 가로세로 비율을 유지해야하는 경우가 많은데요.&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;저의 경우에는 최근에&amp;nbsp;'전면광고' 화면에서 해당 이미지의 가로세로 비율을 유지해야하는 것이 중요한 포인트였습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none; line-height: 2;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 590px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9977FA365C45572814&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9977FA365C45572814&quot; width=&quot;590&quot; height=&quot;209&quot; filename=&quot;스크린샷 2019-01-21 오후 2.22.28.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;해당 이미지가 들어가게 될 ImageView xml 코드입니다.&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;ImageView의 background를 준 이유는 여백을 확인해보기 위함이었습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;서버 통신으로 받아온 url을 glide를 사용하여 위 이미지뷰에 넣어주었는데요,&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none; line-height: 2;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 728px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99CC7C385C4557C52D&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99CC7C385C4557C52D&quot; width=&quot;728&quot; height=&quot;1294&quot; filename=&quot;KakaoTalk_Image_2019-01-21-14-24-19.jpg&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;( 사전 동의를 구하고 업로드하는 저희 프로젝트 개발서버 테스트 화면입니다. )&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;이미지뷰의 background로 주었던 색이 보이게 되는데요,&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;이로써 이미지뷰의 높이가 실제 이미지보다 더 크게 잡혀있는 것을 확인해보실 수 있습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;그리고 '건너뛰기' 의 xml 코드상으로 constraintLayout을 사용하여 이미지뷰의 하단 오른쪽에 위치하게 해주었는데, 이 상황에서는 실제 이미지보다 더 크게잡혀있어서 해당 '건너뛰기' 버튼의 위치도 이상했습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;처음에 해결방법으로 생각했던 방법은, Glide의 onResourceReady 함수에서 해당 이미지의 가로와 세로길이를 구하고, ratio를 구해서 imageView의 크기를 설정해주는 것이었습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;하지만 초기에 이미 resize 작업을 거친 뒤에 glide를 통해 이미지를 이미지뷰에 넣어주는 작업을 했기 때문에 효율적이지 못하다고 생각했습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;그래서 다시 ImageView 속성으로 초점을 돌렸습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;역시 ImageView에 위와 같은 문제상황을 단번에 해결할만한 기능이 있었습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none; line-height: 2;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 582px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99F019365C455C6F27&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99F019365C455C6F27&quot; width=&quot;582&quot; height=&quot;230&quot; filename=&quot;스크린샷 2019-01-21 오후 2.07.53.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;해결방법&lt;/h2&gt;&lt;h2&gt;&lt;u&gt;&lt;span style=&quot;font-size: 19px;&quot;&gt;바로 ImageView의&amp;nbsp;adjustBounds 속성&lt;/span&gt;&lt;span style=&quot;font-size: 19px;&quot;&gt; 입니다.&lt;/span&gt;&lt;/u&gt;&lt;/h2&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/android/widget/ImageView&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;https://developer.android.com/reference/android/widget/ImageView&lt;/a&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;위 링크에서 설명하고 있듯이, adjustBounds는 이미지의 가로세로 ratio를 유지하기 위한 옵션입니다. adjustBounds에 true 값을 주니 이미지의 가로세로 비율이 유지되면서,&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;이미지뷰가 실제 이미지의 크기만큼만 높이를 갖게 되었습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none; line-height: 2;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 728px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9953C7365C455C5B22&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9953C7365C455C5B22&quot; width=&quot;728&quot; height=&quot;1294&quot; filename=&quot;KakaoTalk_Image_2019-01-21-14-44-38.jpg&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;정말 간단하게 해결할 수 있는 문제였지만, 다시한번 ImageView 속성값들에 대해 공부할 수 있었던 계기가 되었습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;아래 링크는 구글 플레이스토어에 있는 제가 개발하고 있는 android wenwo app 다운로드링크입니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.likealocal.wenwo.dev.wenwo_android&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;https://play.google.com/store/apps/details?id=com.likealocal.wenwo.dev.wenwo_android&lt;/a&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; line-height: 2;&quot;&gt;피드백주시면서, 서로 소통할 수 있었으면 좋겠습니다.&lt;/p&gt;</description>
      <category>Android</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/71</guid>
      <comments>https://eso0609.tistory.com/71#entry71comment</comments>
      <pubDate>Mon, 21 Jan 2019 15:21:15 +0900</pubDate>
    </item>
    <item>
      <title>realm 레코드 순서</title>
      <link>https://eso0609.tistory.com/70</link>
      <description>&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;&lt;p&gt;Realm 객체 삭제 후 삽입시 레코드 순서 이상 현상&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;문제상황&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;프로젝트를 진행하면서 최근 검색어 기능을 작업을 진행하면서,&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;생성했던 Realm 객체를 최근 검색 순서대로 정렬해서 보여줘야 하는 경우가 생겼다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;요구사항으로는 최근 검색어는 5개까지만 노출해서 보여주도록 만들어야 했다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;처음 접근했던 방식은, Realm 데이터를 단순하게 생성하고, RecyclerView에서 reverseLayout을 true 값을 주었다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;그렇게해서 가장 마지막에 생성되었던 Realm 객체가 최상단에 보이는 RecyclerView를 만들었다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;하지만,&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;특정한 사이즈만큼만 RecyclerView에서 보여주는 과정에서 문제가 발생했다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;해당 검색어를 삭제하고, 다른 검색어를 입력하여 Realm 객체를 생성하고 어댑터를 갱신했는데,&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;최근 검색어가 5개가 있을 때 0번째 index의 아이템을 삭제해주고 Realm 객체를 추가해주면 당연하게 마지막인덱스에 데이터가 쌓일 줄 알았다. 아니었다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;Realm 레코드들은 기본적으로 정렬되지 않았고, 명시적으로 정렬하지 않으면 정렬되지 않은 세트로 간주해야 했다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;일반적으로 레코드들은 삽입 한 순서대로 나오지만, 그걸 보장할 수는 없다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;기술적인 이유는 디스크의 데이터를 압축하는 것이므로 목록 중간에있는 항목을 삭제하면 마지막 항목이 해당 위치로 이동하는 경우가 발생한다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;해결방안&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;결론은, realm 객체들을&amp;nbsp;명시적으로 sort를 해주어야 한다.&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8; text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 728px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9958B73D5C4033C110&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9958B73D5C4033C110&quot; width=&quot;728&quot; height=&quot;125&quot; filename=&quot;스크린샷 2019-01-17 오후 4.49.40.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;최근 검색어를 realm 추가하는 소스이다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;기존에는 primary key 없이 realm 객체를 생성했는데, 명시적으로 정렬을 해주기 위해서 primary key를 사용했다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;primary key값을 검색이 이루어진 현재 시간값으로 설정하였다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8; text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 728px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/990AA3345C40345B2A&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F990AA3345C40345B2A&quot; width=&quot;728&quot; height=&quot;81&quot; filename=&quot;스크린샷 2019-01-17 오후 4.52.47.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;그리고 realm 데이터를 불러올 때, primary key 필드명 time을 사용하여 Descending 정렬을 해주었다. 이 과정에서 RecyclerView의 reverseLayout값을 false 값을 주었다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8; text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Android/Realm</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/70</guid>
      <comments>https://eso0609.tistory.com/70#entry70comment</comments>
      <pubDate>Thu, 17 Jan 2019 17:31:44 +0900</pubDate>
    </item>
    <item>
      <title>Can not perform this action after onSaveInstanceState</title>
      <link>https://eso0609.tistory.com/69</link>
      <description>&lt;p style=&quot;line-height: 1.8;&quot;&gt;Fabric [ New Fatal Issue] 가 전송되면 항상 조마조마한게 개발자의 마음...&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;이번 에러 메세지를 해결하는 과정을 기록해보았습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;이번 에러 메세지는 다음과 같습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;Fatal Exception: java.lang.illegalStateException&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;Can not perform this action after onSaveInstanceState&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;이러한 경우는 테스트할 때 한 번도 본 적이 없는 에러였습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;직역을 해보자면,&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;u&gt;&lt;i&gt;'onSaveInstanceState 후에 action을 수행할 수 없다'&lt;/i&gt;&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;우선, developer.android.com에서 onSaveInstanceState 메소드에 대해서&amp;nbsp;살펴보았습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;간단하게 보고 지나갔던 기억이 있었는데, 다시 보니 중요한 내용이 많았습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;이 문제를 해결하면서&amp;nbsp;중요하다고 생각하는 부분을 정리해 보았습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;( 안드로이드 공식 가이드 문서&amp;nbsp;참고 )&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;정상적인 앱 동작으로 인해 액티비티가 소멸되는 경우로,&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot;&gt;&lt;li&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;사용자가 Back 버튼을 누르는 경우&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;액티비티가 finish() 를 호출하여 자체적인 소멸 신호를 보내는 경우&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;액티비티가 현재 정지되어 있고 장시간 사용되지 않는 경우&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;전면에 있는 액티비티가 더 많은 리소스를 필요로 하여 시스템이 백그라운드 프로세스를 종료해서 메모리를 회수해야 할 필요가 있는 경우 (&amp;nbsp;시스템이 액티비티를 소멸시킬 수 있음 )&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;사용자가 Back 버튼을 누르거나 액티비티가 스스로 종료되어 소멸된 경우, Activity 인스턴스에 대한 시스템의 개념은 완전히 사라지게 됩니다. 왜냐하면 동작이 액티비티가 더 이상 필요치 않다는 것을 나타내기 때문입니다. 하지만 시스템이 정상적인 앱 동작이 아닌 시스템 제약 조건으로 인해 액티비티를 소멸한 경우, 실제 Activity 인스턴스는 사라지지만 시스템은 그것이 존재하고 있었음을 기억합니다. 예를 들어 사용자가 다시 해당 액티비티를 탐색하면, 시스템은 소멸된 액티비티의 상태를 설명하는 저장된 데이터 세트를 사용하여 액티비티의 새 인스턴스를 생성합니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;시스템이 이전 상태를 복원하기 위해 사용하는 저장된 데이터를 &quot;인스턴스 상태&quot;라고 하며, 이는 Bundle 객체에 저장된 키-값 쌍의 컬렉션입니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;기본적으로 시스템은 Bundle 인스턴스 상태를 사용하여 액티비티 레이아웃의 각 View 객체에 대한 정보를 저장합니다. 따라서 액티비티 인스턴스가 소멸되고 재생성된 경우, 레이아웃의 상태는 별도의 코드 요청 없이 이전 상태로 복원됩니다. 하지만 액티비티에서 사용자 진행 상태를 추적하는 멤버 변수처럼 액티비티에 복원하고자 하는 상태 정보가 더 많이 있는 경우도 있습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;u&gt;※ Android 시스템이 액티비티에서 뷰의 상태를 복원하기 위해서는 android:id 특성으로 제공되는 고유 ID가 각 뷰마다 있어여 합니다.&lt;/u&gt;&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;액티비티 상태에 대한 추가 데이터를 저장하려면 onSaveInstance() 콜백 메서드를 재정의해야 합니다. 시스템은 사용자가 액티비티를 떠날 경우 이 메서드를 호출하며, 액티비티가 예기치 않게 소멸될 경우 저장되는 Bundle 객체로 전달합니다. 시스템이 나중에 액티비티 인스턴스를 재생성해야 하는 경우, 동일한 Bundle 객체를 onRestoreInstanceState() 및 onCreate() 메서드 모두에 전달합니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8; text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 728px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/996AAE4B5BF4C90002&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F996AAE4B5BF4C90002&quot; width=&quot;728&quot; height=&quot;338&quot; filename=&quot;스크린샷 2018-11-21 오전 11.54.37.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;onSaveInstanceState() 메서드가 무엇인지 파악하다보니, 자연스럽게 무엇이 문제였는지 깨달았습니다.&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;i&gt;문제 상황의 원인&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;i&gt;Fragment를 생성할 때, commit() 메서드를 호출하는 시점은 Activity가 상태를 저장하기 전에 이루어져야 하는데, Activity의 상태 저장 후에 이루어졌기 때문입니다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;이 문제를 해결하기 위해서 즉, Activity가 상태를 저장하고 난 후에 commit()를 하기 위해서는&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;u&gt;&lt;i&gt;commitAllowingStateLoss()&lt;/i&gt;&lt;/u&gt;&lt;/b&gt; 메서드를 이용해서 commit를 진행하면 됩니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;참고 사이트 :&amp;nbsp;https://developer.android.com/training/basics/activity-lifecycle/recreating?hl=ko&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;http://gogorchg.tistory.com/entry/Android-Can-not-perform-this-action-after-onSaveInstanceState&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;http://unikys.tistory.com/318&lt;/p&gt;</description>
      <category>Android/[Android] Error solution</category>
      <category>android</category>
      <category>android error</category>
      <category>Can not perform this action after onSaveInstanceState</category>
      <category>onSaveInstanceState</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/69</guid>
      <comments>https://eso0609.tistory.com/69#entry69comment</comments>
      <pubDate>Wed, 21 Nov 2018 12:06:45 +0900</pubDate>
    </item>
    <item>
      <title>기존 앱에 App Bundle 적용하기</title>
      <link>https://eso0609.tistory.com/68</link>
      <description>&lt;p style=&quot;line-height: 1.8;&quot;&gt;2018 Google IO에서 App Bundle에 대한 세미나를&amp;nbsp;들었습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;'적용해야지' 말만 하다가 이번 기회에 적용해보았습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h1 style=&quot;line-height: 1.8;&quot;&gt;App Bundle 이란?&lt;/h1&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;App Bundle 이란 APK 와 비슷하지만 모든 코드, 리소스, CPU 아키텍처와 메다데이터를 압축한 zip파일입니다. 그래서 Google Play는 App Bundle에서 사용자 기기에 필요한 코드와 리소스만을 선택해 빌드될 수 있는 것입니다.&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;App Bundle 을 빌드하면 .aab 파일이 생성됩니다.&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;빌드된 aab 파일을 Play Store에 업로드하면 Play Store가 각각 기기에 최적화된 APK를 빌드합니다.&lt;/div&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;img border=&quot;0&quot; data-original-height=&quot;540&quot; data-original-width=&quot;960&quot; src=&quot;https://3.bp.blogspot.com/-2NVwoO_tuU8/W0SuXZ0-mmI/AAAAAAAAAC4/bWiLSSW2WfYz8nILrfU1F_rO9eVzQ__ygCLcBGAs/s1600/image3.gif&quot;&gt;
&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;( 위의 자료는 정리를 잘해두신 zerogdev 블로그를 참고하였습니다. )&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;안드로이드 앱 번들은 모든 응용 프로그램의 컴파일 된 코드와 리소스를 포함하는 새로운 업로드 형식이지만, Google Play 에 APK 생성 및 서명을 연기합니다.&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;그런 다음, Google Play의 새로운 앱 게제 모델인 Dynamic Delivery를 이용하여,&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;App Bundle을 사용자의 기기 구성에 최적화된 APK를 생성하고 게제함으로써 앱을 실행하는 데 필요한 코드와 리소스만 다운로드합니다.&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;이제는, &lt;b&gt;서로 다른 장치를 지원하기 위해 더 이상 APK를 작성, 서명 및 관리 할 필요가 없으며 사용자는 더 작고 최적화 된 APK를 다운로드 받을 수 있습니다.&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;center&gt;
&lt;script async=&quot;&quot; src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;!-- banner_01 --&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block&quot; data-ad-client=&quot;ca-pub-5799939464120235&quot; data-ad-slot=&quot;4078831414&quot; data-ad-format=&quot;auto&quot; data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
(adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/center&gt;
&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;h1&gt;App Bundle 생성&lt;/h1&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;서비스 중인 앱의 App Bundle을 생성하는 방법은 간단합니다.&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;Build -&amp;gt; Generate Bundle / APK -&amp;gt; Android App Bundle&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;그런데 저는 아래와 같은 에러 메시지를 확인했습니다.&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;p style=&quot;line-height: 1.8;&quot;&gt;&lt;b&gt;The Android Gradle plugin supports only Kotlin Gradle plugin version 1.2.51&amp;nbsp;and higher. Project '~~~' is using version 1.2.20.&lt;/b&gt;&lt;/p&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;Kotlin은 사용하고있는데, Kotlin Gradle plugin version이 낮아서 발생하는 에러였습니다.&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;그래서 해당 플러그인을 업데이트하려고 보니, 안드로이드 스튜디오 버전을 올리기로 결심.&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;( 저는&amp;nbsp;3.2 Preview 버전을 사용하고 있었습니다. )&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 1.8;&quot;&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 577px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/996C13365BEE72E830&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F996C13365BEE72E830&quot; width=&quot;577&quot; height=&quot;345&quot; filename=&quot;스크린샷 2018-11-16 오후 4.33.35.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;3.2 버전으로 올리니 위와 같은 화면을 볼 수 있네요.&amp;nbsp;&lt;/p&gt;&lt;p&gt;코틀린 플러그인 버전은 1.2.51을 사용했습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 518px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99E58C475BEE7C0E0C&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99E58C475BEE7C0E0C&quot; width=&quot;518&quot; height=&quot;42&quot; filename=&quot;스크린샷 2018-11-16 오후 5.12.39.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;App Bundle로 생성하니 .aab가 나오긴하는데... 용량이 기존 용량과 비슷합니다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;기존 용량은&amp;nbsp;google play store에 올렸을 때 28MB 정도였습니다.&lt;/p&gt;&lt;p&gt;의심을 품고, 업로드 해보겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 728px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/995A9C4F5BEE7CA236&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F995A9C4F5BEE7CA236&quot; width=&quot;728&quot; height=&quot;294&quot; filename=&quot;스크린샷 2018-11-16 오후 5.15.10.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;음 이건... 55%나 감소되는거면 기존에 안쓰는 리소스가 상당했나 봅니다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 728px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99485F405BEE7D5E10&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99485F405BEE7D5E10&quot; width=&quot;728&quot; height=&quot;103&quot; filename=&quot;스크린샷 2018-11-16 오후 5.18.23.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;놀랍게도 정말 용량이 확 줄은 것을 확인할 수 있습니다.&lt;/p&gt;&lt;p&gt;기쁜 마음을 추스리고,&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;배포 후 다운로드 받아서 이상이 없습니다!!&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;App Bundle 사용하시고, 사용자가 다운받기에 부담스럽지 않은 용량이 되도록 노력해보세요!&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;읽어주셔서 감사합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;혹시나 제가 잘 못 이해한 부분이나, 추가 조언, 피드백 환영합니다.&lt;/p&gt;&lt;p&gt;주저마시고 말해주세요.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Android/Android App Bundle 적용</category>
      <category>apk 용량 감소</category>
      <category>app bundle</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/68</guid>
      <comments>https://eso0609.tistory.com/68#entry68comment</comments>
      <pubDate>Fri, 16 Nov 2018 17:47:33 +0900</pubDate>
    </item>
    <item>
      <title>apksigner을 사용하여 unsigned.apk 서명</title>
      <link>https://eso0609.tistory.com/67</link>
      <description>&lt;h1&gt;apksigner&lt;/h1&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;프로젝트 진행중 중국 Baidu 앱 스토어에 앱을 재등록해야하는 상황을 겪었습니다.&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;Baidu에서 제공하는 apk파일을 다운로드한 후에, 서명을 하고 업로드를 진행해야합니다.&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 659px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99D7FA4A5BE535E113&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99D7FA4A5BE535E113&quot; width=&quot;659&quot; height=&quot;159&quot; filename=&quot;baidu01.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;참고로 저는 중국어를 못해서 번역 돌렸습니다.&lt;/p&gt;&lt;p&gt;( 번역돌려도 무슨말인지... 한참을 생각했습니다. )&lt;/p&gt;&lt;p&gt;이 과정에서 '서명되지 않은 apk 파일을 어떻게 서명할까'라는 의문이 들었고,&lt;/p&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;&lt;b style=&quot;font-style: italic;&quot;&gt;&lt;a href=&quot;https://developer.android.com/studio/command-line/apksigner?hl=ko&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;apksigner&lt;/a&gt;&amp;nbsp;&lt;/b&gt;를 알게되었습니다.&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;apksigner는 간단히 말하자면 이 도구를 사용해서 apk에 서명하거나, 해당 apk가 지원하는 모든 Android 플랫폼 버전에서 서명이 성공적으로 검증되는지를 확인할 수 있는 기능을 제공하는 도구입니다.&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;제가 해야하는 일은 apksigner를 통해 unsigned.apk 즉, 서명되지 않은 apk를 수동으로 서명해야 했습니다.&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;주의 사항 : apksigner 도구를 사용하려면 수정 버전 24.0.3 이상의 Android SDK 빌드 도구를 설치해야 합니다. SDK Manager를 사용하여 이 패키지를 업데이트할 수 있습니다.&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;하지만, &lt;i&gt;&lt;a href=&quot;https://developer.android.com/studio/publish/app-signing#sign-manually&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;서명되지 않은 APK 수동으로 서명하기&lt;/a&gt;&amp;nbsp; &lt;/i&gt;해당 페이지를 보고 시도해보았지만..&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;gradlew 명령어도, apksigner 명령어도 먹질않았습니다.&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;center&gt;
&lt;script async=&quot;&quot; src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;!-- banner_01 --&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block&quot; data-ad-client=&quot;ca-pub-5799939464120235&quot; data-ad-slot=&quot;4078831414&quot; data-ad-format=&quot;auto&quot; data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
(adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/center&gt;
&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 2;&quot;&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;
&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;
&lt;/span&gt;&lt;style type=&quot;text/css&quot;&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px 'Andale Mono'; color: #2fff12; background-color: #000000; background-color: rgba(0, 0, 0, 0.9)}
span.s1 {font-variant-ligatures: no-common-ligatures}
&lt;/span&gt;&lt;/style&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;


&lt;/span&gt;&lt;p class=&quot;p1&quot;&gt;&lt;span class=&quot;s1&quot; style=&quot;font-size: 18pt; background-color: rgb(255, 228, 0); color: rgb(0, 0, 0);&quot;&gt;-bash: apksigner: command not found&lt;/span&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;위와 같은 문구가 뜨면서 말이죠.&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;apksigner는 어디서 쓸 수 있는걸까...&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;이런 생각중에 google developer reference에 있는 주의사항이 생각났습니다.&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;apksigner 도구를 사용하려면 수정 버전 24.0.3 이상의 Android SDK 빌드 도구를 설치해야 합니다. SDK Manager를 사용하여 이 패키지를 업데이트할 수 있습니다.&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;'아 경로를 변경해서 해봐야지' 라는 생각과 함께...&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;b&gt;안드로이드가 설치된 경로 -&amp;gt; sdk -&amp;gt; build-tools -&amp;gt; 사용하고있는 버전 ( 저는 26.0.2 ) -&amp;gt; apksigner&lt;/b&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;이 경로에 와보니 apksigner이 보입니다.&lt;/p&gt;&lt;pre class=&quot;no-pretty-print&quot; style=&quot;box-sizing: inherit; background: rgb(247, 247, 247); color: rgb(55, 71, 79); font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; font-size: 14px; line-height: 20px; font-family: &amp;quot;Roboto Mono&amp;quot;, monospace; padding: 8px; margin-top: 16px; margin-bottom: 16px; overflow-x: auto; position: relative;&quot;&gt;apksigner sign --ks my-release-key.jks --out my-app-release.apk my-app-unsigned-aligned.apk&lt;/pre&gt;&lt;p class=&quot;p1&quot;&gt;그럼 이제 터미널로 돌아와서, 위의 양식대로 작성하시면 됩니다.&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;b&gt;예시 : ~/Android/sdk/build-tools/26.0.2/apksigner sign --ks [서명키경로] --out [signed.apk경로] [unsigned.apk경로]&lt;/b&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;[서명키경로] : Baidu 앱 업로드의 경우 Baidu에서 제공하는 ~unsigned.apk를 다운로드 받고, 업로드할 앱의 서명을 입혀줘야합니다. &lt;b&gt;즉, [서명키경로]에는 업로드할 앱의 키파일(.jks) 경로를 입력합니다.&lt;/b&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;예시 : /Users/admin/AndroidProject/myProject/testApp/key/test_keystore.jks&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;[signed.apk경로] : unsigned.apk가 서명된 후, 서명된 apk가 만들어질 경로와 해당 apk파일의 이름을 입렵합니다.&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;예시 :&amp;nbsp;&amp;nbsp;/Users/admin/Desktop/my_signed.apk 입력하시면 데스크탑 폴더에 my_signed.apk가 생성됩니다.&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;[unsigned.apk경로] : 서명해야할 apk 경로, 즉 서명되지않은 apk 경로를 입력합니다.&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;예시 :&amp;nbsp; /Users/admin/Desktop/my_unsigned.apk&amp;nbsp;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;옳바르게 입력하셨다면 &lt;b&gt;Kestore password for signer #1:&lt;/b&gt;&amp;nbsp;보이실겁니다.&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;Key password를 입력하시면 signed.apk경로에 서명된 apk 파일이 생성됩니다!&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;읽어주셔서 감사합니다.&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;참고&amp;nbsp;&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;https://developer.android.com/studio/command-line/apksigner&lt;/p&gt;&lt;p class=&quot;p1&quot;&gt;https://developer.android.com/studio/publish/app-signing&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Android/android apksigner</category>
      <category>apksigner</category>
      <category>baidu</category>
      <category>unsigned apk sign</category>
      <category>서명되지않은apk서명</category>
      <category>수동으로앱서명</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/67</guid>
      <comments>https://eso0609.tistory.com/67#entry67comment</comments>
      <pubDate>Fri, 9 Nov 2018 18:27:07 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 GIF 로딩 구현</title>
      <link>https://eso0609.tistory.com/66</link>
      <description>&lt;h1&gt;안드로이드 raw directory 생성&lt;/h1&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;안드로이드 res 하위에 Android Resource Directory를 생성합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 559px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/998ABC3C5BD6CFC01B&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F998ABC3C5BD6CFC01B&quot; width=&quot;559&quot; height=&quot;695&quot; filename=&quot;스크린샷 2018-10-29 오후 5.37.01.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;안드로이드에서 gif는 리소스 타입이 raw인 폴더에 보관해야 합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 728px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/998E85405BD6D03B14&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F998E85405BD6D03B14&quot; width=&quot;728&quot; height=&quot;421&quot; filename=&quot;스크린샷 2018-10-29 오후 6.16.43.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;raw를 생성한 뒤, 해당 경로에 .gif 파일을 넣어줍니다.&lt;/p&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;제 프로젝트의 경우 모든 Activity, Fragment에서 상속받는 부모 Activity에서&lt;/p&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;showLoading() 함수를 구현했습니다.&lt;/p&gt;&lt;pre style=&quot;background-color:#2b2b2b;color:#a9b7c6;font-family:'Menlo';font-size:13.5pt;&quot;&gt;&lt;p&gt;&lt;span style=&quot;color:#cc7832;&quot;&gt;fun &lt;/span&gt;&lt;span style=&quot;color:#ffc66d;&quot;&gt;showLoading&lt;/span&gt;() {&lt;br /&gt;&lt;span style=&quot;color:#808080;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:#808080;&quot;&gt;    &lt;/span&gt;&lt;span style=&quot;color:#cc7832;&quot;&gt;if &lt;/span&gt;(&lt;span style=&quot;color:#9876aa;&quot;&gt;progressDialog &lt;/span&gt;== &lt;span style=&quot;color:#cc7832;&quot;&gt;null&lt;/span&gt;) {&lt;br /&gt;        &lt;span style=&quot;color:#9876aa;&quot;&gt;progressDialog &lt;/span&gt;= ProgressNetDialog(&lt;span style=&quot;color:#cc7832;&quot;&gt;this&lt;/span&gt;)&lt;br /&gt;        &lt;span style=&quot;color:#9876aa;&quot;&gt;progressDialog&lt;/span&gt;!!.setCancelable(&lt;span style=&quot;color:#cc7832;&quot;&gt;false&lt;/span&gt;)&lt;br /&gt;        &lt;span style=&quot;color:#9876aa;&quot;&gt;progressDialog&lt;/span&gt;!!.setCanceledOnTouchOutside(&lt;span style=&quot;color:#cc7832;&quot;&gt;false&lt;/span&gt;)&lt;br /&gt;        &lt;span style=&quot;color:#9876aa;&quot;&gt;progressDialog&lt;/span&gt;!!.setOnDismissListener &lt;span style=&quot;font-weight:bold;&quot;&gt;{&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-weight:bold;&quot;&gt;            &lt;/span&gt;&lt;span style=&quot;color:#cc7832;&quot;&gt;try &lt;/span&gt;{&lt;br /&gt;                Thread.interrupted() &lt;span style=&quot;color:#808080;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color:#808080;&quot;&gt;            &lt;/span&gt;} &lt;span style=&quot;color:#cc7832;&quot;&gt;catch &lt;/span&gt;(e: Exception) {&lt;br /&gt;            }&lt;br /&gt;        &lt;span style=&quot;font-weight:bold;&quot;&gt;}&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-weight:bold;&quot;&gt;        &lt;/span&gt;&lt;span style=&quot;color:#cc7832;&quot;&gt;if &lt;/span&gt;(!&lt;span style=&quot;color:#cc7832;&quot;&gt;this&lt;/span&gt;.&lt;span style=&quot;color:#9876aa;font-style:italic;&quot;&gt;isFinishing&lt;/span&gt;) {&lt;br /&gt;            &lt;span style=&quot;color:#9876aa;&quot;&gt;progressDialog&lt;/span&gt;!!.show()&lt;br /&gt;        }&lt;br /&gt;    } &lt;span style=&quot;color:#cc7832;&quot;&gt;else if &lt;/span&gt;(!&lt;span style=&quot;color:#9876aa;&quot;&gt;progressDialog&lt;/span&gt;!!.&lt;span style=&quot;color:#9876aa;font-style:italic;&quot;&gt;isShowing&lt;/span&gt;) {&lt;br /&gt;        &lt;span style=&quot;color:#cc7832;&quot;&gt;if &lt;/span&gt;(!&lt;span style=&quot;color:#cc7832;&quot;&gt;this&lt;/span&gt;.&lt;span style=&quot;color:#9876aa;font-style:italic;&quot;&gt;isFinishing&lt;/span&gt;) {&lt;br /&gt;            &lt;span style=&quot;color:#9876aa;&quot;&gt;progressDialog&lt;/span&gt;!!.show()&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;해당 부모 클래스를 상속 받는 모든 Activity와 Fragment에서 간단하게 로딩을 띄울 수 있습니다.&lt;/p&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위 코드에서 ProgressNetDialog 클래스를 로딩을 구현하고 싶은 화면으로 커스텀하시면 됩니다.&lt;/p&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;만약 Glide를 사용하신다면 다음의 코드가 도움이 될 것입니다.&lt;/p&gt;&lt;pre style=&quot;background-color: rgb(43, 43, 43); font-family: Menlo; font-size: 13.5pt;&quot;&gt;&lt;p style=&quot;&quot;&gt;&lt;font color=&quot;#a9b7c6&quot;&gt;GlideApp.&lt;/font&gt;&lt;span style=&quot;color: rgb(169, 183, 198); font-style: italic;&quot;&gt;with&lt;/span&gt;&lt;font color=&quot;#a9b7c6&quot;&gt;(context)&lt;br /&gt;        .asGif()&lt;br /&gt;        .override(&lt;/font&gt;&lt;span style=&quot;color: rgb(104, 151, 187);&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: rgb(204, 120, 50);&quot;&gt;,&lt;/span&gt;&lt;font color=&quot;#6897bb&quot;&gt;height&lt;/font&gt;&lt;font color=&quot;#a9b7c6&quot;&gt;)&lt;br /&gt;        .load(R.raw.&lt;/font&gt;&lt;span style=&quot;color: rgb(152, 118, 170); font-style: italic;&quot;&gt;loading&lt;/span&gt;&lt;font color=&quot;#a9b7c6&quot;&gt;)&lt;br /&gt;        .diskCacheStrategy(DiskCacheStrategy.&lt;/font&gt;&lt;span style=&quot;color: rgb(152, 118, 170); font-style: italic;&quot;&gt;RESOURCE&lt;/span&gt;&lt;font color=&quot;#a9b7c6&quot;&gt;)&lt;br /&gt;        .into(imageView)&lt;/font&gt;&lt;span style=&quot;color: rgb(204, 120, 50);&quot;&gt;;&lt;/span&gt;&lt;/p&gt;&lt;/pre&gt;&lt;h3&gt;&lt;i&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/i&gt;&lt;/h3&gt;&lt;h2&gt;&lt;i&gt;&lt;b&gt;.asGif()&lt;/b&gt;&lt;/i&gt;&lt;/h2&gt;&lt;div style=&quot;line-height: 0.5;&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/i&gt;&lt;/div&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;단순 이미지 뿐만 아니라 GIF도&amp;nbsp;로딩할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;&lt;i&gt;.override(width,height)&lt;/i&gt;&lt;/h2&gt;&lt;div style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;line-height: 0.5;&quot;&gt;지정한 이미지 크기만큼 불러 올 수 있습니다.&lt;/div&gt;&lt;div style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;override 메소드는 이미지를 최적화 하는 경우 유용하게 사용할 수 있습니다.&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;&lt;i&gt;&lt;b&gt;.diskCacheStrategy(DiskCacheStrategy.RESOURCE)&amp;nbsp;&lt;/b&gt;&lt;/i&gt;&lt;/h2&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;Glide에서 해당 리소스를 캐싱하고, 캐싱된 리소스와 load할 리소스가 같은 리소스일 때&lt;/p&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;별도의 절차 없이 캐싱된 리소스를 사용하기 때문에,&lt;/p&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;만약 안드로이드에서 gif를 사용하는데 속도가 느리신 경우에 해결 방법이 될 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;참고 사이트&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;https://github.com/bumptech/glide/issues/600&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;line-height: 0.5;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;https://bumptech.github.io/glide/&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Android</category>
      <category>android raw directory</category>
      <category>diskCacheStrategy</category>
      <category>Glide</category>
      <category>안드로이드 gif</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/66</guid>
      <comments>https://eso0609.tistory.com/66#entry66comment</comments>
      <pubDate>Mon, 29 Oct 2018 18:57:12 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 O (오레오 ) Notifications Channel</title>
      <link>https://eso0609.tistory.com/64</link>
      <description>&lt;h1&gt;Notification Channels&lt;/h1&gt;&lt;div&gt;프로젝트 작업중 푸시 기능 테스트가 필요한 작업이 있어 테스트 하던중 백그라운드 상태에서 앱의 알림이 오지 않는&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;문제에 직면하게 되었습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;'기존 코드가 잘못되었나 ?' 라는 생각에 기존 코드들을 살펴보았고, 다른 기기로 테스트해보았습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;웬걸, 다른 기기는 잘되네요. 왜그럴까요?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;바로 안드로이드 O ( 오레오 )&amp;nbsp;, 8.0버전부터는&amp;nbsp;Notification Channel을 따로 만들어야 알림을 받을 수 있습니다.&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;구글은 안드로이드 8.0 ( API 26 ) 부터 모든 알림을 채널에 할당하고, 각 채널에 대해 해당 채널의 모든 알림에 적용되는&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;시각, 청각 동작을 설정할 수 있습니다. 그리고 사용자는 이러한 설정을 변경하고 앱의 알림 채널을 방해하거나&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;눈에 띄게할지 결정할 수 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2&gt;채널을 지정하지 않은 경우 에러 테스트&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;만약, 안드로이드 8.0 이상에서 채널을 지정하지 않고 알림을 게시하는 경우 로그에 다음과 같은 에러가 뜹니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;background-color: rgb(255, 228, 0); color: rgb(255, 0, 0);&quot;&gt;E/NotificationService: No Channel found for pkg=~~~~~&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;그리고 안드로이드 스튜디오가아닌 단말기에서도 채널을 지정하지 않고 알림을 게시하는 경우에 대한 시스템 경고를&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;아래의 방법을 통해 확인할 수 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 343px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/993C73425BCD76680F&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F993C73425BCD76680F&quot; width=&quot;343&quot; height=&quot;612&quot; filename=&quot;alarm_error01.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;( 시스템 설정 -&amp;gt; 개발자 옵션 -&amp;gt; 알림 채널 경고 표시 활성화 )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;위와 같은 설정 후 채널을 지정하지 않고 알림을 게시하면 다음과 같은 경고가 단말기에 표시되네요.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 231px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99A8814C5BCD773C10&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99A8814C5BCD773C10&quot; width=&quot;231&quot; height=&quot;106&quot; filename=&quot;alarm_error02.jpg&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;패키지명은 모자이크 처리했습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;채널이 &quot;null&quot;이라고 뜹니다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;그럼 이제 오류의 원인이 명확해 졌으니, 오류를 해결해보도록 합시다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;Create a notification channel&amp;nbsp;&lt;/h3&gt;&lt;ol style=&quot;list-style-type: decimal;&quot;&gt;&lt;li&gt;NotificationChannel의 ID 객체를 생성.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;시스템 설정에서 볼 수 있는 description을 설정. setDescription() ( 선택 사항 )&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;알림 채널 등록 createNotificationChannel()&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;pre class=&quot;prettyprint lang-kotlin&quot; style=&quot;box-sizing: inherit; background: rgb(247, 247, 247); color: rgb(55, 71, 79); font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; font-size: 14px; line-height: 20px; font-family: &amp;quot;Roboto Mono&amp;quot;, monospace; padding: 7px; margin: -7px; overflow-x: auto; position: relative;&quot;&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;SDK&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;INT &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;CODES&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;O&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;com&quot; style=&quot;box-sizing: inherit; color: rgb(216, 27, 96);&quot;&gt;// Create the NotificationChannel&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; name &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; getString&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; descriptionText &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; getString&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; importance &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;NotificationManager&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;IMPORTANCE&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;DEFAULT&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; mChannel &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;NotificationChannel&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;CHANNEL&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; name&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; importance&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; mChannel&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;description &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; descriptionText&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;com&quot; style=&quot;box-sizing: inherit; color: rgb(216, 27, 96);&quot;&gt;// Register the channel with the system; you can't change the importance&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;com&quot; style=&quot;box-sizing: inherit; color: rgb(216, 27, 96);&quot;&gt;// or other notification behaviors after this&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; notificationManager &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; getSystemService&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;NOTIFICATION&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;SERVICE&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;NotificationManager&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; notificationManager&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;createNotificationChannel&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;mChannel&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;devsite-code-button-clone&quot; style=&quot;box-sizing: inherit; background: rgb(247, 247, 247); color: rgb(55, 71, 79); font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; font-size: 14px; line-height: 20px; font-family: &amp;quot;Roboto Mono&amp;quot;, monospace; padding: 8px; margin-top: 16px; margin-bottom: 16px; overflow-x: auto; position: absolute; left: -99999px;&quot;&gt;&lt;div class=&quot;devsite-code-button-wrapper&quot; style=&quot;box-sizing: inherit; margin: -8px 0px 0px; padding: 0px; position: absolute; right: 0px;&quot;&gt;&lt;br class=&quot;Apple-interchange-newline&quot;&gt;&lt;div class=&quot;devsite-code-button gc-analytics-event material-icons devsite-click-to-copy-button&quot; data-category=&quot;Site-Wide Custom Events&quot; data-label=&quot;Click To Copy&quot; track-type=&quot;exampleCode&quot; track-name=&quot;clickToCopy&quot; data-tooltip-align=&quot;b,c&quot; data-tooltip=&quot;클릭하여 복사&quot; aria-label=&quot;클릭하여 복사&quot; data-title=&quot;클릭하여 복사&quot; style=&quot;box-sizing: inherit; margin: 0px; padding: 3px; font-family: &amp;quot;Material Icons&amp;quot;; font-size: 18px; line-height: 1; display: inline-block; white-space: nowrap; overflow-wrap: normal; direction: ltr; font-feature-settings: &amp;quot;liga&amp;quot;; -webkit-font-smoothing: antialiased; background: rgba(0, 0, 0, 0.26); color: rgb(255, 255, 255); cursor: pointer; transition: background 0.2s ease 0s;&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;SDK&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;INT &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;CODES&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;O&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;com&quot; style=&quot;box-sizing: inherit; color: rgb(216, 27, 96);&quot;&gt;// Create the NotificationChannel&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; name &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; getString&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; descriptionText &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; getString&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; importance &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;NotificationManager&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;IMPORTANCE&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;DEFAULT&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; mChannel &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;NotificationChannel&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;CHANNEL&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; name&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; importance&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; mChannel&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;description &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; descriptionText&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;com&quot; style=&quot;box-sizing: inherit; color: rgb(216, 27, 96);&quot;&gt;// Register the channel with the system; you can't change the importance&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;com&quot; style=&quot;box-sizing: inherit; color: rgb(216, 27, 96);&quot;&gt;// or other notification behaviors after this&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; notificationManager &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; getSystemService&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;NOTIFICATION&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;SERVICE&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;NotificationManager&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&amp;nbsp; &amp;nbsp; notificationManager&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;createNotificationChannel&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;mChannel&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;d&lt;/p&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위 코드는 안드로이드 공식 홈페이지에서 권장하는 코드입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;만약 해당 채널의 기본 알림 동작을 추가해야할 필요가 있는 경우 enableLights(), setLightColor() 등의 메소드를 통해,&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;불빛, 색상, 진동패턴등 설정이 가능합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;(&amp;nbsp;https://developer.android.com/training/notify-user/channels 참고 )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;채널 아이디와, 이름을 설정하고, 채널은&amp;nbsp;한번만 생성해줍니다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;저의 경우 앱이 시작할때&amp;nbsp;SharedPreference의 boolean값을&amp;nbsp;이용해서 채널을 한번만 만들어주었습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;채널을 생성한 후, 채널의 ID값을 Notification.Builder(context,CHANNEL_ID) 형식으로 넣어주면 됩니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;저의 경우..&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 575px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/992BF6435BCD85F709&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F992BF6435BCD85F709&quot; width=&quot;575&quot; height=&quot;48&quot; filename=&quot;android01.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이미 deprecated 경고표시가 떠있었네요.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Builder의 인자값으로 context,CHANNEL_ID를 넣어주세요.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 843px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9957E3485BCD868F2E&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9957E3485BCD868F2E&quot; width=&quot;843&quot; height=&quot;54&quot; filename=&quot;android02.jpg&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;속이 시원합니다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;테스트해보시면 알림이 잘 오는 것을 확인해볼 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;테스트 단말기의 시스템 설정에서 알림 -&amp;gt; 해당 Application에 가보시면 알림 유형에 생성했던 noti channel이 보입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 338px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/994312375BCD8A4918&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F994312375BCD8A4918&quot; width=&quot;338&quot; height=&quot;604&quot; filename=&quot;android03.jpg&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위와 같은 화면까지 떴다면 이제 채널을 삭제해볼 차례입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;Delete a notification channel&lt;/h3&gt;&lt;div&gt;채널을 지우는 것 또한 간단합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;deleteNotificationChannel() 메소드를 이용하면 됩니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;prettyprint lang-kotlin&quot; style=&quot;box-sizing: inherit; background: rgb(247, 247, 247); color: rgb(55, 71, 79); font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; font-size: 14px; line-height: 20px; font-family: &amp;quot;Roboto Mono&amp;quot;, monospace; padding: 7px; margin: -7px; overflow-x: auto; position: relative;&quot;&gt;&lt;span class=&quot;com&quot; style=&quot;box-sizing: inherit; color: rgb(216, 27, 96);&quot;&gt;// The id of the channel.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; notificationManager &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; getSystemService&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;NOTIFICATION&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;SERVICE&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;NotificationManager&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;str&quot; style=&quot;box-sizing: inherit; color: rgb(13, 144, 79);&quot;&gt;&quot;my_channel_01&quot;&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;notificationManager&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;deleteNotificationChannel&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;알림 설정 화면에는 스팸 방지 메커니즘으로 삭제된 채널 수가 표시됩니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;앱을 재설치하거나 앱 데이터를 삭제하여 개발 기기의 테스트 채널을 삭제할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;테스트는 채널을 여러 개 만들고 삭제해보시면 됩니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;Create a notification channel group&lt;/h3&gt;&lt;div&gt;알림 채널 그룹이라는 기능도 생겼습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;온라인 협업 툴인 Slack을 예를들어 설명해보겠습니다.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;평상시 개인적으로 사용할 때, 업무중에 공적인 일로 사용할 때 여러 알림 채널이 필요할 수 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;이러한 경우에 알림 채널 그룹을 사용하면 좋습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;알림 채널 그룹을 생성하는 코드는 다음과 같습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;prettyprint lang-kotlin&quot; style=&quot;box-sizing: inherit; background: rgb(247, 247, 247); color: rgb(55, 71, 79); font-variant-numeric: normal; font-variant-east-asian: normal; font-stretch: normal; font-size: 14px; line-height: 20px; font-family: &amp;quot;Roboto Mono&amp;quot;, monospace; padding: 7px; margin: -7px; overflow-x: auto; position: relative;&quot;&gt;&lt;span class=&quot;com&quot; style=&quot;box-sizing: inherit; color: rgb(216, 27, 96);&quot;&gt;// The id of the group.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; groupId &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;str&quot; style=&quot;box-sizing: inherit; color: rgb(13, 144, 79);&quot;&gt;&quot;my_group_01&quot;&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;com&quot; style=&quot;box-sizing: inherit; color: rgb(216, 27, 96);&quot;&gt;// The user-visible name of the group.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; groupName &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; getString&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; notificationManager &lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; getSystemService&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;NOTIFICATION&lt;/span&gt;&lt;span class=&quot;lit&quot; style=&quot;box-sizing: inherit; color: rgb(197, 57, 41);&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;SERVICE&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kwd&quot; style=&quot;box-sizing: inherit; color: rgb(59, 120, 231);&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; &lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;NotificationManager&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;&lt;br style=&quot;box-sizing: inherit;&quot;&gt;&lt;/span&gt;&lt;b style=&quot;box-sizing: inherit;&quot;&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;notificationManager&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;createNotificationChannelGroup&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;typ&quot; style=&quot;box-sizing: inherit; color: rgb(156, 39, 176);&quot;&gt;NotificationChannelGroup&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt;groupId&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;pln&quot; style=&quot;box-sizing: inherit;&quot;&gt; groupName&lt;/span&gt;&lt;span class=&quot;pun&quot; style=&quot;box-sizing: inherit;&quot;&gt;))&lt;/span&gt;&lt;/b&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;그룹을 생성했으면 채널을 만들기전 ( createNotificationChannel() 메소드 호출 전 ) setGroup() 메소드를 사용하여&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;해당 채널이 어느 그룹에 속하는지 설정합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;저는 그룹을 두개로 나누어봤습니다. ( 테스트 삼아 )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 305px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/997B5B3D5BCD94AC37&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F997B5B3D5BCD94AC37&quot; width=&quot;305&quot; height=&quot;537&quot; filename=&quot;android05.jpg&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;상당히 많이 쓰이고 있는 기능이고, 유용한 기능이라고 생각합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;오늘도 또 한 번 버전별 동작 변경 사항 숙지에 대한 중요성을 깨닫습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;새로운 버전이 나오면 즉각 즉각 대응하는 습관을 갖도록 합시다 ㅎㅎ&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;읽어주셔서 너무 감사하고, 피드백 또는 조언 주시면 감사하겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;참고자료 :&amp;nbsp;https://developer.android.com/training/notify-user/channels&lt;/p&gt;</description>
      <category>Android</category>
      <category>notification channel</category>
      <category>안드로이드 notification</category>
      <category>안드로이드 O 변경 사항</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/64</guid>
      <comments>https://eso0609.tistory.com/64#entry64comment</comments>
      <pubDate>Mon, 22 Oct 2018 18:21:13 +0900</pubDate>
    </item>
    <item>
      <title>javascript 배열의 모든 것</title>
      <link>https://eso0609.tistory.com/61</link>
      <description>&lt;h1 style=&quot;text-align: left;&quot;&gt;JavaScript 배열&lt;/h1&gt;&lt;div&gt;배열은 프로토타입으로 탐색과 변형 작업을 수행하는 메서드를 갖는, 리스트와 비슷한 객체입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;JavaScript에서는 배열의 길이와 요소들의 타입은 고정되어 있지 않습니다. 배열의 길이가 언제든지 늘어나거나 줄어들 수 있기 때문에 자바스크립트 배열들은 밀집도가 보장되지 않습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;보통 이 성질은 편리하지만, 목적에 따라서 타입을 가진 배열 (typed array)을 사용하는 것을 고려해 볼 수 있습니다.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;배열 선언 &amp;amp;&amp;amp; 인덱스로 배열의 항목 접근&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 470px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99BF91475BC9735C0C&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99BF91475BC9735C0C&quot; width=&quot;470&quot; height=&quot;258&quot; filename=&quot;스크린샷 2018-10-19 오후 3.01.51.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;배열 forEach&amp;nbsp;&lt;/h3&gt;&lt;div&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 493px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/997AA34B5BC971552B&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F997AA34B5BC971552B&quot; width=&quot;493&quot; height=&quot;116&quot; filename=&quot;스크린샷 2018-10-19 오후 2.53.12.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;Javascript에서의 forEach 메소드는 배열에 있는 각 요소에 대해 한 번 씩 인덱스 순서대로 callbackfn을 호출.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;배열 외에도 forEach 메소드를 사용하여 length 속성을 포함하는 개체(entity)와 숫자로 인덱싱된 속성 이름을 포함하는 모든 개체에서 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 626px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99083F4D5BC9717D07&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99083F4D5BC9717D07&quot; width=&quot;626&quot; height=&quot;221&quot; filename=&quot;스크린샷 2018-10-19 오후 2.51.10.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;value는 배열 요소의 값을, index는 배열 요소의 인덱스값을, array는 요소가 포함된 배열 객체를 나타낸다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;배열 끝에 항목 추가 &amp;amp;&amp;amp; 배열 끝에 항목 제거&lt;/h3&gt;&lt;div&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 467px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/992D164F5BC975DE0A&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F992D164F5BC975DE0A&quot; width=&quot;467&quot; height=&quot;311&quot; filename=&quot;스크린샷 2018-10-19 오후 3.07.46.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;배열 앞에 항목 추가 &amp;amp;&amp;amp; 배열 앞에 항목 제거&lt;/h3&gt;&lt;div&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 412px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/999F064E5BC9774106&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F999F064E5BC9774106&quot; width=&quot;412&quot; height=&quot;312&quot; filename=&quot;스크린샷 2018-10-19 오후 3.18.19.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;배열 indexOf &amp;amp;&amp;amp; 배열 splice&lt;/h3&gt;&lt;div&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 557px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/995BF8495BC979F218&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F995BF8495BC979F218&quot; width=&quot;557&quot; height=&quot;311&quot; filename=&quot;스크린샷 2018-10-19 오후 3.29.56.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;인덱스 위치에서 부터 여러개의 항목 제거 &amp;amp;&amp;amp; 배열 복사&lt;/h3&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 475px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/998231455BC97A3B1A&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F998231455BC97A3B1A&quot; width=&quot;475&quot; height=&quot;343&quot; filename=&quot;스크린샷 2018-10-19 오후 3.31.10.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;참고 자료 :&amp;nbsp;https://developer.mozilla.org/ko/&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>웹/JavaScript</category>
      <category>javascript array</category>
      <category>배열</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/61</guid>
      <comments>https://eso0609.tistory.com/61#entry61comment</comments>
      <pubDate>Fri, 19 Oct 2018 15:39:37 +0900</pubDate>
    </item>
    <item>
      <title>JavaSctipt 시작하기</title>
      <link>https://eso0609.tistory.com/59</link>
      <description>&lt;h1&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;JavaScript 시작하기&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;&lt;div&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;HTML, CSS를 학습하고 JavaScript 학습을 시작했습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;HTML, CSS JavaScript를 사용하고 있는 툴은 Brackets입니다. 이번 포스팅에서는 기본적인 부분을 정리해보겠습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;우선 HTML 코드에서 JavaScript를 사용할 수 있게 해주는 태그는 &amp;lt;script&amp;gt; &amp;lt;/script&amp;gt; 태그 입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;script 태그를 통해서 웹브라우저에게 지금부터 자바스크립트가 시작된다는 것을 알려줍니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;그리고, script태그 안에서 JavaScript&amp;nbsp;사용해보겠습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;document 사용한 Hello world 출력&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;기본은 Hello world를 찍어봐야겠죠?&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;각각의 웹 페이지에서는 자신의 document 객체를 갖습니다. document 인터페이스는 웹 페이지의 컨텐츠에 대해 접근할&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;수 있도록 하는 기능을 제공하고, 해당 문서에 대한 전역 기능을 제공합니다. ( 참조 :&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Document&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt; Document Reference&lt;/a&gt; )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9964FF405BB9B4DB31&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9964FF405BB9B4DB31&quot; width=&quot;900&quot; height=&quot;181&quot; filename=&quot;스크린샷 2018-10-07 오후 4.24.08.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;( 사진 출처 :&amp;nbsp;w3schools )&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Hello world를 찍어보기 위해, document객체를 사용하는 가장 일반적인 방법인 &amp;lt;script&amp;gt;태그에서 사용해보겠습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;DOM은 문서 객체 모델이라고도 합니다. DOM은 문서의 구조화된 표현과 DOM 구조에 접근할 수 있는 방법을 제공하여,&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;문서의 구조나 스타일, 내용 등을 변경할 수 있게 합니다.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;DOM 속성이나 메소드에 대한 자세한 설명은 &lt;a href=&quot;https://www.w3schools.com/jsref/dom_obj_document.asp&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;w3chools링크&lt;/a&gt;를 참고하시면 좋을 것 같습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 400px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99ECEF345BB9B81C41&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99ECEF345BB9B81C41&quot; width=&quot;400&quot; height=&quot;351&quot; filename=&quot;스크린샷 2018-10-07 오후 4.38.46.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위처럼 Hello world를 찍는 방법은 매우 간단합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;하지만 왜 JavaScript를 사용하여 Hello world를 화면에 나타내는 걸까요? HTML의 태그를 통해서 간단하게 텍스트를&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;표현할 수 있을텐데.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이유는 바로 JavaScript는 데이터를 동적으로 화면에 표현할 수 있기 때문입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위의 Hello world같은 경우는 &amp;lt;h1&amp;gt;태그를 사용해서도 간단하게 표현할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;하지만, 동적인 데이터( 예를 들어&amp;nbsp;간단한 계산된&amp;nbsp;데이터를 표현하고 싶을 경우 )를 표현하고 싶은경우 JavaScript를 사용&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;하여 웹 페이지 화면에 표현할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 400px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99AF5A395BB9B93728&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99AF5A395BB9B93728&quot; width=&quot;400&quot; height=&quot;423&quot; filename=&quot;스크린샷 2018-10-07 오후 4.43.29.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위처럼 간단하게 JavaScript를 사용하여 10232 * 42 의 결과값을 웹 페이지에 표현하게됩니다.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;center&gt;
&lt;script async=&quot;&quot; src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;!-- banner_01 --&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block&quot; data-ad-client=&quot;ca-pub-5799939464120235&quot; data-ad-slot=&quot;4078831414&quot; data-ad-format=&quot;auto&quot; data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
(adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/center&gt;
&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;JavaScript 이벤트 사용해보기&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p style=&quot;text-align: left;&quot;&gt;JavaScript를 사용하여 다양한 이벤트를 사용할 수 있습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;수많은 이벤트 중 onchange, onclick, onmouseover, onmouseout, onkeydown, onload에 대해 정리해보았습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;onchange&lt;/h3&gt;&lt;/div&gt;&lt;div&gt;onchange 이벤트는 HTML 엘리먼트가 변경되었을 때 발생하는 이벤트.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;onclick&lt;/h3&gt;&lt;div&gt;onclick 이벤트는 HTML 엘리먼트를 클릭했을때 발생하는 이벤트.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3&gt;onmouseover&lt;/h3&gt;&lt;div&gt;onmouseover 이벤트는 마우스를 HTML 엘리먼트 위로 이동하게되면 발생하는 이벤트.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3&gt;onmouseout&lt;/h3&gt;&lt;div&gt;onmouseout 이벤트는 마우스를 HTML 엘리먼트 밖으로 이동하면 발생하는 이벤트.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3&gt;onkeydown&lt;/h3&gt;&lt;div&gt;onkeydown 이벤트는 유저가 키보드를 눌렀을때 발생하는 이벤트.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3&gt;onload&lt;/h3&gt;&lt;div&gt;onload 이벤트는 웹 브라우저가 페이지를 불러오는것을 완료했을때 발생하는 이벤트.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>웹/JavaScript</category>
      <category>Document</category>
      <category>JavaScript</category>
      <category>javascript event</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/59</guid>
      <comments>https://eso0609.tistory.com/59#entry59comment</comments>
      <pubDate>Sun, 7 Oct 2018 17:13:08 +0900</pubDate>
    </item>
    <item>
      <title>맥 모하비( Mac Mojave ) 설치부터 기능까지</title>
      <link>https://eso0609.tistory.com/58</link>
      <description>&lt;h1&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;맥 모하비 ( Mac Mojave )&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;&lt;div&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;맥 OS 모하비가 2018년 9월 24일 출시되었습니다. 맥OS 하이 시에라 ( High Sierra )에 이은 대형 업데이트입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;모하비 업데이트 사항으로 사용자 인터페이스에 다크 모드 추가, 갤러리 뷰, 퀵 액션, 스택 사용으로 인한 파일 정리 등&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;사용자 편의를 위한 기능이 추가되거나 업데이트 되었습니다. 업데이트된 기능들 중에 맥 사용자의 이목을 끌만한 기능&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;지금부터 함께 살펴보겠습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;blockquote class=&quot;tx-quote-tistory&quot;&gt;&lt;h2&gt;맥 모하비( Mac Mojave ) 설치&lt;br /&gt;&lt;/h2&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;애플에서는 모하비부터 앱 스토어를 강조하기&amp;nbsp;위해 기존 소프트웨어 업그레이드 기능을&amp;nbsp;통해서 버전을 업데이트 하는 대신&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;앱 스토어를 통해 다운받는 형식으로 방식의 변화를 주었습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 580px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99ADEF465BB22F4702&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99ADEF465BB22F4702&quot; width=&quot;580&quot; height=&quot;466&quot; filename=&quot;스크린샷 2018-10-01 오후 1.23.38.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;앱 스토어에 들어가서 모하비를 다운로드 하시거나,&amp;nbsp; &lt;a href=&quot;https://itunes.apple.com/kr/app/macos-mojave/id1398502828?mt=12&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;macOS Mojave 다운로드 링크&lt;/a&gt;를&amp;nbsp;통해 다운로드 받으실 수 있습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;h2&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;다크 모드&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;첫 번째로, 다크 모드 입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;평소 개발툴을 사용할 때에도 전체적으로 검은색 바탕에서 작업을 해서인지, 검은색이 좋더라구요.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;맥 모하비의 다크모드는 검은색 테마라고 생각하시면 됩니다.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;화면이 밝으면 눈이 너무 쉽게 피로해지는 기분이 들던 분들이&amp;nbsp;오랫동안 기다렸던 기능입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;다크 모드를 사용하는 방법은 매우 간단합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. Apple(&amp;nbsp;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;SF Pro KR&amp;quot;, &amp;quot;SF Pro Text&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;Apple Gothic&amp;quot;, &amp;quot;HY Gulim&amp;quot;, MalgunGothic, &amp;quot;HY Dotum&amp;quot;, &amp;quot;Lexi Gulim&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 17px;&quot;&gt;&amp;nbsp;&lt;/span&gt;) 메뉴에서 '시스템 환경설정'을 선택합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2. '일반'을 클릭합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;3. 윈도우 상단의 화면 모드 옵션에서 '다크 모드'를 선택합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;( 모하비 기능중 다이나믹 데스크탑 사용 시 다크 모드를 켜면 야간 이미지만 보입니다. )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: left; clear: none; float: none; margin-left: 2em; line-height: 0.5;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 400px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99A9013A5BB2217729&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99A9013A5BB2217729&quot; width=&quot;400&quot; height=&quot;268&quot; filename=&quot;스크린샷 2018-10-01 오후 10.26.07.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 400px; margin-left: 10px;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/991BBB3A5BB2217809&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F991BBB3A5BB2217809&quot; width=&quot;400&quot; height=&quot;268&quot; filename=&quot;스크린샷 2018-10-01 오후 10.27.14.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;margin-left: 10px;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;[ 출처&amp;nbsp;&lt;a href=&quot;https://www.apple.com/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;https://www.apple.com&amp;nbsp;&lt;/a&gt;]&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;별 것 아니라고 생각하실 수도 있지만, 작업중인 부분이 확실하게 더 부각되어, 사용자가 보고자 하는 콘텐츠를 상대적으로&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;강조시키는 효과또한 기대해 볼 수 있습니다.&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;스택 사용&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;스택 기능은 이미지, 프리젠테이션, PDF 및 텍스트 등의 파일을 파일 유형별로 손쉽게 정렬해주는 기능입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;비슷한 기능을 제공해주는 앱도 있다고하는데, 애플에서 정식으로 지원해주는 만큼 믿고 써보는 것도 좋을 것 같습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9996C13C5BB223DD10&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9996C13C5BB223DD10&quot; width=&quot;900&quot; height=&quot;508&quot; filename=&quot;Apple-MacBookPro-iOS12-Stacks-09252018.gif&quot; filemime=&quot;image/gif&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;[ 출처 &lt;a href=&quot;https://www.apple.com&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;https://www.apple.com &lt;/a&gt;]&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;스택은 기본적으로 이미지, PDF, 프레젠테이션등 유형별로 그룹화 되지만, 스택을 수정된 날짜, 태그 등 기타 카테고리별로&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;그룹화하기 위해서는 메뉴 막대-&amp;gt; 보기-&amp;gt; '다음으로 스택 그룹화'를 선택하시면 됩니다.&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;스크린샷&lt;/span&gt;&lt;br /&gt;&lt;/h2&gt;&lt;div&gt;새로워진 스크린샷 기능을 통해, 새로운 동영상 녹화 기능에 빠르게 접근할 수 있고, 사용자의 편의성을 한 층 더&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;업그레이드 시켜줄 제어 기능이 화면에 표시됩니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;해당 기능은 스크린샷 유틸리티 또는 shift-command-5 단축키를 통해 이용할 수 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;또한, 타이머 설정 옵션과 더불어 스크린샷 저장 장소 선택, Mac 화면의 정지 화면 및 동영상을 포착할 수 있는 다양한 제어&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;기능이 제공됩니다. 기존 QuickTime 앱에서 제공되었던 화면 녹화기능이었지만 이번 업데이트를 통해서 사용자의&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;편의성을 더욱 향상시켜 주었습니다.&amp;nbsp;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 580px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99F28D4D5BB22CFC08&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99F28D4D5BB22CFC08&quot; width=&quot;580&quot; height=&quot;364&quot; filename=&quot;스크린샷 2018-10-01 오후 11.19.09.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;[ 출처 : &lt;a href=&quot;https://www.apple.com&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;https://www.apple.com&lt;/a&gt; ]&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;위 화면은 shift-command(&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;SF Pro KR&amp;quot;, &amp;quot;SF Pro Text&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;Apple Gothic&amp;quot;, &amp;quot;HY Gulim&amp;quot;, MalgunGothic, &amp;quot;HY Dotum&amp;quot;, &amp;quot;Lexi Gulim&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 17px;&quot;&gt;⌘&lt;/span&gt;)&amp;nbsp;또는 스크린샷 유틸리티를 사용했을때 나타나는 제어 기능 화면입니다.&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;해당 버튼들을 통해 전체 화면 캡처&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&amp;nbsp;(&amp;nbsp;&lt;/span&gt;&lt;img src=&quot;https://support.apple.com/library/content/dam/edam/applecare/images/en_US/macos/macos-mojave-screenshot-menu-fullscreen-inline-icon.png&quot; width=&quot;24&quot; alt=&quot;전체 화면 캡처 버튼&quot; style=&quot;border: 0px; word-break: keep-all; max-width: 100%; display: inline; color: rgb(51, 51, 51); font-family: &amp;quot;SF Pro KR&amp;quot;, &amp;quot;SF Pro Text&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;Apple Gothic&amp;quot;, &amp;quot;HY Gulim&amp;quot;, MalgunGothic, &amp;quot;HY Dotum&amp;quot;, &amp;quot;Lexi Gulim&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 17px; height: auto !important;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&amp;nbsp;), 윈도우 하나 캡처 (&amp;nbsp;&lt;/span&gt;&lt;img src=&quot;https://support.apple.com/library/content/dam/edam/applecare/images/en_US/macos/macos-mojave-screenshot-menu-window-inline-icon.png&quot; width=&quot;24&quot; alt=&quot;선택한 윈도우 캡처 버튼&quot; style=&quot;border: 0px; word-break: keep-all; max-width: 100%; display: inline; color: rgb(51, 51, 51); font-family: &amp;quot;SF Pro KR&amp;quot;, &amp;quot;SF Pro Text&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;Apple Gothic&amp;quot;, &amp;quot;HY Gulim&amp;quot;, MalgunGothic, &amp;quot;HY Dotum&amp;quot;, &amp;quot;Lexi Gulim&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 17px; height: auto !important;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&amp;nbsp;), 선택한 부분 화면 캡처 (&amp;nbsp;&lt;/span&gt;&lt;img src=&quot;https://support.apple.com/library/content/dam/edam/applecare/images/en_US/macos/macos-mojave-screenshot-menu-selection-inline-icon.png&quot; width=&quot;24&quot; alt=&quot;선택 부분 캡처 버튼&quot; style=&quot;border: 0px; word-break: keep-all; max-width: 100%; display: inline; color: rgb(51, 51, 51); font-family: &amp;quot;SF Pro KR&amp;quot;, &amp;quot;SF Pro Text&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;Apple Gothic&amp;quot;, &amp;quot;HY Gulim&amp;quot;, MalgunGothic, &amp;quot;HY Dotum&amp;quot;, &amp;quot;Lexi Gulim&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 17px; height: auto !important;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;)등의 기능을 이용할 수&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;SF Pro KR&amp;quot;, &amp;quot;SF Pro Text&amp;quot;, &amp;quot;SF Pro Icons&amp;quot;, &amp;quot;Apple Gothic&amp;quot;, &amp;quot;HY Gulim&amp;quot;, MalgunGothic, &amp;quot;HY Dotum&amp;quot;, &amp;quot;Lexi Gulim&amp;quot;, &amp;quot;Helvetica Neue&amp;quot;, Helvetica, Arial, sans-serif; font-size: 17px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;저처럼 이미지나 동영상파일을 자주 사용하시는 분들에게는 최고의 업데이트 기능이 되겠네요.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;이상 포스팅을 마치겠습니다. 읽어주셔서 감사합니다.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: rgb(51, 51, 51); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;자료&amp;nbsp;출처 :&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://www.apple.com/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot; style=&quot;text-align: center;&quot;&gt;https://www.apple.com&lt;/a&gt;&lt;span style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>이슈</category>
      <category>Mac Mojave</category>
      <category>맥 OS</category>
      <category>맥 다크모드</category>
      <category>맥 모하비</category>
      <category>맥 모하비 업데이트</category>
      <category>맥 스크린샷</category>
      <category>맥 스택</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/58</guid>
      <comments>https://eso0609.tistory.com/58#entry58comment</comments>
      <pubDate>Mon, 1 Oct 2018 23:54:11 +0900</pubDate>
    </item>
    <item>
      <title>position 파헤치기</title>
      <link>https://eso0609.tistory.com/57</link>
      <description>&lt;h1&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;position 종류&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;html &amp;amp;&amp;amp; css에서 엘리먼트의 위치를 지정하는 방법은 4가지로 분류됩니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;static (정적 위치), relative (상대적 위치), absolute (절대적 위치), fixed(고정 위치)가 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;position:static;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;우선 static의 예를 살펴보죠. 아래 이미지는 각각의 엘리먼트에 아무 속성도 추가하지 않았을 때의 화면입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 700px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9919634B5BACBBEF0B&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9919634B5BACBBEF0B&quot; width=&quot;700&quot; height=&quot;112&quot; filename=&quot;스크린샷 2018-09-27 오후 8.15.15.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;other라는 독자적인 엘리먼트, 그리고 parent를 하위의 child엘리먼트가 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 304px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9981CA4D5BACBC4E39&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9981CA4D5BACBC4E39&quot; width=&quot;304&quot; height=&quot;213&quot; filename=&quot;스크린샷 2018-09-27 오후 8.17.17.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;child 엘리먼트에 position:static; 을 추가해보고, 화면을 보세요 달라지는게 있나요?&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;달라지는 건 없습니다. 즉 position의 default value는 static이라는 것을 알 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;center&gt;
&lt;script async=&quot;&quot; src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;!-- banner_01 --&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block&quot; data-ad-client=&quot;ca-pub-5799939464120235&quot; data-ad-slot=&quot;4078831414&quot; data-ad-format=&quot;auto&quot; data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
(adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/center&gt;
&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;position:relative;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;relative라는 단어에서 유추할 수 있듯이, position:relative는 상대적은 위치값을 나타냅니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;위와 같이 other, parent, child엘리먼트가 존재할 때 child에 position을 relative로 설정해 보겠습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 294px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99A373455BACBE4217&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99A373455BACBE4217&quot; width=&quot;294&quot; height=&quot;177&quot; filename=&quot;스크린샷 2018-09-27 오후 8.25.37.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;화면이 어떻게 변했을까요?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;( left는 왼쪽을 기준으로 50px만큼 떨어져서 위치해라, top은 상단을 기준으로 100px만큼 떨어져서 위치시켜라. )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/997254475BACBEEF22&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F997254475BACBEEF22&quot; width=&quot;900&quot; height=&quot;246&quot; filename=&quot;스크린샷 2018-09-27 오후 8.26.04.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;위와 같은 이미지처럼 child는 parent를 원래의 위치에서 left, top값만큼 위치가 변경된 것을 알 수 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;position:absolute;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;absolute는 절대적 위치를 나타냅니다. absolute에서는 다소 헷갈리는 부분이 있으니 집중해주세요.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;absolute는 부모의 position값이 static이냐 아니냐에 따라 상이한 결과를 보여줍니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;parent의 position을 설정하지 않거나, static으로 주었을 때 부터 살펴보겠습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 410px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/993934435BACC06725&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F993934435BACC06725&quot; width=&quot;410&quot; height=&quot;332&quot; filename=&quot;스크린샷 2018-09-27 오후 8.34.48.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;위의 경우, child는 parent기준으로 left,top이 적용될까요? 아니면 html (최상위) 기준으로 적용될까요?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;정답은 html 기준으로 적용됩니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99D06C425BACC12304&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99D06C425BACC12304&quot; width=&quot;900&quot; height=&quot;147&quot; filename=&quot;스크린샷 2018-09-27 오후 8.37.51.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;absolute는 바로 상위의 엘리먼트에서 position:static; 혹은 position을 설정하지 않았다면, 최상위 html 엘리먼트를 기준&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;으로 위치를 설정하게 됩니다. 또한, absoulte의 특성상 부모 엘리먼트와의 연결이 끊기게 되면서 부모 엘리먼트와는 무관&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;하게 됩니다. 즉, 크기를 지정할 수 없게 되면서 자기 자신의 콘텐트 크기만 남게되는것입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;( 이 경우, 크기를 지정하고 싶으면 width,height값을 따로 주면됩니다. )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;그러면서 자연스럽게? 부모 또한 자식 엘리먼트가 차지하고 있던 공간을 빼버리게 되는 것입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;이제 parent의 position을 relative( static이 아닌 다른 값 )을 넣어보겠습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99161E465BACC23424&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99161E465BACC23424&quot; width=&quot;900&quot; height=&quot;214&quot; filename=&quot;스크린샷 2018-09-27 오후 8.42.24.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위에서 볼 수 있듯이, 이제는 최상위 엘리먼트인 html을 기준으로 child의 위치값이 정해지는 것을 확인할 수 있습니다.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;참고 자료 :&amp;nbsp;https://opentutorials.org/course/2418/13414&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>웹/HTML</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/57</guid>
      <comments>https://eso0609.tistory.com/57#entry57comment</comments>
      <pubDate>Thu, 27 Sep 2018 20:44:13 +0900</pubDate>
    </item>
    <item>
      <title>초간단 웹 호스팅(github) &amp;amp;&amp;amp; 웹 서버 설치 ( MAC )</title>
      <link>https://eso0609.tistory.com/56</link>
      <description>&lt;h1&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;웹 호스팅이란?&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;&lt;div&gt;웹 호스팅이란 무엇일까?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;웹 브라우저는 웹 서버에 데이터를 요청 (request)하고, 웹 서버는 웹 브라우저에 응답 (respnse) 합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;이를 통해 우리는 웹 서버의 하드디스크에 있는 특정 파일을 화면에 나타낼 수 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;하지만, 웹 서버를 우리가 직접 운영하는것은 인터넷에 대한 이해 뿐만아니라 여러가지 지식이 수반됩니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;이 어려움들을 해결해 주는 것이 웹 호스팅 서비스입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;호스팅이란 '한 행사의 사회자로서의 역할을 수행하는 것' 입니다. - 위키 백과&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;즉, 웹 호스팅은 개인과 단체가 월드 와이드 웹을 통하여 웹사이트를 제공하는 것을 뜻합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;웹 호스트는 인터넷 연결을 제공할뿐 아니라, 일반적으로 데이터 센터에서 클라이언트 이용에 대한 임대 또는 소유하는&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;서버의 공간을 제공하는 것입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;개발자라면 깃 헙(github)에 익숙할 것입니다. 지금부터 깃 헙을 이용하여 웹 호스팅을 해보겠습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;먼저 깃 헙에 로그인 후, 오른쪽 상단의 아이콘을 클릭한 후, New repository를 눌러 새로운 저장공간을 생성합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;그리고 작성한 코드를 업로드합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 700px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99B834405BA48AB715&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99B834405BA48AB715&quot; width=&quot;700&quot; height=&quot;476&quot; filename=&quot;스크린샷 2018-09-21 오후 3.07.23.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;그러면 위와 같은 화면을 볼 수 있습니다. 위 화면에서 오른쪽 상단의 Settings를 눌러줍니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;화면이 이동한 뒤 스크롤을 아래로 내리면 GitHub Pages가 보입니다.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 780px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/992D473B5BA48B0B38&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F992D473B5BA48B0B38&quot; width=&quot;780&quot; height=&quot;430&quot; filename=&quot;스크린샷 2018-09-21 오후 2.14.26.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Select source에서 master branch를 클릭하고 저장합니다.&amp;nbsp;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 640px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99FAFD345BA48B3D04&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99FAFD345BA48B3D04&quot; width=&quot;640&quot; height=&quot;543&quot; filename=&quot;스크린샷 2018-09-21 오후 2.14.43.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;그러면 파란색 창에 주소가 생성된 것을 볼 수 있습니다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;앞으로 위의 도메인이 나만의 웹 사이트 주소가 되는 것입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;해당 주소를 클릭하면 작성해둔 코드에 해당하는 웹 페이지를 확인할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h1&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;웹 서버 설치 ( MAC )&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;&lt;div&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;맥에서 웹 서버를 설치하는 것은 간단합니다 !&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;요즘에는 Apache를 직접 설치하고, 환경 설정해주는 것은 상당히 까다롭습니다. 하지만 MAMP를 다운 받으시면&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;비교적 간단하게 작업을 진행할 수 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://bitnami.com/stack/mamp&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;MAMP 링크&lt;/a&gt;를 통해 프로그램을 다운로드합니다. ( MAMP는 Mac Apache Mysql Php의 약자입니다. )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;프로그램을 다운로드 한 후 해당 파일을 실행하면 웹 페이지가 하나 뜨고, 프로그램이 하나 실행됩니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 640px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9966A43D5BA48E3303&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9966A43D5BA48E3303&quot; width=&quot;640&quot; height=&quot;454&quot; filename=&quot;스크린샷 2018-09-21 오후 2.52.33.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;위의 프로그램은 Management Console인데, Apache라는 웹 서버를 관리하는 도구입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Manage Servers를 클릭하면 아래와 같은 화면이 뜹니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 640px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99E2D83C5BA48E890F&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99E2D83C5BA48E890F&quot; width=&quot;640&quot; height=&quot;449&quot; filename=&quot;스크린샷 2018-09-21 오후 2.53.00.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;옆의 Start, Stop, Restart, Configure를 통해서 웹 서버를 관리할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;어떻게 확인할 수 있을까요?&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;아까 프로그램 설치를 마친 뒤 화면에 나타는 웹 페이지로 확인할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;서버를 멈춘뒤 웹 페이지에서 새로고침을 누르면 어떻게 될까요?&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 640px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9904A6365BA48F001F&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9904A6365BA48F001F&quot; width=&quot;640&quot; height=&quot;396&quot; filename=&quot;스크린샷 2018-09-21 오후 2.53.59.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;






&lt;p style=&quot;margin:0in;font-family:Calibri;font-size:11.0pt&quot; lang=&quot;en-US&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;margin:0in;font-family:Calibri;font-size:11.0pt&quot; lang=&quot;en-US&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;위 화면에서 127.0.0.1은 IP ( 인터넷 프로토콜 ) 즉, 인터넷 규약을 의미합니다. 이는 인터넷에 연결되어 있는&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;각각의 컴퓨터들의 고유한 주소를 의미하죠.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;그리고 뒤의 :8080은 포트 번호를 뜻합니다. MAC에는 기본적으로 웹 서버가 깔려있다고합니다. 기존 8080으로 접속이&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;안되셨다면 그것을 지워보는 것도 한 방법이 될 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h3&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;웹 브라우저와 웹 서버 실습 방법&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;저희에게는 실습용 컴퓨터가 두 대 있습니다. 바로 지금 사용하시는 컴퓨터와 스마트폰입니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;위에서 설치한 MAMP를 통해 서버를 운영하고, 이를 웹 브라우저에 띄워주었습니다. 웹 브라우저에서 띄워주는&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;파일들의 위치는 응용프로그램 -&amp;gt; mampstack~ -&amp;gt; apache2 -&amp;gt; htdocs에 존재합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;이전에 작성한 자신의 프로젝트를 해당 폴더에 넣고, 위의 웹 브라우저를 새로고침하면 자신이 작성한 코드에&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;해당하는 화면을 볼 수 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;또한 같은 IP 에 접속한 두 대의 컴퓨터에서도 확인할 수 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;(스마트폰으로 같은 IP에 접속해서 확인해보세요.)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;참고 자료 :&amp;nbsp;https://www.youtube.com/watch?v=roETNgQf3x8&amp;amp;index=23&amp;amp;list=PLuHgQVnccGMDZP7FJ_ZsUrdCGH68ppvPb&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>웹/HTML</category>
      <category>Apache</category>
      <category>github hosting</category>
      <category>HTML</category>
      <category>MAMP</category>
      <category>웹 호스팅</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/56</guid>
      <comments>https://eso0609.tistory.com/56#entry56comment</comments>
      <pubDate>Sat, 22 Sep 2018 01:00:00 +0900</pubDate>
    </item>
    <item>
      <title>HTML 태그를 어떻게 사용해야 할까?</title>
      <link>https://eso0609.tistory.com/55</link>
      <description>&lt;h1&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;HTML 태그( tag )란?&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;&lt;div&gt;태그는 원래 우편물이나 화물, 택배물 등에 붙여 화물의 분류나 송수신인, 취급 주의 등을 나타내는 인식표였다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;뒤에 옷 등에도 쓰였고, &lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;&lt;b&gt;컴퓨터 프로그램 등에도 비슷하게 쓰이게 되었다.&lt;/b&gt;&lt;/span&gt; - 위키 백과&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;말 그대로 태그는 컴퓨터 프로그램에서도 하나의 '인식표'로 인식하면 됩니다.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;정확하게 정의를 내리자면, HTML에서 태그는&lt;b&gt; HTML 문서를&amp;nbsp;&lt;/b&gt;&lt;b&gt;이루는 문법적 표시라고 할 수 있습니다.&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;&lt;h2&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;HTML 태그의 종류&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;1960년대 인터넷이 등장하고, 1990년 웹이 등장했습니다. 1990년대에 등장한 첫 웹 보셨나요?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;생활코딩 이고잉님의 강의를 들으며 '&lt;a href=&quot;http://info.cern.ch/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;웹의 메소포타미아&lt;/a&gt;'라는 곳에 저도 방문을 해보았습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;( 이고잉님의 강의를 듣게 되면, 정말 웹에 대한 흥미가 생기고 보다 재미있게 공부할 수 있는 것 같습니다. )&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;본론으로 돌아와서 html 태그의 종류에 대해 알아보겠습니다.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;html에서&amp;nbsp;2017년도 기준 150+@개&lt;/b&gt; 정도의 태그가 존재합니다. 물론 모든 태그를 공부해야겠지만, 시작은 중요 태그들을&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;우선적으로 공부하는 것이 좋은 것 같습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 580px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9954E44D5BA45A9622&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9954E44D5BA45A9622&quot; width=&quot;580&quot; height=&quot;651&quot; filename=&quot;스크린샷 2018-09-20 오후 3.52.25.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위 이미지를 보면, 수많은 태그 중에 자주 쓰이는 태그가 무엇이고, 그 태그를 위주로 먼저 학습하는 것이 좋겠다는&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;생각이 들겁니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;html에서 태그를 크게보면 최상위 태그인 &amp;lt;html&amp;gt; 태그, 본문을 설명하는&amp;nbsp;&amp;lt;head&amp;gt; 태그, 본문을 나타내는 &amp;lt;body&amp;gt; 태그&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;학습한 내용을 정리하면 구조를 한 번 정리해보았습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 400px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/997655335BA451B229&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F997655335BA451B229&quot; width=&quot;400&quot; height=&quot;404&quot; filename=&quot;스크린샷 2018-09-21 오전 11.01.17.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;최상위 &amp;lt;html&amp;gt;태그 위에 html에서는 관용적으로 &amp;lt;!doctype html&amp;gt;태그를 써줍니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;위의 형식아래에서 수많은 태그를 사용하면 됩니다. 수많은 태그를 &lt;a href=&quot;https://www.w3schools.com/tags/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;여기 링크&lt;/a&gt;를 통해서 검색하는 능력만 있으셔도 충분히&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;html을 정복할 수 있습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3&gt;적절한 태그 사용의 중요성&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;제가 포스팅할 태그는 &amp;lt;br&amp;gt;태그와 &amp;lt;p&amp;gt;태그입니다. &amp;lt;br&amp;gt;태그는 줄바꿈 태그이고, &amp;lt;p&amp;gt; 태그는 문단을 나눌 수 있게 해주는&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;태그입니다. 처음 사용할 때는 &amp;lt;br&amp;gt;을 연달아서 사용하여 문단을 나누었습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;하지만, HTML의 사용목적과 그 중요함을 알게되고, 각 사용목적에 따라 나누어 사용하게 되었습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;그 사용목적과 중요함은 뭘까요?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;HTML이 중요한 이유는 시각적인 이미지가아닌, 정보로서의 표현이 중요합니다.&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99C10B4E5BA4556D1B&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99C10B4E5BA4556D1B&quot; width=&quot;900&quot; height=&quot;319&quot; filename=&quot;스크린샷 2018-09-21 오전 11.19.53.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;위의 코드에서 &amp;lt;br&amp;gt;태그를 사용하여 단순히 시각적으로 줄이 바뀌었구나를 표현했습니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;lt;p&amp;gt;태그를 이용해서는 문단을 표현했구요.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;오늘날 검색 엔진들은 이 태그들을 키워드로 수많은 웹사이트들을 분석하고, 분류합니다.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;블로그를 하는 사람에게 검색 누락은 상상하기도 싫은 일이죠.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;검색 엔진들의 분석에서 좋은 결과를 얻기 위해서는 바로 이 태그들을 적절한 곳에서, 적절한 태그로 사용해야합니다!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;티스토리를 예로 든다면,&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 849px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/997371465BA4564707&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F997371465BA4564707&quot; width=&quot;849&quot; height=&quot;71&quot; filename=&quot;스크린샷 2018-09-21 오전 11.23.33.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;제목을 입력할 때 어떻게 하시나요?&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;글을 입력후 글씨체를 바꾸고, 폰트를 바꾸시나요? 아니면 폰트를 바꾸고 글씨체를 바꾸시나요?&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;아니면, 제일 왼쪽의 본문을 클릭하시나요? ( 저는 본문을 클릭해서 바꾼적이 없습니다. )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;앞 포스팅에서 말했듯이, html을 배우고 난 후와 전 확연히 다를겁니다. 티스토의 경우 HTML 체크박스를 클릭하여&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;HTML 코드를 볼 수 있습니다. 이 기능을 이용해서 태그를 적절하게 사용하고 있는지 꼭 확인해보세요.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;&lt;div style=&quot;margin-left: 20em; text-align: left;&quot;&gt;참고 자료 :&amp;nbsp;https://www.youtube.com/watch?v=pYOEy_mAMpI&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>웹/HTML</category>
      <category>!doctype html</category>
      <category>body</category>
      <category>br</category>
      <category>head</category>
      <category>HTML</category>
      <category>p</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/55</guid>
      <comments>https://eso0609.tistory.com/55#entry55comment</comments>
      <pubDate>Fri, 21 Sep 2018 11:35:23 +0900</pubDate>
    </item>
    <item>
      <title>처음이자 마지막 HTML - 실습환경 구축 ( Atom )</title>
      <link>https://eso0609.tistory.com/54</link>
      <description>&lt;h1&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;HTML 실습환경 구축 ( Atom )&lt;/span&gt;&lt;/span&gt;&lt;/h1&gt;&lt;p&gt;웹을 시작하게 되면서, 가장 기본이라고 할 수 있는 html 공부부터 시작했습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;제목에서 알 수 있듯이 처음이자 마지막인 html ( 시작하자마자 끝나는... )&amp;nbsp;학습 내용 포스팅을 시작하겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;우선 html을 언어를 사용하고, 편집할 수 있는 툴을 설치해야합니다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;저는 Atom ( 아톰 ) 을 사용했습니다. ( &lt;a href=&quot;https://atom.io/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;https://atom.io/&lt;/a&gt; 링크를 통해 해당하는 OS에 맞게 다운로드하시면 됩니다. )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/999D72455BA36AF925&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F999D72455BA36AF925&quot; width=&quot;900&quot; height=&quot;527&quot; filename=&quot;스크린샷 2018-09-20 오후 2.58.47.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;아톰을 다운로드 받고, 실행하면 위와 같은 화면을 볼 수 있습니다. 아톰 설치 후 프로젝트의 디렉토리가 될 폴더를 만듭니다&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;폴더를 만들고, File -&amp;gt; Open...을 통해 해당 폴더를 우리의 프로젝트의 디렉토리로 정합니다.&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 580px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99FF2B455BA36AF933&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99FF2B455BA36AF933&quot; width=&quot;580&quot; height=&quot;619&quot; filename=&quot;스크린샷 2018-09-20 오후 3.00.51.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이제 html 파일을 만들어야 겠죠?&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 320px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9924FD465BA36BC232&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9924FD465BA36BC232&quot; width=&quot;320&quot; height=&quot;508&quot; filename=&quot;스크린샷 2018-09-20 오후 3.03.12.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;해당 기능을 통해 .html 확장자의 파일을 생성합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;해당 파일에 Hello web을 찍어봐요. ( 개발자 숙명&amp;nbsp;Hello world 부터 시작하는 느낌으로 )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;작성한 코드( Hello web 뿐이지만 ) 를 웹브라우저로 실행해보겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;( 마이크로소프트 엣지 브라우저를 사용하시는분들은 다른 브라우저를 이용하셔야 합니다. )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;우선 사용하시는 브라우저를 열고 command o(알파벳)을 눌러줍니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;( 저는 맥을 사용중이기 때문에 단축키는 command o(알파벳)입니다. 윈도우의 경우 command대신에 ctrl이 되겠습니다. )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;작성한 파일을 열어줍니다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 580px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9926B33F5BA36E4722&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9926B33F5BA36E4722&quot; width=&quot;580&quot; height=&quot;254&quot; filename=&quot;스크린샷 2018-09-20 오후 3.09.08.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;너무 쉽죠?&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;시작이 반이라는 말이 있습니다. html이 그렇습니다. 이제 반은 끝낸 겁니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;제가 이고잉님의 강의를 들으면서 크게 공감한 부분이 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;'여러분의 인생은 태그를 배우기 전과, 후로 나뉠 수 있다' 특히, 블로그를 하는 저로서는 크게 공감 갔습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이제 본격적으로 HTML을 시작해보도록 합시다! ( 다음 포스팅에서 )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;참고 동영상&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;- https://www.youtube.com/watch?v=tZooW6PritE&amp;amp;index=1&amp;amp;list=PLuHgQVnccGMDZP7FJ_ZsUrdCGH68ppvPb&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>웹/HTML</category>
      <category>ATOM</category>
      <category>HTML</category>
      <category>웹</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/54</guid>
      <comments>https://eso0609.tistory.com/54#entry54comment</comments>
      <pubDate>Thu, 20 Sep 2018 19:01:18 +0900</pubDate>
    </item>
    <item>
      <title>filter, map 함수 사용법</title>
      <link>https://eso0609.tistory.com/53</link>
      <description>&lt;p&gt;프로젝트를 진행하며, 코틀린의 유용한 함수에 대한 기본적인 코드를 학습하며 정리한 내용을 포스팅해 보았습니다.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;font-size: 24pt; background-color: rgb(0, 0, 0); color: rgb(255, 255, 255);&quot;&gt;filter&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;filter함수는 map함수와 함께 컬렉션을 활용할 때 기반이 되는 함수입니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 400px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/997E323F5BA21C0718&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F997E323F5BA21C0718&quot; width=&quot;400&quot; height=&quot;277&quot; filename=&quot;스크린샷 2018-09-19 오후 6.50.16.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위와 같이 Person 형태의 people 리스트가 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;woman은 람다식을 변수에 저장한 것으로, Person객체가 W( woman ) 이면 true를 반환합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/997489385BA21C9E2E&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F997489385BA21C9E2E&quot; width=&quot;900&quot; height=&quot;46&quot; filename=&quot;스크린샷 2018-09-19 오후 6.53.15.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;people에 filter 함수를 사용하여 여자인 객체만을 갖는 새로운 컬렉션을 출력해 보았습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99AD6D495BA21EBA0F&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99AD6D495BA21EBA0F&quot; width=&quot;900&quot; height=&quot;23&quot; filename=&quot;스크린샷 2018-09-19 오후 7.01.59.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99AD2E365BA2209B0E&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99AD2E365BA2209B0E&quot; width=&quot;900&quot; height=&quot;51&quot; filename=&quot;스크린샷 2018-09-19 오후 7.10.15.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;filter 람다에서는 it이 Person을 가르키고 있습니다. 이를 이요하여 27살 이상인 사람들을 출력해보겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99E2CB365BA21F4401&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99E2CB365BA21F4401&quot; width=&quot;900&quot; height=&quot;53&quot; filename=&quot;스크린샷 2018-09-19 오후 7.04.38.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이처럼 filter함수를 이용하여 컬렉션에서 원치 않는 원소를 간단하게 제거할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;하지만 filter는 원소를 변환할 수는 없습니다. 원소를 변환하기 위해서는 map 함수를 사용해야 합니다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(255, 255, 255); font-size: 32px; background-color: rgb(229, 69, 208);&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: rgb(255, 255, 255); font-size: 32px; background-color: rgb(0, 0, 0);&quot;&gt;map&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(255, 255, 255); font-size: 32px; background-color: rgb(229, 69, 208);&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); font-size: 32px; background-color: rgb(255, 255, 255);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;map 함수는 주어진 람다를 컬렉션의 각 원소에 적용한 결과를 모아서 새 컬렉션을 만듭니다.&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;위 filter의 예제에서 27살 초과하는 사람들의 이름으로 map함수를 통해 새로운 컬렉션을 만들 수 있습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 682px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9920913D5BA2213713&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9920913D5BA2213713&quot; width=&quot;682&quot; height=&quot;57&quot; filename=&quot;스크린샷 2018-09-19 오후 7.11.57.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;아래 코드는 위 코드에 코틀린의 멤버 참조 개념을 적용한 것입니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 583px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99083B3E5BA2260C28&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99083B3E5BA2260C28&quot; width=&quot;583&quot; height=&quot;56&quot; filename=&quot;스크린샷 2018-09-19 오후 7.33.30.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;위와 같이 filter와 map 함수의 개념은 생각보다 간단합니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;앞으로의 프로젝트에서는 filter, map 함수를 자주 볼 수 있기를 기대하면서 포스팅을 마치겠습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: right;&quot;&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;참고문헌 : Kotlin in Action&lt;/span&gt;&lt;/p&gt;</description>
      <category>Kotlin/kotlin filter, map 함수</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/53</guid>
      <comments>https://eso0609.tistory.com/53#entry53comment</comments>
      <pubDate>Wed, 19 Sep 2018 19:38:58 +0900</pubDate>
    </item>
    <item>
      <title>RecyclerView 보이는 아이템 먼저 불러오기 -(1)</title>
      <link>https://eso0609.tistory.com/52</link>
      <description>&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;span style=&quot;color: rgb(255, 255, 255); background-color: rgb(229, 69, 208);&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: rgb(0, 0, 0); background-color: rgb(255, 255, 255);&quot;&gt;RecyclerView 화면에 보이는 아이템 추출&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;span style=&quot;color: rgb(255, 255, 255); background-color: rgb(229, 69, 208);&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); background-color: rgb(255, 255, 255);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;span style=&quot;color: rgb(255, 255, 255); background-color: rgb(229, 69, 208);&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); background-color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); font-size: 14pt;&quot;&gt;프로젝트 진행중에 화면에 띄워주어야 할 아이템은 많은데, 불려지는 순서대로 아이템의 이미지를 로딩하다보니&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;span style=&quot;color: rgb(255, 255, 255); background-color: rgb(229, 69, 208);&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); background-color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;스크롤하면서 나중에 불려진 아이템의 이미지 로딩이 상당히 느린 상황을 겪게 되었습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;저는 안드로이드에서 자주사용하는 image load lib로 glide를 사용중입니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;glide에는 priority라는 개념의 메소드가 있죠. 해당 메소드는 glide작업시 우선순위를 두고 우선순위가 높은 이미지부터&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;load하는 것입니다. 간단하게 살펴본 우선순위 타입은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;Priority.Low&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;Priority.NORMAL&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;Priority.HIGH&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;Priority.IMMEDIATE&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;하지만 저는 위의 기능이 필요한 것이 아니었습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;그래서 저는 보이는 아이템, 즉 화면에 출력된 아이템들을 우선순위로 정하고, 해당 아이템의 데이터를 bind 시켜주는&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;작업을 시작 하게되었습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(255, 255, 255); font-size: 32px; background-color: rgb(229, 69, 208);&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: rgb(0, 0, 0); font-size: 32px; background-color: rgb(255, 255, 255);&quot;&gt;findFirstVisibleItemPosition&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(255, 255, 255); font-size: 32px; background-color: rgb(229, 69, 208);&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); font-size: 32px; background-color: rgb(255, 255, 255);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;위 메소드는 화면에 보이는 영역 즉, 조금이라도 보이는 아이템의 포지션을 리턴합니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: center;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 320px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/999F203A5B924D260F&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F999F203A5B924D260F&quot; width=&quot;320&quot; height=&quot;456&quot; filename=&quot;스크린샷 2018-09-07 오후 7.02.43.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;위와&amp;nbsp;같은 아이템이 있다고 가정해 보겠습니다.&lt;/span&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;findFirstVisibleItemPosition()메소드를 이용하면 0&amp;nbsp;을 리턴하게됩니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이는 화면에 보이는 제일 상단의 아이템 (item0) 의 포지션 값입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 32px;&quot;&gt;findFirstCompletelyVisibleItemPosition&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위 메소드는 첫 번째로 설명드린 메소드와는 다소 차이가 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이번에는 조금 다른 UI의 아이템으로 설명드리겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 320px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99C2B63D5B924E1B0A&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99C2B63D5B924E1B0A&quot; width=&quot;320&quot; height=&quot;487&quot; filename=&quot;스크린샷 2018-09-07 오후 7.08.02.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위 사진과 같이 화면이 보인다고 가정해 보죠.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;item0은 사용자의 스크롤에 의해 부분적으로 조금만 보이게됩니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;바로 부분적으로 조금이라도 보이는 부분! 바로 findFirstCompletelyVisibleItemPosition의 리턴값입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;그렇기 때문에,&amp;nbsp;item0의 포지션값인 0을 리턴하게됩니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;font-size: 24pt; background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;findLastVisibleItemPosition&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이제 감이 오실겁니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;findLastVisibleItemPosition은 화면에 보이게되는 마지막 아이템을 리턴하게 됩니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 320px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9955793E5B924D7A14&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9955793E5B924D7A14&quot; width=&quot;320&quot; height=&quot;461&quot; filename=&quot;스크린샷 2018-09-07 오후 7.05.18.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;font-size: 24pt; background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;findLastCompletelyVisibleItemPosition&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;마찬가지로 화면에 조금이라도 보이게되는 마지막 아이템의 포지션을 출력합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 320px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99C38F405B924DF238&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99C38F405B924DF238&quot; width=&quot;320&quot; height=&quot;484&quot; filename=&quot;스크린샷 2018-09-07 오후 7.06.57.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;위의 함수들을 사용하여 다음과 같이 화면에 보여지는 아이템들의 범위를 구하는 코드를 추출해 냈습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;pre style=&quot;background-color:#2b2b2b;color:#a9b7c6;font-family:'Menlo';font-size:13.5pt;&quot;&gt;&lt;p&gt;&lt;span style=&quot;color:#cc7832;&quot;&gt;if&lt;/span&gt;(position&amp;gt;= &lt;span style=&quot;color:#9876aa;&quot;&gt;firstCompletlyVisibleItem &lt;/span&gt;&amp;amp;&amp;amp; position&amp;lt;= &lt;span style=&quot;color:#9876aa;&quot;&gt;lastCompletlyVisibleItem&lt;/span&gt;){&lt;br /&gt;    Log.e(&lt;span style=&quot;color:#6a8759;&quot;&gt;&quot;visibleItem&quot;&lt;/span&gt;&lt;span style=&quot;color:#cc7832;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#6a8759;&quot;&gt;&quot;position : &lt;/span&gt;&lt;span style=&quot;color:#cc7832;&quot;&gt;$&lt;/span&gt;position&lt;span style=&quot;color:#6a8759;&quot;&gt;&quot;&lt;/span&gt;)&lt;br /&gt;}&lt;/p&gt;&lt;/pre&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이제 위의 기능들을 사용하여, 해당 ViewHolder에서 bind작업을 하면 될 것 같습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;전체 소스가 궁금하시거나, 피드백을 원하시는분들은 댓글 부탁드립니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;감사합니다.&lt;/p&gt;</description>
      <category>Android/RecyclerView 보이는 이미지 먼저 불러오기</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/52</guid>
      <comments>https://eso0609.tistory.com/52#entry52comment</comments>
      <pubDate>Fri, 7 Sep 2018 19:22:33 +0900</pubDate>
    </item>
    <item>
      <title>Android resource linking failed error</title>
      <link>https://eso0609.tistory.com/51</link>
      <description>&lt;p&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;span style=&quot;font-size: 24pt; background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;Android resource linking failed error&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(255, 255, 255); color: rgb(0, 0, 0);&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;프로젝트를 진행중 Android resource linking failed 라는 문구로 시작되는 에러를 겪었습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;AAPT~ 문구가 보여서 당황했습니다. ( AAPT 관련 에러를 심하게 겪은적이 있기 때문에.. )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;우선 처음 시도해본 것은 AndroidStudio 프로젝트 구조 ( Project Structure ) 에서 컴파일 SDK version 을 수정했습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 742px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99109D3F5B908FB42F&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99109D3F5B908FB42F&quot; width=&quot;742&quot; height=&quot;292&quot; filename=&quot;스크린샷 2018-09-06 오전 11.03.50.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;( 저 같은 경우에는 해당란이 unresolved~~ 로 되어있었습니다. )&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;하지만, 해당 작업을 해도 문제는 해결되지 않았습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;저의 경우 문제는 말그대로 안드로이드 리소스 연결 실패...&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 679px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99D528385B90903832&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99D528385B90903832&quot; width=&quot;679&quot; height=&quot;24&quot; filename=&quot;스크린샷 2018-09-06 오전 11.21.11.jpg&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;이유는 해당 액티비티를 찾기 위해, 전체 파일 검색을 하던중 해당 문구가 들어가 있는 코드부분을 지워버린 것이었습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;( 황당한 실수지만, 원인을 알아가는 것이 중요하다고 생각했습니다. )&lt;/span&gt;&lt;/p&gt;</description>
      <category>Android/Android resource linking failed</category>
      <category>android</category>
      <category>android resource linking failed</category>
      <category>compile sdk version</category>
      <category>error</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/51</guid>
      <comments>https://eso0609.tistory.com/51#entry51comment</comments>
      <pubDate>Thu, 6 Sep 2018 11:29:03 +0900</pubDate>
    </item>
    <item>
      <title>구글 애드센스 정책 위반 계정 정지( 무효 클릭 )</title>
      <link>https://eso0609.tistory.com/50</link>
      <description>&lt;p&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;font-size: 24pt; background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); font-size: 24pt;&quot;&gt;구글 애드센스 계정 정지 ( 무효 클릭: 직접 클릭 )&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: rgb(229, 69, 208); color: rgb(255, 255, 255);&quot;&gt;&lt;span style=&quot;background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); font-size: 24pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;b&gt;2018.08.24&lt;/b&gt; , 약 3달간의 무한 검토과정을 마치고 드디어 구글 애드센스 계정이 활성화 되었습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;근방에는 매일 아침 일어나서 눈 뜨자 마자 애드센스 앱에 들어가서 예상수입을 봅니다. ( 8월 24일 부터..ㅎㅎ )&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99C03B395B8B71E01C&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99C03B395B8B71E01C&quot; width=&quot;900&quot; height=&quot;286&quot; filename=&quot;스크린샷 2018-09-02 오후 12.27.03.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;무효 클릭: 직접 클릭? 혹시...? 설마...? 맞습니다. 설마가 사람 잡습니다...&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;처음 광고를 게시한 후, 게시물을 올릴때 마다 해당 광고를 서식을 통해 추가로 넣는 작업을 하는 경우도 있었습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;그럴 때 마다 해당 광고가 잘 게시 되었는지, 광고를 테스트하는 겸해서 클릭해보는 일이 잦았습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;( 지금 생각해보니 광고주들&amp;nbsp;광고를 제가 테스트한 것 같다는 생각이 들기도 하네요... )&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;위 상황을 겪은 후 너무 당황스러워서, 해당 링크에 접속했습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/992D33395B8B750721&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F992D33395B8B750721&quot; width=&quot;900&quot; height=&quot;253&quot; filename=&quot;스크린샷 2018-09-02 오후 2.23.41.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;해당 애드센스 프로그램 정책은 상당히 많습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;해당 적책 사항은 &lt;a href=&quot;https://support.google.com/adsense/answer/48182&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;구글 애드센스 정책 링크&lt;/a&gt;를 통해 확인하시면 좋을 것 같습니다. ( 애드센스 입문자분들 께서는 필수 )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;무효 클릭: 직접 클릭에 대한 자세한 사항은 다음과 같습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/996881405B8B75D334&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F996881405B8B75D334&quot; width=&quot;900&quot; height=&quot;336&quot; filename=&quot;스크린샷 2018-09-02 오후 2.23.56.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;수입이 많은 상황은 아니어서 그나마 다행이지만, 한 달간 다시 기다려야하는 상황입니다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;아직도 드는 의문점은 애드센스 측에서는 이의 제기를 할 수 없도록 했다는 점입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;저는 처음에 정책 위반이 봇들로 인한 트래픽 증가 때문이라고 생각했는데... 실수로라도 자기 광고 누르면 어떻게 되는지&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;궁금하네요.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;혹시 관련 상황이나 조언 가능하신분들 피드백 환영입니다. 읽어주셔서 감사합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: right;&quot;&gt;참고사이트 :&amp;nbsp;https://support.google.com/adsense/answer/48182&lt;/p&gt;</description>
      <category>구글 애드센스/애드센스 계정 정지</category>
      <category>광고</category>
      <category>무효 클릭</category>
      <category>애드센스</category>
      <category>정책 위반</category>
      <category>직접 클릭</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/50</guid>
      <comments>https://eso0609.tistory.com/50#entry50comment</comments>
      <pubDate>Sun, 2 Sep 2018 14:36:51 +0900</pubDate>
    </item>
    <item>
      <title>Activity간 데이터 전송</title>
      <link>https://eso0609.tistory.com/49</link>
      <description>&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: rgb(0, 0, 0); background-color: rgb(255, 0, 221); font-size: 24pt;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;startActivity&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;프로젝트를 진행하면서 단연 안쓸 수 없는 메소드 중에 하나입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;startActivity는 intent를 통해 설정된 activity로 이동을&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;시켜주는 메소드입니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 863px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99AEC63C5B87A4DC03&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99AEC63C5B87A4DC03&quot; width=&quot;863&quot; height=&quot;300&quot; filename=&quot;스크린샷 2018-08-30 오후 5.03.13.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;해당 버튼을 누르면 SecondActivity로 화면전환이 이루어지겠죠?&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;SecondActivity에 존재하는 버튼을 통해 단순히 finish()를 실행하게 되면, 당연히&amp;nbsp;MainActivity 화면이 보입니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 625px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99181E355B87A6341C&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99181E355B87A6341C&quot; width=&quot;625&quot; height=&quot;275&quot; filename=&quot;스크린샷 2018-08-30 오후 5.07.53.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;단순하게 다른 activity로의 전환뿐만아니라 전환 후 결과 값을 받아야한다면?&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;위 상황에서 사용할 수 있는 메소드가 startActivityForResult 입니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;center&gt;
&lt;script async=&quot;&quot; src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-5799939464120235&quot; data-ad-slot=&quot;6585151859&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/center&gt;
&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: rgb(255, 0, 221); font-size: 24pt;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;startActivityForResult&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;위의 startActivity와는 다르게 startActivityForResult를 사용하면 activity로의 전환 후 requestCode를 통해 특정 행위를 할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 703px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99FAC7375B87A6D51D&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99FAC7375B87A6D51D&quot; width=&quot;703&quot; height=&quot;131&quot; filename=&quot;스크린샷 2018-08-30 오후 5.10.03.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;startActivity와는 다르게, 첫 번째 인자는 Intent, 두 번째 인자는 Int값을 매개변수로 필요로합니다.&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;두 번째 Int값은 requestCode로 사용됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 888px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/998B8E415B87A82E2E&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F998B8E415B87A82E2E&quot; width=&quot;888&quot; height=&quot;311&quot; filename=&quot;스크린샷 2018-08-30 오후 5.17.29.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;startActivityForResult에 대한 처 즉, MainActivity에서 startActivityForResult를 통해 SecondActivity에서의 특정 행위&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;에 대한 결과값을 처리하기&amp;nbsp;위해 onActivityResult를 오버라이드합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;SecondActivity에는 버튼을 2개 추가하여 회원가입, 로그인을 처리한다고 가정해 보겠습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 725px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9962974B5B87ACDC07&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9962974B5B87ACDC07&quot; width=&quot;725&quot; height=&quot;466&quot; filename=&quot;스크린샷 2018-08-30 오후 5.37.35.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;setResult를 통해 resultCode를 반환 후, MainActivity에서 resultCode값을 통해 각각의 다른 처리를 할 수 있습니다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/994DEF475B87AE2204&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F994DEF475B87AE2204&quot; width=&quot;900&quot; height=&quot;255&quot; filename=&quot;스크린샷 2018-08-30 오후 5.42.54.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;각 버튼을 눌렀을 때 해당 resultCode별로 분기처리 할 수 있습니다.&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;저 같은 경우에는 setResult를 선언하지 않고, 해당 Activity를 종료시켜 다음과 같은 에러가 발생했었습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;( 실 패키지명은 packageName로 대체하였습니다. )&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp;java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=65537, result=0, data=null} to activity {~~&amp;nbsp;packageName}: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter data&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at android.app.ActivityThread.deliverResults(ActivityThread.java:4318)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at android.app.ActivityThread.handleSendResult(ActivityThread.java:4362)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at android.app.ActivityThread.-wrap19(Unknown Source:0)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1672)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at android.os.Handler.dispatchMessage(Handler.java:106)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at android.os.Looper.loop(Looper.java:171)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at android.app.ActivityThread.main(ActivityThread.java:6637)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at java.lang.reflect.Method.invoke(Native Method)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:518)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:824)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter data&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at packageName.EventFragment.onActivityResult(Unknown Source:2)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at android.support.v4.app.FragmentActivity.onActivityResult(FragmentActivity.java:151)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; at android.app.Activity.dispatchActivityResult(Activity.java:7351)&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;위와 같은 에러는 정확한 코드의 위치도 알 수 없었고, 단순하게 널이아닌 곳에서 널이 발생했다&amp;nbsp;정도만 명시하고 있습니다.&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;알고보니 A-&amp;gt;B-&amp;gt;A 화면전환이 이루어지는데 A-&amp;gt;B로 갈때 startActivityForResult로 반환값을 요구하고 있었고, 해당 resultCode에서 특정 작업을 하고 있었습니다.&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;결국, B-&amp;gt;A로 전환시 finish()전에 setResult를 해주었더니 해결되었습니다.&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Android/StartActivity &amp;amp;&amp;amp; StartActivityForResult</category>
      <category>NullpointerException</category>
      <category>onActivityResult</category>
      <category>startActivity</category>
      <category>startActivityForResult</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/49</guid>
      <comments>https://eso0609.tistory.com/49#entry49comment</comments>
      <pubDate>Thu, 30 Aug 2018 17:59:39 +0900</pubDate>
    </item>
    <item>
      <title>데이터 바인딩 이벤트 처리</title>
      <link>https://eso0609.tistory.com/47</link>
      <description>&lt;p&gt;&lt;span style=&quot;background-color: rgb(255, 0, 221); font-size: 24pt;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;이벤트 처리&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;데이터 바인딩을 사용하여 뷰에서 발송되는 이벤트를 처리하는 식을 작성할 수 있습니다. ( 예를 들면 onClick )&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;이벤트 특성 이름은 몇 가지 예외를 제외하면 리스너 메소드의 이름에 따라 결정됩니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;예를 들어, View.OnLongClickListener 에는 메소드 onLongClick()이 있으므로, 이 이벤트에 대한 특성은&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;span style=&quot;background-color: rgb(234, 234, 234); font-size: 14pt;&quot;&gt;android:onLongClick&lt;/span&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt; 입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;데이터 바인딩에서 이벤트를 처리하는 두 가지 방법이 있습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;- 메소드 참조&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;- 리스너 바인딩&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: rgb(255, 0, 221); font-size: 24pt;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;메소드 참조&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;식에서 리스너 메소드의 서명을 준수하는 메소드를 참조할 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;( 코틀린&lt;/span&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&amp;nbsp;람다는 Java8 람다와 매우 유사합니다. Java8에서의 Method Reference(메소드 참조) 를 생각하시면 됩니다. )&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;식이 메소드 참조로 평가되면 데이터 바인딩은&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;메소드 참조와 소유자 객체를 리스너에 래핑하고, 대상 뷰에서 리스너를 설정합니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;즉, 객체를 가지고 있는 소유자를 리스너로 감싸게 되고, 해당 뷰에서 리스너를 사용하게 됩니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;식이 null로 평가되면 데이터 바인딩은 리스너를 생성하지 않고, 그 대신 null 리스너를 설정합니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;메소드 참조는 해당 Activity에 존재하는 메소드에 &lt;/span&gt;&lt;span style=&quot;background-color: rgb(234, 234, 234); font-size: 14pt;&quot;&gt;android:onClick&lt;/span&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;을 할당하는 것과 비슷한 방법으로&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;이벤트를 핸들러 메소드에 직접 바인딩 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;기존 View#onClick 특성에 비해 한 가지 주요 장점은&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt; 컴파일 시에 식이 처리&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;되므로, 메소드가 존재하지 않거나&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;메소드의 서명이 올바르지 않을 경우 컴파일 시 오류를 발생시켜, 개발자가 이를 알아 차릴 수 있습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;메소드 참조에 대한 코드를 함께 살펴 보겠습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 491px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/994912415B8627622F&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F994912415B8627622F&quot; width=&quot;491&quot; height=&quot;187&quot; filename=&quot;스크린샷 2018-08-29 오후 1.55.13.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;center&gt;
&lt;script async=&quot;&quot; src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-5799939464120235&quot; data-ad-slot=&quot;6585151859&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/center&gt;
&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;user::onUserClick 이 부분이 메소드 참조입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;해당 버튼을 클릭하게되면 User 클래스의 onUserClick메소드가 실행됩니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99C862345B8627FB03&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99C862345B8627FB03&quot; width=&quot;900&quot; height=&quot;122&quot; filename=&quot;스크린샷 2018-08-29 오후 1.57.53.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;위에서 언급하였듯이, 메소드 참조는 컴파일 시점에 식이 처리됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;컴파일 시점이아니라 실제 이벤트 발생 시 식을 처리하기 위해서 리스너 바인딩을 사용해야 합니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;처리하기 위해서 리스너 바인딩을 사용해야 합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: rgb(255, 0, 221); font-size: 24pt;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;리스너 바인딩&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;리스너 바인딩은 메소드 참조와 달리 이벤트 발생 시 식이 처리됩니다. 또한, 리스너 바인딩을 사용하면 임의의 데이터&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;바인딩 식을 실행할 수 있습니다. 이 기능은 Android Gradle Plugin for Gradle 버전 2.0 이상에서 사용 가능합니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;메소드 참조에서는 메소드의 매개변수가 이벤트 리스너의 매개변수와 일치해야 합니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;( 위의 예제에서는 onClick의 매개변수 view : View 를 생각하시면 됩니다. )&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;하지만, 리스너 바인딩에서는 반환 값만 리스너의 예상 반환 값과 일치하면 됩니다. ( void 제외 )&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 729px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99121C365B862B741B&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99121C365B862B741B&quot; width=&quot;729&quot; height=&quot;123&quot; filename=&quot;스크린샷 2018-08-29 오후 2.12.41.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;view는 어디가고 age 값만 받는 걸까요?&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;리스너는 식의 루트 요소로만 허용되는 람다 식으로 표현됩니다. 식에서 콜백이 사용될 때 데이터 바인딩이 필요한 리스너&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;를 자동으로 생성하고 이벤트에 등록합니다. 뷰가 이벤트를 발생시키면 데이터 바인딩이 주어진 식을 계산하게 됩니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;정규 바인딩 식에서처럼, 이 리스너 식이 평가되는 동안 여전히 데이터 바인딩의 null 및 스레드의 안전이 보장됩니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 798px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9931C73F5B862B5E04&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9931C73F5B862B5E04&quot; width=&quot;798&quot; height=&quot;169&quot; filename=&quot;스크린샷 2018-08-29 오후 2.12.28.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;위 onClick에서는 람다식을 이용하여 식을 처리했습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;혹시나 view가 필요한 경우는 다음과 같이 코드를 작성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99CA15345B862D5923&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99CA15345B862D5923&quot; width=&quot;900&quot; height=&quot;77&quot; filename=&quot;스크린샷 2018-08-29 오후 2.21.12.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;해당 메소드의 매개 변수로 View, String 값을 버튼에서는 다음과 같이 설정할 수 있습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 886px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/996F2A3F5B862D4224&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F996F2A3F5B862D4224&quot; width=&quot;886&quot; height=&quot;170&quot; filename=&quot;스크린샷 2018-08-29 오후 2.20.48.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: right;&quot;&gt;&lt;span style=&quot;font-size: 14pt;&quot;&gt;참고 사이트 :&amp;nbsp;https://developer.android.com/topic/libraries/data-binding/&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: right;&quot;&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Android/데이터 바인딩(DataBinding) [2] - 이벤트 처리</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/47</guid>
      <comments>https://eso0609.tistory.com/47#entry47comment</comments>
      <pubDate>Wed, 29 Aug 2018 14:27:12 +0900</pubDate>
    </item>
    <item>
      <title>데이터 바인딩 (DataBinding)</title>
      <link>https://eso0609.tistory.com/46</link>
      <description>&lt;p&gt;&lt;span style=&quot;background-color: rgb(255, 0, 221); font-size: 24pt;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;데이터 바인딩(DataBinding) 사용 목적&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;안드로이드에서&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;다양한 리소스( 예를 들면, layout, value, string 등)를 바인딩하는 것은 상당히 반복되는 코드가 많아 질 수&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;있고, 번거로울 수 있습니다. 물론, 코틀린을 사용하면 코틀린 extension을 사용하여 비교적 편리하게 리소스에 접근할 수&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;있습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;하지만, 최근 패턴 사용에 대한 개발자들의 긍정적인 시선,&amp;nbsp;코드 분리를 통한 최적화의 방법으로&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;버터나이프, 데이터바인딩 라이브러리를&amp;nbsp;사용하는 개발자가 많습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;저 또한 최근 프로젝트를 진행하면서, 코드 분리 및 최적화를 위해 App에 MVVM 패턴을 적용하기로 했습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;제가 버터나이프 대신&amp;nbsp;데이터 바인딩을 사용하는 가장 큰 이유는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 400px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99601B445B8380F92F&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99601B445B8380F92F&quot; width=&quot;400&quot; height=&quot;429&quot; filename=&quot;스크린샷 2018-08-27 오후 1.39.20.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;첫 번째,&amp;nbsp;&lt;a href=&quot;https://developer.android.com/topic/libraries/data-binding/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;안드로이드에서 지원하는 데이터 바인딩&lt;/a&gt;과 다르게 &lt;a href=&quot;http://jakewharton.github.io/butterknife/&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt;버터나이프 (ButterKnife)&lt;/a&gt;는 Third Party 라이브러리입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;여기서 말하는 Third Party 라이브러리란&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;이와 같은 이유때문에 데이터 바인딩을 사용했을때 &lt;b&gt;deprecated에 대한 우려를 저희는 걱정할 필요가 없습니다.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;두 번째, 안드로이드에서 java, kotlin 코드를 통해서 했던 작업들을 xml에서 처리할 수 있기 때문에, &lt;b&gt;View 자체에서 데이터&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;변경에 대한 동적 처리가 가능&lt;/b&gt;해집니다. 즉, View Model에 데이터만 남게 함으로써 View- ViewModel 분리가 용이합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: rgb(255, 0, 221); font-size: 24pt;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;데이터 바인딩(DataBinding) 사용&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;우선 데이터 바인딩 사용을 위해&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;build.gradle(app)에 다음 코드를 추가합니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 336px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99C005435B838A4603&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99C005435B838A4603&quot; width=&quot;336&quot; height=&quot;111&quot; filename=&quot;스크린샷 2018-08-27 오후 2.04.12.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;코틀린에서는 추가로 kapt도 추가해주어야 합니다.&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;apply plugin:'kotlin-kapt'&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;여기서 중요한 변경 사항이 있습니다. 예전에 데이터 바인딩을 할 때만해도&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;pre style=&quot;margin-top: 0px; margin-bottom: 0px; padding: 0px; outline: none; background-color: rgb(255, 255, 255);&quot;&gt;&lt;p style=&quot;color: rgb(102, 102, 102); font-size: 14px;&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; color: rgb(0, 0, 0); font-size: 14pt;&quot;&gt;kapt &lt;/span&gt;&lt;span style=&quot;color: rgb(0, 0, 0); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&quot;com.android.databinding:compiler:&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; color: rgb(0, 0, 0); font-size: 14pt;&quot;&gt;$android_plugin_version&lt;/span&gt;&lt;span style=&quot;color: rgb(0, 0, 0); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&quot; 와 같은 코드를 추가해야했습니다.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;color: rgb(102, 102, 102); font-size: 14px;&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif; font-size: 14pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;&quot;&gt;&lt;span style=&quot;color: rgb(0, 0, 0); font-size: 14pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;하지만 &lt;/span&gt;&lt;font face=&quot;맑은 고딕, sans-serif&quot;&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;https://developer.android.com/topic/libraries/data-binding/start 확인해본 결과&lt;/span&gt;&lt;/font&gt;&lt;/p&gt;&lt;p style=&quot;&quot;&gt;&lt;font face=&quot;맑은 고딕, sans-serif&quot;&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/font&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99EEB0365B83A3D932&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99EEB0365B83A3D932&quot; width=&quot;900&quot; height=&quot;208&quot; filename=&quot;스크린샷 2018-08-27 오후 4.07.35.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;&quot;&gt;&lt;font face=&quot;맑은 고딕, sans-serif&quot;&gt;&lt;span style=&quot;font-size: 18.6667px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/font&gt;&lt;/p&gt;&lt;/pre&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;위와 같은 내용을 확인할 수 있습니다. ( apply plugin:'kotlin-kapt' 코드는 넣어주셔야 합니다. )&lt;br style=&quot;color: rgb(102, 102, 102); font-family: &amp;quot;Noto Sans&amp;quot;, sans-serif; font-size: 14px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;이제 xml 파일에서&amp;nbsp;데이터 바인딩 적용 전, 적용 후 소스를 살펴보겠습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;b&gt;- 데이터 바인딩 적용 전 xml 소스&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 767px; text-align: center;; height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99ECB53E5B838B9C13&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99ECB53E5B838B9C13&quot; width=&quot;767&quot; height=&quot;555&quot; filename=&quot;스크린샷 2018-08-27 오후 2.26.10.png&quot; filemime=&quot;image/jpeg&quot; style=&quot;text-align: center;&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;텍스트뷰가 하나있는 간단한 xml 코드입니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;데이터 바인딩을 사용하기 위해서 해당 xml의 최상위에 layout을 추가해야합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;- 데이터 바인딩 적용 후&amp;nbsp;xml 소스&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 757px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99B14A385B838C310A&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99B14A385B838C310A&quot; width=&quot;757&quot; height=&quot;244&quot; filename=&quot;스크린샷 2018-08-27 오후 2.28.57.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;data를 통해 MainActiviy를 객체화해서 사용할 수 있습니다. 해당 액티비티의 객체명을 통해 MainActivity 클래스의&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;변수나 메소드에 접근할 수 있고, 이를 통해 소스 코드를 줄일 수 있습니다.&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;기존 데이터 바인딩 사용전, 통신을 통해 받아온 객체를 일일이 setText 해주던 것과는 다르게, 데이터 바인딩을 통해&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;xml 에서 위 작업을 대신할 수 있습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 735px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/991A533C5B838CE417&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F991A533C5B838CE417&quot; width=&quot;735&quot; height=&quot;429&quot; filename=&quot;스크린샷 2018-08-27 오후 2.31.02.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;레이아웃 내에 있는 &quot;@{}&quot; 구문을 사용하여 값을 부여할 수 있습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;이제 코틀린 코드상에서 데이터 바인딩을 적용해 보겠습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 753px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/996FBD4B5B83997B21&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F996FBD4B5B83997B21&quot; width=&quot;753&quot; height=&quot;449&quot; filename=&quot;스크린샷 2018-08-27 오후 3.25.30.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;메인에서 하게되는 작업은 간단합니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;기존에 setContentView 대신에 DataBindingUtil을 통해 setContentView를 하시면됩니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 900px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99FF16375B86022A06&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99FF16375B86022A06&quot; width=&quot;900&quot; height=&quot;301&quot; filename=&quot;스크린샷 2018-08-29 오전 11.16.03.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;user_name_tv 에는 &quot;Seonoh&quot;, user_age_tv에는&amp;nbsp;27 값이 설정된 것을&amp;nbsp;확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;간단하게 살펴본 DataBinding 적용하는 방법이었습니다.&lt;/p&gt;&lt;p style=&quot;text-align: left; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: right; clear: none; float: none;&quot;&gt;참고 사이트 :&amp;nbsp;https://developer.android.com/topic/libraries/data-binding/start&lt;/p&gt;</description>
      <category>Android/데이터 바인딩 (DataBinding) [1] - 시작하기</category>
      <category>databinding</category>
      <category>MVVM</category>
      <category>third party library</category>
      <category>thrid party</category>
      <category>데이터바인딩</category>
      <category>데이터바인딩 장점</category>
      <category>버터나이프</category>
      <category>버터나이프 데이터바인딩 차이점</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/46</guid>
      <comments>https://eso0609.tistory.com/46#entry46comment</comments>
      <pubDate>Mon, 27 Aug 2018 16:14:31 +0900</pubDate>
    </item>
    <item>
      <title>샤오미 6Pro 언락(Unlock) 성공</title>
      <link>https://eso0609.tistory.com/45</link>
      <description>&lt;p&gt;&lt;span style=&quot;background-color: rgb(255, 0, 221); font-size: 32px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;샤오미 언락(Unlock) 실패 경험&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 24pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size:14pt;&quot;&gt;샤오미 6 pro를 얻은지는 벌써 1달이 지났습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;처음 6 pro를 접하고 들뜬 마음으로 디버깅 시도했다가 뜻밖의 Unlock 때문에 실망했을 때가 생각납니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;샤오미에서는 언락이라는 기능을 두고, 단말기에 대한 디버깅을 막아두고 있는데, 무려 15일후에 언락을 풀 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;( 샤오미에서 언락을 도입한 이유는 다음&lt;a href=&quot;http://en.miui.com/thread-201477-1-1.html&quot; target=&quot;_blank&quot; class=&quot;tx-link&quot;&gt; 샤오미 포럼에 올라온 게시물 링크&lt;/a&gt;를 확인해보시면 좋을 것 같습니다. )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;15일인데 제가 30일 넘게 걸린 이유는 뭘까요...?&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;바로 &lt;b&gt;MI&lt;/b&gt;&amp;nbsp;&lt;b&gt;계정 변경&lt;/b&gt;때문입니다. 처음에는 A 계정으로했다가 인터넷에 돌아다니는 각종 언락 금방 푸는 방법 등을 시도해보면서,&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;계정을 B계정 C계정까지해보았고, 마지막에 C계정으로 작업을 마무리 지었습니다. ( 15일 기다리는 것으로 )&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;하지만 재시도할 때 A계정으로 시도를 하였고, 이는 다시 360시간을 기다리는 것으로 결론났습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;B, C계정또한 마찬가지였죠.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;결국 A계정으로 마지막으로 로그인하고, 5일 간격으로 남은 시간을 확인했습니다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;즉, &lt;b&gt;가장 확실한 방법은 한 계정으로 언락을 시도하고, 남은 시간 확인작업만 하시면서 15일 기다리는 것입니다.&lt;/b&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;center&gt;
&lt;script async=&quot;&quot; src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-5799939464120235&quot; data-ad-slot=&quot;6585151859&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/center&gt;
&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 32px; background-color: rgb(255, 0, 221);&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 32px;&quot;&gt;샤오미 언락(Unlock) 성공&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;드디어 15일 또 흐르고, 출근하자마자 언락 작업을 진행했습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 640px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9951A43B5B835F3A17&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9951A43B5B835F3A17&quot; width=&quot;640&quot; height=&quot;438&quot; filename=&quot;샤오미언락_02.jpg&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;익숙한 화면입니다. 샤오미 폰 부트모드로 전환 후, miflash를 실행하고 로그인한 후 언락을 진행하는 첫 화면이었습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;다음 퍼센트 화면이나오고... 드디어 언락에 성공했습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 640px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99FF49415B835FC00E&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99FF49415B835FC00E&quot; width=&quot;640&quot; height=&quot;439&quot; filename=&quot;샤오미언락_03.jpg&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;성공후에 개발자모드를 다시 설정하고, 드디어 Install via USB를 활성화 했습니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 580px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99788E4C5B83605B25&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99788E4C5B83605B25&quot; width=&quot;580&quot; height=&quot;773&quot; filename=&quot;샤오미언락04.jpg&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;안드로이드 스튜디오에서 앱을 빌드해보니 아주 잘됩니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;샤오미폰을 테스트로하시는 개발자분들 혹은 샤오미폰에 대해서 피드백해주실 내용이 있으신분들, 피드백 환영합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;읽어주셔서 감사합니다.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>단말기 기종별 특이사항/샤오미 6Pro 언락(Unlock)</category>
      <category>360 hours after</category>
      <category>Install via USB</category>
      <category>부트로더</category>
      <category>샤오미</category>
      <category>샤오미 6 pro</category>
      <category>샤오미 unlock</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/45</guid>
      <comments>https://eso0609.tistory.com/45#entry45comment</comments>
      <pubDate>Mon, 27 Aug 2018 11:26:33 +0900</pubDate>
    </item>
    <item>
      <title>구글 에드센스 승인 및 콘텐츠 중간 광고 삽입</title>
      <link>https://eso0609.tistory.com/44</link>
      <description>&lt;p&gt;&lt;b style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; background-color: rgb(255, 0, 221); font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&amp;nbsp;구글 애드센스란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;애드센스(AdSense)는 광고주를 위한 구글의 광고 프로그램입니다. 웹사이트 소유자는 애드센스에 가입함으로써 광고 수익을 구글과&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;나눌 수 있습니다. 광고 수익은 사용자가 에드센스 광고를 클릭함으로써 광고 게시자는 구글에 광고비를 지급하고, 구글은 그렇게 적립된&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;광고비를 웹사이트 제작자와 나누어 갖게 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;b style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: center;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 400px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99F556335B8110B111&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99F556335B8110B111&quot; width=&quot;400&quot; height=&quot;270&quot; filename=&quot;스크린샷 2018-08-25 오후 5.16.25.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;&lt;b&gt;애드센스는 구글에 가입된 광고풀 가운데 웹페이지와 가장 연관성이 높은 광고가 나오게&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;b style=&quot;font-size: 12pt;&quot;&gt;되지만,&amp;nbsp;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b style=&quot;font-size: 12pt;&quot;&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b style=&quot;font-size: 12pt;&quot;&gt;그렇지 못한 경우에 공익 광고가 나오게 됩니다. 공익광고는 사용자가 클릭을 했다고 해도 수익이 생기지 않습니다.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;b style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; background-color: rgb(255, 0, 221); font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&amp;nbsp;구글 애드센스 무한 검토 후 승인 해결 방법 및 후기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 12pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;b&gt;&lt;/b&gt;저는&amp;nbsp;티스토리를 학습한 내용을 정리하는 용도로 사용했습니다. 블로그 게시물 수가 40개가 넘어서 구글 애드센스에 대해 알게&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 12pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 12pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;되었습니다. 경험삼아 광고를 붙여볼까 라는 생각으로 시작하여 구글 애드센스 홈페이지에서 가입을 진행했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 12pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 12pt;&quot;&gt;(&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;https://www.google.com/intl/ko_kr/adsense/start/#/?modal_active=none )&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;가입절차는 타 블로그에도 자세히 설명되어 수월하게 진행할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;하지만, 애드센스 계정 승인에는 3달 넘게 걸린 것 같습니다....!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: center; clear: none; float: none;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 400px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99B876335B8110FF06&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99B876335B8110FF06&quot; width=&quot;400&quot; height=&quot;259&quot; filename=&quot;스크린샷 2018-08-24 오후 6.18.36.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;1차 시기에는 6주 정도 검토과정을 기다렸고, 그 이후 관련 자료를 검색하며 ( 생활 코딩, 구글링 등 )&amp;nbsp;해결 방법을 찾아 보았습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;제가 시도한 방법은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;center&gt;
&lt;script async=&quot;&quot; src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot;&gt;&lt;/script&gt;
&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display:block; text-align:center;&quot; data-ad-layout=&quot;in-article&quot; data-ad-format=&quot;fluid&quot; data-ad-client=&quot;ca-pub-5799939464120235&quot; data-ad-slot=&quot;6585151859&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []).push({});
&lt;/script&gt;
&lt;/center&gt;
&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;1. 무한 검토에 빠진 후, 티스토리 HTML 소스에서 애드센스 코드를 제거한 후 계정 활성화 거절 통보를 받기.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;2. 거절 통보를 받은 후 다시 티스토리 HTML 소스에 코드 붙여 넣기.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;3, 애드센스 사용자 게시판에 게시글 올리기.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&amp;nbsp;( 지푸라기라도 잡는 심정으로 올렸습니다. 이것 때문이지 정확하진 않지만, 새로운 콘텐츠를 올리지도 않고 해당 게시글 업로드 다음날에 승인!! )&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;b style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; background-color: rgb(255, 0, 221); font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&amp;nbsp;구글 애드센스 콘텐츠 중간에 광고 삽입&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b style=&quot;font-family: &amp;quot;Ubuntu Condensed&amp;quot;, &amp;quot;Noto Sans Korean&amp;quot;; font-size: 15px;&quot;&gt;&lt;span style=&quot;box-sizing: border-box; font-size: 24pt; font-family: &amp;quot;맑은 고딕&amp;quot;, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: 12pt;&quot;&gt;오매불망 기다리던 애드센스 계정이 활성화 되고 제일 먼저한 작업은 콘텐츠 중간에 광고를 붙이는 작업이었습니다. ( 승인 받고, 들뜬 기분으로 !! )&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: center;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 640px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/997812335B8113652E&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F997812335B8113652E&quot; width=&quot;640&quot; height=&quot;285&quot; filename=&quot;스크린샷 2018-08-25 오후 4.49.44.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;애드센스 홈페이지에서 광고 -&amp;gt; 광고 단위를 클릭하시면 위와 같은 화면을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none;&quot;&gt;저는 콘텐츠 내 자동 삽입 광고를 선택하였습니다.&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: center;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 640px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/999CBF335B8113660E&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F999CBF335B8113660E&quot; width=&quot;640&quot; height=&quot;408&quot; filename=&quot;스크린샷 2018-08-25 오후 4.50.59.png&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;저장 및 코드 생성을 누르면 광고에 대한 코드를 확인할 수 있습니다.&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;이 코드를 티스토리 서식 추가( 서식&amp;nbsp;글쓰기 화면에서 오른쪽 상단 HTML 버튼 클릭 )를 통해서 글을 쓸때마다 코드를 붙여&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;넣는 대신 서식을 통해 처리할 수 있었습니다.&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: center;&quot;&gt;&lt;span class=&quot;imageblock&quot; style=&quot;display: inline-block; width: 640px;  height: auto; max-width: 100%;&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/991018435B81143121&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F991018435B81143121&quot; width=&quot;640&quot; height=&quot;556&quot; filename=&quot;스크린샷 2018-08-25 오후 4.53.53.jpg&quot; filemime=&quot;image/jpeg&quot;/&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;주석을 달아 놓으면 추후 수정할 때 보기가 편리할 것 같아 표시해 두었습니다.&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: left;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;clear: none; float: none; text-align: right;&quot;&gt;참고 사이트&amp;nbsp;&lt;span style=&quot;font-size: 12pt;&quot;&gt;:&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-size: 16px;&quot;&gt;https://ko.wikipedia.org/wiki/%EC%95%A0%EB%93%9C%EC%84%BC%EC%8A%A4&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>구글 애드센스/애드센스 시작 및 콘텐츠 중간 광고 삽입</category>
      <category>구글 애드센스</category>
      <category>무한 검토</category>
      <category>무한 검토 해결 방법</category>
      <category>콘텐츠 내 자동 삽입 광고</category>
      <category>티스토리 서식</category>
      <author>엄코딩</author>
      <guid isPermaLink="true">https://eso0609.tistory.com/44</guid>
      <comments>https://eso0609.tistory.com/44#entry44comment</comments>
      <pubDate>Sat, 25 Aug 2018 17:37:31 +0900</pubDate>
    </item>
  </channel>
</rss>