<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Hun's Blog</title>
    <link>https://jroomstudio.tistory.com/</link>
    <description>Over and over again.</description>
    <language>ko</language>
    <pubDate>Fri, 3 Jul 2026 19:15:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jhk-im</managingEditor>
    <image>
      <title>Hun's Blog</title>
      <url>https://tistory1.daumcdn.net/tistory/3798929/attach/cab3a60fd563420f82f5d7b1c1f94b5a</url>
      <link>https://jroomstudio.tistory.com</link>
    </image>
    <item>
      <title>Kotlin algorithm - Hash (2)</title>
      <link>https://jroomstudio.tistory.com/79</link>
      <description>&lt;figure id=&quot;og_1623332814162&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/parts/12077&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/parts/12077&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/t3Tyf/hyKwED0Kal/SkomQGHahRRUGIzwBWazf0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/crvGYc/hyKwxdPbdG/TIL7yWhmez4FgRDFoB4N4K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/parts/12077&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/parts/12077&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/t3Tyf/hyKwED0Kal/SkomQGHahRRUGIzwBWazf0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/crvGYc/hyKwxdPbdG/TIL7yWhmez4FgRDFoB4N4K/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; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jhk-im/jhk-algorithm/tree/main/kotlin/01_hash/test2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/jhk-im/jhk-algorithm/tree/main/kotlin/01_hash/test2&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1656155385939&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - jhk-im/jhk-algorithm&quot; data-og-description=&quot;Contribute to jhk-im/jhk-algorithm development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jhk-im/jhk-algorithm/tree/main/kotlin/01_hash/test2&quot; data-og-url=&quot;https://github.com/jhk-im/jhk-algorithm&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lzsAo/hyOSPOtsNF/3CK3aIXs4xfiglk65MxWt1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jhk-im/jhk-algorithm/tree/main/kotlin/01_hash/test2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jhk-im/jhk-algorithm/tree/main/kotlin/01_hash/test2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lzsAo/hyOSPOtsNF/3CK3aIXs4xfiglk65MxWt1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - jhk-im/jhk-algorithm&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to jhk-im/jhk-algorithm development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 프로그래머스 2번째 코딩테스트의 문제와 제한사항&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;전화번호부에 적힌 전화번호 중, 한 번호가 다른 번호의 접두어인 경우가 있는지 확인하려 한다. 전화번호부에 적힌 전화번호를 담은 배열 phone_book 이 solution 함수의 매개변수로 주어질 때, 어떤 번호가 다른 번호의 접두어인 경우가 있으면 false를 그렇지 않으면 true를 return 하도록 solution 함수를 작성하라.&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;phone_book의 길이는 1 이상 1,000,000 이하&lt;/li&gt;
&lt;li&gt;각 전화번호의 길이는 1 이상 20 이하&lt;/li&gt;
&lt;li&gt;중복된 번호 없음&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 프로그래머스에 작성된 답안을 보면 Hash를 사용하지 않고 통과한 답안도 있다. 중복된 번호가 없다는 점에서 배열을 전부 탐색하지 않고 중간에 break으로 빠져나갈 수 있기도 하고 startWith() 메소드를 사용해서 비교적 쉽게 접두어 포함여부를 확인할 수 있기 때문인 것 같다. 그렇긴 하지만 Hash카테고리 안에 있는 테스트이므로 위 문제가 Hashing의 장점을 활용해 어떻게 해결될 수 있는지를 아는 것 도 중요한 것 같다.&amp;nbsp;&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;1. Hash를 활용하기 위해 우선 입력되는 배열을 Hash에 전부 담는데 이 때 Key , Value에 모두 전화번호를 입력한다.&amp;nbsp; 이번 문제에서 Value는 사용되지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623333994212&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val phoneBook = arrayOf(&quot;119&quot;, &quot;97674223&quot;, &quot;1195524421&quot;)

val hashMap = HashMap&amp;lt;String, String?&amp;gt;()

for (phone in phoneBook) {
    hashMap[phone] = phone
}

---

&amp;lt;&quot;119&quot;, &quot;119&quot;&amp;gt;
&amp;lt;&quot;91674223&quot;, &quot;91674223&quot;&amp;gt;
&amp;lt;&quot;1195524421&quot;, &quot;1195524421&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. HashMap은 keys (java의 keySet()) 를 통해서 키 리스트를 배열처럼 활용할 수 있다. 해쉬맵에 담긴 전화번호를 다시한번 검색한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623334401274&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (key in hashMap.keys) {
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Hash의 장점은 배열을 전부 검색하지 않더라도 Key를 입력하면 다이렉트로 입력된 Key값의 테이블에 접근 가능하다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*HashMap의 containsKey() 메소드에 값을 입력하면 해당 값이 현재 HashMap에 Key로 존재하는지를 확인할 수있다.&amp;nbsp; Object가져올 필요 없으니 아주 적당한 메소드라고 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623334910495&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hashMap[&quot;119&quot;] // 119
hashMap.containsKey(&quot;119&quot;) // true
hashMap.containsKey(&quot;112&quot;) // false&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;4. 바로 접근 가능하다는것도 알겠고 containsKey()를 활용한다는 것도 알겠는데 가장 중요한 것은 접두어가 존재하는 지를 파악해야 한다는 것이다. 어떻게 하면 될까?&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;* key를 한글자씩 추가하면서 containsKey()에 입력하면 된다. &quot;119&quot;를 하나씩 추가한다고 했을때 &quot;119&quot;가 입력되면 이는 접두어가 아닌 자기 자신을 찾게되는 것이므로 &quot;1&quot; 과 &quot;11&quot;만 확인한다.&amp;nbsp; 결과적으로 &quot;119&quot;의 접두어가 &quot;1&quot;, &quot;11&quot;이 될수 있으므로 해당 값이 HashMap에 Key로 존재하면 접두어가 존재하게 되므로 false를 반환한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623336325068&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;&quot;119&quot;, &quot;119&quot;&amp;gt;
1 
11

&amp;lt;&quot;91674223&quot;, &quot;91674223&quot;&amp;gt;
9
97
976
9767
97674
976742
9767422

&amp;lt;&quot;1195524421&quot;, &quot;1195524421&quot;&amp;gt;
1
11
119
false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 추출된 key를 마지막 한글자만 빼고 하나씩 추가하면서 입력하는 방법은 다음과 같다. 자바가 조금 더 직관적이라 같이 비교해서 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1623337395976&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// java
for (String key : hashMap.keySet()) {
	// key.length() 보다 작을때까지 반복한다. 즉 마지막 글자 이전까지 반복한다. 
    for (int i = 0; i &amp;lt; key.length(); i++) {
        // Key가 첫 글자부터 더해지는 것은 substring() 메소드를 활용하면 된다.  
        // &quot;(0,0) = &quot;1&quot;  -&amp;gt; (0,1) = &quot;11&quot; -&amp;gt; (0,2) = &quot;119&quot;
        if (hashMap.containsKey(key.substring(0,i))) return false;
    }
}

// kotlin
for (key in hashMap.keys) {
    for (i in key.indices) {
        if (hashMap.containsKey(key.substring(0, i))) return false
    }
}&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;* solution()&lt;/p&gt;
&lt;pre id=&quot;code_1623337831597&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HashCodingTest2 {

    fun solution(phoneBook: Array&amp;lt;String&amp;gt;): Boolean {
        val hashMap = HashMap&amp;lt;String, String?&amp;gt;()
        for (phone in phoneBook) {
            hashMap[phone] = phone
        }
        for (key in hashMap.keys) {
            for (i in key.indices) {
                println(key.substring(0, i))
                if (hashMap.containsKey(key.substring(0, i))) return false
            }
        }
        return true
    }
}&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;&amp;nbsp;* test&lt;/p&gt;
&lt;pre id=&quot;code_1623338015877&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import hash.test2.HashCodingTest2

val hash2 by lazy { HashCodingTest2() }

val phoneBook1 = arrayOf(&quot;119&quot;, &quot;97674223&quot;, &quot;1195524421&quot;)
val phoneBook2 = arrayOf(&quot;123&quot;, &quot;456&quot;, &quot;789&quot;)
val phoneBook3 = arrayOf(&quot;12&quot;,&quot;123&quot;,&quot;1235&quot;,&quot;567&quot;,&quot;88&quot;)

fun main(args: Array&amp;lt;String&amp;gt;) {
    println(hash2.solution(phoneBook1))
    println(hash2.solution(phoneBook2))
    println(hash2.solution(phoneBook3))
}&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;결과&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623338063520&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1
11

9
97
976
9767
97674
976742
9767422

1
11
119
false

1
12

4
45

7
78
true

8

1

1
12
false&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm</category>
      <category>Algorithm</category>
      <category>Coding Test</category>
      <category>hash</category>
      <category>Kotlin</category>
      <category>전화번호 목록</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>jhk-im</author>
      <guid isPermaLink="true">https://jroomstudio.tistory.com/79</guid>
      <comments>https://jroomstudio.tistory.com/79#entry79comment</comments>
      <pubDate>Fri, 11 Jun 2021 00:17:01 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 라이브러리 만들기</title>
      <link>https://jroomstudio.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/studio/projects/android-library?hl=ko&quot;&gt;https://developer.android.com/studio/projects/android-library?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623073186900&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Android 라이브러리 만들기 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;Android 라이브러리를 생성하는 방법을 알아보세요.&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/studio/projects/android-library?hl=ko&quot; data-og-url=&quot;https://developer.android.com/studio/projects/android-library?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SqZja/hyKvDxrteq/z5LJlKYb2Ux6LbZKgAZBLk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676,https://scrap.kakaocdn.net/dn/QZgq0/hyKvu8kYL7/uyt66F8XDsHbiK4CHvKhP0/img.png?width=947&amp;amp;height=522&amp;amp;face=0_0_947_522,https://scrap.kakaocdn.net/dn/XTc7X/hyKurejKFf/NsZSiYsQQbeJcF3b2j5TsK/img.png?width=946&amp;amp;height=522&amp;amp;face=0_0_946_522&quot;&gt;&lt;a href=&quot;https://developer.android.com/studio/projects/android-library?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/studio/projects/android-library?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SqZja/hyKvDxrteq/z5LJlKYb2Ux6LbZKgAZBLk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676,https://scrap.kakaocdn.net/dn/QZgq0/hyKvu8kYL7/uyt66F8XDsHbiK4CHvKhP0/img.png?width=947&amp;amp;height=522&amp;amp;face=0_0_947_522,https://scrap.kakaocdn.net/dn/XTc7X/hyKurejKFf/NsZSiYsQQbeJcF3b2j5TsK/img.png?width=946&amp;amp;height=522&amp;amp;face=0_0_946_522');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Android 라이브러리 만들기 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Android 라이브러리를 생성하는 방법을 알아보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MAC OS&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Android studio 4.2.1&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;java&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Openjdk 1.8&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Android Library&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Android 라이브러리는 구조적으로 안드로이드 앱 모듈과 동일&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;소스 코드, 리소스 파일, 매니페스트를 비롯하여 앱을 빌드하는 데 필요한 모든 항목이 포함될 수 있음&lt;/li&gt;
&lt;li&gt;APK로 컴파일되는 대신 Android 앱 모듈의 종속 항목으로 사용할 수 있는 AAR로 컴파일&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AAR ?&lt;/b&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;자바 클래스 및 메서드 외에 레이아웃 및 드로어블과 같은 공유 리소스를 번들로 구성할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 앱 모듈의 C/C++ 코드에서 사용할 라이브러리를 포함할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라이브러리의 유용성&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;액티비티, 서비스 등 일부 구성요소를 동일하게 사용하는 여러 앱을 빌드하는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;여러&lt;span&gt; APK &lt;/span&gt;변형에&lt;span&gt; &lt;/span&gt;포함되는&lt;span&gt; &lt;/span&gt;앱을&lt;span&gt; &lt;/span&gt;빌드하며&lt;span&gt; &lt;/span&gt;두&lt;span&gt; &lt;/span&gt;버전에서&lt;span&gt; &lt;/span&gt;모두&lt;span&gt; &lt;/span&gt;동일한&lt;span&gt; &lt;/span&gt;핵심&lt;span&gt; &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;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;u&gt;아래 2가지 항목을 바탕으로 간단한 샘플을 만들어보고 라이브러리 모듈생성의 아주아주 기초적인 흐름을 정리해보자.&amp;nbsp;&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-06-07 오후 11.31.26.png&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eRd5ih/btq6MTAnCuB/1kPtrCDsUiB3jIUsRWguOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eRd5ih/btq6MTAnCuB/1kPtrCDsUiB3jIUsRWguOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eRd5ih/btq6MTAnCuB/1kPtrCDsUiB3jIUsRWguOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeRd5ih%2Fbtq6MTAnCuB%2F1kPtrCDsUiB3jIUsRWguOk%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;349&quot; height=&quot;407&quot; data-filename=&quot;스크린샷 2021-06-07 오후 11.31.26.png&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 라이브러리 모듈 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-06-07 오후 10.46.45.png&quot; data-origin-width=&quot;2232&quot; data-origin-height=&quot;1614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DtBp0/btq6DSpHbag/rKSfsMICaffU4GoEVHa241/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DtBp0/btq6DSpHbag/rKSfsMICaffU4GoEVHa241/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DtBp0/btq6DSpHbag/rKSfsMICaffU4GoEVHa241/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDtBp0%2Fbtq6DSpHbag%2FrKSfsMICaffU4GoEVHa241%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;477&quot; height=&quot;345&quot; data-filename=&quot;스크린샷 2021-06-07 오후 10.46.45.png&quot; data-origin-width=&quot;2232&quot; data-origin-height=&quot;1614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;New project -&amp;gt; No Activity 프로젝트 생성(empty activity도 상관없음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-06-09 오전 12.09.33.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;1220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfsb0s/btq6MAbsgAc/XKjR64HxAAIbQ47Nh4jGi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfsb0s/btq6MAbsgAc/XKjR64HxAAIbQ47Nh4jGi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfsb0s/btq6MAbsgAc/XKjR64HxAAIbQ47Nh4jGi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdfsb0s%2Fbtq6MAbsgAc%2FXKjR64HxAAIbQ47Nh4jGi1%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;1700&quot; height=&quot;1220&quot; data-filename=&quot;스크린샷 2021-06-09 오전 12.09.33.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;1220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;File -&amp;gt; New -&amp;gt; New Module -&amp;gt; Android Library -&amp;gt; Finish&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-06-09 오전 12.07.36.png&quot; data-origin-width=&quot;2262&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mnYWj/btq6PDZzGQr/bbHpbzmQiv6d0Nb0g5DTbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mnYWj/btq6PDZzGQr/bbHpbzmQiv6d0Nb0g5DTbK/img.png&quot; data-alt=&quot;app/build.gradle&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mnYWj/btq6PDZzGQr/bbHpbzmQiv6d0Nb0g5DTbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmnYWj%2Fbtq6PDZzGQr%2FbbHpbzmQiv6d0Nb0g5DTbK%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;2262&quot; height=&quot;842&quot; data-filename=&quot;스크린샷 2021-06-09 오전 12.07.36.png&quot; data-origin-width=&quot;2262&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/build.gradle&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;plugins에서 id &lt;span style=&quot;color: #6a8759;&quot;&gt;'com.android.application'&lt;/span&gt; -&amp;gt; id &lt;span style=&quot;color: #6a8759;&quot;&gt;'com.android.library'&lt;/span&gt; &amp;nbsp;변경&lt;/li&gt;
&lt;li&gt;defaultConfig에서 applicationId &lt;span style=&quot;color: #6a8759;&quot;&gt;&quot;com.example.libsample&quot; &lt;span style=&quot;color: #000000;&quot;&gt;삭제&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-06-07 오후 10.59.52.png&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE2L9I/btq6JnCq4MG/rWaxSsi94kko1m3jfae6f1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE2L9I/btq6JnCq4MG/rWaxSsi94kko1m3jfae6f1/img.png&quot; data-alt=&quot;Sync Project with Gradle Files&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE2L9I/btq6JnCq4MG/rWaxSsi94kko1m3jfae6f1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE2L9I%2Fbtq6JnCq4MG%2FrWaxSsi94kko1m3jfae6f1%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;386&quot; height=&quot;64&quot; data-filename=&quot;스크린샷 2021-06-07 오후 10.59.52.png&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Sync Project with Gradle Files&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #6a8759;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Sync Project with Gradle Files&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;를 클릭 or Sync Now&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* toast 함수 추가&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;samplelib/src/main/java/com.example.samplelib/Test.java&lt;/p&gt;
&lt;pre id=&quot;code_1623074876189&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.samplelib;

import android.content.Context;
import android.widget.Toast;

public class Test {

    public void showToast(Context context, String text) {
        Toast.makeText(context, text, Toast.LENGTH_LONG).show();
    }
}&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;* aar 빌드&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-06-09 오전 12.10.11.png&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8LLS2/btq6RLPLIK6/pSp9AnuiRa5DmCtAd4kWwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8LLS2/btq6RLPLIK6/pSp9AnuiRa5DmCtAd4kWwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8LLS2/btq6RLPLIK6/pSp9AnuiRa5DmCtAd4kWwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8LLS2%2Fbtq6RLPLIK6%2FpSp9AnuiRa5DmCtAd4kWwk%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;812&quot; height=&quot;494&quot; data-filename=&quot;스크린샷 2021-06-09 오전 12.10.11.png&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 A&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project에서 mylibrary선택 -&amp;gt; build -&amp;gt; Make Module 'LibSample.samplelib'&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;방법 B&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terminal -&amp;gt; ./gradlew assembleDebug&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;* aar 경로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;samplelib/build/output/aar/samplelib-debug.aar&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.&amp;nbsp; AAR 또는 JAR을 종속 항목으로 추가&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;empty activity 프로젝트 생성 후 TestApplication/app/libs 경로에 위에서 생성한 aar추가 &lt;/li&gt;
&lt;li&gt;&lt;b&gt;File&amp;nbsp;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Project Structure&amp;nbsp;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Dependencies&lt;/b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;로 이동&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;b&gt;Declared Dependencies&lt;/b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;탭 -&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Jar/AAR Dependency 선택&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;b&gt;libs/samplelib-debug.aar 입력후 ok&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;b&gt;File &amp;gt; Sync Project with Gradle Files 실행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추가된 라이브러리 확인 &lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-06-09 오전 12.16.23.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOafHq/btq6PCzCb2x/IBZYelwOKTTgXQGu2vH4EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOafHq/btq6PCzCb2x/IBZYelwOKTTgXQGu2vH4EK/img.png&quot; data-alt=&quot;Module - build.gradle&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOafHq/btq6PCzCb2x/IBZYelwOKTTgXQGu2vH4EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOafHq%2Fbtq6PCzCb2x%2FIBZYelwOKTTgXQGu2vH4EK%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;1260&quot; height=&quot;486&quot; data-filename=&quot;스크린샷 2021-06-09 오전 12.16.23.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Module - build.gradle&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;b&gt;*메인 액티비티에서 라이브러리 함수 호출 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623165500950&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.testapplication;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

import com.example.samplelib.Test;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Test test = new Test();
        test.showToast(this, &quot;library_sample&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;&amp;nbsp;&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;span style=&quot;color: #202124;&quot;&gt;&lt;b&gt;* 앱실행 혹은 Terminal -&amp;gt; ./gradlew installDebug&amp;nbsp;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-06-09 오전 12.21.55.png&quot; data-origin-width=&quot;2236&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPvEE3/btq6QPyrvkl/A7WyvAvB7PAxCGBnjtK791/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPvEE3/btq6QPyrvkl/A7WyvAvB7PAxCGBnjtK791/img.png&quot; data-alt=&quot;Terminal&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPvEE3/btq6QPyrvkl/A7WyvAvB7PAxCGBnjtK791/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPvEE3%2Fbtq6QPyrvkl%2FA7WyvAvB7PAxCGBnjtK791%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;2236&quot; height=&quot;476&quot; data-filename=&quot;스크린샷 2021-06-09 오전 12.21.55.png&quot; data-origin-width=&quot;2236&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Terminal&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;b&gt;* 설치된 앱 실행 확인&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2021-06-09 오전 12.32.50.gif&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;833&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JkqpJ/btq6RM2e0t2/ynvqDIXvCWavKaCghLcRg1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JkqpJ/btq6RM2e0t2/ynvqDIXvCWavKaCghLcRg1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JkqpJ/btq6RM2e0t2/ynvqDIXvCWavKaCghLcRg1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/JkqpJ/btq6RM2e0t2/ynvqDIXvCWavKaCghLcRg1/img.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; loading=&quot;lazy&quot; width=&quot;269&quot; height=&quot;833&quot; data-filename=&quot;화면 기록 2021-06-09 오전 12.32.50.gif&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;833&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Android</category>
      <category>android library</category>
      <category>라이브러리 만들기</category>
      <category>안드로이드 라이브러리</category>
      <author>jhk-im</author>
      <guid isPermaLink="true">https://jroomstudio.tistory.com/78</guid>
      <comments>https://jroomstudio.tistory.com/78#entry78comment</comments>
      <pubDate>Wed, 9 Jun 2021 00:35:51 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin algorithm - Hash (1)</title>
      <link>https://jroomstudio.tistory.com/77</link>
      <description>&lt;figure id=&quot;og_1621316812549&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/parts/12077&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/parts/12077&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oPwSV/hyKfc2Olcj/c345oO0u4nNy4DBIy2zock/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/gwOWb/hyKe0afD5H/6SIaeHrKwzzjveMdSTM25k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/parts/12077&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/parts/12077&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oPwSV/hyKfc2Olcj/c345oO0u4nNy4DBIy2zock/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/gwOWb/hyKe0afD5H/6SIaeHrKwzzjveMdSTM25k/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; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jhk-im/jhk-algorithm/tree/main/kotlin/01_hash/test1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/jhk-im/jhk-algorithm/tree/main/kotlin/01_hash/test1&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1656155416648&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - jhk-im/jhk-algorithm&quot; data-og-description=&quot;Contribute to jhk-im/jhk-algorithm development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jhk-im/jhk-algorithm/tree/main/kotlin/01_hash/test1&quot; data-og-url=&quot;https://github.com/jhk-im/jhk-algorithm&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dqgOZ4/hyOSU3kd9s/K3yO9AmJ50N5kHskWCo0D1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jhk-im/jhk-algorithm/tree/main/kotlin/01_hash/test1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jhk-im/jhk-algorithm/tree/main/kotlin/01_hash/test1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dqgOZ4/hyOSU3kd9s/K3yO9AmJ50N5kHskWCo0D1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - jhk-im/jhk-algorithm&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to jhk-im/jhk-algorithm development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ArrayList는 인덱스를 이용하여 검색이 한번에 이루어지기 때문에 검색 속도를 보장하는 반면 데이터의 추가/삭제시에는 많은 데이터가 다같이 이동해야 하기 때문에 많은 시간이 소요된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog_img_1.png&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QJkd3/btq5bNIm8vJ/uSrg4zKAytIAoB9LAVFEyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QJkd3/btq5bNIm8vJ/uSrg4zKAytIAoB9LAVFEyk/img.png&quot; data-alt=&quot;array_img&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QJkd3/btq5bNIm8vJ/uSrg4zKAytIAoB9LAVFEyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQJkd3%2Fbtq5bNIm8vJ%2FuSrg4zKAytIAoB9LAVFEyk%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;962&quot; height=&quot;222&quot; data-filename=&quot;blog_img_1.png&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;array_img&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;* 추가/삭제시 마주하는 한계를 극복하기 위해 제시된 방법이 Hash이다. 기존 데이터를 밀어내거나 당기는 작업이 필요없도록 특별한 알고리즘을 이용하여 데이터와 관련된 고유한 숫자를 만들고 이를 인덱스로 사용한다. Key-Value 방식으로 저장하며 여기서 Key값을 계산하는 것이 Hash Method이다. 그렇게 반환 된 데이터 고유 값을 Hash Code라고 한다. 그리고 이러한 방식으로 Table에 저장하고 검색하는 방법을 Hashing이라 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog_img_2.png&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pdqUe/btq454x3Kuh/0WSMVtgKzkbOIeyZlKYPY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pdqUe/btq454x3Kuh/0WSMVtgKzkbOIeyZlKYPY1/img.png&quot; data-alt=&quot;hash_img&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pdqUe/btq454x3Kuh/0WSMVtgKzkbOIeyZlKYPY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpdqUe%2Fbtq454x3Kuh%2F0WSMVtgKzkbOIeyZlKYPY1%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;321&quot; height=&quot;221&quot; data-filename=&quot;blog_img_2.png&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;hash_img&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;* String 클래스에서 Hash Method인 hashCode()를 오버라이딩 하여 문자열의 내용으로 고유값을 생성한다. 서로 다른 인스턴스이지만 같은 내용의 문자열을 가졌기 때문에 hashCode()호출 시 같은 값을 얻는다. HashMap도 같은 방법으로 객체를 구별하며 이미 존재하는 키와 동일한 값을 저장하는 경우 기존의 값에 덮어쓴다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621274226527&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val s1 = &quot;string&quot;
val s2 = &quot;string&quot;
print(s1.hashcode()) // -891985903
print(s2.hashcode()) // -891985903
print(s1==s2) // true

val hashMap = HashMap&amp;lt;String, Int&amp;gt;()
hashMap[&quot;s1&quot;] = 1
hashMap[&quot;s2&quot;] = 3
hashMap[&quot;s2&quot;] = 2
print(hashMap)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog_img_3.png&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/es5ayh/btq44PuomHH/n3aSJI7OoWA3jL7nuSPtTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/es5ayh/btq44PuomHH/n3aSJI7OoWA3jL7nuSPtTk/img.png&quot; data-alt=&quot;hash_img_2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/es5ayh/btq44PuomHH/n3aSJI7OoWA3jL7nuSPtTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fes5ayh%2Fbtq44PuomHH%2Fn3aSJI7OoWA3jL7nuSPtTk%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;961&quot; height=&quot;221&quot; data-filename=&quot;blog_img_3.png&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;hash_img_2&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;* 프로그래머스 코딩테스트의 문제와 제한사항은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 선수 배열과 완주 선수 배열이 주어질 때 완주하지못한 참가자를 찾아라. (완주하지 못한 선수는 1명)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하&lt;/li&gt;
&lt;li&gt;completion의 길이는 participant의 길이보다 1 작음&lt;/li&gt;
&lt;li&gt;참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있음&lt;/li&gt;
&lt;li&gt;참가자 중에는 동명이인이 있을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ArrayList를 사용할 때 문제점은 무엇인가?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index는 단순히 순서를 나타내는 고유값이다. 그렇기 때문에 player A의 완주유무를 검사한다고 했을 때 전체선수 배열의 player A의 index와 완주선수 배열의 player A의 index가 동일할 것이라고 보장할 수 없으므로 player A의 완주 유무를 판단하기 위해서는 완주선수 배열에서 매치될 때 까지 전부 검사해야한다. 각각의 index의 검사 완료 시점또한 동일하지 않다. 중복된 player를 검사하는 것 까지 추가되면 더욱 복잡해진다. (ArrayList로 해결한 답안도 있다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog_img_4.png&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgVnvc/btq49RLvrHQ/5oPZ36VuGtaU6ttb23uFck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgVnvc/btq49RLvrHQ/5oPZ36VuGtaU6ttb23uFck/img.png&quot; data-alt=&quot;arrayList match&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgVnvc/btq49RLvrHQ/5oPZ36VuGtaU6ttb23uFck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgVnvc%2Fbtq49RLvrHQ%2F5oPZ36VuGtaU6ttb23uFck%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;321&quot; height=&quot;221&quot; data-filename=&quot;blog_img_4.png&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;arrayList match&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* HashMap을 사용할 때 어떠한 이점이 있는가?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체선수 배열에서 선수의 이름을 Key값으로 하여 HashMap으로 정리 된다면 완주선수 배열의 선수명으로 HashMap Key값을 이용해서&amp;nbsp; 빠르게 매치시킬 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog_img_5.png&quot; data-origin-width=&quot;461&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mtXnZ/btq5ap9aj0N/A340Hck4OjlN7Q6NzZmYVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mtXnZ/btq5ap9aj0N/A340Hck4OjlN7Q6NzZmYVK/img.png&quot; data-alt=&quot;hash map match&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mtXnZ/btq5ap9aj0N/A340Hck4OjlN7Q6NzZmYVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmtXnZ%2Fbtq5ap9aj0N%2FA340Hck4OjlN7Q6NzZmYVK%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;461&quot; height=&quot;231&quot; data-filename=&quot;blog_img_5.png&quot; data-origin-width=&quot;461&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;hash map match&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체선수 배열을 HashMap에 저장할 때 HashMap의 Key를 생성하는 hashCode()는 중복을 허용하지 않기 때문에 중복된 선수명 key값은 덮어쓰게 되는 문제가 있는데 다음과 같은 방식으로 중복 검사를 진행하여 해결한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621304946360&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val map = HashMap&amp;lt;String, Int&amp;gt;()

// 1. 전체 선수배열에서 선수의 이름을 Key값으로 하여 HashMap에 저장한다. 
for (player : String in participant) {

    // 2. 이미 저장했던 선수인지를 let {} 을 통해 판단한다. 
    //    let {}은 자바의 if(value != null)을 대체하여 유용하게 사용된다. 
    map[player]?.let {
        // 3. 이미 저장했던 선수인 경우 기존 value에 + 1을 더한다. 
        map[player] = it + 1
    }

    // 4. 중복되지 않은 선수는 value를 1로 지정한다. 
    if (map[player] == null) {
        map[player] = 1
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위 코드는 HashMap의 getOrDefault(Object, defaultValue)를 사용하면 간략하게 중복검사를 진행할 수 있다. getOrDefault()는 입력한 key값이 존재하는 경우 해당 key의 value를 반환하고 없을 경우 default값을 반환한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621305673799&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (player : String in participant) {
    map[player] = map.getOrDefault(player, 0) + 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog_img_6.png&quot; data-origin-width=&quot;461&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOgwOM/btq454ZhVcU/Kk6qqqLkjvGEcjBwp8Okd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOgwOM/btq454ZhVcU/Kk6qqqLkjvGEcjBwp8Okd0/img.png&quot; data-alt=&quot;중복검사&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOgwOM/btq454ZhVcU/Kk6qqqLkjvGEcjBwp8Okd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOgwOM%2Fbtq454ZhVcU%2FKk6qqqLkjvGEcjBwp8Okd0%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;461&quot; height=&quot;231&quot; data-filename=&quot;blog_img_6.png&quot; data-origin-width=&quot;461&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;중복검사&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 완주선수 배열의 선수명과 전체선수 HashMap의 Key를 매치시키고 value값에 1을 빼도록 한다. 완주한 선수는 0이되고 완주하지 못한 선수는 1이 될 것이다. 중복된 선수중 한명만 완주 했다면 마찬가지로 해당 선수의 vlaue는 1이 된다. 모두 완주했다면 value는 0이 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1621307266380&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (player : String in completion) {
    map[player]?.apply {
        map[player] = this - 1
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완주선수 배열과 매치가 끝난 HashMap은 아래와 같이 Key값을 검사하고 value가 1인 선수를 찾으면 문제가 해결된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1621307586015&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (key : String in map.keys) {
    if (map[key] != 0) {
        // map[key] -&amp;gt; 완주하지 못한 선수!
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm</category>
      <category>Algorithm</category>
      <category>hash</category>
      <category>Kotlin</category>
      <author>jhk-im</author>
      <guid isPermaLink="true">https://jroomstudio.tistory.com/77</guid>
      <comments>https://jroomstudio.tistory.com/77#entry77comment</comments>
      <pubDate>Tue, 18 May 2021 12:16:07 +0900</pubDate>
    </item>
    <item>
      <title>Android Emulator on Apple M1</title>
      <link>https://jroomstudio.tistory.com/76</link>
      <description>&lt;figure id=&quot;og_1609053153108&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;google/android-emulator-m1-preview&quot; data-og-description=&quot;Contribute to google/android-emulator-m1-preview development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/google/android-emulator-m1-preview&quot; data-og-url=&quot;https://github.com/google/android-emulator-m1-preview&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gy6hM/hyIHK7QZMi/IC76vvDHrGX1Zbg3rkZbeK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://github.com/google/android-emulator-m1-preview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/google/android-emulator-m1-preview&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gy6hM/hyIHK7QZMi/IC76vvDHrGX1Zbg3rkZbeK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;google/android-emulator-m1-preview&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Contribute to google/android-emulator-m1-preview development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;애플 m1칩이 내장된 맥북에서는 안드로이드 스튜디오 애뮬레이터가 동작하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리하여 구글에서 급하게..? m1에서 실행할 수 있는 안드로이드 애뮬레이터를 공개하였다.&lt;/p&gt;
&lt;p&gt;설치하는 방법을 간단하게 정리해본다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위에 있는 android-emulator-m1-preview에 들어간 후 releases로 이동한다.&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;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.24.52.png&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;438&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzrUem/btqRAjOg0xU/KjzYKWZCDnyuwH5ZhMWwjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzrUem/btqRAjOg0xU/KjzYKWZCDnyuwH5ZhMWwjk/img.png&quot; data-alt=&quot;android emulator m1 releases&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzrUem/btqRAjOg0xU/KjzYKWZCDnyuwH5ZhMWwjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzrUem%2FbtqRAjOg0xU%2FKjzYKWZCDnyuwH5ZhMWwjk%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.24.52.png&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;438&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;android emulator m1 releases&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;최신 release에서 Assets를 확인하고 dmg 파일을 다운로드 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.22.39.png&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;575&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PQ2Nv/btqRx1f2ow3/QK4Apm07HgOmloy2PSojkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PQ2Nv/btqRx1f2ow3/QK4Apm07HgOmloy2PSojkk/img.png&quot; data-alt=&quot;download dmg file&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PQ2Nv/btqRx1f2ow3/QK4Apm07HgOmloy2PSojkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPQ2Nv%2FbtqRx1f2ow3%2FQK4Apm07HgOmloy2PSojkk%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.22.39.png&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;575&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;download dmg file&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;다운로드 받은 dmg파일을 실행하고 다음과 같이 Emulator파일을 Application폴더로 이동시킨다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.30.19.png&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;310&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sdpD6/btqRptynZTe/MA718HnpJ4MKErGNjjITjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sdpD6/btqRptynZTe/MA718HnpJ4MKErGNjjITjk/img.png&quot; data-alt=&quot;emulator file이동&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sdpD6/btqRptynZTe/MA718HnpJ4MKErGNjjITjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsdpD6%2FbtqRptynZTe%2FMA718HnpJ4MKErGNjjITjk%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.30.19.png&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;310&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;emulator file이동&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-filename=&quot;스크린샷 2020-12-27 오후 4.33.22.png&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;289&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/omDPV/btqRx02vt5d/diaA1vS4xk9Fzqs46gnPvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/omDPV/btqRx02vt5d/diaA1vS4xk9Fzqs46gnPvk/img.png&quot; data-alt=&quot;경고 메세지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/omDPV/btqRx02vt5d/diaA1vS4xk9Fzqs46gnPvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FomDPV%2FbtqRx02vt5d%2FdiaA1vS4xk9Fzqs46gnPvk%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.33.22.png&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;289&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;경고 메세지&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시스템 환경설정 -&amp;gt; 보안 및 개인정보 보호 -&amp;gt; 확인 없이 열기&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.37.29.png&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;290&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ej1fQS/btqRCVfh2Pc/2kQI5VJ9a59k3fYaCzBCNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ej1fQS/btqRCVfh2Pc/2kQI5VJ9a59k3fYaCzBCNk/img.png&quot; data-alt=&quot;설정 변경&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ej1fQS/btqRCVfh2Pc/2kQI5VJ9a59k3fYaCzBCNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fej1fQS%2FbtqRCVfh2Pc%2F2kQI5VJ9a59k3fYaCzBCNk%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.37.29.png&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;290&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;설정 변경&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;애뮬레이터가 문제없이 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;463&quot; data-origin-height=&quot;749&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.39.58.png&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzV3y2/btqRx1mNnrg/xktAkF7wcVwgBc7sHUK0oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzV3y2/btqRx1mNnrg/xktAkF7wcVwgBc7sHUK0oK/img.png&quot; data-alt=&quot;에뮬레이터 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzV3y2/btqRx1mNnrg/xktAkF7wcVwgBc7sHUK0oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzV3y2%2FbtqRx1mNnrg%2FxktAkF7wcVwgBc7sHUK0oK%2Fimg.png&quot; data-origin-width=&quot;463&quot; data-origin-height=&quot;749&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.39.58.png&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;에뮬레이터 실행&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-filename=&quot;스크린샷 2020-12-27 오후 4.40.06.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;55&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vqplf/btqRrPtTof5/CtRcZzKcenkCNPwWjTzMBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vqplf/btqRrPtTof5/CtRcZzKcenkCNPwWjTzMBk/img.png&quot; data-alt=&quot;안드로이드 스튜디오&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vqplf/btqRrPtTof5/CtRcZzKcenkCNPwWjTzMBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvqplf%2FbtqRrPtTof5%2FCtRcZzKcenkCNPwWjTzMBk%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-27 오후 4.40.06.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;55&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;안드로이드 스튜디오&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;1. Webview AOSP 버전에서 작동하지 않음&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2. 크롬 동작하지 않음&lt;/p&gt;
&lt;p&gt;3. Device skin없음&lt;/p&gt;
&lt;p&gt;3. 비디오 코덱 작동하지 않음&lt;/p&gt;
&lt;p&gt;4. 32비트 ARM 앱 작동하지 않음&lt;/p&gt;
&lt;p&gt;5. 일부 Vulkan app 작동하지 않음&lt;/p&gt;
&lt;p&gt;6. 시작 시 adb경로를 찾을 수 없다는 팝업&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고자료&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=YjUCXGGJu7E&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/nsc9F/hyIHARI3AX/EIaVJpfwiAs8ChLL4fKLnK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/YjUCXGGJu7E&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&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>Others</category>
      <category>android emulator m1</category>
      <category>macbook m1</category>
      <author>jhk-im</author>
      <guid isPermaLink="true">https://jroomstudio.tistory.com/76</guid>
      <comments>https://jroomstudio.tistory.com/76#entry76comment</comments>
      <pubDate>Sun, 27 Dec 2020 16:47:30 +0900</pubDate>
    </item>
    <item>
      <title>Setup Android  Development Envrionment on Mac OS</title>
      <link>https://jroomstudio.tistory.com/75</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;MAC OS에서 안드로이드 개발환경 셋팅&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Mac Command Line Tools&lt;/h3&gt;
&lt;pre id=&quot;code_1608091241692&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 터미널 입력

xcode-select --install&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Homebrew&amp;nbsp;&lt;/h3&gt;
&lt;figure id=&quot;og_1608091425653&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Homebrew&quot; data-og-description=&quot;The Missing Package Manager for macOS (or Linux).&quot; data-og-host=&quot;brew.sh&quot; data-og-source-url=&quot;https://brew.sh/&quot; data-og-url=&quot;https://brew.sh/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LRjDD/hyIARk4nr5/AUylc5R1UpIlxOC4UCjgZ1/img.png?width=1200&amp;amp;height=560&amp;amp;face=0_0_1200_560,https://scrap.kakaocdn.net/dn/6klIu/hyIAVVhGnW/xuKwokcK7iIhzdZoRuPR90/img.png?width=1200&amp;amp;height=560&amp;amp;face=0_0_1200_560,https://scrap.kakaocdn.net/dn/cHZtqv/hyIAUa0LTf/t2Fuc9B4F1gRHoNnurOtC1/img.png?width=298&amp;amp;height=298&amp;amp;face=0_0_298_298&quot;&gt;&lt;a href=&quot;https://brew.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://brew.sh/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LRjDD/hyIARk4nr5/AUylc5R1UpIlxOC4UCjgZ1/img.png?width=1200&amp;amp;height=560&amp;amp;face=0_0_1200_560,https://scrap.kakaocdn.net/dn/6klIu/hyIAVVhGnW/xuKwokcK7iIhzdZoRuPR90/img.png?width=1200&amp;amp;height=560&amp;amp;face=0_0_1200_560,https://scrap.kakaocdn.net/dn/cHZtqv/hyIAUa0LTf/t2Fuc9B4F1gRHoNnurOtC1/img.png?width=298&amp;amp;height=298&amp;amp;face=0_0_298_298');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Homebrew&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;The Missing Package Manager for macOS (or Linux).&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;brew.sh&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Install Homebrew&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1608091472492&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 터미널 입력

/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. ZSH&lt;/h3&gt;
&lt;figure id=&quot;og_1608090631432&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;ohmyzsh/ohmyzsh&quot; data-og-description=&quot;  A delightful community-driven (with 1700+ contributors) framework for managing your zsh configuration. Includes nearly 300 optional plugins (rails, git, OSX, hub, capistrano, brew, ant, php, pyt...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/ohmyzsh/ohmyzsh&quot; data-og-url=&quot;https://github.com/ohmyzsh/ohmyzsh&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Kxm93/hyIAUB4yrH/9XKGOdFkF7h7ofBnE7AXy1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cnAQBH/hyIAPALQFB/L6JHywaALD97SPFkSC1BNk/img.png?width=633&amp;amp;height=346&amp;amp;face=0_0_633_346,https://scrap.kakaocdn.net/dn/ScLx1/hyIAIVV3vf/hrQUo6Lp6tKaWnyJZ4WOUk/img.png?width=337&amp;amp;height=208&amp;amp;face=0_0_337_208&quot;&gt;&lt;a href=&quot;https://github.com/ohmyzsh/ohmyzsh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/ohmyzsh/ohmyzsh&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Kxm93/hyIAUB4yrH/9XKGOdFkF7h7ofBnE7AXy1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cnAQBH/hyIAPALQFB/L6JHywaALD97SPFkSC1BNk/img.png?width=633&amp;amp;height=346&amp;amp;face=0_0_633_346,https://scrap.kakaocdn.net/dn/ScLx1/hyIAIVV3vf/hrQUo6Lp6tKaWnyJZ4WOUk/img.png?width=337&amp;amp;height=208&amp;amp;face=0_0_337_208');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;ohmyzsh/ohmyzsh&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;  A delightful community-driven (with 1700+ contributors) framework for managing your zsh configuration. Includes nearly 300 optional plugins (rails, git, OSX, hub, capistrano, brew, ant, php, pyt...&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Basic Installation&lt;/h4&gt;
&lt;pre id=&quot;code_1608090714791&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 터미널 입력

sh -c &quot;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. ZSH Highlighting&lt;/h3&gt;
&lt;figure id=&quot;og_1608092749080&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;zsh-users/zsh-syntax-highlighting&quot; data-og-description=&quot;Fish shell like syntax highlighting for Zsh. Contribute to zsh-users/zsh-syntax-highlighting development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/INSTALL.md&quot; data-og-url=&quot;https://github.com/zsh-users/zsh-syntax-highlighting&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/KEAGa/hyIAJggYay/Q5WQ4AxQqWeDaVG4msWQe1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/INSTALL.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/INSTALL.md&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/KEAGa/hyIAJggYay/Q5WQ4AxQqWeDaVG4msWQe1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;zsh-users/zsh-syntax-highlighting&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Fish shell like syntax highlighting for Zsh. Contribute to zsh-users/zsh-syntax-highlighting development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;In your ~/.zshrc&lt;/h4&gt;
&lt;pre id=&quot;code_1608092384378&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 터미널

git clone https://github.com/zsh-users/zsh-syntax-highlighting.git
echo &quot;source ${(q-)PWD}/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh&quot; &amp;gt;&amp;gt; ${ZDOTDIR:-$HOME}/.zshrc


source ./zsh-syntax-highlighting/zsh-syntax-highlighting.zsh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. ZSH Auto suggestions&lt;/h3&gt;
&lt;figure id=&quot;og_1608092721761&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;zsh-users/zsh-autosuggestions&quot; data-og-description=&quot;Fish-like autosuggestions for zsh. Contribute to zsh-users/zsh-autosuggestions development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md&quot; data-og-url=&quot;https://github.com/zsh-users/zsh-autosuggestions&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bqkaGD/hyIAMD3MD3/9HkbufL4TybpqTU5Min1RK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/dF8oiF/hyIARFn008/2pHEEQkPQq3LW0D3RLEekk/img.png?width=1114&amp;amp;height=862&amp;amp;face=0_0_1114_862&quot;&gt;&lt;a href=&quot;https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bqkaGD/hyIAMD3MD3/9HkbufL4TybpqTU5Min1RK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/dF8oiF/hyIARFn008/2pHEEQkPQq3LW0D3RLEekk/img.png?width=1114&amp;amp;height=862&amp;amp;face=0_0_1114_862');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;zsh-users/zsh-autosuggestions&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Fish-like autosuggestions for zsh. Contribute to zsh-users/zsh-autosuggestions development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Oh My Zsh&lt;/h4&gt;
&lt;pre id=&quot;code_1608092805443&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 터미널

git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1608092860342&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 터미널

vim ~/.zshrc&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1608093054567&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 플러그인 추가

...
plugins=(
        git
        zsh-autosuggestions
)
...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. zsh - powerlevel10k&lt;/h3&gt;
&lt;figure id=&quot;og_1608093875798&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;romkatv/powerlevel10k&quot; data-og-description=&quot;A Zsh theme. Contribute to romkatv/powerlevel10k development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/romkatv/powerlevel10k#oh-my-zsh&quot; data-og-url=&quot;https://github.com/romkatv/powerlevel10k&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/clJtzd/hyIAQ0N3YR/UiAZT7PYKuFqitrWAXKVIk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=159_79_284_216,https://scrap.kakaocdn.net/dn/bsUIw1/hyIAH3QfTP/wfMkGWcahcC5iMj4Cw0VG1/img.png?width=765&amp;amp;height=516&amp;amp;face=0_0_765_516&quot;&gt;&lt;a href=&quot;https://github.com/romkatv/powerlevel10k#oh-my-zsh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/romkatv/powerlevel10k#oh-my-zsh&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/clJtzd/hyIAQ0N3YR/UiAZT7PYKuFqitrWAXKVIk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=159_79_284_216,https://scrap.kakaocdn.net/dn/bsUIw1/hyIAH3QfTP/wfMkGWcahcC5iMj4Cw0VG1/img.png?width=765&amp;amp;height=516&amp;amp;face=0_0_765_516');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;romkatv/powerlevel10k&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;A Zsh theme. Contribute to romkatv/powerlevel10k development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Oh My Zsh&lt;/h4&gt;
&lt;pre id=&quot;code_1608093941681&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 터미널 입력

git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1608094064367&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 터미널 입력

vim ~/.zshrc&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1608094095206&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 테마변경

ZSH_THEME=&quot;powerlevel10k/powerlevel10k&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-16 오후 1.53.51.png&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;954&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/olFxi/btqQgy8m8V6/KVlVuLePh6BAYEuxZHc5L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/olFxi/btqQgy8m8V6/KVlVuLePh6BAYEuxZHc5L0/img.png&quot; data-alt=&quot;Terminal 최종결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/olFxi/btqQgy8m8V6/KVlVuLePh6BAYEuxZHc5L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FolFxi%2FbtqQgy8m8V6%2FKVlVuLePh6BAYEuxZHc5L0%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-16 오후 1.53.51.png&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;954&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;Terminal 최종결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. Android Studio&lt;/h3&gt;
&lt;figure id=&quot;og_1608094538219&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Download Android Studio and SDK tools &amp;nbsp;|&amp;nbsp; Android 스튜디오&quot; data-og-description=&quot;&amp;lt;!-- hide description --&amp;gt;&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/studio&quot; data-og-url=&quot;https://developer.android.com/studio?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uUVTC/hyIAK7nk3Q/S8KI5S75rHSgeJn9yrtL31/img.jpg?width=1352&amp;amp;height=818&amp;amp;face=0_0_1352_818,https://scrap.kakaocdn.net/dn/ePmN7/hyIALyoSeN/28k2GuK0Zu8CluY1Uzl9W0/img.png?width=725&amp;amp;height=578&amp;amp;face=0_0_725_578,https://scrap.kakaocdn.net/dn/bnMfEF/hyIAVgJ1WA/DdtxNHL5CNwEjz2SLQVOK1/img.png?width=725&amp;amp;height=556&amp;amp;face=0_0_725_556&quot;&gt;&lt;a href=&quot;https://developer.android.com/studio&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/studio&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uUVTC/hyIAK7nk3Q/S8KI5S75rHSgeJn9yrtL31/img.jpg?width=1352&amp;amp;height=818&amp;amp;face=0_0_1352_818,https://scrap.kakaocdn.net/dn/ePmN7/hyIALyoSeN/28k2GuK0Zu8CluY1Uzl9W0/img.png?width=725&amp;amp;height=578&amp;amp;face=0_0_725_578,https://scrap.kakaocdn.net/dn/bnMfEF/hyIAVgJ1WA/DdtxNHL5CNwEjz2SLQVOK1/img.png?width=725&amp;amp;height=556&amp;amp;face=0_0_725_556');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Download Android Studio and SDK tools &amp;nbsp;|&amp;nbsp; Android 스튜디오&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;&lt;!-- hide description --&gt;&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;안드로이드 스튜디오 설치 후 sdk, avd 확인&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-16 오후 1.59.24.png&quot; data-origin-width=&quot;123&quot; data-origin-height=&quot;117&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5vCHi/btqQhKOlKNw/ZHlFpMPqqtg5ApKGoidNi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5vCHi/btqQhKOlKNw/ZHlFpMPqqtg5ApKGoidNi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5vCHi/btqQhKOlKNw/ZHlFpMPqqtg5ApKGoidNi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5vCHi%2FbtqQhKOlKNw%2FZHlFpMPqqtg5ApKGoidNi0%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-16 오후 1.59.24.png&quot; data-origin-width=&quot;123&quot; data-origin-height=&quot;117&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안드로이드 스튜디오 창 가장오른쪽 AVD Manager와 SDK Manager를 통해 설치&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-16 오후 1.57.32.png&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;341&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpjPDl/btqQhMkXxvx/77mMIcWBGXCehnNZUsGd3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpjPDl/btqQhMkXxvx/77mMIcWBGXCehnNZUsGd3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpjPDl/btqQhMkXxvx/77mMIcWBGXCehnNZUsGd3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpjPDl%2FbtqQhMkXxvx%2F77mMIcWBGXCehnNZUsGd3K%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-16 오후 1.57.32.png&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;341&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;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-16 오후 1.58.09.png&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;422&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLZy3/btqQnYSg05Z/qgGF5krplRxJjeiM2rAoI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLZy3/btqQnYSg05Z/qgGF5krplRxJjeiM2rAoI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLZy3/btqQnYSg05Z/qgGF5krplRxJjeiM2rAoI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLZy3%2FbtqQnYSg05Z%2FqgGF5krplRxJjeiM2rAoI1%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-16 오후 1.58.09.png&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;422&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;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-16 오후 2.02.22.png&quot; data-origin-width=&quot;1732&quot; data-origin-height=&quot;871&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ8Cb8/btqQsskXrlA/8WZmDcsT1umSv0Nk2czbWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ8Cb8/btqQsskXrlA/8WZmDcsT1umSv0Nk2czbWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ8Cb8/btqQsskXrlA/8WZmDcsT1umSv0Nk2czbWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ8Cb8%2FbtqQsskXrlA%2F8WZmDcsT1umSv0Nk2czbWk%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-16 오후 2.02.22.png&quot; data-origin-width=&quot;1732&quot; data-origin-height=&quot;871&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;마지막으로 안드로이드 스튜디오 터미널에 zsh 적용 되었는지 확인&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-16 오후 2.04.48.png&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;264&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkgfVM/btqQhMkXNlZ/NT89VpuUNtkJLkf6rKw37k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkgfVM/btqQhMkXNlZ/NT89VpuUNtkJLkf6rKw37k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkgfVM/btqQhMkXNlZ/NT89VpuUNtkJLkf6rKw37k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkgfVM%2FbtqQhMkXNlZ%2FNT89VpuUNtkJLkf6rKw37k%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-16 오후 2.04.48.png&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;264&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;&amp;nbsp;글자크기 변경은 활성화해두자&amp;nbsp;&lt;/p&gt;
&lt;p&gt;SDK Manager icon -&amp;gt; Editor -&amp;gt; General -&amp;gt; Change font size with Command+Mouse Wheel check!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-12-16 오후 2.18.39.png&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;496&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vicY1/btqQlKAgdwG/WpBdAJlmnCSgGRcVNLD4iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vicY1/btqQlKAgdwG/WpBdAJlmnCSgGRcVNLD4iK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vicY1/btqQlKAgdwG/WpBdAJlmnCSgGRcVNLD4iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvicY1%2FbtqQlKAgdwG%2FWpBdAJlmnCSgGRcVNLD4iK%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-12-16 오후 2.18.39.png&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;496&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;</description>
      <category>Others</category>
      <author>jhk-im</author>
      <guid isPermaLink="true">https://jroomstudio.tistory.com/75</guid>
      <comments>https://jroomstudio.tistory.com/75#entry75comment</comments>
      <pubDate>Wed, 16 Dec 2020 14:05:35 +0900</pubDate>
    </item>
    <item>
      <title>Android GraphQL tutorials (1) - Apollo Client? / setup apollo schema</title>
      <link>https://jroomstudio.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Nexus GraphQL tutorials 를 통해서 Nexus - Prisma - PostgreSQL로 이루어진 서버가 간단하게 구현되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 서버에 구현되어있는 쿼리와 뮤테이션은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;Screenshot from 2020-12-11 16-14-32.png&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdIsUi/btqPKtNBLwS/t6msZh2kXosb9KCbTeqYQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdIsUi/btqPKtNBLwS/t6msZh2kXosb9KCbTeqYQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdIsUi/btqPKtNBLwS/t6msZh2kXosb9KCbTeqYQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdIsUi%2FbtqPKtNBLwS%2Ft6msZh2kXosb9KCbTeqYQ1%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;555&quot; height=&quot;628&quot; data-filename=&quot;Screenshot from 2020-12-11 16-14-32.png&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;해당 화면은 Graphql playground이며 구현된 쿼리와 뮤테이션을 다음과 같이 사용해볼 수있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Draft 쿼리에 대하여 결과를 확인해 볼 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;Screenshot from 2020-12-11 16-17-35.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2do00/btqPTCPPrNb/LnYiTIXwEAOjy6doK3X0V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2do00/btqPTCPPrNb/LnYiTIXwEAOjy6doK3X0V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2do00/btqPTCPPrNb/LnYiTIXwEAOjy6doK3X0V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2do00%2FbtqPTCPPrNb%2FLnYiTIXwEAOjy6doK3X0V1%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;1026&quot; height=&quot;324&quot; data-filename=&quot;Screenshot from 2020-12-11 16-17-35.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Android Kotlin 환경에서 해당 쿼리와 뮤테이션을 사용해보도록 하겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1607671191328&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Get started with Kotlin&quot; data-og-description=&quot;A guide to using Apollo with Android&quot; data-og-host=&quot;www.apollographql.com&quot; data-og-source-url=&quot;https://www.apollographql.com/docs/android/essentials/get-started-kotlin/&quot; data-og-url=&quot;https://www.apollographql.com/docs/android/essentials/get-started-kotlin/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cMFDTd/hyIxhi60Ts/4LfvqQuKBSdnmror7Kg1S0/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/sJFX9/hyIwwoDH1N/xE7nvFXgdnfjKVnP8R7pCK/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628&quot;&gt;&lt;a href=&quot;https://www.apollographql.com/docs/android/essentials/get-started-kotlin/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.apollographql.com/docs/android/essentials/get-started-kotlin/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cMFDTd/hyIxhi60Ts/4LfvqQuKBSdnmror7Kg1S0/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/sJFX9/hyIwwoDH1N/xE7nvFXgdnfjKVnP8R7pCK/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Get started with Kotlin&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A guide to using Apollo with Android&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.apollographql.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apollo graphql은 안드로이드 환경에서 apollo client를 사용할 수 있도록 문서를 제공하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 가이드를 참고하여 서버에서 구현한 createDraft, publish, drafts, posts를 테스트해보도록 하겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;add-the-gradle-plugin&quot; data-ke-size=&quot;size26&quot;&gt;Add the Gradle Plugin&lt;/h2&gt;
&lt;pre id=&quot;code_1607671673981&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle(project)
...

ext {
...
    // apollo graphql
    apolloVersion = '2.4.6'
    
    // coroutines
    coroutinesVersion = '1.4.0'
...
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1607671804450&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle(app)
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'kotlin-android-extensions'
    id 'com.apollographql.apollo' version &quot;$apolloVersion&quot;
}

...

apollo {
    // instruct the compiler to generate Kotlin models
    generateKotlinModels.set(true)
}

dependencies {
...
    // apollo graphql
    implementation &quot;com.apollographql.apollo:apollo-runtime:$apolloVersion&quot;
    implementation &quot;com.apollographql.apollo:apollo-coroutines-support:$apolloVersion&quot;

    // coroutines
    implementation &quot;org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion&quot;
    implementation &quot;org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion&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;Download schema.json file&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버로부터 schema 정보를 받아 json 파일에 저장해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 graphql에 관련된 파일들이 위치할 디렉토리를 생성하고 schema.json 파일과 LauchDetails.graphql 파일을 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;Screenshot from 2020-12-11 16-36-57.png&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wlp93/btqPYj91MzJ/nsFMhVii2whDz5h9xenXp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wlp93/btqPYj91MzJ/nsFMhVii2whDz5h9xenXp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wlp93/btqPYj91MzJ/nsFMhVii2whDz5h9xenXp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWlp93%2FbtqPYj91MzJ%2FnsFMhVii2whDz5h9xenXp0%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;331&quot; height=&quot;239&quot; data-filename=&quot;Screenshot from 2020-12-11 16-36-57.png&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 서버가 돌아가고있는 상태에서 안드로이드 스튜디오 터미널에 다음과 같은 명령어를 입력한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607673046689&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./gradlew downloadApolloSchema 
--endpoint=&quot;http://localhost:4000/graphql/endpoint&quot; 
--schema=&quot;app/src/main/graphql/jrooms/example/schema.json&quot; &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--endpoint에 로컬서버의 endpoint 경로를 입력한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--shcema에 위에서 생성한 sc&lt;span style=&quot;color: #333333;&quot;&gt;hema.json 파일의 경로를 입력한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;Screenshot from 2020-12-11 16-49-16.png&quot; data-origin-width=&quot;1749&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3X4EF/btqPWYkVQFx/eYxLlNxfAEdKZQfeYRIUH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3X4EF/btqPWYkVQFx/eYxLlNxfAEdKZQfeYRIUH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3X4EF/btqPWYkVQFx/eYxLlNxfAEdKZQfeYRIUH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3X4EF%2FbtqPWYkVQFx%2FeYxLlNxfAEdKZQfeYRIUH0%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;1749&quot; height=&quot;275&quot; data-filename=&quot;Screenshot from 2020-12-11 16-49-16.png&quot; data-origin-width=&quot;1749&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&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;해당 명령어를 실행하면 schema.json 파일에 다음과같이 서버의 schema 정보가 자동으로 입력된다.&amp;nbsp;&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-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;Screenshot from 2020-12-11 16-53-28.png&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v1jb2/btqPYjCdRRa/R3iZ2Wl0XxnfDYKbjybSxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v1jb2/btqPYjCdRRa/R3iZ2Wl0XxnfDYKbjybSxK/img.png&quot; data-alt=&quot;shcema.json&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v1jb2/btqPYjCdRRa/R3iZ2Wl0XxnfDYKbjybSxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv1jb2%2FbtqPYjCdRRa%2FR3iZ2Wl0XxnfDYKbjybSxK%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;661&quot; height=&quot;693&quot; data-filename=&quot;Screenshot from 2020-12-11 16-53-28.png&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;shcema.json&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LaunchDetails.graphql 파일에는 playground에 입력한 쿼리와 뮤테이션을 다음과 같이 입력해주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607673343151&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// LaunchDetails.graphql

mutation CreateDraft($title: String!, $body: String!) {
    createDraft(title:$title, body: $body) {
        title
        id
    }
}

mutation Publish($id: Int!) {
    publish(draftId: $id){
        id
        title
    }
}

query Drafts {
    drafts {
        id
        published
    }
}

query Posts {
    posts {
        id
        published
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 안드로이드 스튜디오의 Build -&amp;gt; Clean Poject와 Rebuild Project를 차례대로 실행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Build to Apollo Client&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Activity onCreate() 내부에 다음과 같은 코드를 추가한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607673884109&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // First, create an `ApolloClient`
        // Replace the serverUrl with your GraphQL endpoint
        val apolloClient = ApolloClient.builder()
            .serverUrl(&quot;http://10.0.2.2:4000/graphql/endpoint&quot;)
            .build()
            
            
            ...

 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apolloClient 를 build하는 코드를 보면 serverUrl을 입력한다.&amp;nbsp;&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;로컬 서버를 연결하기위해 endpoint에 http://localhost:4000 을 입력하면 될것같지만 안드로이드 애뮬레이터에서는 localhost로 입력했을 때에는 경로를 찾지 못한다. localhost 대신 10.0.2.2를 입력하면 된다는 글을 많이 보았으나 동작하지 않았다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;res/xml/network_security_config.xml 을 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;Screenshot from 2020-12-11 16-43-31.png&quot; data-origin-width=&quot;311&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y8wqi/btqPLOD2k6K/br5nCJDqCZd6UrBdh6U4k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y8wqi/btqPLOD2k6K/br5nCJDqCZd6UrBdh6U4k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y8wqi/btqPLOD2k6K/br5nCJDqCZd6UrBdh6U4k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy8wqi%2FbtqPLOD2k6K%2Fbr5nCJDqCZd6UrBdh6U4k1%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;311&quot; height=&quot;153&quot; data-filename=&quot;Screenshot from 2020-12-11 16-43-31.png&quot; data-origin-width=&quot;311&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 내용을 추가해준다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607672679815&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&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;!--
reference
https://qastack.kr/programming/6760585/accessing-localhostport-from-android-emulator
--&amp;gt;
&amp;lt;network-security-config&amp;gt;
    &amp;lt;domain-config cleartextTrafficPermitted=&quot;true&quot;&amp;gt;
        &amp;lt;domain includeSubdomains=&quot;true&quot;&amp;gt;localhost&amp;lt;/domain&amp;gt;
        &amp;lt;domain includeSubdomains=&quot;true&quot;&amp;gt;10.0.2.2&amp;lt;/domain&amp;gt;
    &amp;lt;/domain-config&amp;gt;
&amp;lt;/network-security-config&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AndroidManifest.xml&lt;/p&gt;
&lt;pre id=&quot;code_1607672793456&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    &amp;lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&amp;gt;

    &amp;lt;application
        ...
        android:networkSecurityConfig=&quot;@xml/network_security_config&quot;
        tools:targetApi=&quot;n&quot;&amp;gt;
    &amp;lt;/application
    ...&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;Test ApolloClient with Coroutines&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 onCreate() 내부에 코루틴 + Apollo client를 활용하여 로컬 서버의 쿼리와 뮤테이션을 테스트하는 코드를 추가한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607674155962&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        ...

        GlobalScope.launch {
            val response = try {
                apolloClient.mutate(CreateDraftMutation(&quot;android&quot;, &quot;client&quot;)).toFlow().collect {
                    Log.e(&quot;response&quot;, &quot;${it.data?.createDraft}&quot;)
                }
                apolloClient.mutate(PublishMutation(3)).toFlow().collect {
                    Log.e(&quot;response&quot;,&quot;${it.data?.publish}&quot;)
                }
                apolloClient.query(PostsQuery()).toFlow().collect {
                    Log.e(&quot;response&quot;, &quot;${it.data?.posts}&quot;)
                }
                apolloClient.query(DraftsQuery()).toFlow().collect {
                    Log.e(&quot;response&quot;, &quot;${it.data?.drafts}&quot;)
                }
            } catch (e: ApolloException) {
                // handle protocol errors
                Log.e(&quot;exception&quot;, &quot;${e.message}&quot;)
                return@launch
            }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서부터 CreateDraftMutation, PublishMutation, PostsQuery, DraftQuery 를 차례대로 실행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 요청이기 때문에 코루틴 스코프 내부에서 사용되며 try{}catch{}를 통해 exception을 체크한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toFlow().collect {} 를 통해서 로컬서버의 뮤테이션과 쿼리를 성공적으로 사용한 후 결과값을 log를 통해 출력한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;log로 출력되는 내용은 playground에서 출력되는 내용과 동일하다.&amp;nbsp;&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;Screenshot from 2020-12-11 17-03-44.png&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1ikZf/btqPYTpQMNz/86XJcbqLXuxRyouo1XUNF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1ikZf/btqPYTpQMNz/86XJcbqLXuxRyouo1XUNF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1ikZf/btqPYTpQMNz/86XJcbqLXuxRyouo1XUNF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1ikZf%2FbtqPYTpQMNz%2F86XJcbqLXuxRyouo1XUNF0%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;1782&quot; height=&quot;131&quot; data-filename=&quot;Screenshot from 2020-12-11 17-03-44.png&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;131&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dbeaver + postgreSQL을 통해 Post 테이블이 뮤테이션과 쿼리의 결과를 잘 반영하였는지 확인해보았다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;Screenshot from 2020-12-11 17-13-53.png&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kO0FE/btqPLORFjfo/flr5873glZqsV8MQnTIkMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kO0FE/btqPLORFjfo/flr5873glZqsV8MQnTIkMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kO0FE/btqPLORFjfo/flr5873glZqsV8MQnTIkMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkO0FE%2FbtqPLORFjfo%2Fflr5873glZqsV8MQnTIkMk%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;435&quot; height=&quot;183&quot; data-filename=&quot;Screenshot from 2020-12-11 17-13-53.png&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&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;&lt;a href=&quot;https://github.com/jhk-im/gql-talk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/jhk-im/gql-talk&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1656146190098&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - jhk-im/gql-talk: Android Kotlin Client &amp;amp; TypeScript Nexus GraphQL Server&quot; data-og-description=&quot;Android Kotlin Client &amp;amp; TypeScript Nexus GraphQL Server - GitHub - jhk-im/gql-talk: Android Kotlin Client &amp;amp; TypeScript Nexus GraphQL Server&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jhk-im/gql-talk&quot; data-og-url=&quot;https://github.com/jhk-im/gql-talk&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/z21tJ/hyOSOhF7Mx/cp3x73edT5za4iHYoVwff1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jhk-im/gql-talk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jhk-im/gql-talk&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/z21tJ/hyOSOhF7Mx/cp3x73edT5za4iHYoVwff1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - jhk-im/gql-talk: Android Kotlin Client &amp;amp; TypeScript Nexus GraphQL Server&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Android Kotlin Client &amp;amp; TypeScript Nexus GraphQL Server - GitHub - jhk-im/gql-talk: Android Kotlin Client &amp;amp; TypeScript Nexus GraphQL Server&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/GraphQL</category>
      <category>Android GraphQL</category>
      <category>Apollo GraphQL</category>
      <category>GraphQL client</category>
      <category>Kotlin GraphQL</category>
      <author>jhk-im</author>
      <guid isPermaLink="true">https://jroomstudio.tistory.com/74</guid>
      <comments>https://jroomstudio.tistory.com/74#entry74comment</comments>
      <pubDate>Fri, 11 Dec 2020 17:16:26 +0900</pubDate>
    </item>
    <item>
      <title>Nexus GraphQL tutorials (5) - Persisting data (via Prisma)</title>
      <link>https://jroomstudio.tistory.com/73</link>
      <description>&lt;p&gt;&lt;b&gt;&lt;i&gt;GraphQL을 개인 프로젝트에 적용하기위해 제대로 학습해보기 시리즈&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;i&gt;해당 글은 Nexus 를 개인 프로젝트에 적용하고자 제대로 학습하기 위해 Nexus tutorial의 내용을 정리한 것입니다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;i&gt;Nexus tutorial에도 상세하게 나와있음을 알려드립니다.&amp;nbsp;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1607316926679&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;5. Persisting data (via Prisma)&quot; data-og-description=&quot;5. Persisting data (via Prisma)&quot; data-og-host=&quot;nexusjs.org&quot; data-og-source-url=&quot;https://nexusjs.org/docs/getting-started/tutorial/chapter-persisting-data-via-prisma&quot; data-og-url=&quot;https://nexusjs.org/docs/getting-started/tutorial/chapter-persisting-data-via-prisma&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://nexusjs.org/docs/getting-started/tutorial/chapter-persisting-data-via-prisma&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nexusjs.org/docs/getting-started/tutorial/chapter-persisting-data-via-prisma&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;5. Persisting data (via Prisma)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;5. Persisting data (via Prisma)&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;nexusjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;지금까지의 튜토리얼은 in-memory 데이터로 작업해왔다. 이제부터는 데이터에 초점을 두고 Nexus가 데이터베이스와 함께 사용하는 방법에 대해 알아보자.&amp;nbsp;해당 튜토리얼에선 PostgreSQL과 Prisma를 활용한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;PostgreSQL은 오픈소스 관계형 데이터베이스이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Nexus는 이러한 기술들을 요구하지 않으며 데이터베이스 추상화(raw SQL, Query builder, ORM ..)와 함께 사용될 수 있지만 Nexus는 Prisma의 팀에 의해 만들어졌기 때문에&amp;nbsp; 둘 사이에 유용하게 통합된 부분이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;what-is-prisma&quot;&gt;What is Prisma?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Prisma Client&lt;/b&gt;: Node.js &amp;amp; TypeScript 용 자동 생성 및 Type-safe Query Builder&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Prisma Migrate&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(experimental): Declarative data modeling &amp;amp; Migration system&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Prisma Studio&lt;/b&gt;: 데이터베이스의 데이터를 눈으로 확인하고 편집하는 GUI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prisma의 중심에는 Schema.prisma라는 파일이있다. 데이터베이스 스키마, 데이터베이스 연결 등을 인코딩하고 domain spcific 언어를 사용하는 Declarative 파일이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;connect-to-your-database&quot;&gt;Connect to your database&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Prisma 클라이언트, Prisma CLI 설치&lt;/li&gt;
&lt;li&gt;api/schema.ts 모듈에서 사용&lt;/li&gt;
&lt;li&gt;Prisma 스키마 만들기&lt;/li&gt;
&lt;li&gt;데이터베이스 인증내용을 저장할 .env 파일 만들기&lt;/li&gt;
&lt;li&gt;데이터베이스 연결&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1607321583130&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @prisma/client &amp;amp;&amp;amp; yarn add --dev @prisma/cli  &lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1607321597110&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx prisma init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screenshot from 2020-12-07 15-19-32.png&quot; data-origin-width=&quot;330&quot; data-origin-height=&quot;97&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lHdzO/btqPnb6tStC/Nsp9Oly68np5mUllrK7iZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lHdzO/btqPnb6tStC/Nsp9Oly68np5mUllrK7iZk/img.png&quot; data-alt=&quot;prisma&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lHdzO/btqPnb6tStC/Nsp9Oly68np5mUllrK7iZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlHdzO%2FbtqPnb6tStC%2FNsp9Oly68np5mUllrK7iZk%2Fimg.png&quot; data-filename=&quot;Screenshot from 2020-12-07 15-19-32.png&quot; data-origin-width=&quot;330&quot; data-origin-height=&quot;97&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;prisma&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이제 postgreSQL을 설치해보자. 현재 Ubuntu 20.04를 사용하고있고 그에 맞게 설치해보도록 하겠다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607322709549&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update
sudo apt install postgresql postgresql-contrib&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1607323070136&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//PostgreSQL 접속
sudo -u postgres psql&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1607348498104&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// psql

// user 생성 
CREATE USER jrooms WITH PASSWORD 'jrooms123!';

// user 권한설정
ALTER ROLE jrooms superuser createrole createdb;

// database 생성
CREATE DATABASE gqltalk OWNER jrooms;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1607349475827&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// prisma/.env
DATABASE_URL=&quot;postgresql://jrooms:jrooms123!@localhost:5432/gqltalk&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;create-your-database-schema&quot;&gt;Create your database schema&lt;/h2&gt;
&lt;p&gt;in-memory 데이터를 실제 데이터베이스의 테이블로 바꿔보자.&amp;nbsp;이를위해 prisma 스키마에 모델을 작성한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607349330513&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// prisma/schema.prisma
datasource db {
  provider = &quot;postgresql&quot;
  url      = env(&quot;DATABASE_URL&quot;)
}

generator client {
  provider = &quot;prisma-client-js&quot;
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  body      String
  published Boolean
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;데이터베이스 스키마를 지정하고 다음을 입력하여 첫 번째 데이터베이스 마이그레이션을 진행해보자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607349354862&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx prisma migrate save --experimental&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;마이그레이션을 지정한 후 다음을 입력하여 마이그레이션을 적용한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607349567113&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx prisma migrate up --experimental&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screenshot from 2020-12-07 23-18-18.png&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;122&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/niARA/btqPsDBMboX/ydXM3VPTUUOOW6WkiRXHV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/niARA/btqPsDBMboX/ydXM3VPTUUOOW6WkiRXHV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/niARA/btqPsDBMboX/ydXM3VPTUUOOW6WkiRXHV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FniARA%2FbtqPsDBMboX%2FydXM3VPTUUOOW6WkiRXHV0%2Fimg.png&quot; data-filename=&quot;Screenshot from 2020-12-07 23-18-18.png&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;122&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;h2 id=&quot;access-your-database&quot;&gt;Access your database&lt;/h2&gt;
&lt;p&gt;이제 in-memory 데이터를 버리고 Prisma 클라이언트로 대체해보자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607349953543&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// api/db.ts
import { PrismaClient } from '@prisma/client'

export const db = new PrismaClient()&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1607349968616&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx prisma generate&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1607349979967&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// api/graphql/Post.ts

export const PostQuery = extendType({
  type: 'Query',
  definition(t) {
    t.list.field('drafts', {
      type: 'Post',
      resolve(_root, _args, ctx) {
        return ctx.db.post.findMany({ where: { published: false } })
      },
    })
    t.list.field('posts', {
      type: 'Post',
      resolve(_root, _args, ctx) {
        return ctx.db.post.findMany({ where: { published: true } })
      },
    })
  },
})

export const PostMutation = extendType({
  type: 'Mutation',
  definition(t) {
    t.field('createDraft', {
      type: 'Post',
      args: {
        title: nonNull(stringArg()),
        body: nonNull(stringArg()),
      },
      resolve(_root, args, ctx) {
        const draft = {
          title: args.title,
          body: args.body,
          published: false,
        }
        return ctx.db.post.create({ data: draft })
      },
    })
    t.field('publish', {
      type: 'Post',
      args: {
        draftId: nonNull(intArg()),
      },
      resolve(_root, args, ctx) {
        return ctx.db.post.update({
          where: {
            id: args.draftId
          },
          data: {
            published: true,
          },
        })
      },
    })
  },
})&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;try-it-out&quot;&gt;Try it out&lt;/h2&gt;
&lt;pre id=&quot;code_1607350383605&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;playground&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screenshot from 2020-12-07 23-14-56.png&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;267&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF34Wh/btqPqk3wZUT/mNd4GrBRpYml2espdQbxuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF34Wh/btqPqk3wZUT/mNd4GrBRpYml2espdQbxuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF34Wh/btqPqk3wZUT/mNd4GrBRpYml2espdQbxuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF34Wh%2FbtqPqk3wZUT%2FmNd4GrBRpYml2espdQbxuk%2Fimg.png&quot; data-filename=&quot;Screenshot from 2020-12-07 23-14-56.png&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;267&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;dbeaver&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screenshot from 2020-12-07 23-16-09.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;329&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ERiz7/btqPsEgrUHz/UHfQlmfDifVnoPBHN5DNI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ERiz7/btqPsEgrUHz/UHfQlmfDifVnoPBHN5DNI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ERiz7/btqPsEgrUHz/UHfQlmfDifVnoPBHN5DNI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FERiz7%2FbtqPsEgrUHz%2FUHfQlmfDifVnoPBHN5DNI0%2Fimg.png&quot; data-filename=&quot;Screenshot from 2020-12-07 23-16-09.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;329&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;</description>
      <category>Backend/GraphQL</category>
      <category>Apollo GraphQL</category>
      <category>graphQL</category>
      <category>nexus</category>
      <category>Nexus GraphQL</category>
      <category>prisma</category>
      <author>jhk-im</author>
      <guid isPermaLink="true">https://jroomstudio.tistory.com/73</guid>
      <comments>https://jroomstudio.tistory.com/73#entry73comment</comments>
      <pubDate>Mon, 7 Dec 2020 23:16:33 +0900</pubDate>
    </item>
    <item>
      <title>Nexus GraphQL tutorials (4) - Testing your API</title>
      <link>https://jroomstudio.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;i&gt;GraphQL을 개인 프로젝트에 적용하기위해 제대로 학습해보기 시리즈&lt;/i&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;i&gt;해당 글은 Nexus 를 개인 프로젝트에 적용하고자 제대로 학습하기 위해 Nexus tutorial의 내용을 정리한 것입니다. &lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;i&gt;Nexus tutorial에도 상세하게 나와있음을 알려드립니다.&amp;nbsp;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h1&gt;Testing your API&lt;/h1&gt;
&lt;figure id=&quot;og_1607262746112&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;4. Testing your API&quot; data-og-description=&quot;4. Testing your API&quot; data-og-host=&quot;nexusjs.org&quot; data-og-source-url=&quot;https://nexusjs.org/docs/getting-started/tutorial/chapter-testing-your-api&quot; data-og-url=&quot;https://nexusjs.org/docs/getting-started/tutorial/chapter-testing-your-api&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://nexusjs.org/docs/getting-started/tutorial/chapter-testing-your-api&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nexusjs.org/docs/getting-started/tutorial/chapter-testing-your-api&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;4. Testing your API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;4. Testing your API&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;nexusjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;지금까지 구현된 내용은 Playground를 통해 수동으로 검증하였다. 처음에는 문제가 없지만 어느 시점부터는 자동화 된 테스트를 원할 것이다. 해당 챕터에서 몇 가지 자동화된 테스트를 추가해보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;GraphQL API 테스트 방법은 여러가지가 있다. 한 가지 방법은 Resolver를 분리하여 분리 된 함수에 추출한 후 유닛 테스트를 하는 것이다. 이러한 기능은 pure function이 아니기 때문에 부분 통합 테스트 혹은 테스트 유닛 level을 유지하기 위한 Mock이 도입된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Resolver &lt;b&gt;Unit Test&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;의 문제&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추후 사용하게 될 Prisma는 Resolver를 쓸 필요가 없을 수 있다. 이 경우 Reslover를 테스트하는 것은 무의미하다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Nexus가 가져오는 Static type safety 덕분에 입력 및 예상 출력 유형에 대한 테스트가 크게 감소할 수 있다. ( 예를들어 Resolver가 null을 검사하는지, 올바른 유형을 반환하는지를 테스트할 필요가 없다.)&lt;/li&gt;
&lt;li&gt;Resolver는 내부적인 것에 초점을 맞추기 때문에 정확한 client의 관점을 제공할 수 없다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nexus는 실제 Client 처럼 API를 실제로 실행하여 테스트 하는 System testing을 서포트한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Setting up your test environment&lt;/h2&gt;
&lt;p&gt;Jest를 사용한다. 의무사항은 아니지만 Nexus에서 추천하는 방법이다.&lt;/p&gt;
&lt;p&gt;선호하는 테스트 프레임워크를 사용해도 무방하다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607300726443&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add --dev jest @types/jest ts-jest graphql-request get-port&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1607300817879&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// package.json
&quot;scripts&quot;: {
	...
  &quot;generate&quot;: &quot;ts-node --transpile-only api/schema&quot;,
  &quot;test&quot;: &quot;npm run generate &amp;amp;&amp;amp; jest&quot;
},
	...
&quot;jest&quot;: {
  &quot;preset&quot;: &quot;ts-jest&quot;,
  &quot;globals&quot;: {
    &quot;ts-jest&quot;: {
      &quot;diagnostics&quot;: { &quot;warnOnly&quot;: true }
    }
  },
  &quot;testEnvironment&quot;: &quot;node&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1607300838843&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir tests &amp;amp;&amp;amp; touch tests/Post.test.ts&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;testing-the-publish-mutation&quot;&gt;Testing the&lt;span&gt;&amp;nbsp;&lt;/span&gt;publish&lt;span&gt;&amp;nbsp;&lt;/span&gt;mutation&lt;/h2&gt;
&lt;p&gt;쉬운 테스트를 위해서 통합 테스트를 실행하도록 설계 된 createTestContext 유틸리티를 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;테스트가 실행되면 동일한 프로세스에서 앱을 부팅하고 테스트와 상호작용하기 위한 인터페이스를 노출한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Jest는 각 테스트를 자체 프로세스로 실행하므로 8개의 테스트를 병렬로 실행하면 8개의 앱 프로세스도 실행됨을 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607301740987&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// tests/__helpers.ts                                            // 1
import { ServerInfo } from &quot;apollo-server&quot;;
import getPort, { makeRange } from &quot;get-port&quot;;
import { GraphQLClient } from &quot;graphql-request&quot;;
import { server } from &quot;../api/server&quot;;

type TestContext = {
  client: GraphQLClient;
};

export function createTestContext(): TestContext {
  let ctx = {} as TestContext;
  const graphqlCtx = graphqlTestContext();
  beforeEach(async () =&amp;gt; {                                        // 2
    const client = await graphqlCtx.before();
    Object.assign(ctx, {
      client,
    });
  });
  afterEach(async () =&amp;gt; {                                         // 3
    await graphqlCtx.after();
  });
  return ctx;                                                     // 8
}

function graphqlTestContext() {
  let serverInstance: ServerInfo | null = null;
  return {
    async before() {
      const port = await getPort({ port: makeRange(4000, 6000) });  // 4
      serverInstance = await server.listen({ port });               // 5
      return new GraphQLClient(`http://localhost:${port}`);         // 6
    },
    async after() {
      serverInstance?.server.close();                               // 7
    },
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모듈 네임의 prefix(__helpers.ts)는 Jest 스냅샷 폴더의 __snapthots__과 일치한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Jest 라이프사이클 beforeEach를 활용한다. 각각의 테스트 이전에 Jest에 의해 호출된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Jest 라이프사이클 afterEach를 활용한다. 각각의 테스트 종료 후 Jest에 의해 호출된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;여러개의 서버를 동시에 실행할수 있도록 random 포트를 사용한다. Jest가 기본적으로 동일한 파일에서 테스트를 동시에 실행할 때 유용하다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;테스트를 시작하기 전에 GraphQL 서버를 시작한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;테스트 컨텍스트에 pre-configured GraphQL 클라이언트를 추가하여 각각의 테스트가 GraphQL 서버로 쉽게 쿼리를 보낼 수 있도록 한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;테스트가 종료되었을 때 GraphQL 서버를 멈춘다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;GraphQL 클라이언트가 configured 객체를 반환하여 GraphQL 서버로 쿼리를 보낸다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이제 mutation을 테스트해보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선 메모리에 미리 셋팅된 데이터를 제거한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607310694108&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// api/db.ts
...
export const db = {
 posts: []
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;테스트 코드를 다음과 같이 작성한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607310772642&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// tests/Post.test.ts
import { createTestContext } from './__helpers'

const ctx = createTestContext()

it('ensures that a draft can be created and published', async () =&amp;gt; {
  // Create a new draft
  const draftResult = await ctx.client.request(`            # 1
    mutation {
      createDraft(title: &quot;Nexus&quot;, body: &quot;...&quot;) {            # 2
        id
        title
        body
        published
      }
    }
  `)

  // Snapshot that draft and expect `published` to be false
  expect(draftResult).toMatchInlineSnapshot()              // 3
  
  // Publish the previously created draft
  const publishResult = await ctx.client.request(`
    mutation publishDraft($draftId: Int!) {
      publish(draftId: $draftId) {
        id
        title
        body
        published
      }
    }
  `,
    { draftId: draftResult.createDraft.id }
  )
  
  // Snapshot the published draft and expect `published` to be true
  expect(publishResult).toMatchInlineSnapshot()
})&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트 컨텍스트는 ctx.client.request에 GraphQL 클라이언트를 노출한다. 이곳에 테스트할 mutation을 작성한다.&lt;/li&gt;
&lt;li&gt;이전에 작성한 createDraft() mutation이다.&lt;/li&gt;
&lt;li&gt;테스트 결과로 스냅샷 인라인으로 입력과 출력이 정렬된 것을 볼 수 있게 해준다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;try-it-out&quot;&gt;Try it out&lt;/h2&gt;
&lt;p&gt;prettier를 추가하라는 error 메세지가 떠서 추가하였다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607311878050&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add --dev prettier&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;테스트 실행&lt;/p&gt;
&lt;pre id=&quot;code_1607311860113&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn test&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다음과 같이 Object가 .toMatchInlineSnapshot에 추가된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607311979734&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// tests/Post.test.ts
...
  // Snapshot that draft and expect `published` to be false
  expect(draftResult).toMatchInlineSnapshot(`
    Object {
      &quot;createDraft&quot;: Object {
        &quot;body&quot;: &quot;...&quot;,
        &quot;id&quot;: 1,
        &quot;published&quot;: false,
        &quot;title&quot;: &quot;Nexus&quot;,
      },
    }
  `); // 3
  
    // Snapshot the published draft and expect `published` to be true
  expect(publishResult).toMatchInlineSnapshot(`
    Object {
      &quot;publish&quot;: Object {
        &quot;body&quot;: &quot;...&quot;,
        &quot;id&quot;: 1,
        &quot;published&quot;: true,
        &quot;title&quot;: &quot;Nexus&quot;,
      },
    }
  `);
  
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screenshot from 2020-12-07 12-33-59.png&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;442&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GQqNX/btqPgIqb6rb/mRxOEm0KSCgeXDZphoHYh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GQqNX/btqPgIqb6rb/mRxOEm0KSCgeXDZphoHYh1/img.png&quot; data-alt=&quot;테스트 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GQqNX/btqPgIqb6rb/mRxOEm0KSCgeXDZphoHYh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGQqNX%2FbtqPgIqb6rb%2FmRxOEm0KSCgeXDZphoHYh1%2Fimg.png&quot; data-filename=&quot;Screenshot from 2020-12-07 12-33-59.png&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;442&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;테스트 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/GraphQL</category>
      <category>Apollo GraphQL</category>
      <category>graphQL</category>
      <category>Jest test</category>
      <category>Nexus GraphQL</category>
      <author>jhk-im</author>
      <guid isPermaLink="true">https://jroomstudio.tistory.com/72</guid>
      <comments>https://jroomstudio.tistory.com/72#entry72comment</comments>
      <pubDate>Mon, 7 Dec 2020 12:36:45 +0900</pubDate>
    </item>
    <item>
      <title>개인 프로젝트(Bookmark-kotlin)를 통해 알아보는 ViewModel의 생성</title>
      <link>https://jroomstudio.tistory.com/71</link>
      <description>&lt;figure id=&quot;og_1607576828009&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ViewModel 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel&quot; data-og-url=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bzAOHR/hyIwsTdMUr/wWcKQkm37721WSGiTxFcp0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676,https://scrap.kakaocdn.net/dn/1ViGe/hyIwwgZSPz/4t2WKsR2hkm6ysDatcYROk/img.png?width=1077&amp;amp;height=452&amp;amp;face=0_0_1077_452,https://scrap.kakaocdn.net/dn/s5Dfk/hyIwp91Suv/XiXVkX9kL6XpY2npH01Osk/img.png?width=1024&amp;amp;height=341&amp;amp;face=0_0_1024_341&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bzAOHR/hyIwsTdMUr/wWcKQkm37721WSGiTxFcp0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676,https://scrap.kakaocdn.net/dn/1ViGe/hyIwwgZSPz/4t2WKsR2hkm6ysDatcYROk/img.png?width=1077&amp;amp;height=452&amp;amp;face=0_0_1077_452,https://scrap.kakaocdn.net/dn/s5Dfk/hyIwp91Suv/XiXVkX9kL6XpY2npH01Osk/img.png?width=1024&amp;amp;height=341&amp;amp;face=0_0_1024_341');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ViewModel 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ViewModel&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본래&amp;nbsp; MVVM 디자인 패턴의 ViewModel 에서 파생되었으며 Android에서는 Jetpack에 포함된 ViewModel기능을 제공한다.&amp;nbsp;&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;안드로이드의 개발 환경을 살펴보면 XML을 사용하여 레이아웃과 같은 View를 표시하게 되는데 Button의 on/off와 같은 사용자에 의한 간단한 UI 상호작용을 담당하며 이를 UI Controller(Activity, Fragment..)내부에서 View로부터 발생한 상호작용과 Model에서 가지고있는 데이터 사이에서 복합적인 상호작용을 구현하여 다양한 화면을 구성하게 된다. 여기서 시간이 지날수록 UI Controller에 방대한 코드가 자리잡게 된다.&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;span style=&quot;color: #333333;&quot;&gt;MVVM은 View와 ViewModel을 분리하는데 이는 Activity, Fragment와 같은 UI Controller에 쌓이게 되는 코드를 분담하여 유지보수 및 재사용성, 테스트를 용이하게 해준다. UI Controller를 View의 일부분으로 묶기 때문에 UI Controller 내부에 뷰를 표시하는 것 이외에 모델과의 상호작용하는 로직을 ViewModel에서 구현하는 것이 원칙이다.&amp;nbsp;&lt;/span&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;span style=&quot;color: #333333;&quot;&gt;다시한번 처음으로 돌아가서 Android 에서는 Jetpack에 ViewModel이 포함되어있다. 이는 Android의 개발환경에 알맞게 설계되어 제공된다는 의미일 것이다. 어떠한 기능이 있는지 공식문서의 내용을 통해 정리해보도록 하겠다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;* Android Architecture ViewModel 이라해서 ACC ViewModel이라 부른다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;ACC ViewModel&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ACC ViewModel 클래스는 안드로이드 생명주기를 의식하여 UI와 관련된 데이터를 저장하고 관리하도록 설계되었다. 안드로이드 프레임워크는 Activity, Fragment와 같은 UI Controller의 생명주기를 관리하는데 이는 다음과 같은 문제가 있다.&amp;nbsp;&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;1. UI Controller를 파괴하거나 다시 생성하면 저장되었던 모든 UI관련 데이터가 손실된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. UI Controller가 비동기로 구현되어야 할때 다소 시간이 걸릴 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. UI Controller가 데이터베이스, 네트워크 로딩을 담당하는 경우 클래스가 거대해진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 과도한 책임을 부여받은 클래스는 테스트를 어렵게 만든다.&amp;nbsp;&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;u&gt;그렇기 때문에 View 데이터관련 로직을 UI Controller에서 분리하는 것이 효율적이다.&amp;nbsp;&lt;/u&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;다음은 ACC ViewModel에 대한 설명이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. LifecycleOwner 혹은 특정 View 인스턴스보다 오래 지속되도록 설계되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Lifecycle혹은 View 객체를 모르더라도 테스트코드를 쉽게 적용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. LiveData 객체와 같은 LifecycleObserver를 포함할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 생명주기를 인식하는 Observable의 변경사항을 관찰하면 안된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ACC ViewModel 생명주기&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewModel 객체의 범위는 ViewModelProvider에 전달되는 Lifecycle로 지정된다. ViewModel은 범위가 지정된 Lifecycle이 끝날 때까지 메모리에 남아있으며 Activity가 종료되거나 Fragment의 경우 분리될 때 까지 메모리에 남아있다.&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;다음의 이미지는 Activity의 Lifecycle이 지정된 ViewModel의 주기를 나타낸다.&amp;nbsp; Fragment에도 동일하게 적용된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br29XM/btqPQtZa6l2/qIkSl1V5Tbw3G37YkDZ1zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br29XM/btqPQtZa6l2/qIkSl1V5Tbw3G37YkDZ1zK/img.png&quot; data-alt=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br29XM/btqPQtZa6l2/qIkSl1V5Tbw3G37YkDZ1zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr29XM%2FbtqPQtZa6l2%2FqIkSl1V5Tbw3G37YkDZ1zK%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;522&quot; height=&quot;543&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://developer.android.com/topic/libraries/architecture/viewmodel&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 Activity의 onCreate() 메서드가 호출될 때 ViewModel을 요청한다. ViewModel이 처음 요청되고나서 Activity가 onDestroy() 될 때까지 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Fragment 사이에 데이터 공유&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity에 속한 두개 이상의 프래그먼트간의 데이터를 공유할 때 각각 인터페이스 설명을 정의해야 하며, Owner Activity에서 두가지를 함께 묶어야 하므로 간단히 처리할 수 있는 작업이 아니다. 또한 다른 프래그먼트가 아직 생성되지 않았거나 표시되지 않은 시나리오도 처리해야 한다. 이러한 고충을 ViewModel 객체를 사용하여 해결할 수 있다.&amp;nbsp;&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;두 프래그먼트 모두 자신이 포함된 Activity를 검색하고 각 프래그먼트에서 ViewModelProvider를 가져올 때 Activity로 범위가 지정된 동일한 ViewModel 인스턴스를 받는다.&amp;nbsp;&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;이러한 접근 방법은 다음과 같은 이점이 있다.&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;Activity는 아무것도 하지 않아도 되거나 데이터 공유에 관해 어떤 것도 알 필요가 없다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;각 프래그먼트는 ViewModel외에 서로에 관해 알 필요가 없다. 프래그먼트가 사라져도 다른 프래그먼트는 평소대로 동작한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;각 프래그먼트에 자체 생명주기가 있으며, 다른 프래그먼트의 생명주기에 영향을 받지 않는다.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;/ul&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;i&gt;여기 까지는 안드로이드 공식 문서에 나오는 ViewModel에 관한 설명이었다. 이제 프로젝트에서 ViewModel이 어떻게 활용되는지 살펴보고 위에서 설명하는 내용이나 이점들이 잘 녹아들었는지 살펴보도록 하겠다.&amp;nbsp;&lt;/i&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BookmarkActivity&lt;/h2&gt;
&lt;pre id=&quot;code_1607608063869&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BookmarkActivity : AppCompatActivity(), BookmarkNavigator, BookmarkItemNavigator {

  private lateinit var viewModel: BookmarkViewModel

  @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    overridePendingTransition(R.anim.fadein, R.anim.fadeout)
    setContentView(R.layout.bookmark_act)
	
    ...

    viewModel = obtainViewModel().apply {
		...
    }
  }
  
  fun obtainViewModel(): BookmarkViewModel =
    obtainViewModel(BookmarkViewModel::class.java, this)
   
   ...
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;우선 액티비티가 생성될 때 obtainViewModel() 메서드를 활용하여 ViewModel을 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;obtainViewModel()메서드는 동일한 이름의 메서드를 통해 ViewModel을 생성하고 반환한다. 메서드에는 생성될 ViewModel 클래스와 현재 액티비티를 입력한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메서드는 다음과 같은 경로에 선언되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1607608599924&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// util/AppcompatActivityExt.kt

...
fun &amp;lt;T : ViewModel&amp;gt; AppCompatActivity.obtainViewModel(
  viewModelClass: Class&amp;lt;T&amp;gt;,
  owner: ViewModelStoreOwner
) =
  ViewModelProvider(owner, ViewModelFactory.getInstance(application)).get(viewModelClass)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 보았던 공식문서와 같이 ViewModelProvider를 통해 ViewModel을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 있는 ViewModelFactory를 통해&amp;nbsp; Factory를 생성하고 입력하게 된다. 이부분을 조금 더 자세히 살펴보도록 하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ViewModelFactory&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일반적으로 ViewModel을 생성하기위한 코드는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1607611301675&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
		.get(MainViewModel::class.java)&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;ViewModelFactory는 위와 동일하게 ViewModelProvider.NewInstanceFactory()를 통해 생성되는 Factory를 반환한다. 다만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Factory가 생성될 때 사용되는 create 함수를 오버라이드하고 재정의하여 사용한다. 해당 부분을 살펴보도록 하겠다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1607610162677&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ViewModelFactory private constructor(
  private val itemsRepository: ItemsRepository
) : ViewModelProvider.NewInstanceFactory() {

  override fun &amp;lt;T : ViewModel?&amp;gt; create(modelClass: Class&amp;lt;T&amp;gt;) =
    with(modelClass) {
      when {
        isAssignableFrom(BookmarkViewModel::class.java) -&amp;gt;
          BookmarkViewModel(itemsRepository)
        isAssignableFrom(AddEditBookmarkViewModel::class.java) -&amp;gt;
          AddEditBookmarkViewModel(itemsRepository)
        isAssignableFrom(BookmarkDetailViewModel::class.java) -&amp;gt;
          BookmarkDetailViewModel(itemsRepository)
        isAssignableFrom(DeleteBookmarkViewModel::class.java) -&amp;gt;
          DeleteBookmarkViewModel(itemsRepository)
        else -&amp;gt;
          throw IllegalArgumentException(&quot;Unknown ViewModel Class: ${modelClass.name}&quot;)
      }
    } as T

  companion object {

    @SuppressLint(&quot;StaticFieldLeak&quot;)
    @Volatile
    private var INSTANCE: ViewModelFactory? = null

    fun getInstance(application: Application) =
      INSTANCE ?: synchronized(ViewModelFactory::class.java) {
        INSTANCE ?: ViewModelFactory(
          Injection.provideBookmarkRepository(application.applicationContext)
        ).also { INSTANCE = it }
      }

    @VisibleForTesting
    fun destroyInstance() {
      INSTANCE = null
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;싱글턴으로 되어있는 ViewModelFactory는 인스턴스가 생성될 때 Injection클래스를 통해 ItemRepository를 주입한다. 해당 부부은 나중에 자세히 다뤄 보도록하겠다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;ViewModelFactory가 생성될 때 오버라이드 된 create 메서드를 통해 ViewModel 클래스를 구분하고 현재 생성해야 할 ViewModel을 선택하여 생성한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;ViewModel 클래스의 구분은 ViewModelProvider에서 .get() 메서드로 입력되는 ViewModel 클래스에 의해 선택된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 위와같은 흐름을 활용하면 각각 다르게 구현된 프래그먼트와 ViewModel을 불필요한 코드의 반복없이 obtainViewModel()에 입력하는 것 만으로 ViewModel을 생성할수 있다.&amp;nbsp;&amp;nbsp;&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;이렇게 생성된 ViewModel은 프래그먼트의 onCreateView() 에서 다음과 같이 연결된다.&lt;/p&gt;
&lt;pre id=&quot;code_1607612524383&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View? {
    viewDataBinding = BookmarkFragBinding.inflate(inflater, container, false).apply {
      viewModel = (activity as BookmarkActivity).obtainViewModel()
    }
    setHasOptionsMenu(true)
    return viewDataBinding.root
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;databinding의 viewModel에 액티비티의 obtainViewModel()을 통해 얻은 ViewModel의 인스턴스를 입력하여 연결한다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jhk-im/bookmark-kotlin-mvvm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/jhk-im/bookmark-kotlin-mvvm&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1656146255476&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - jhk-im/bookmark-kotlin-mvvm: bookmark se refactoring with kotlin&quot; data-og-description=&quot;bookmark se refactoring with kotlin. Contribute to jhk-im/bookmark-kotlin-mvvm development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jhk-im/bookmark-kotlin-mvvm&quot; data-og-url=&quot;https://github.com/jhk-im/bookmark-kotlin-mvvm&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pzwKm/hyORODAlbe/bqiK20Hy5ALKUSaSr2QIvk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jhk-im/bookmark-kotlin-mvvm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jhk-im/bookmark-kotlin-mvvm&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pzwKm/hyORODAlbe/bqiK20Hy5ALKUSaSr2QIvk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - jhk-im/bookmark-kotlin-mvvm: bookmark se refactoring with kotlin&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;bookmark se refactoring with kotlin. Contribute to jhk-im/bookmark-kotlin-mvvm development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Android</category>
      <category>Android</category>
      <category>Android ViewModel</category>
      <category>MVVM pattern</category>
      <category>ViewModel</category>
      <category>ViewModelFactory</category>
      <author>jhk-im</author>
      <guid isPermaLink="true">https://jroomstudio.tistory.com/71</guid>
      <comments>https://jroomstudio.tistory.com/71#entry71comment</comments>
      <pubDate>Fri, 4 Dec 2020 11:12:01 +0900</pubDate>
    </item>
    <item>
      <title>개인 프로젝트(Bookmark-kotlin)를 통해 알아보는 MVC-MVP-MVVM</title>
      <link>https://jroomstudio.tistory.com/70</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nGZEk/btqO0fuge6k/ufmvKXtYKBOYUzYKe5ulE0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nGZEk/btqO0fuge6k/ufmvKXtYKBOYUzYKe5ulE0/img.gif&quot; data-alt=&quot;Bookmark-kotlin&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nGZEk/btqO0fuge6k/ufmvKXtYKBOYUzYKe5ulE0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/nGZEk/btqO0fuge6k/ufmvKXtYKBOYUzYKe5ulE0/img.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; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;500&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;892&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Bookmark-kotlin&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;해당 프로젝트는 MVVM 패턴으로 구현되어있다. MVVM패턴에 대해 정리하고 MVC - MVP 패턴과의 비교를 통해 예전에 확실하게 감을잡지 못했던 부분들을 다시한번 정리해보려한다.&amp;nbsp;&amp;nbsp;&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;프로젝트의 내용은 간단하다. Bookmark를 저장하고 저장된 Bookmark의 정보를 활용해 편집/삭제를 하거나 웹뷰를 통해 웹사이트를 검색하는 기능을 구현하였다. 메인 화면에서 Bookmark 리스트를 표현하고있는 화면을 기준으로 정리해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MVC&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model + View + Controler&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Model&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Bookmark의 데이터에 해당한다. url, title, favicon과 같은 상태정보를 가지고있는 객체이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;View&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 표시되는 영역이며 메인화면을 나타내는 main_act.xml이 이에 해당한다. View에서는 표시되는 UI와 사용자가 상호작용할 때 Controller에 전달하는 역할을 한다. Model에 대한 정보를 완전히 모르는 상태이기 때문에 단순히 UI가 눌렸다/안눌렸다 등의 간단한 정보를 전달하게 된다. Model에 대한 정보가 없기때문에 눌렸다/안눌렸다를 전달하더라도 Controller에서의 처리에 따라서 다양한 데이터와 각각의 상태에 따라 유연하게 로직을 구현할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Controller&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 -&amp;gt; View와 Model 사이에서 서로가 연동하여 실질적으로 구현하고자 하는 로직를 작성하는 곳이다. 사용자의 상호작용을 View가 Controller에 전달하면 그에맞는 상황을 Model의 데이터와 비즈니스 로직을 활용하여 최종적인 결과물을 만들어내는 곳이라 볼 수 있다. 안드로이드에서 Activity, Fragment 등을 UI Controller라고 부른다. 따라서 main_act.xml과 상호작용하는 MainActivity가 Controller에 해당한다고 볼 수 있다.&amp;nbsp;&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;MVC는 모델과 뷰를 분리한다. 모델이 어느곳에도 묶여있지 않기 때문에 모델에 대하여 쉽게 테스트를 진행 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MVC의 문제점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨트롤러는 안드로이드API에 종속되므로 유닛 테스트가 어렵다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;컨트롤러와 뷰가 긴밀하게 결합되기 때문에 뷰를 변경하면 컨트롤러도 변경해야한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;앱이 커질수록 컨트롤러에 수많은 코드줄이 형성되어 유지보수를 어렵게 한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MVP&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model + View + Presenter&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Model&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC 모델과 동일한 역할&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;View&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI Controller(Activity, Fragment)를 View의 일부분으로 간주한다. 즉, MVC 컨트롤러에서 뷰를 표시하는 로직과 모델의 상태에 따른 뷰의 변화를 구현하는 로직을 담당하게 되는데 MVP에선 뷰와 컨트롤러를 하나로 보기때문에 컨트롤러에선 뷰를 표시하는 부분만 담당하고 나머지는 Presenter에게 역할을 분담한다고 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Presenter&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC의 컨트롤러와 같지만 뷰와 연결되지 않는 인터페이스라는 점이 다르다. 안드로이드API에도 종속되지 않기 때문에 유닛테스트가 어려웠던 문제를 해결한다. 또한 MVC 컨트롤러에선 뷰의 표시에 해당하는 로직과 모델의 상태에 따라 작성되는 코드가 섞여있었는데 Presenter에는 뷰를 표시하는 로직이 없으므로 모델과 상호작용하는 코드만 작성하면 된다.&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;MVC의 컨트롤러의 문제점을 MVP를 통해 보완하였다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MVP의 문제점&amp;nbsp;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드의 비중이 뷰를 표시하는 코드보다는 모델과 상호작용하는 코드가 더 많기 때문에 Presnter에도 시간이 지남에 따라 코드가 늘어나는 문제가 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MVVM&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model + View + ViewModel&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Model&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC 모델과 동일한 역할&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;View&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터바인딩을 사용하여 act_main.xml등과 같은 XML 레이아웃을 작성한다. 이렇게 작성된 레이아웃은 컨트롤러에서 뷰를 표시하는 로직없이 해당 레이아웃의 데이터 바인딩을 연결하는 로직만으로&amp;nbsp; 레이아웃과 내부의 모든 뷰에 접근할 수 있게된다. 또한 XML 레이아웃에 뷰모델을 연결하여 뷰모델 내부에서 모델의 상태를 관찰하고있는 Observer 데이터에 의해 컨트롤러에서의 뷰 표시,변동 로직없이 UI 완성할 수 있다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ViewModel&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveData를 통해 모델의 데이터를 관찰한다. 이렇게 관찰되는 데이터를 XML 레이아웃에서 접근할 수 있으며 원하는 곳에 셋팅하여 데이터를 업데이트 한다. 여기서 뷰는 뷰모델에 접근할 수 있지만 뷰모델은 뷰에 접근할 수 없다.&amp;nbsp;&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;MVVM에선 뷰를 표시하는 로직과 모델과의 상호작용에 의한 뷰의 변경 로직이 획기적으로 줄어들게 되었다. 또한 뷰모델은 뷰에대한 의존성이 전혀 없으므로 유닛테스트를 더욱 쉽게 구현할수 있도록 도와준다. 모델이 변경되는 시점에 LiveData로 관찰하는 변수가 제대로 설정되었는지 확인하면 되는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;figure id=&quot;og_1606925448845&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;Article&quot; data-og-title=&quot;안드로이드의 MVC, MVP, MVVM 종합 안내서&quot; data-og-description=&quot;안드로이드 앱을 만드는 개발자를 위한 MVC, MVP, MVVM 패턴 사용법과 장단점에 대한 안내서입니다.&quot; data-og-host=&quot;academy.realm.io&quot; data-og-source-url=&quot;https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/&quot; data-og-url=&quot;http://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bbxlp1/hyIrs0ffVv/rqawkfU8nuftvYvpCc5LhK/img.png?width=1200&amp;amp;height=627&amp;amp;face=0_0_1200_627,https://scrap.kakaocdn.net/dn/c2OMo4/hyIroQ42Re/dp3PsO17VbF0yb51Dp9wZK/img.png?width=1200&amp;amp;height=627&amp;amp;face=0_0_1200_627,https://scrap.kakaocdn.net/dn/c7biCb/hyIrw9qhsq/DaEpW2XkVjiIrcHqE0WfuK/img.png?width=626&amp;amp;height=1282&amp;amp;face=0_0_626_1282&quot;&gt;&lt;a href=&quot;https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bbxlp1/hyIrs0ffVv/rqawkfU8nuftvYvpCc5LhK/img.png?width=1200&amp;amp;height=627&amp;amp;face=0_0_1200_627,https://scrap.kakaocdn.net/dn/c2OMo4/hyIroQ42Re/dp3PsO17VbF0yb51Dp9wZK/img.png?width=1200&amp;amp;height=627&amp;amp;face=0_0_1200_627,https://scrap.kakaocdn.net/dn/c7biCb/hyIrw9qhsq/DaEpW2XkVjiIrcHqE0WfuK/img.png?width=626&amp;amp;height=1282&amp;amp;face=0_0_626_1282');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드의 MVC, MVP, MVVM 종합 안내서&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 앱을 만드는 개발자를 위한 MVC, MVP, MVVM 패턴 사용법과 장단점에 대한 안내서입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;academy.realm.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jhk-im/bookmark-kotlin-mvvm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/jhk-im/bookmark-kotlin-mvvm&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1656146280366&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - jhk-im/bookmark-kotlin-mvvm: bookmark se refactoring with kotlin&quot; data-og-description=&quot;bookmark se refactoring with kotlin. Contribute to jhk-im/bookmark-kotlin-mvvm development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jhk-im/bookmark-kotlin-mvvm&quot; data-og-url=&quot;https://github.com/jhk-im/bookmark-kotlin-mvvm&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pzwKm/hyORODAlbe/bqiK20Hy5ALKUSaSr2QIvk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jhk-im/bookmark-kotlin-mvvm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jhk-im/bookmark-kotlin-mvvm&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pzwKm/hyORODAlbe/bqiK20Hy5ALKUSaSr2QIvk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - jhk-im/bookmark-kotlin-mvvm: bookmark se refactoring with kotlin&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;bookmark se refactoring with kotlin. Contribute to jhk-im/bookmark-kotlin-mvvm development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <category>Android Design Pattern</category>
      <category>MVC</category>
      <category>mvp</category>
      <category>mvvm</category>
      <author>jhk-im</author>
      <guid isPermaLink="true">https://jroomstudio.tistory.com/70</guid>
      <comments>https://jroomstudio.tistory.com/70#entry70comment</comments>
      <pubDate>Thu, 3 Dec 2020 01:11:57 +0900</pubDate>
    </item>
  </channel>
</rss>