<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>게으른J 의 테크로그</title>
    <link>https://studyroadmap-kkm.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 5 Apr 2026 18:11:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>gangmini</managingEditor>
    <image>
      <title>게으른J 의 테크로그</title>
      <url>https://tistory1.daumcdn.net/tistory/5293959/attach/2df7235e4ac14665a7b9a41beb3d1cb7</url>
      <link>https://studyroadmap-kkm.tistory.com</link>
    </image>
    <item>
      <title>[Android/Coroutine] Debounce로 검색 기능 문제 해결하기 / 검색 딜레이 주기</title>
      <link>https://studyroadmap-kkm.tistory.com/188</link>
      <description>&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;문제상황&lt;/b&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'이디야' 를 검색하고 싶다고 가정할때,&amp;nbsp;이 -&amp;gt; 이디 -&amp;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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 원인을 찾기 위해 여러가지 가정을 해보고 실험을 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) EditText에 문자열이 제대로 입력되는가? =&amp;gt; 문자열은 잘 입력되고 Watcher에도 잘 잡힘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) cancelable 때문에 네트워크 요청이 취소되는가? =&amp;gt; cancelable은 동일한 요청 url 에 대해서만 요청을 취소하는 것이기 때문에 이 문제와 관련없다는 결론&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;현재 나는 Retrofit 통신 라이브러리를 사용하고 있는데 라이브러리에서 자체적으로 엄청 빠른 시간 안에 연속적으로 API 호출이 발생하는 경우 요청을 취소하거나 다 처리하지 못하는 것이다.&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;&lt;b&gt;해결방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 문제를 해결하고 나면 검색 기능에 딜레이를 주어 실제 입력이 모두 끝났을 때만 검색 API 가 요청되는 기능을 구현해 효율성을 높이는 작업을 진행해보려고 했다.&amp;nbsp;그런데 원인을 분석해보니 이 2가지 문제를 한번에 해결할 수 있겠다는 생각이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시말해, 사용자가 빠르게 '이디야' 를 입력한다면 사실 '이' 나 '이디' 에 대한 검색 결과는 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;처음에는 이 기능을 구현하기 위해 타이머..? 같은걸 사용해야 하나 싶었다. EditText에 입력이 들어오기 시작하면 타이머를 재기 시작하고 일정 시간 안에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;새로운 입력이 들어오면 넘어가고 그렇지 않으면 검색 API 요청을 해당 검색어로 보내는 것이다.&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;그러다가 코루틴 Flow에서 Debounce 라는 것을 지원한다는 것을 알게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Debounce의 개념적 의미는&amp;nbsp;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;연속적으로 발생한 이벤트를 하나로 처리하는 방식&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt; 이고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;코루틴 Flow 내부에서 debounce 라는 함수를 통해 &lt;b&gt;일정 시간 동안 입력값이 없는 경우에만 Flow가 방출되는 방식으로 동작&lt;/b&gt;한다.&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;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;1. debounce를 사용하기 위해서는 우선 Flow 를 사용해야 하는데, 이전에는 EditText를 통해 입력받은 데이터를 그냥 var 변수에 저장했다면 이제는 MutableStateFlow (변경 가능한 StateFlow) 에 저장하고 관리하고 있다.&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;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;2. 그리고 &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;나는 0.3 초를 기준으로 잡고 0.3초 동안 새로운 검색어가 입력되지 않는 경우, 가장 마지막에 입력된 검색어를 사용해 검색 API 요청을 보내기로 했다. (만약, 0.3초 안에 다른 입력어가 입력되는 경우에는 코루틴이 자체적으로 취소된다.)&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;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;3. 0.3초 동안 입력이 없는 경우에만 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;MutableStateFlow 값이 방출되면서 viewmodel.initList() 가 실행된다.&amp;nbsp;&lt;/span&gt;&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;방출되는 값이 가장 마지막값이여야 하기 때문에 collectLastest 를 써야 하는 것 아닌가 생각할 수도 있지만 나는 사실 collect를 통해 방출되는 값을 사용하지 않기 때문에 크게 의미가 없다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 달 전 검색 기능을 구현 초기에, 무한스크롤 기능을 구현하기 위해서 새롭게 입력되는 키워드 값을 그때그때 뷰모델에 선언한 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;LiveData(현재는 StateFlow) &lt;/span&gt;변수에 저장하고 관리해서 재사용하고자 했었다. 그래서 검색 요청을 하는 함수인 initList() 함수도 매개변수로 키워드 값을 전달하는 것이 아니라 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 LiveData(현재는 StateFlow) 를 통해 키워드값을 받아오고 usecase 함수 호출시 매개변수로 넘겨주는 방식으로 구현했었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1724236936902&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;binding.EditTextSearchBar.addTextChangedListener {
            viewmodel._keyword.value = it.toString()
            
            // 새로운 입력값 생길때마다 새로운 코루틴 생성
            lifecycleScope.launch { 
                viewmodel._keyword
                    .debounce(300) // 0.3초 동안 입력값이 없는 경우만 방출
                    .collect {
                        viewmodel.initList()
                    }
            }
        }&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;Debounce를 적용하기 전에는 모든 입력값에 대한 검색 결과를 전달받았고 심지어 실제 검색하고 싶었던 '이디야' 에 대한 검색 결과는 나오지도 않았다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAGby8/btsJauRBrRU/DJxO7ubBEdW2ekE88VLGhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAGby8/btsJauRBrRU/DJxO7ubBEdW2ekE88VLGhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAGby8/btsJauRBrRU/DJxO7ubBEdW2ekE88VLGhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAGby8%2FbtsJauRBrRU%2FDJxO7ubBEdW2ekE88VLGhk%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;1266&quot; height=&quot;194&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;194&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: center;&quot;&gt;Debounce를 적용하고 나니 엄청 빠르게 '이디야' 검색어를 입력했을 때, 딱 '이디야' 에 대한 검색어만 잘 나오는 것을 확인할 수 있었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2900&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7o09E/btsJb2MSxFY/D7NKJKLsKlSLALSWDZv3pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7o09E/btsJb2MSxFY/D7NKJKLsKlSLALSWDZv3pK/img.png&quot; data-alt=&quot;Debounce를 적용한 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7o09E/btsJb2MSxFY/D7NKJKLsKlSLALSWDZv3pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7o09E%2FbtsJb2MSxFY%2FD7NKJKLsKlSLALSWDZv3pK%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;2900&quot; height=&quot;412&quot; data-origin-width=&quot;2900&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Debounce를 적용한 모습&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이렇게 Debounce 기능 구현 덕분에 사용자가 검색 키워드를 입력할 때마다 API를 호출하는 대신, 사용자가 입력을 멈춘 후 일정 시간 동안 추가 입력이 없을 때에만 API를 호출하도록 할 수 있도록 해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;** 나처럼 Flow에서 제공해주는 debounce를 함수를 사용하지 않고 직접 코루틴을 사용해 구현하게 된다면 다음과 같이 구현할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ho-taek/Android-debounce-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B5%AC%ED%98%84&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@ho-taek/Android-debounce-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B5%AC%ED%98%84&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724177567506&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Android] debounce 코루틴 구현&quot; data-og-description=&quot;!빵동여지도 앱을 개발하면서 빵집 검색에 있어서 타이핑이 될 때마다 즉시 서버 통신으로 받아오게끔 로직을 짰는데 이 부분에서 서버에 큰 부하를 주게 된다는 점을 알려주시며 debounce를 추천&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@ho-taek/Android-debounce-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B5%AC%ED%98%84&quot; data-og-url=&quot;https://velog.io/@ho-taek/Android-debounce-코루틴-구현&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rSKiL/hyWShJWKIi/l0p7ghGM4WBKDA4u2m27S0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/vKQG1/hyWSmdpJrG/kLiFqqx5WtOxrS0EXqmbZ1/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bpp1Hr/hyWSk0ZffS/HE4WWxbfa80lBCIxiKsAkk/img.jpg?width=682&amp;amp;height=1440&amp;amp;face=301_670_537_928&quot;&gt;&lt;a href=&quot;https://velog.io/@ho-taek/Android-debounce-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B5%AC%ED%98%84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@ho-taek/Android-debounce-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B5%AC%ED%98%84&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rSKiL/hyWShJWKIi/l0p7ghGM4WBKDA4u2m27S0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/vKQG1/hyWSmdpJrG/kLiFqqx5WtOxrS0EXqmbZ1/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bpp1Hr/hyWSk0ZffS/HE4WWxbfa80lBCIxiKsAkk/img.jpg?width=682&amp;amp;height=1440&amp;amp;face=301_670_537_928');&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] debounce 코루틴 구현&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;!빵동여지도 앱을 개발하면서 빵집 검색에 있어서 타이핑이 될 때마다 즉시 서버 통신으로 받아오게끔 로직을 짰는데 이 부분에서 서버에 큰 부하를 주게 된다는 점을 알려주시며 debounce를 추천&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&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</category>
      <category>coroutine</category>
      <category>debounce</category>
      <category>flow</category>
      <category>Kotlin</category>
      <category>검색</category>
      <category>안드로이드</category>
      <category>코루틴</category>
      <category>코틀린</category>
      <author>gangmini</author>
      <guid isPermaLink="true">https://studyroadmap-kkm.tistory.com/188</guid>
      <comments>https://studyroadmap-kkm.tistory.com/188#entry188comment</comments>
      <pubDate>Tue, 20 Aug 2024 22:24:45 +0900</pubDate>
    </item>
    <item>
      <title>[Android/세미나실] 2024 Google I/O 참가 후기 및 정리</title>
      <link>https://studyroadmap-kkm.tistory.com/187</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;연구실 생활을 할 때 교수님을 따라 컨퍼런스 발표나 참관 등을 해본 경험은 있지만 스스로 적극적으로 나서서 참가해본 적은 없었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Google I/O는 안드로이드 개발자로서 더 폭넓은 지식을 알아가고 싶고 성장하고 싶다는 생각에 처음으로 내돈내산 내발로 참여해보는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;안드로이드 관련 대외 컨퍼런스, 세미나 행사였고 그래서 더 의미가 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨퍼런스는 인천 송도에서 열렸는데 뭔가 &quot;멋있다~&quot; 라는 생각이 들었지만 막상 가보니 '드릅게 먼 곳에서 하는구나' 라는 생각이....&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;혹시 너무 어렵고 막 지목해서 질문하고(학교냐고ㅋㅋㅋ) 그럴까봐 걱정했는데 후기를 찾아보니 진짜 그냥 듣고 오는거고, 강의도 30분 내외로 짧게 이루어지기 때문에 어떻게 보면 오히려 가볍게 느껴질 수도 있다는 이야기가 있었다.&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;미리 안드로이드 섹션 강의 리스트를 확인해보니 역시나 요즘 대세인 Compose와 관련된 내용들로 가득했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 Compose를 제대로 알지 못하는 나였기에 기본적인 개념은 떼고 가야 인천 송도까지 간 보람이 있겠다고 생각해서 유튜브 강의로 기본 컴포넌트에 대한 실습을 따라해보며 공부해갔다. 그래서인지 다행히 어느정도는 알아들을 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qIqzv/btsI3ZLpHfW/FYtv4dhx2sEKgd1jcTIfk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qIqzv/btsI3ZLpHfW/FYtv4dhx2sEKgd1jcTIfk1/img.png&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;485&quot; data-is-animation=&quot;false&quot; style=&quot;width: 31.7999%; margin-right: 10px;&quot; data-widthpercent=&quot;32.56&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qIqzv/btsI3ZLpHfW/FYtv4dhx2sEKgd1jcTIfk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqIqzv%2FbtsI3ZLpHfW%2FFYtv4dhx2sEKgd1jcTIfk1%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;369&quot; height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEW3mb/btsI4xOpVs9/lpC4VSdrtpsds23WoC2uhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEW3mb/btsI4xOpVs9/lpC4VSdrtpsds23WoC2uhk/img.png&quot; data-origin-width=&quot;2078&quot; data-origin-height=&quot;2462&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.2776%; margin-right: 10px;&quot; data-widthpercent=&quot;36.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEW3mb/btsI4xOpVs9/lpC4VSdrtpsds23WoC2uhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEW3mb%2FbtsI4xOpVs9%2FlpC4VSdrtpsds23WoC2uhk%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;2078&quot; height=&quot;2462&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VmxbC/btsI5egyioD/Mmd1JH8KN0e3m6qZ95WZzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VmxbC/btsI5egyioD/Mmd1JH8KN0e3m6qZ95WZzk/img.png&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;2325&quot; data-is-animation=&quot;false&quot; width=&quot;406&quot; height=&quot;555&quot; style=&quot;width: 30.5969%;&quot; data-widthpercent=&quot;31.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VmxbC/btsI5egyioD/Mmd1JH8KN0e3m6qZ95WZzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVmxbC%2FbtsI5egyioD%2FMmd1JH8KN0e3m6qZ95WZzk%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;1702&quot; height=&quot;2325&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;바로 앞에 오크우드 타워(포스코)와 쉐라톤 호텔이..! 이전에 가족들과 종종 호캉스하던 곳(TMI)&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;다른 페스타(?)도 진행중이라 Google 페스타가 진행되는 구역으로 찾아가야 하는데 구역&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;widthOrigin&quot; data-origin-width=&quot;2891&quot; data-origin-height=&quot;2038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cM5I27/btsI4p37Ijb/ebiO5snBMcAkI9MfUaysy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cM5I27/btsI4p37Ijb/ebiO5snBMcAkI9MfUaysy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cM5I27/btsI4p37Ijb/ebiO5snBMcAkI9MfUaysy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcM5I27%2FbtsI4p37Ijb%2FebiO5snBMcAkI9MfUaysy0%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;541&quot; height=&quot;381&quot; data-origin-width=&quot;2891&quot; data-origin-height=&quot;2038&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;기념품으로는 구글 티셔츠를 주는데 난 좀 늦게 갔더니 증정 티셔츠가 2XL 밖에 안 남았다. (이럴거면 미리 설문조사 왜 한겨..)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스탭들은 등에도 프린팅이 있어서 탐났다ㅋㅋ&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;한 5시간동안 5개의 강연을 듣고 왔는데&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;&lt;b&gt;Compose 성능 끌어올리기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 마음에 드는 강의였다. 연사님의 강의력이 군더더기나 쓸데없는 농담 없이 딱 핵심 내용만 30분 안에 깔끔하게 말씀해주셔서 전달력이 가장 좋았다.&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;b&gt;1. 컴포즈&lt;span&gt; &lt;/span&gt;버전&lt;span&gt; &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 style=&quot;color: #333333; text-align: left;&quot;&gt;가장 간단하면서도 중요한 시도라는 점이 웃겼다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;2. 불필요한&lt;/span&gt; Recomposition &lt;span&gt;줄이기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; 컴포즈는 Composition -&amp;gt; layout -&amp;gt; drawing 이렇게 총 3단계에서 걸쳐 화면 UI를 그린다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; 코드를 작성하면 컴포넌트를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;트리 구조로&lt;span&gt; 구성하고 연산하는 작업이 Composition / 이를 layout 형태로 배치하는 것이 layout /&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; layout을 실제 UI 화면에 그리는 것을 drawing 이라고 알고 있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&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&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; XML 과 비교했을 때 Compose의 장점 중 하나가 Composition 과정에서 컴포넌트 요소들을 한 번씩만 방문해 연산량과 속도가 더&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; 개선된다는 점이라고 알고 있다. 그래서 이 Composition 과정이 성능을 좌우하는 요소라고 단번에 와닿았다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIp710/btsI5YjYmmJ/pPuVlQyaKDPXkzuDwjMtr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIp710/btsI5YjYmmJ/pPuVlQyaKDPXkzuDwjMtr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIp710/btsI5YjYmmJ/pPuVlQyaKDPXkzuDwjMtr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIp710%2FbtsI5YjYmmJ%2FpPuVlQyaKDPXkzuDwjMtr0%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;694&quot; height=&quot;342&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그렇다면 UI에 변화가 생길때 저 3단계가 다시 실행될 것이다. 하지만 이때 Compose 컴포넌트를 적절히 잘 작성하거나 함수로 분리하면 딱 변화가 생긴 부분만 Recomposition이 일어나고 그렇지 않은 부분은 재연산을 하지 않음으로서 성능을 개선할 수 있는 것이다.&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;그렇다면 이 Recomposition이 언제 일어나는지를 이해해야 하고 skippable 과 restartable 상태에 대해 알아야 하는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;skippable&lt;/b&gt;은 파라미터 값이 변경되지 않았다면 Recomposition이 가능한 상태이고 &lt;b&gt;restartable&lt;/b&gt;는&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;Recomposition을 다시 요청할 수 있는 상태이다. &lt;b&gt;컴파일러에 의해 입력걊이나 상태가 변하면 그 지점이&amp;nbsp;restartable 지점이 되면서 하위 컴포넌트들이 전부 Recomposition 된다.&amp;nbsp;&lt;/b&gt;&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;widthOrigin&quot; data-origin-width=&quot;1035&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x1O0Z/btsI5e8EasR/s67UK3Rn6vHNe5a7qnDFj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x1O0Z/btsI5e8EasR/s67UK3Rn6vHNe5a7qnDFj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x1O0Z/btsI5e8EasR/s67UK3Rn6vHNe5a7qnDFj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx1O0Z%2FbtsI5e8EasR%2Fs67UK3Rn6vHNe5a7qnDFj1%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;1035&quot; height=&quot;363&quot; data-origin-width=&quot;1035&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 상태 변화는 favorite 에 대해서만 일어났는데 가장 가까운 restartable 지점이 TestListScreen 컴포져블 함수이기 때문에 Recomposition 범위가 저렇게 잡힌다.&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;widthOrigin&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JzhKq/btsI5NbCLRo/P0Ibvgj29GnPlEukXFpaC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JzhKq/btsI5NbCLRo/P0Ibvgj29GnPlEukXFpaC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JzhKq/btsI5NbCLRo/P0Ibvgj29GnPlEukXFpaC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJzhKq%2FbtsI5NbCLRo%2FP0Ibvgj29GnPlEukXFpaC0%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;1038&quot; height=&quot;381&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;381&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; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;restartable 지점이 Button 이기 때문에 Recomposition가 대폭 줄어든다.&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;b&gt;restartable 지점이 기본적으로 Compose 함수가 호출되는 지점이기 때문에 저렇게 Button 이라는 Compose 함수를 하위 컴포넌트로 적절히 잘 사용해줌으로서 가장 가까운 Recomposition 지점을 조정할 수 있다.&amp;nbsp;&lt;/b&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;u&gt;&lt;b&gt;그렇다면 skippable 과 restartable 지점이 어딘지 확인할 수 있다면 그걸 활용해서 컴포즈 함수를 잘 활용해 Recomposition 줄이고 성능을 높일 수 있다는 결론이다.&amp;nbsp;&lt;/b&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WRyTf/btsI6zqq6KU/1BGEOXRDvtKcOf7jiXokp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WRyTf/btsI6zqq6KU/1BGEOXRDvtKcOf7jiXokp0/img.png&quot; data-alt=&quot;어떤게 Stable 이고 어떤게 Unstable 인지 정리해주셨다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WRyTf/btsI6zqq6KU/1BGEOXRDvtKcOf7jiXokp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWRyTf%2FbtsI6zqq6KU%2F1BGEOXRDvtKcOf7jiXokp0%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;1086&quot; height=&quot;416&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어떤게 Stable 이고 어떤게 Unstable 인지 정리해주셨다&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;그렇다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;skippable 과 restartable 지점이 어딘지&lt;span&gt;&amp;nbsp;&lt;/span&gt;어떻게 확인할 수 있고, 또 그렇게 규정되는 기준은 무엇일까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 이는 Compiler에 의해 결정되는 것이기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;Compose Compiler Reports&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 확인해보면 알 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;그리고 위 자료처럼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;stable&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;한 부분은 skip이 가능하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;unstable&lt;/b&gt;한 부분은 restart 될 수 있다고 이해했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;Report 분석을 통해 적절하게 컴포즈 함수를 작성할 수도 있겠지만 개발자가 명시적으로&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;@stable 과&lt;/b&gt;&lt;/span&gt;&lt;b&gt; @Immutable&lt;/b&gt; 어노테이션을 지정해 컴포넌트를 Stable 가능하고 이는 즉 Skippable 가능한 형태로 만들 수도 있다.&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;@Immutable은&amp;nbsp; 항상 불변임을 보장하도록 컴파일러와 약속하는 것이다. 즉,&amp;nbsp; 변경 되어도 불변을 약속 했기에 UI 가 안 바뀌는 등 문제 발생 가능성 있고 @Immutabl이 명시된 곳의 모든 프로퍼티가 변경 불가능한 val 로 정의되어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그래서 &lt;b&gt;차라리 @Immutable보다는 좀 느슨한 약속인&lt;/b&gt;&lt;/span&gt;&lt;b&gt; @stable &lt;span&gt;을&lt;/span&gt; &lt;span&gt;사용하는게&lt;/span&gt;&amp;nbsp;낫다&lt;/b&gt;고 한다. Recomposition &lt;span&gt;일어날 수 있는 지점이더라도 입력값, 파라미터 등이&amp;nbsp;&lt;/span&gt;&lt;span&gt;동일하면&lt;/span&gt; skip &lt;span&gt;해준다.&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&gt;하지만 이는 컴파일러가 자동으로 처리해줘야 할 일을 개발자가 인위적으로 조정하는 것이다. 물론 컴파일러가 신은 아니지만 아무튼 자주 해서는 안 될 일이기 때문에&amp;nbsp;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;클린코드에 과도하게 빠져 반복을 절대 용납하지 못하고 재사용성에 미쳐버려서는 안 된다는 말이 생각난다ㅋㅋ&lt;/s&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;3. &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;/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;/b&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;b&gt;MacroBenchMark&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeLab에서 따라해볼 수 있으니 그거 참고해서 해보면 좋을듯&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckdnms/btsI6AplIIc/oQvHwb60xKTxYKZeTPagHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckdnms/btsI6AplIIc/oQvHwb60xKTxYKZeTPagHk/img.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;510&quot; data-is-animation=&quot;false&quot; style=&quot;width: 41.0515%; margin-right: 10px;&quot; data-widthpercent=&quot;41.53&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckdnms/btsI6AplIIc/oQvHwb60xKTxYKZeTPagHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fckdnms%2FbtsI6AplIIc%2FoQvHwb60xKTxYKZeTPagHk%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;809&quot; height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJfgP6/btsI6BV5ew0/3ctXAYyLMETk1RqFAvr91K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJfgP6/btsI6BV5ew0/3ctXAYyLMETk1RqFAvr91K/img.png&quot; data-origin-width=&quot;1045&quot; data-origin-height=&quot;468&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;58.47&quot; style=&quot;width: 57.7857%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJfgP6/btsI6BV5ew0/3ctXAYyLMETk1RqFAvr91K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJfgP6%2FbtsI6BV5ew0%2F3ctXAYyLMETk1RqFAvr91K%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;1045&quot; height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/div&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;(아마 네모 박스 부분이 좀 중요한 수치이고,  양수가 크면 로딩 속도가 오래걸리는거고 P99 로 갈수록 고성능에 대한 측정 결과이기 때문에 P99까지 최적화가 되면 완전 성능 굿~ 이라고 해석하면 됐던 것 같다...ㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Gemini in Android Studio&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3958&quot; data-origin-height=&quot;2794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAR0Rc/btsI5xmv48u/LNluvHkYRhnX6KR8N6E9v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAR0Rc/btsI5xmv48u/LNluvHkYRhnX6KR8N6E9v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAR0Rc/btsI5xmv48u/LNluvHkYRhnX6KR8N6E9v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAR0Rc%2FbtsI5xmv48u%2FLNluvHkYRhnX6KR8N6E9v0%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;686&quot; height=&quot;484&quot; data-origin-width=&quot;3958&quot; data-origin-height=&quot;2794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 핫한 생성형 AI, 그중에서도 Google에서 밀고 있는 Gemini(&lt;s&gt;잼민이&lt;/s&gt;) 에 관심이 가서 들어봤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연사님이 조~금만 더 스피치 연습을 해서 오셨더라면 좀 더 몰입되었을 것 같은데 아무튼 내용 자체로 흥미로웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 취한 내용은 별건 아니고...&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Gemini가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;안드로이드 디바이스에서 사용하기에 최적화 되어있는 모델인데 모바일 개발은 최적화, 경량화가 중요하기 때문에 이부분이 아주 맘에 들었다&lt;/li&gt;
&lt;li&gt;현재 LoRA 모델 사용하고 있는데&amp;nbsp; fine tuning 을 통해 충분히 모델 성능도 개선할 수 있다&amp;nbsp;&lt;/li&gt;
&lt;li&gt;인터넷 연결 없이 사용 가능 / 개인 정보 민감한 콘텐츠 다루는 경우 좋은 선택 /&lt;span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&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;그리고 안드로이드 스튜디오에서 Gemini를 설치해 다음과 같은 것들을 해볼 수 있다는 것이었다.&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;다양한 톤으로 메세지 응답 / 음성 녹음 요약 / 키보드 문자 메세지에 대한 스마트 답변 생성 (현재 사용되고 있는 서비스)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;추천 키워드 생성 (음식점 검색시 ~)&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;Code Description 작성&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9CMXS/btsI40W2Pda/WASrKvwkmzzymhN6GMNWT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9CMXS/btsI40W2Pda/WASrKvwkmzzymhN6GMNWT0/img.png&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;597&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.8972%; margin-right: 10px;&quot; data-widthpercent=&quot;50.48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9CMXS/btsI40W2Pda/WASrKvwkmzzymhN6GMNWT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9CMXS%2FbtsI40W2Pda%2FWASrKvwkmzzymhN6GMNWT0%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;1126&quot; height=&quot;597&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rqb92/btsI4WNSMIm/Rg333uAELNmKAsPdPA48WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rqb92/btsI4WNSMIm/Rg333uAELNmKAsPdPA48WK/img.png&quot; data-origin-width=&quot;1097&quot; data-origin-height=&quot;593&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.94%;&quot; data-widthpercent=&quot;49.52&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rqb92/btsI4WNSMIm/Rg333uAELNmKAsPdPA48WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRqb92%2FbtsI4WNSMIm%2FRg333uAELNmKAsPdPA48WK%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;1097&quot; height=&quot;593&quot;/&gt;&lt;/span&gt;&lt;/div&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;테스트 시나리오(코드) 작성 부분은 찐으로 꼭 사용해보고 싶었다. 아직 테스트 코드 작성 무경험자인 내가 쉽게 입문할 수 있도록 도움을 받을 수 있을 것 같고, 현재 출시된 '어비로' 앱이 어느정도 규모와 복잡도 있는 서비스이기 때문에 나 혼자 모든 테스트 코드를 다 작성하기 보다는 이런 AI의 힘을 빌리면 더 효율적일 것 같았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;Code Description&lt;span&gt; 도 실무에서는 이런 작업이 중요하다고 알고 있는데 아직 나에게는 익숙하지 않은 작업이기에 도움을 받을 수 있을 것 같다고 생각했다.&amp;nbsp;&lt;/span&gt;&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;추천 키워드 같은 기능은 코드 작성에 도움이 되는게 아닌 서비스 기능에 활용할 수 있는 부분인데 '비건 음식점 추천 기능' 등에 활용할 수 있지 않을까 싶었다.&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;widthOrigin&quot; data-origin-width=&quot;3962&quot; data-origin-height=&quot;2632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SLiJE/btsI30jg84m/w2jroTploP8vKjFSbKHfWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SLiJE/btsI30jg84m/w2jroTploP8vKjFSbKHfWk/img.png&quot; data-alt=&quot;이 강의는 엄청 큰 홀에서 진행됐다!! 홀 앞 뷰는 오크우드(포스코) 빌딩과 쉐라톤 호텔~ 전에 가족들과 종종 묶었던 호텔들ㅎㅎ (TMI)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SLiJE/btsI30jg84m/w2jroTploP8vKjFSbKHfWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSLiJE%2FbtsI30jg84m%2Fw2jroTploP8vKjFSbKHfWk%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;655&quot; height=&quot;435&quot; data-origin-width=&quot;3962&quot; data-origin-height=&quot;2632&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이 강의는 엄청 큰 홀에서 진행됐다!! 홀 앞 뷰는 오크우드(포스코) 빌딩과 쉐라톤 호텔~ 전에 가족들과 종종 묶었던 호텔들ㅎㅎ (TMI)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Compose 에서 함수 분리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DataDog &lt;/b&gt;&lt;span&gt;&lt;b&gt;를&lt;/b&gt;&lt;/span&gt;&lt;b&gt; &lt;/b&gt;&lt;span&gt;&lt;b&gt;모든&lt;/b&gt;&lt;/span&gt;&lt;b&gt; application&lt;/b&gt;&lt;span&gt;&lt;b&gt;에서&lt;/b&gt;&lt;/span&gt;&lt;b&gt; &lt;/b&gt;&lt;span&gt;&lt;b&gt;사용&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataDog 을 너무 좋아하시는 연사님의 무한 DataDog &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: center;&quot;&gt;PR&lt;/span&gt; 을 듣고 올 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataDog은 백엔드 개발자인 동기가 종종 사용하는 백엔드용 모니터링, 로그 분석 툴 정도로 알고 있었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 모든 Application에 적용할 수 있다길래 안드로이드 환경에서도 적용할 수 있을까 하는 관심이 가서 들어봤다.&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;b&gt;1. 다양한 데이터를 수집해&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;span&gt; &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;하나의 플랫폼에서 이벤트, 트래픽, 에러, 로그 등 여러 데이터를 분석할 수 있기 때문에 백엔드 개발팀이 쓰는 분석툴, 프론트엔드 개발팀이 쓰는 분석툴, 기획팀이 쓰는 분석툴 이런식으로 여러개 사용할 필요없이 올인원으로 사용 가능하다는 장점이라고 이해했다&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;2. 로그&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;저장을&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;할&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;때&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;span&gt;불필요한&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;로그&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;리소스는&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;제거를&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;해줌&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 프론트엔드 단 모니터인 RUM 기능도 잘 제공함&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;/li&gt;
&lt;li&gt;클라이언트 에러, 페이지 종속성, 로딩 속도, 헤비 유저 등을 확인할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;아무래도 난 안드로이드(프론트엔드)개발자이기 때문에 이부분에 관심이 갔다&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;&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;현재는 기획자분께서 이벤트 로그를 수집해 분석하기 위해 Amplitude(앰플리튜드)를 사용중인데 더 좋은 툴이 있다면 내가 먼저 제안해보는 것도 좋겠다 싶었다.&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 data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://steady-study.super.site/datadog-rum&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://steady-study.super.site/datadog-rum&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723721586822&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;운영환경 Datadog RUM 2개월 사용 후기&quot; data-og-description=&quot;에러 모니터링, 이벤트 추적, 분석 모두 Datadog으로 하려고 했던 시도는 실패였다.&quot; data-og-host=&quot;steady-study.super.site&quot; data-og-source-url=&quot;https://steady-study.super.site/datadog-rum&quot; data-og-url=&quot;https://steady-study.super.site/datadog-rum&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://steady-study.super.site/datadog-rum&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://steady-study.super.site/datadog-rum&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; data-ke-size=&quot;size16&quot;&gt;운영환경 Datadog RUM 2개월 사용 후기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;에러 모니터링, 이벤트 추적, 분석 모두 Datadog으로 하려고 했던 시도는 실패였다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;steady-study.super.site&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;블로그의 내용은 결론적으로 DataDog 만으로 올인원 하기는 좀 힘들었고 이벤트 분석은 Amplitude, 프론트엔드 에러 모니터링은 &lt;span style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;Sentry 가 훨씬 낫다는 이야기다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot;&gt;난 프론트엔드 개발자이기 때문에 유저 에러 모니터링이 중요한데 RUM은 안 잡히는 에러도 많다고 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;DataDog은 역시 백엔드 개발자가 디버깅용으로 사용하기에는 좋은 것 같다.&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;또 비용 문제도 있는데 수익이 완전 0₩ 인 사이드 프로젝트 팀에는 적용하기 쉽지 않은 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;P.S. 원래 컨퍼런스라는게 약간 만남의 장, 친목 이런 목적도 있기 때문에 혹시나~ 다른 개발자들과 친해져볼 수 있을까? 하는 기대도 내심 있었지만 사실 그런 분위기도 그럴 힘도 없었기에 얌전히 발표만 듣고 왔다ㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 당연히 나는 지식을 나누는 기쁨을 아는 사람이기에 언젠가는 나도 저기 서서 발표 해보고 싶다는 생각도 들었다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Android</category>
      <category>Android</category>
      <category>compose</category>
      <category>google</category>
      <category>Google I/O</category>
      <category>안드로이드</category>
      <category>컨퍼런스</category>
      <author>gangmini</author>
      <guid isPermaLink="true">https://studyroadmap-kkm.tistory.com/187</guid>
      <comments>https://studyroadmap-kkm.tistory.com/187#entry187comment</comments>
      <pubDate>Thu, 15 Aug 2024 19:13:19 +0900</pubDate>
    </item>
    <item>
      <title>[Android/세미나실] 안드로이드를 더 잘하기 위한 자료 모음집</title>
      <link>https://studyroadmap-kkm.tistory.com/186</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;서류 지원서나 면접 질문에서 종종 물어보는 질문이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'(안드로이드)개발을 잘하기 위해서 무슨 노력을 해봤나요?/ 어디까지 해봤나요?'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 '팀을 꾸려서 프로젝트를 해봤다, 오랜 시간 기능 개발/버그해결을 위해 고민해봤다.' 라고 대답했지만 이런 이야기는 너무 흔해빠졌다고 생각했다. 그리고 실제로 안드로이드 개발자로서 성장하기 위해 뭔가 깊게 생각하고 노력한 적은 없다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 뭘 할 수 있을까....에 대해 생각해보았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;그 외에도 동기를 통해 알게된 데보션 블로그, 최근에 호기심 반 성장욕구 반으로 참석하게 된 구글 I/O 컨퍼런스 등 다양한 안드로이드 이야기들을 접할 수 있는 곳이 많다고 생각하게 되었다.&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;혼자서 공부하게 되면 '코루틴', '컴포즈' 이렇게 잘 알려진 큰 기술들의 이론만 공부하느라 머리도 아프고 뭔가 하나를 공부하는데 시간도 오래걸렸는데&amp;nbsp; '&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;Compose 기반의 UI 테스트 코드 작성법 : WebView 테스트 코드' 이라는 제목의 글을 휙~ 읽고 나면&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;더 구체적이고 다양한 주제, 상황에 대한 내용들을 공부할 수 있어서 좋은 것 같다.&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;text-align: start;&quot;&gt;꼭 글의 내용을 100% 완벽하게 숙지하고 이해하는 것을 목표로 하지는 않는다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;다양한 상황과 그 상황을 어떻게 해결하는지에 대한 아이디어를 얻어가는 것만으로도 나는 성장했다고 느낀다. &lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;그래서 일주일에 적어도 1개 이상의 자료를 읽는 것이 내가 생각해낸 '안드로이드 개발을 더 잘하기 위한 나만의 방법' 이다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그!래!서!&lt;/span&gt;&lt;/blockquote&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[데보션]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://devocean.sk.com/tech&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://devocean.sk.com/tech&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723466051974&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;데보션 (DEVOCEAN) 기술 블로그 &amp;amp; 커뮤니티&quot; data-og-description=&quot;데보션 (DEVOCEAN) 기술 블로그 , 개발자 커뮤니티이자 내/외부 소통과 성장 플랫폼&quot; data-og-host=&quot;devocean.sk.com&quot; data-og-source-url=&quot;https://devocean.sk.com/tech&quot; data-og-url=&quot;https://devocean.sk.com/tech?&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BU6KJ/hyWOc2QHbl/aXb92rWyijWn7Bos6QPetK/img.png?width=270&amp;amp;height=270&amp;amp;face=0_0_270_270,https://scrap.kakaocdn.net/dn/dSeHXg/hyWKz6tg6M/nFg7Ke9KVvy5kub53jIt5K/img.png?width=270&amp;amp;height=270&amp;amp;face=0_0_270_270,https://scrap.kakaocdn.net/dn/bCOkIv/hyWKEUgcKp/ofDrYPhB2cFgmhRMBn2pr0/img.jpg?width=1023&amp;amp;height=1024&amp;amp;face=310_236_634_591&quot;&gt;&lt;a href=&quot;https://devocean.sk.com/tech&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devocean.sk.com/tech&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BU6KJ/hyWOc2QHbl/aXb92rWyijWn7Bos6QPetK/img.png?width=270&amp;amp;height=270&amp;amp;face=0_0_270_270,https://scrap.kakaocdn.net/dn/dSeHXg/hyWKz6tg6M/nFg7Ke9KVvy5kub53jIt5K/img.png?width=270&amp;amp;height=270&amp;amp;face=0_0_270_270,https://scrap.kakaocdn.net/dn/bCOkIv/hyWKEUgcKp/ofDrYPhB2cFgmhRMBn2pr0/img.jpg?width=1023&amp;amp;height=1024&amp;amp;face=310_236_634_591');&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;데보션 (DEVOCEAN) 기술 블로그 &amp;amp; 커뮤니티&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;데보션 (DEVOCEAN) 기술 블로그 , 개발자 커뮤니티이자 내/외부 소통과 성장 플랫폼&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devocean.sk.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;[올리브영 기술블로그]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://oliveyoung.tech/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://oliveyoung.tech/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723466113934&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;oliveyoung.tech&quot; data-og-source-url=&quot;https://oliveyoung.tech/&quot; data-og-url=&quot;https://oliveyoung.techundefined&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bizcTT/hyWOhiNYrt/g0JxMKpK8bDot6vpuC09B1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b7z8Uz/hyWOezCh51/uPLqqF6i6siUrw0LnZ2KuK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://oliveyoung.tech/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://oliveyoung.tech/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bizcTT/hyWOhiNYrt/g0JxMKpK8bDot6vpuC09B1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b7z8Uz/hyWOezCh51/uPLqqF6i6siUrw0LnZ2KuK/img.jpg?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;oliveyoung.tech&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;[강남언니 기술블로그]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.gangnamunni.com/blog/android/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.gangnamunni.com/blog/android/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723466130433&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;Android #ViewPager#ViewPager2#Android&quot; data-og-host=&quot;blog.gangnamunni.com&quot; data-og-source-url=&quot;https://blog.gangnamunni.com/blog/android/&quot; data-og-url=&quot;https://blog.gangnamunni.com/blog/android/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://blog.gangnamunni.com/blog/android/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.gangnamunni.com/blog/android/&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; data-ke-size=&quot;size16&quot;&gt;강남언니 공식 블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Android #ViewPager#ViewPager2#Android&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.gangnamunni.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;
&lt;p data-ke-size=&quot;size16&quot;&gt;[뱅크샐러드 기술블로그]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.banksalad.com/tech/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.banksalad.com/tech/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723466067037&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;blog.banksalad.com&quot; data-og-source-url=&quot;https://blog.banksalad.com/tech/&quot; data-og-url=&quot;https://blog.banksalad.com/tech/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cL6vU8/hyWKJutoqk/WUGpNBMajWeTqKjYyj40JK/img.png?width=491&amp;amp;height=491&amp;amp;face=0_0_491_491&quot;&gt;&lt;a href=&quot;https://blog.banksalad.com/tech/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.banksalad.com/tech/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cL6vU8/hyWKJutoqk/WUGpNBMajWeTqKjYyj40JK/img.png?width=491&amp;amp;height=491&amp;amp;face=0_0_491_491');&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;blog.banksalad.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;[당근마켓 기술블로그]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/daangn/tagged/android&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/daangn/tagged/android&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723466195210&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;medium-com:collection&quot; data-og-title=&quot;Android &amp;ndash; 당근 테크 블로그 &amp;ndash; Medium&quot; data-og-description=&quot;Read writing about Android in 당근 테크 블로그. 당근은 동네 이웃 간의 연결을 도와 따뜻하고 활발한 교류가 있는 지역 사회를 꿈꾸고 있어요.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/daangn/tagged/android&quot; data-og-url=&quot;https://medium.com/daangn/tagged/android&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BL9OA/hyWOq07N8D/8m6Va2yOoWlHHLgkCd0uAK/img.png?width=1600&amp;amp;height=480&amp;amp;face=0_0_1600_480,https://scrap.kakaocdn.net/dn/7QTdt/hyWOl6yj3U/V9KlqpQ6iQfldmGx9Sa5h0/img.png?width=1600&amp;amp;height=480&amp;amp;face=0_0_1600_480&quot;&gt;&lt;a href=&quot;https://medium.com/daangn/tagged/android&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/daangn/tagged/android&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BL9OA/hyWOq07N8D/8m6Va2yOoWlHHLgkCd0uAK/img.png?width=1600&amp;amp;height=480&amp;amp;face=0_0_1600_480,https://scrap.kakaocdn.net/dn/7QTdt/hyWOl6yj3U/V9KlqpQ6iQfldmGx9Sa5h0/img.png?width=1600&amp;amp;height=480&amp;amp;face=0_0_1600_480');&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;ndash; 당근 테크 블로그 &amp;ndash; Medium&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Read writing about Android in 당근 테크 블로그. 당근은 동네 이웃 간의 연결을 도와 따뜻하고 활발한 교류가 있는 지역 사회를 꿈꾸고 있어요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.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;[토스 기술블로그]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://toss.tech/tech&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://toss.tech/tech&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723466213551&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;toss.tech&quot; data-og-source-url=&quot;https://toss.tech/tech&quot; data-og-url=&quot;https://toss.tech/tech&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/IJwx5/hyWOlMfHvv/13VcR72C6RYaRlZ8UMtDHK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/v3D0j/hyWKxAQ85x/dd4sK5inb1SAbkKvJRDfRK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cvHgZX/hyWKwPpEmJ/hKVS7aCJaowGlLfp7l8Uv1/img.png?width=3275&amp;amp;height=680&amp;amp;face=0_0_3275_680&quot;&gt;&lt;a href=&quot;https://toss.tech/tech&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://toss.tech/tech&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/IJwx5/hyWOlMfHvv/13VcR72C6RYaRlZ8UMtDHK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/v3D0j/hyWKxAQ85x/dd4sK5inb1SAbkKvJRDfRK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cvHgZX/hyWKwPpEmJ/hKVS7aCJaowGlLfp7l8Uv1/img.png?width=3275&amp;amp;height=680&amp;amp;face=0_0_3275_680');&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;toss.tech&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;[2024 Google I/O 발표자료]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://danielkim88.notion.site/I-O-Extended-Incheon-2024-56c512b101364504876923213728cb16&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://danielkim88.notion.site/I-O-Extended-Incheon-2024-56c512b101364504876923213728cb16&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723466263851&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;I/O Extended Incheon 2024 발표 자료 | Notion&quot; data-og-description=&quot;안녕하세요! GDG Incheon / GDG Songdo 입니다.&quot; data-og-host=&quot;danielkim88.notion.site&quot; data-og-source-url=&quot;https://danielkim88.notion.site/I-O-Extended-Incheon-2024-56c512b101364504876923213728cb16&quot; data-og-url=&quot;https://danielkim88.notion.site/56c512b101364504876923213728cb16&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xWjnW/hyWKII7IHo/QU6fn8H8ADmTxJFAXVVSF1/img.png?width=2000&amp;amp;height=800&amp;amp;face=0_0_2000_800,https://scrap.kakaocdn.net/dn/fj3bz/hyWKGxLJKH/0zNIBI1BFnIAIQQy27of6k/img.png?width=2000&amp;amp;height=800&amp;amp;face=0_0_2000_800&quot;&gt;&lt;a href=&quot;https://danielkim88.notion.site/I-O-Extended-Incheon-2024-56c512b101364504876923213728cb16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://danielkim88.notion.site/I-O-Extended-Incheon-2024-56c512b101364504876923213728cb16&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xWjnW/hyWKII7IHo/QU6fn8H8ADmTxJFAXVVSF1/img.png?width=2000&amp;amp;height=800&amp;amp;face=0_0_2000_800,https://scrap.kakaocdn.net/dn/fj3bz/hyWKGxLJKH/0zNIBI1BFnIAIQQy27of6k/img.png?width=2000&amp;amp;height=800&amp;amp;face=0_0_2000_800');&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;I/O Extended Incheon 2024 발표 자료 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요! GDG Incheon / GDG Songdo 입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;danielkim88.notion.site&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;[크몽로그]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kmong.com/article/1751--%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-%EC%B4%88%EB%B3%B4%EC%9E%90%EB%8F%84-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%95%B1-%EA%B0%9C%EB%B0%9C%EC%9D%80&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kmong.com/article/1751--%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90-%EC%B4%88%EB%B3%B4%EC%9E%90%EB%8F%84-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%95%B1-%EA%B0%9C%EB%B0%9C%EC%9D%80&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[패스트캠퍼스 듣고 싶은 강좌]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fastcampus.co.kr/search?keyword=%EC%BD%94%ED%8B%80%EB%A6%B0%20%ED%94%8C%EB%A1%9C%EC%9A%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fastcampus.co.kr/search?keyword=%EC%BD%94%ED%8B%80%EB%A6%B0%20%ED%94%8C%EB%A1%9C%EC%9A%B0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[접근성 관련 글]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nuli.navercorp.com/community/article/1133004&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nuli.navercorp.com/community/article/1133004&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[IT 뉴스 볼 수 있는 곳]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://maily.so/blackcon/posts/7287d00e&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://maily.so/blackcon/posts/7287d00e&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723797648679&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;아이폰에서 '삼성페이'가능   / 영화 제작의 판도를 바꿀 AI   / 현기차, '베터리 관리 시스템' &quot; data-og-description=&quot;구독자 님이 잠든 사이에 있었던 핫 한 이슈들을 모아보았는데요. 어떤 일들이 있었는지 보기 좋게 정리해서 공유드립니다.&quot; data-og-host=&quot;maily.so&quot; data-og-source-url=&quot;https://maily.so/blackcon/posts/7287d00e&quot; data-og-url=&quot;https://maily.so/blackcon/posts/7287d00e&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HYu1k/hyWOjoriMy/loXzKr7KquOi3SQabqCBRK/img.png?width=1617&amp;amp;height=919&amp;amp;face=0_0_1617_919,https://scrap.kakaocdn.net/dn/cwcmYA/hyWSmX5LcC/grVS5uIe55OOtk2zwtGshK/img.png?width=2214&amp;amp;height=1268&amp;amp;face=0_0_2214_1268,https://scrap.kakaocdn.net/dn/6d9sh/hyWSjtwXjp/uHafAMclgjcyo1apcIjzVK/img.png?width=1965&amp;amp;height=1113&amp;amp;face=534_181_782_454&quot;&gt;&lt;a href=&quot;https://maily.so/blackcon/posts/7287d00e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://maily.so/blackcon/posts/7287d00e&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HYu1k/hyWOjoriMy/loXzKr7KquOi3SQabqCBRK/img.png?width=1617&amp;amp;height=919&amp;amp;face=0_0_1617_919,https://scrap.kakaocdn.net/dn/cwcmYA/hyWSmX5LcC/grVS5uIe55OOtk2zwtGshK/img.png?width=2214&amp;amp;height=1268&amp;amp;face=0_0_2214_1268,https://scrap.kakaocdn.net/dn/6d9sh/hyWSjtwXjp/uHafAMclgjcyo1apcIjzVK/img.png?width=1965&amp;amp;height=1113&amp;amp;face=534_181_782_454');&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;아이폰에서 '삼성페이'가능   / 영화 제작의 판도를 바꿀 AI   / 현기차, '베터리 관리 시스템'&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;maily.so&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;[프로그래머스 - Django]&lt;br /&gt;&lt;a href=&quot;https://career.programmers.co.kr/posts/tag/Django&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://career.programmers.co.kr/posts/tag/Django&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724395521496&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;Django - 테크 피드&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;career.programmers.co.kr&quot; data-og-source-url=&quot;https://career.programmers.co.kr/posts/tag/Django&quot; data-og-url=&quot;https://career.programmers.co.kr/posts/tag/Django&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/l8a1S/hyWSoQB9PA/vTuK6Qy3C8AOKys2Rcs290/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cCWVgk/hyWSpIJZcU/jU0996c1YORztNZoaAFeaK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://career.programmers.co.kr/posts/tag/Django&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://career.programmers.co.kr/posts/tag/Django&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/l8a1S/hyWSoQB9PA/vTuK6Qy3C8AOKys2Rcs290/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cCWVgk/hyWSpIJZcU/jU0996c1YORztNZoaAFeaK/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;Django - 테크 피드&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;career.programmers.co.kr&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</category>
      <category>개발</category>
      <category>개발공부</category>
      <category>기술블로그</category>
      <category>성장</category>
      <category>안드로이드</category>
      <author>gangmini</author>
      <guid isPermaLink="true">https://studyroadmap-kkm.tistory.com/186</guid>
      <comments>https://studyroadmap-kkm.tistory.com/186#entry186comment</comments>
      <pubDate>Mon, 12 Aug 2024 21:38:02 +0900</pubDate>
    </item>
    <item>
      <title>[Git / Command] 내가 보려고 만든 Git 명령어 모음집</title>
      <link>https://studyroadmap-kkm.tistory.com/184</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;3일만 안 써도 까먹는 깃 명령어.... &lt;s&gt;진짜 빡대가리..&lt;/s&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;브랜치 변경&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1722400915305&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git checkout 변경할브랜치&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;브랜치 생성 및 전환&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1722401163927&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git checkout -b 생성할브랜치이름&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;브랜치 삭제&amp;nbsp;&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;&lt;b&gt;&amp;nbsp;Local 브랜치 삭제&amp;nbsp;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1722400844817&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git branch -d 로컬브랜치이름&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Remote 브랜치 삭제&amp;nbsp;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1722400875460&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git push origin --delete 원격브랜치이름&lt;/code&gt;&lt;/pre&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;Merge&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;현재 브랜치가 dev 인데 main 브랜치의 내용을 dev에 반영하고 싶을때&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1722400719149&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 현재 브랜치는 dev
git merge main&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Local에서 작업 후 add - commit - push&amp;nbsp; 루틴&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1722401053988&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 
git add . 혹은 git add 파일명

2. 
git commit -m &quot;커밋메세지&quot;

3. 
git push origin(원격저장소 이름) 브랜치명&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Remote 내용을 Local 로 pull 땡기기 (가져오기)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1722401092555&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git pull origin 땡겨올브랜치&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Local에서 작업한 내용 처음으로 원격저장소에 올리기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 깃 초기화&lt;/p&gt;
&lt;pre id=&quot;code_1723373276401&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git init

// git config --global user.email &quot;keung903@naver.com&quot;
// git config --global user.name &quot;honor-sky&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;2. Local에서 작업한 내용 모두 add 해주고 commit 해주기 -&amp;gt; local 의 origin 이라는 곳으로 올라감&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;만약 모든 파일을 한번에 올릴게 아니라 파일별로 조금씩 올리고 싶다면 add 다음에 . 가 아니라 파일이나 폴더명을 써주고 commit 해주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723373293907&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git add .

git commit -m &quot;first commit&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;3. 원격 저장소 만들기 ( ** 주의할 점!!)&lt;b&gt;&lt;/b&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;b&gt;인증 토큰&lt;/b&gt;이 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃헙 계정의 setting (repository setting 아니고 전체 설정) 에 들어가서 developers -&amp;gt; personal access tokens -&amp;gt; classic token 에서 토큰을 만들어준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 반드시 &lt;b&gt;토큰은 어딘가에 저장해줘야 한다.&lt;/b&gt; (잊어버리면 제거하고 다시 만들면 됨)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 반드시 repo 부분을 다 체크해줘야지 403 에러가 안 나고 정상적으로 연결될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PKYB2/btsI1u36MEc/aKIkMuBPLS0HZFXk799CNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PKYB2/btsI1u36MEc/aKIkMuBPLS0HZFXk799CNK/img.png&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;454&quot; data-is-animation=&quot;false&quot; width=&quot;598&quot; height=&quot;399&quot; style=&quot;width: 35.071%; margin-right: 10px;&quot; data-widthpercent=&quot;35.48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PKYB2/btsI1u36MEc/aKIkMuBPLS0HZFXk799CNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPKYB2%2FbtsI1u36MEc%2FaKIkMuBPLS0HZFXk799CNK%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;680&quot; height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwupE0/btsI01nNuyJ/YTgFXqJ3DkyF3wk00cn281/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwupE0/btsI01nNuyJ/YTgFXqJ3DkyF3wk00cn281/img.png&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;412&quot; data-is-animation=&quot;false&quot; width=&quot;705&quot; height=&quot;259&quot; style=&quot;width: 63.7662%;&quot; data-widthpercent=&quot;64.52&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwupE0/btsI01nNuyJ/YTgFXqJ3DkyF3wk00cn281/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwupE0%2FbtsI01nNuyJ%2FYTgFXqJ3DkyF3wk00cn281%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;1122&quot; height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/div&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;4. Local의 origin 의 내용 본격적으로 remote 저장소에 추가하기&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723373380507&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git remote add origin https://github.com/honor-sky/BitCoinWebSocket.git&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;이 명령어를 치고 나면, 깃허브 userName 과 password 를 입력하는 명령이 뜨는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 userName은 userName 을 그대로 치면 되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;password에는 위 3번에서 만들어준 토큰을 입력해준다.&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;5. 원격저장소의 브랜치를 main 으로 설정&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; (만약 main이 아닌 다른 저장소에 저장하고 싶은거라면 다른 브랜치명 써주면 됨)&lt;/p&gt;
&lt;pre id=&quot;code_1723373391036&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git branch -M main&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;6. origin의 내용을 remote의 main 브랜치로 push 해주기&lt;br /&gt;&amp;nbsp; &amp;nbsp; (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;만약&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;main이 아닌 다른 저장소에 저장하고 싶은거라면 다른 브랜치명 써주면 됨)&lt;/p&gt;
&lt;pre id=&quot;code_1723373397529&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git push origin +main&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;원격저장소 Repository를&amp;nbsp; Local로가져오기&amp;nbsp;&amp;nbsp;&lt;/s&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;s&gt;Local에 연결된 원격저장소 연결 끊기&amp;nbsp;&lt;/s&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;s&gt;upstream 설정하기&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Git</category>
      <category>GIT</category>
      <category>git command</category>
      <category>깃</category>
      <category>깃명령어</category>
      <category>안드로이드</category>
      <category>앱개발자</category>
      <author>gangmini</author>
      <guid isPermaLink="true">https://studyroadmap-kkm.tistory.com/184</guid>
      <comments>https://studyroadmap-kkm.tistory.com/184#entry184comment</comments>
      <pubDate>Wed, 31 Jul 2024 13:48:49 +0900</pubDate>
    </item>
    <item>
      <title>[Git / Git Flow] Git Flow 수립 도전기!</title>
      <link>https://studyroadmap-kkm.tistory.com/182</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; Git Flow 란?&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;버전 관리와 개발자 간의 분업을 위해 일종의 규칙과 프로세스에 맞춰 git을 사용하는 것&lt;/li&gt;
&lt;li&gt;상황에 맞게 브랜치를 만들고 머지&amp;nbsp;&lt;/li&gt;
&lt;li&gt;기본적인 틀이 있지만 팀바팀!&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로 git flow에서 사용되는 브랜치는 main / dev / feature / hotfix 로 이루어져 있다. 브랜치 별로 각 역할과 의미가 있지만 팀마다 조금씩 다르게 규칙을 정의해 운영하기도 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;현재 진행 중인 사이드 프로젝트 '어비로' 가 구글 출시 심사 과정에 있고, 본격적인 운영에 앞서 Git Flow를 적용하는 것이 좋다는 말을 들었다. 그래서 같은 팀의 ios 개발자이신 성훈님의 가르침과 여러가지 사항들을 고려해 &amp;lsquo;어비로&amp;rsquo; 만의 git flow 를 구축해 운영해보려고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;b&gt;✔️ Main&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최종 &lt;b&gt;배포&lt;/b&gt; 버전 브랜치 / 운영 브랜치&lt;/li&gt;
&lt;li&gt;모든 개발이 완료되고, 최종 배포를 할 경우 이곳으로 merge 한다 (배포를 위해 모이는 곳)&lt;/li&gt;
&lt;li&gt;만약 심사과정 혹은 배포 이후 오류/버그/피드백가 발견된 경우, &lt;b&gt;hotfix&lt;/b&gt; 브랜치에서 처리한다&lt;/li&gt;
&lt;li&gt;(추후 : 실제 플레이스토어에 출시하기 위한 배포를 main 브랜치에서 자동화 하고자 한다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;Dev&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최종 &lt;b&gt;개발&lt;/b&gt; 버전 브랜치&lt;/li&gt;
&lt;li&gt;새로운 개발이 시작되었을때, Dev 브랜치에서 각 기능별로 Feature 브랜치들을 파서 개발을 시작한다.&lt;/li&gt;
&lt;li&gt;Feature브랜치에서 개발이 완료되면 이곳으로 merge 한다 (개발이 완료되면 모이는 곳)&lt;/li&gt;
&lt;li&gt;(추후 : 실제 출시용 배포 전 내부 테스트를 위한 배포를 Dev 브랜치에서 자동화 하고자 한다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;Feature&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;개발 중인 각 기능들을 각각의 feature 브랜치로 만들어 개발&lt;br /&gt;예를 들어 &amp;lsquo;사진 업로드 기능&amp;rsquo; 이 추가되었을 경우, &amp;lsquo;가게 등록 사진 업로드&amp;rsquo; 와 &amp;lsquo;후기 등록 사진 업로드&amp;rsquo; 이렇게 크게 2가지 기능으로 나뉠 수 있고 각각 Feature/image_resgister 와 Feature/image_review 이렇게 브랜치를 나눠 각각 개발할 수 있다&lt;br /&gt;(개발자가 여러명인 경우, 각자 맡은 기능 개발을 브랜치로 따로 만들어 개발하면 관리가 쉽다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;Hotfix&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;✔️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;Release&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;배포를 위한 브랜치이다. main 에서 처리해도 되지만 이렇게 버전별 릴리즈 브랜치를 만들어 dev에서 모아진 최종 개발 결과물을 배포한다. Ex) release/release-1.0.0&lt;/li&gt;
&lt;li&gt;릴리즈용이기 때문에 해당 릴리즈가 끝나면 해당 브랜치는 삭제된다&lt;/li&gt;
&lt;li&gt;&lt;s&gt;이렇게 하게 되면 CI/CD 구축이 힘들수도 있지 않을까 싶다&lt;/s&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;CI/CD 구축이라는 것이 특정 브랜치에 배포하고자 하는 결과물이 merge 되면 자동으로 배포가 되는 자동화를 구축하는 것인데, 이렇게 매번 다른 브랜치를 생성해 사용하게 되면 구축이 어렵지 않을까?&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;과거의 내가 Git을 사용하던 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것도 사실 매번 냅다 main만 사용하다가 좀 없어보여서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴리즈 버전별로 브랜치 만들어 개발하고 배포할때마다 main 에 merge 하려고 시도했던 흔적&amp;hellip;ㅎㅎ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eiaaLf/btsIEsMZkSt/weWIDGnZLS05f3OoI7xgS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eiaaLf/btsIEsMZkSt/weWIDGnZLS05f3OoI7xgS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eiaaLf/btsIEsMZkSt/weWIDGnZLS05f3OoI7xgS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeiaaLf%2FbtsIEsMZkSt%2FweWIDGnZLS05f3OoI7xgS1%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;272&quot; height=&quot;308&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;406&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;현재 (Git Flow 적용!)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 그동안 뭘 한건지 모르겠지만,,,,,, 새마음 새뜻으로 시작하기 위해 현재 브랜치들을 정리해주었다&amp;hellip;.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;1.0.0 브랜치 제거&lt;/li&gt;
&lt;li&gt;1.1.0 브랜치 &amp;rarr; dev 로 변경
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가장 최신 코드들이 반영되어 있는 브랜치이므로 이걸 dev로 변경해주었다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;dev 를 main 으로 merge
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;현재 dev의 코드로 aab 파일을 만들어 프로덕션 배포한 상태이기 때문에 dev 내용을 main 으로 merge 시켜 상태를 맞춰 주었다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우, release 브랜치는 따로 운영하지 않고, main 브랜치를 release 브랜치처럼 사용하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 어비로 official Repository를 fork 해서 upstream 방식으로 운영하고 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork 한 내 저장소를 &amp;rarr; origin 이라고 부르고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;official Repository를 &amp;rarr; upstream 이라고 부르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후에 origin의 main에 배포하면 내부테스트용 앱이 배포되고, upstream의 main에 배포하면 실제 출시용 앱이 배포되도록 CI/CD를 구축해보는 것도 나의 목표이다. (바뀔 수도 있음)&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  HotFix 만들어 보기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 앱을 출시하기 위해 프로덕션에 올려 14일간 테스트중인데, 급하게 수정할 사항이 생겼다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안드로이드 API 수준 올리기(target SDK 를 34 = API 수준 14) (구글 요청)&lt;/li&gt;
&lt;li&gt;&amp;lsquo;가게 등록 - 가게 검색&amp;rsquo; 기능에서 발생하는 버그 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 배포했는데 발생하는 문제이기 때문에 hotfix를 실습하기 딱 좋다고 생각했다.&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;1. main 브랜치에서 hotfix 만들어주기 &amp;amp; 작업하기&lt;/b&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;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&amp;nbsp; &amp;nbsp;1-1.&amp;nbsp; hotfix는 main 브랜치에서 처리하는 것으로 정했기 때문에 여기서 브랜치를 만들어준다&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; &amp;nbsp;1-2.&amp;nbsp; local 에서 먼저 브랜치를 만들어 주고, 이걸 원격저장소로 push 해줬다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YODGt/btsIEJ9g7Vu/95P2vKASYJLOmOgG89MFpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YODGt/btsIEJ9g7Vu/95P2vKASYJLOmOgG89MFpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YODGt/btsIEJ9g7Vu/95P2vKASYJLOmOgG89MFpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYODGt%2FbtsIEJ9g7Vu%2F95P2vKASYJLOmOgG89MFpK%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;691&quot; height=&quot;62&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;62&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. &lt;span data-token-index=&quot;0&quot;&gt;작업 완료된 hotfix 브랜치를 main 과 dev로 merge 하기 위해 PR 올리기&lt;/span&gt;&lt;/b&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; &amp;nbsp;2-1.&amp;nbsp; 나는 hotfix의 내용이 빠르게 배포 버전에 반영될 수 있도록 하는 것이 중요하다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;그래서 아래 처럼 main에 PR을 진행했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l26qk/btsIFFZcvB4/AZpE7PECkyis4ZKsdyILg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l26qk/btsIFFZcvB4/AZpE7PECkyis4ZKsdyILg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l26qk/btsIFFZcvB4/AZpE7PECkyis4ZKsdyILg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl26qk%2FbtsIFFZcvB4%2FAZpE7PECkyis4ZKsdyILg1%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;710&quot; height=&quot;185&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;440&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;&amp;nbsp; &amp;nbsp;2-2.&amp;nbsp; 그리고 dev 브랜치 또한 버그가 수정된 main의 상태와 맞춰줘야 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;원격 저장소에서 dev로 merge 해주거나 상황에 따라&amp;nbsp;PR을 올려 진행해주었다.&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;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;3. 로컬에서 브랜치별로 fetch-pull 해줘서 원격의 내용을 반영&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 원격의 main 과 dev 는 hotfix를 통해 버그를 수정한 내용까지 모두 반영되어 있을 것이다. &lt;br /&gt;a 또는 b의 절차를 걸치게 되면, 원격저장소에서 계속 PR을 올렸기 때문에 &lt;u&gt;로컬에는 버그 수정 사항이 반영 되어 있지 않다. &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;또한 협업 상황에서는 내가 아니더라도 다른 누군가가 hotfixt 작업을 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 경우 나는 내 로컬의 main 과 dev에서 fetch-pull 수행해줘야 버그 수정 사항을 반영을 내 로컬 저장소에도 반영할 수 있다&lt;/p&gt;
&lt;pre id=&quot;code_1721308349053&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git fetch

git checkout dev # 로컬에서 dev 브랜치로 전환
git pull

git checkout main # 로컬에서 main 브랜치로 전환
git pull&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;git flow 수립 첫 도전 &amp;amp; hotfix 만들어보기 끝~~&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;+) 데보션에 올라온 글 중에 Git Flow를 야구에 비유해 쉽고 재미있게 설명하는 글이 있어서 공유해본다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://devocean.sk.com//blog/techBoardDetail.do?ID=166708&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://devocean.sk.com//blog/techBoardDetail.do?ID=166708&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724387665746&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;야구로 쉽게 이해해 보는 Git Flow 전략 이야기&quot; data-og-description=&quot; &quot; data-og-host=&quot;devocean.sk.com&quot; data-og-source-url=&quot;https://devocean.sk.com//blog/techBoardDetail.do?ID=166708&quot; data-og-url=&quot;https://devocean.sk.com/blog/techBoardDetail.do?ID=166708&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bl7yGl/hyWSbwUMSy/EcIfKWz8SVKUYhasBRYZK0/img.jpg?width=720&amp;amp;height=397&amp;amp;face=0_0_720_397,https://scrap.kakaocdn.net/dn/baRKbT/hyWSkADHrt/CcgtMK4Yow1o6cPKW6su91/img.jpg?width=720&amp;amp;height=397&amp;amp;face=0_0_720_397,https://scrap.kakaocdn.net/dn/Xbrn3/hyWShcPjL2/s4rmv1ZwkWVK1IkTiT7u81/img.png?width=720&amp;amp;height=602&amp;amp;face=0_0_720_602&quot;&gt;&lt;a href=&quot;https://devocean.sk.com//blog/techBoardDetail.do?ID=166708&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devocean.sk.com//blog/techBoardDetail.do?ID=166708&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bl7yGl/hyWSbwUMSy/EcIfKWz8SVKUYhasBRYZK0/img.jpg?width=720&amp;amp;height=397&amp;amp;face=0_0_720_397,https://scrap.kakaocdn.net/dn/baRKbT/hyWSkADHrt/CcgtMK4Yow1o6cPKW6su91/img.jpg?width=720&amp;amp;height=397&amp;amp;face=0_0_720_397,https://scrap.kakaocdn.net/dn/Xbrn3/hyWShcPjL2/s4rmv1ZwkWVK1IkTiT7u81/img.png?width=720&amp;amp;height=602&amp;amp;face=0_0_720_602');&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;야구로 쉽게 이해해 보는 Git Flow 전략 이야기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devocean.sk.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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;[참고자료]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://evan-moon.github.io/2019/07/28/git-tutorial-advanced/&quot;&gt;Git 뉴비를 위한 기초 사용법 - 버전 관리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@diduya/git-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%ED%98%91%EC%97%85%EC%9D%84-%EC%9C%84%ED%95%9C-Git-Flow-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-git-branch-repository&quot;&gt;[git] 효율적인 협업을 위한 Git-Flow 이해하기 (사용하는 branch 와 repository 구성)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2553/&quot;&gt;우린 Git-flow를 사용하고 있어요 | 우아한형제들 기술블로그&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://overcome-the-limits.tistory.com/7&quot;&gt;[협업] 협업을 위한 Git Flow 설정하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(upstream)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://my-codinglog.tistory.com/7&quot;&gt;[Git] fork한 repository 동기화부터 pull request (PR)를 보내기까지&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(merge 방식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@gmlstjq123/Github-Merge-%EB%B0%A9%EC%8B%9D-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot;&gt;Github Merge 방식 이해하기&lt;/a&gt;&lt;/p&gt;</description>
      <category>Git</category>
      <category>Android</category>
      <category>GIT</category>
      <category>Git Flow</category>
      <category>Kotlin</category>
      <category>깃</category>
      <category>깃플로우</category>
      <category>안드로이드</category>
      <category>코틀린</category>
      <author>gangmini</author>
      <guid isPermaLink="true">https://studyroadmap-kkm.tistory.com/182</guid>
      <comments>https://studyroadmap-kkm.tistory.com/182#entry182comment</comments>
      <pubDate>Thu, 18 Jul 2024 03:55:59 +0900</pubDate>
    </item>
    <item>
      <title>[안드로이드/Coroutine] Coroutine 예제를 통해 본격 딥다이브 &amp;amp; Structured concurrency</title>
      <link>https://studyroadmap-kkm.tistory.com/181</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;중간 점검 차 계속 이론, 개념 공부만 하는 것이 아니라 직접 몇가지 실습을 통해 코루틴을 익혀보려고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브에서 &amp;lsquo;새차원, 코틀린 코루틴&amp;rsquo; 강의를 참고해 공부하면 공식문서 실습코드들을 따라 쳐보면서 공부할 수 있다.&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;내가 이해한건 A라 똑같이 적용해서 풀면 틀리고 B라는 개념이 또 등장한다.&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;pre id=&quot;code_1721240351932&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt;{
	GlobalScope.launch { // 코루틴2 (코루틴1과 다른 생명주기)
	    delay(3000L)
	    println(&quot;world!&quot;)
   }
    println(&quot;hello,&quot;) // 코루틴1

}
/* 실행결과 */
// hello,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721240362183&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt;{
	launch { // 코루틴2 (코루틴1과 같은 생명주기)
	    delay(3000L) //delay()가 없어도 동일한 결과 
	    println(&quot;world!&quot;)
   }
    println(&quot;hello,&quot;) // 코루틴1

}
/* 실행결과 */
// hello, 
// world!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 위 예제를 통해 코루틴에서 중요하게 생각하는 Structured concurrency에 대해 이야기 해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Structured concurrency을 잘 지켜 코루틴을 짜면 더 효율적이고 이쁜 코드 개발이 가능해서 중요하게 생각하는 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  Structured concurrency (구조화된 동시성)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 첫번째 코드는 runBlcoking 블록 내부에서 또 다른 새로운 코루틴 scope인 GlobalScope를 생성해 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;u&gt;runBlocking 도 parent Scope&lt;/u&gt; 이고, &lt;u&gt;GlobalScope도 parent Scope&lt;/u&gt; 이므로 따로국밥으로 &lt;b&gt;각자의 라이프사이클&lt;/b&gt;을 가지고 운영된다는 것이다. 그래서 GlobalScope에서 3000L 대기하는 동안 runBlocking이 끝나면서 실행 자체가 종료되어버리고, &amp;ldquo;World! &amp;rdquo;는 출력되지 못한 것 이다.&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;반면에 두번째 코드는 runBlcoking 내부에서 launch 되고 있다. ( = this.launch)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 runBlcoking과 같은 코루틴 Scope 안에서 코루틴을 생성해 동작하는 것이기 때문에 runBlcoking 블록 안에 모든 내용이 수행될때까지 종료되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 같은 스코프 내에서 코루틴을 생성하도록 구조화해주면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴 수행이 정상적으로 보장받을 수 있고, 코루틴 간에 병렬처리에도 좋다는 것이 Structured concurrency 이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 내가 이 코드를 실습해볼때, 코틀린 파일만 실행하는 법을 몰라서 그냥 앱 단위에서 코드를 실행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한마디로 화면까지 만들어서 액티비티 안에 코드를 적어 실행했다는 것&amp;hellip;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 아래와 같은 코드를 작성하게 되면 원래는 &amp;ldquo;hello,&amp;rdquo; 만 나와야 한다고 이해했는데 이렇게 모두 출력되는 것이었다&amp;hellip;.!&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;같은 Scope 아니니까 main()가 끝나면 println(&quot;world!&quot;) 도 실행이 안 되어야 하는 것 아닌가&amp;hellip;? 이걸로 한참을 고민했다.&lt;/p&gt;
&lt;pre id=&quot;code_1721240423618&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Acticvity onCreate() 에서 실행
class ToolBar : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
    		main()
    }

    fun main() = runBlocking&amp;lt;Unit&amp;gt;{
    	GlobalScope.launch { 
        	delay(3000L)
	    	println(&quot;world!&quot;)
        }
    	println(&quot;hello,&quot;) 
    }
}

/* 실행결과 */
// hello, 
// world!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 바로 앱에서 실행했기 때문이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 코틀린 코드에서 main() 함수를 run 하게 되면 main() 가 끝나면 실행 자체가 종료된다. 하지만 앱에서는 main()도 그냥 일종의 하나의 함수일뿐&amp;hellip;!! 이 함수가 종료된다고 앱이 종료되는 것은 아니다!!! 그렇기 때문에 GlobalScope 이 여전히 살아있을 수 있는 것이고 &amp;ldquo;World!&amp;rdquo; 까지 출력되는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1721240623183&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Acticvity onCreate() 에서 실행
class ToolBar : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
		
        var count = 0
        binding.btn1.setOnClickListener {
            count++
            binding.btn1.text = &quot;${count}&quot;
        }

        binding.blockBtn.setOnClickListener {
        	main()
                Log.d(&quot;CorutineTest&quot;, &quot;main 끝&quot;)
        }
     }
	
     fun main()= runBlocking{
        delay(10000000L)
       	Log.d(&quot;CorutineTest&quot;, &quot;hello,&quot;)
     }
}

/* 실행결과 */
// ANR 에러 발생&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;Dispatcher를 사용해 코루틴을 생성하는 방법에도 크게 2가지가 있다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. CoroutineScope(&lt;b&gt;Dispatchers&lt;/b&gt;.IO)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721241082319&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Acticvity onCreate() 에서 실행
class ToolBar : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
		
        var count = 0
        binding.btn1.setOnClickListener {
            count++
            binding.btn1.text = &quot;${count}&quot;
        }

        binding.blockBtn.setOnClickListener {
            main()
            Log.d(&quot;CorutineTest&quot;, &quot;main 끝&quot;)
        }
     }
}

fun main() = runBlocking&amp;lt;Unit&amp;gt;{  // main 스레드
     CoroutineScope(Dispatchers.IO).launch { // IO 스레드에서 실행되므로 main과 별개
            delay(3000L)
            println(&quot;world!&quot;)
            Log.d(&quot;CorutineTest&quot;, &quot;world!&quot;)
    }
   
    Log.d(&quot;CorutineTest&quot;, &quot;hello,&quot;)

}

/* 실행결과 */
// hello, 
// main 끝
// world!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;b&gt;CoroutineScope(Dispatchers.IO)&lt;/b&gt; 하게 되면 아예 다른 IO Scope에서 코루틴이 생성되면서 main() 함수는 먼저 종료된다. 메인스레드를 runBlocking 했던 것도 끝나기 때문에 UI와 상호작용할 수 있다. 그리고 아예 다른 Scope에 생성 되었던 delay(3000L) 가 지난 후 world! 가 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;서로 다른 범위, 다른 생명주기를 갖기 때문에 구조적 동시성을 제공하지도 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;2. &lt;span data-token-index=&quot;0&quot;&gt;launch(Dispatchers.IO)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721241215799&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Acticvity onCreate() 에서 실행
class ToolBar : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
		
        var count = 0
        binding.btn1.setOnClickListener {
            count++
            binding.btn1.text = &quot;${count}&quot;
        }

        binding.blockBtn.setOnClickListener {
            main()
            Log.d(&quot;CorutineTest&quot;, &quot;main 끝&quot;)
        }
     }
}


fun main() = runBlocking&amp;lt;Unit&amp;gt;{  // main 스레드
	  launch(Dispatchers.IO) { // 다른 스레드에서 실행되지만 main 막힘
        delay(3000L)
        Log.d(&quot;CorutineTest&quot;, &quot;world!&quot;)
    }
    Log.d(&quot;CorutineTest&quot;, &quot;hello,&quot;)

}

/* 실행결과 */
// hello, 
// world!
// main 끝&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;반면에 runBlocking의 내부에서 아예 새로운 Scope를 만드는 것이 아닌 바로 &lt;b&gt;launch(Dispatchers.IO)&lt;/b&gt; 를 하게 될 경우, 메인스레드에서 수행되는 &lt;b&gt;runBlocking 블록의 Scope 생명주기를 따르게 된다.&lt;/b&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;물론 디스패처를 IO로 설정했기 때문에, IO 스레드에서 코루틴이 생성되어 수행된다. 하지만 생명주기 자체는 즉, runBlocking을 따르기 때문에 IO 스레드에서 생성된 코루틴이 모두 수행되기 전까지 runBlocking 은 끝나지 않는다. (자신의 자식 코루틴을 관리하는 셈) 그래서 delay(3000L)동안 메인스레드를 고스란히 blocking 하면서 UI 입력도 차단되게 된다.&lt;/p&gt;</description>
      <category>Android/Coroutine</category>
      <category>Android</category>
      <category>coroutine</category>
      <category>안드로이드</category>
      <category>코루틴</category>
      <category>코틀린</category>
      <author>gangmini</author>
      <guid isPermaLink="true">https://studyroadmap-kkm.tistory.com/181</guid>
      <comments>https://studyroadmap-kkm.tistory.com/181#entry181comment</comments>
      <pubDate>Thu, 18 Jul 2024 03:37:22 +0900</pubDate>
    </item>
    <item>
      <title>[안드로이드/Coroutine] Coroutine Dispatcher</title>
      <link>https://studyroadmap-kkm.tistory.com/180</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Coroutine Dispatcher&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;처음엔 이 디스패쳐가 그냥 스레드 종류를 정해주는 역할을 한다고만 생각했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 그 이름의 뜻을 살펴보면 Dispatch = &amp;lsquo;보내다&amp;rsquo; 라는 뜻이다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;/b&gt;이 바로 &lt;b&gt;Coroutine Dispatcher 이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 제공하고 있는 디스패처는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;✔️ Dispatchers.Main&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;mian 스레드를 관리하는 디스패처이다 main 스레드는 한 개만 생성된다고 알고있다. 따라서 해당 스레드를 blocking 해서 사용하게 되면 ANR 에러가 발생할 수 있기 때문에 사용시 유의해야한다.&lt;/li&gt;
&lt;li&gt;coroutine-android 라이브러리를 추가해야 사용 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;b&gt;✔️ &lt;/b&gt;Dispatchers.IO&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;디스크 또는 네트워크 I/O 작업을 실행하는데 좋은 디스패처&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;b&gt;✔️ &lt;/b&gt;Dispatchers.Default&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;CPU를 많이 사용하는 작업&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;연산량이 많은 작업, 정렬 작업, JSON 파싱 작업에 좋다&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스패처가 스레드풀을 관리하고 그 스레드풀 안에서 1개 이상의 스레드가 생성된다고 했는데 멀티/싱글 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 여러개의 스레드를 관리하는 멀티스레드풀&lt;/p&gt;
&lt;pre id=&quot;code_1721239723669&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val dispatcher = newFixedThreadPoolContext(3, &quot;ThreadPool&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;2. 스레드를 하나만 관리하는 단일스레드(풀)&lt;/p&gt;
&lt;pre id=&quot;code_1721239732504&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val dispatcher = newSingleThreadContext(&quot;SingleThread&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android/Coroutine</category>
      <category>Android</category>
      <category>coroutine</category>
      <category>dispatchers</category>
      <category>Kotlin</category>
      <category>안드로이드</category>
      <category>코루틴</category>
      <category>코틀린</category>
      <author>gangmini</author>
      <guid isPermaLink="true">https://studyroadmap-kkm.tistory.com/180</guid>
      <comments>https://studyroadmap-kkm.tistory.com/180#entry180comment</comments>
      <pubDate>Thu, 18 Jul 2024 03:16:55 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin 문법] apply / run / with / let / also  확장함수</title>
      <link>https://studyroadmap-kkm.tistory.com/179</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&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;상속없이 클래스를 확장하는 것&lt;/li&gt;
&lt;li&gt;&lt;b&gt;객체.확장함수&lt;/b&gt; 형태&amp;nbsp;&lt;/li&gt;
&lt;li&gt;해당 클래스/객체에서 사용할 함수를 만들 수 있음 -&amp;gt; 실제 클래스에 이런 함수가 추가되는 것 X&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719808842850&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person {
    val age = 29
    val name = &quot;똘이&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 위와 같은 형태의 클래스는 age, name 두개의 프로퍼티로만 이루어졌고, 따로 함수는 가지고 있지 않다.&lt;/p&gt;
&lt;pre id=&quot;code_1719809238826&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 내가 만든 확장함수
fun Person.inCrease() {
    println(&quot;$age&quot;)
}

fun main() {

    val person = Person()
    person.inCrease() 
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이렇게 내가 Person 이라는 클래스에 대해 inCrease() 라는 &lt;b&gt;확장함수&lt;/b&gt; 를 만들어 마치 &lt;b&gt;실제 Person 클래스의 함수처럼 사용 가능&lt;/b&gt;하다. 하지만 &lt;b&gt;실제로 Person 클래스에 이 함수가 생긴 것은 아니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1719809267008&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {

    val person = Person()
 
    // run 확장함수
    person.run {
    	age = 29
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 학습하게 될 &lt;b&gt;run / let / apply / also / with &lt;/b&gt;확장함수들은 이미 kotlin 에서 지원하고 있는 확장함수이고, 각자 고유한 특징을 가지고 있기 때문에 여기에 맞게 사용할 수 있는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서는 실제 Person 클래스에 run() 이라는 함수는 없지만 kotlin 에 의해 자동으로 run 이라는 확장함수를 사용할 수 있게 된 것! (확장함수로 선언된 부분은 없지만 내부적으로 사용할 수 있게끔 처리되는 것 같다...!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;run / let / apply / also / with&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 함수들의 차이를 이해하기 위해서는 아래 2가지에 집중해보았다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수신객체 &amp;rarr; 어떤 객체를 받는가?&lt;/li&gt;
&lt;li&gt;반환값 &amp;rarr; 무엇을 반환하는가? (받았던 객체를 그대로 반환하는가? 아님 다릉 return 값이 있는가?)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply run / with let also&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;람다식 내부에서 하는 일&lt;/td&gt;
&lt;td&gt;자신의 프로퍼티 셋팅&lt;/td&gt;
&lt;td&gt;수신객체에 대해 특정한 동작&lt;/td&gt;
&lt;td&gt;수신객체에 대해 특정한 동작&lt;/td&gt;
&lt;td&gt;자신의 프로퍼티 셋팅, 수신객체에 대해 특정한 동작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;수신객체 접근하는 법&lt;/td&gt;
&lt;td&gt;this&lt;/td&gt;
&lt;td&gt;this&lt;/td&gt;
&lt;td&gt;it&lt;/td&gt;
&lt;td&gt;it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;return 값&lt;/td&gt;
&lt;td&gt;자기자신&lt;/td&gt;
&lt;td&gt;Block의 마지막&lt;/td&gt;
&lt;td&gt;Block의 마지막&lt;/td&gt;
&lt;td&gt;자기자신&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;** this : 수신객체를 람다의 수신객체로 전달하기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;** it : 수신객체를 람다의 파라미터로 전달하기 때문&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;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// 예시 클래스 1
data class Person(
    var name: String = &quot;&quot;,
    var age: Int = 0,
    var temperature: Float = 36.5f
)

// 예시 클래스 2
data class Person(
    var name: String = &quot;&quot;,
    var age: Int = 0,
    var temperature: Float = 36.5f
) {
    fun isSick(): Boolean = temperature &amp;gt; 37.5f
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;apply&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0BDeZ/btsIhNjZnyZ/k3coYTIPVMKkVPa3DbhpKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0BDeZ/btsIhNjZnyZ/k3coYTIPVMKkVPa3DbhpKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0BDeZ/btsIhNjZnyZ/k3coYTIPVMKkVPa3DbhpKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0BDeZ%2FbtsIhNjZnyZ%2Fk3coYTIPVMKkVPa3DbhpKK%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;684&quot; height=&quot;202&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;202&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;&amp;lsquo;수신&amp;rsquo; 이라는 것을 쉽게 말해서 &amp;lsquo;받는다&amp;rsquo; 라는 뜻이다. 즉, T 타입의 객체를 apply 함수가 받고, 다시 이 T 타입의 객체를 람다 함수가 받는다는 의미이다.&lt;/li&gt;
&lt;li&gt;같은 객체를 받기 때문에 람다에 수신 객체는 굳이 명시해주지 않아도 된다. (하고 싶다면 this &amp;rarr; 이렇게)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;val person = Person().apply {  // this -&amp;gt; 
    name = &quot;DevCho&quot; // this.name = &quot;DevCho&quot;
    age = 29
    temperature = 36.2f
}
&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;내부 프로퍼티를 변경할 때 사용한다. 그리고 반환 타입을 보면 T 즉, 받았던 객체를 다시 반환한다. Person() 객체를 받아서 인자들을 변경시키고 다시 Person() 객체를 반환해주기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;run&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/29xea/btsIiRFOBBM/ODX4iF0vkwmMgqxRxe8T11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/29xea/btsIiRFOBBM/ODX4iF0vkwmMgqxRxe8T11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/29xea/btsIiRFOBBM/ODX4iF0vkwmMgqxRxe8T11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F29xea%2FbtsIiRFOBBM%2FODX4iF0vkwmMgqxRxe8T11%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;674&quot; height=&quot;262&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;262&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;run이 T 타입의 객체를 받고, 람다식도 T타입의 객체를 받는다. 동일한 객체를 받기 때문에 역시나 람다식에는 수신객체를 명시할 필요는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;val person = Person(name = &quot;Devcho&quot;, age = 29, temperature = 36.5f)

val isPersonSick = person.run { // this -&amp;gt; 
       temperature = 37.2f
       
       temperature // return값 (return은 생략)
  }
&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;이번에는 반환값이 수신객체와 동일한 T 가 아니라 R 이다. 즉, 다른 값을 반환 한다는 의미이고, run은 Block 의 가장 마지막 줄을 반환한다.&lt;/li&gt;
&lt;li&gt;자동으로 자기자신(수신객체)가 반환되던 apply와는 달리 run 은 무엇을 반환할지 개발자가 지정해줘야 한다. 만약 자기 자신을 반환하고 싶다면 this 를 반환하면 된다&lt;/li&gt;
&lt;li&gt;return 은 생략한다. 어차피 block 의 맨마지막 줄을 명시적으로 반환하기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;with&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF57tB/btsIhxO3mjz/jt6EOrWGEeSLrCYZcBbAvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF57tB/btsIhxO3mjz/jt6EOrWGEeSLrCYZcBbAvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF57tB/btsIhxO3mjz/jt6EOrWGEeSLrCYZcBbAvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF57tB%2FbtsIhxO3mjz%2Fjt6EOrWGEeSLrCYZcBbAvK%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;679&quot; height=&quot;281&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt; val person = Person(name = &quot;Devcho&quot;, age = 29, temperature = 36.5f)
    val isPersonSick = with(person) {
        temperature = 38.0f
        temperature // return 값
    }
&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;run 과 완전히 동일하게 동작한다고 한다. 차이점은 run은 객체.run() 이런식으로 확장함수 로 사용하지만 with는 수신객체를 파라미터 사용해 with(객체) 이런식으로 사용한다. 실제로 run 이 더 깔끔하게 사용하기 좋기 때문에 with는 잘 안 쓴다고 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 슬슬 수신객체가 무엇인지, 반환값이 어떻게 다른지 감이 잡힌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속해서 let 과 also 를 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;let&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLRGDn/btsIhfais7B/QB2jHy8ypqMPjx2FYPbQdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLRGDn/btsIhfais7B/QB2jHy8ypqMPjx2FYPbQdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLRGDn/btsIhfais7B/QB2jHy8ypqMPjx2FYPbQdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLRGDn%2FbtsIhfais7B%2FQB2jHy8ypqMPjx2FYPbQdk%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;703&quot; height=&quot;77&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt; val person = Person(name = &quot;Devcho&quot;, age = 29, temperature = 36.5f)
 
 val isPersonSick = person.let { it -&amp;gt; 
        it.temperature = 38.0f
        temperature // return 값
    }
&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;수신객체를 받아 람다식에서 사용하고, 반환값으로는 Block의 마지막줄을 반환하게 된다. 즉, run이나 with와 거의 유사하게 사용된다. 수신객체를 람다식에서 접근할 때, this가 아닌 it을 사용한다는 차이가 있다&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;하지만 실제로 개발할 때 let은 조금 다르게 사용되곤 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null 체크를 해서 null 이 아닐 경우에 let block을 실행하는 것이다.&lt;/li&gt;
&lt;li&gt;null 체크에 사용되는 ? 연산자를 함께 사용해주면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt; val person = Person(name = &quot;Devcho&quot;, age = 29, temperature = 36.5f)
 
 val isPersonSick = person?.let { it -&amp;gt; // person이 null 이 아닌 경우 실행
        it.temperature = 38.0f
        
        isSick() // return 값
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 null 인 경우에 실행을 하고 싶다며 어떻게 사용할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 run 을 block을 실행하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt; val person = Person(name = &quot;Devcho&quot;, age = 29, temperature = 36.5f)
 
 
 val isPersonSick = person?.let { // person이 null 이 아닌 경우 실행
        it.temperature = 38.0f
        
        isSick() // return 값
        
    } ?: run {
		    // person이 null인 경우 실행
    
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도 자연스럽게 이런 방식을 사용하게 되었는데, 실무에서도 let 과 run 을 사용해 nullable 객체에 대해 서로 다른 실행문을 동작시키는 방식을 운영한다고 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;also (상당히 짜즈ㅇ....)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nKu7h/btsIhWgIpMt/mJa5MZEyZQwoiRJBlWTrD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nKu7h/btsIhWgIpMt/mJa5MZEyZQwoiRJBlWTrD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nKu7h/btsIhWgIpMt/mJa5MZEyZQwoiRJBlWTrD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnKu7h%2FbtsIhWgIpMt%2FmJa5MZEyZQwoiRJBlWTrD1%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;703&quot; height=&quot;193&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;193&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;apply처럼 수신객체를 받아 프로퍼티 셋팅을 해주고 추가적으로 객체 자신에 대한 작업을 수행할 수도 있다. 그 다음 수신객체 자신을 반환한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;also 의 반환 값은 자기자신이다.&lt;/li&gt;
&lt;li&gt;수신객체의 프로퍼티를 변경하게 되면 return 값도 프로퍼티가 변경된 수신객체가 반환&lt;/li&gt;
&lt;li&gt;객체를 자체를 다시 생성해 할당하는 경우에는 반환값에 영향 X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;람다식에서 수신객체를 사용하기 위해서는 it을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;var number = 3;

fun getAndIncreaseNumber1() = number.also {
    number++
}

var person = Person(&quot;Devcho&quot;, 29, 36.2f);

fun getAndIncreaseNumber2() = person.also { 
    // 수신객체 프로퍼티 직접 변경(반환할 수신객체에도 영향을 미침
    person.age = it.age + 1
}

fun getAndIncreaseNumber3() = person.also {
    // 새로운 객체를 생성 -&amp;gt; 반환할 수신객체는 원래의 person
    // 이후에 새로운 Person 객체가 person에 셋팅
    person = person.copy(age = it.age + 1)
}

fun main() {
    println(&quot;first number ${getAndIncreaseNumber1()}&quot;)  // 3
    println(&quot;second number ${getAndIncreaseNumber1()}&quot;) // 4
    
    println(&quot;first number ${getAndIncreaseNumber2()}&quot;)  // 30
    println(&quot;second number ${getAndIncreaseNumber2()}&quot;) // 31
    
    println(&quot;first number ${getAndIncreaseNumber3()}&quot;)  // 29
    println(&quot;second number ${getAndIncreaseNumber3()}&quot;) // 30
}

&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;[최고의 출처]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://kotlinworld.com/255#범위&quot;&gt;https://kotlinworld.com/255#범위&lt;/a&gt; 지정 함수(Scope function)란%3F-1&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;</description>
      <category>Kotlin</category>
      <category>Android</category>
      <category>Kotlin</category>
      <category>안드로이드</category>
      <category>코틀린</category>
      <category>프로그래밍언어</category>
      <category>확장함수</category>
      <author>gangmini</author>
      <guid isPermaLink="true">https://studyroadmap-kkm.tistory.com/179</guid>
      <comments>https://studyroadmap-kkm.tistory.com/179#entry179comment</comments>
      <pubDate>Mon, 1 Jul 2024 02:39:00 +0900</pubDate>
    </item>
    <item>
      <title>[안드로이드/Coroutine] Coroutine(코루틴)이란?</title>
      <link>https://studyroadmap-kkm.tistory.com/178</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  코루틴(Coroutine)이란?&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;프로세스 = 실행중인 프로그램, 실행 코드가 메모리에 올라가면 프로세스가 시작된다
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;여러개의 프로그램을 사용중이라면 프로세스도 여러개&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스레드 = 하나의 프로세스 안에 존재하는 여러개의 실행 단위이다
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;스레드가 꼴랑 하나밖에 없단면 프로그램은 너무 힘들 것이다. 그래서 CPU가 여러개인 것도 이런 부분을 커버해주기 위해서 여러개의 스레드를 만들어 여러 수행을 동시에 진행하는 것을 도와주는 것이다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;b&gt;메인 스레드&lt;/b&gt; 로 UI 를 그려주고 사용자가 화면과 상호작용할 수 있도록 해주는 스레드이다. 그런데 이 메인 스레드가 너무 바쁘면 메인 스레드가 블로킹 되고 상호작용을 못하게 되면서 앱이 강제 종료되는 ANR 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 무거운 작업은 다른 스레드를 만들어 수행해줘야 한다. 이렇게 여러 스레드를 만들어 관리해주기 위해 다양한 방법이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thread 를 만들고 관리하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Thread 클래스를 상속받아 사용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드, 핸들러 개념까지 엮어서 가장 기초적으로 배우는 방법인 것 같다.&lt;/li&gt;
&lt;li&gt;Thread() 를 상속받아 클래스를 만들고, run() 메서드를 오버라이딩 해 그 안에 수행 할 내용을 적어주면 된다.&lt;/li&gt;
&lt;li&gt;이렇게 생성된 스레드가 많은 메모리를 차지하면서 재사용이 어려운 단점이 있다. 또한 스레드를 개발자가 직접 관리해야 하기 때문에 메모리 누수의 가능성이 올라간다. (관리라고 하는 것은 스레드를 생성, 삭제, 재사용 이런것들을 말하는 것이다)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1721239186203&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val exampleThread = ExampleThread()

    exampleThread.start()
}

class ExampleThread : Thread() {
  override fun run() {
    println(&quot;[${Thread.currentThread().name}] New Thread Running&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;2. Executor 프레임워크를 사용하는 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자가 스레드를 관리해야 한다는 책임과 부담을 낮춰주고, 재사용성을 높여준다.&lt;/li&gt;
&lt;li&gt;Executor 프레임워크가 스레드의 집합체인 &amp;lsquo;스레드 풀&amp;rsquo; 을 만들고 작업을 제출하면 스레드 풀의 스레드 중 하나에 작업을 할당한다고 한다. 즉, 알아서 스레드 풀에 여유있는 스레드에 작업을 할당하고, 스레드를 재사용하나 보다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1721239273037&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
  // ExecutorService 생성
  val executorService: ExecutorService = Executors.newFixedThreadPool(4)

  // 작업 제출
  executorService.submit {
    println(&quot;[${Thread.currentThread().name}] 새로운 작업1 시작&quot;)
  }

  // 작업 제출
  executorService.submit {
    println(&quot;[${Thread.currentThread().name}] 새로운 작업2 시작&quot;)
  }

  // ExecutorService 종료
  executorService.shutdown()
}

/*
출력
[pool-1-thread-1] 새로운 작업1 시작
[pool-1-thread-2] 새로운 작업2 시작
*/&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;3. Rx 라이브러리를 사용하는 방식&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;솔직히 Rx, RxJava 이런것들을 몰라서 잘 모르겠다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3가지 방법 모두 메인 스레드 외의 다른 스레드들을 만들어 작업을 수행하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;또한 A 스레드에서 작업 수행 중 B 스레드의 수행 결과를 기다려야 하는 경우, 기다리는 동안 A스레드는 꼼짝없이 blocking 되어있는 상황에 처한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;863&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqYmbX/btsIEKlzT1L/bdjshHXsRIpTAJpJv7Dw7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqYmbX/btsIEKlzT1L/bdjshHXsRIpTAJpJv7Dw7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqYmbX/btsIEKlzT1L/bdjshHXsRIpTAJpJv7Dw7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqYmbX%2FbtsIEKlzT1L%2FbdjshHXsRIpTAJpJv7Dw7k%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;442&quot; height=&quot;584&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;863&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서 나온 기술이 코루틴 이다!!&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴은 매번 새로운 스레드를 만드는 것이 아니라 스레드 안에 여러개의 작업 단위(=코루틴)를 만들어 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래 그림처럼 작업1을 수행하는 코루틴1이 스레드1에서 실행되고, 중간에 작업2의 결과를 기다리는 동안 또다른 작업단위인 코루틴3이 스레드1에서 실행될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 코루틴1은 작업2를 기다리는 동안 자신의 작업을 일시중단 하고 코루틴3에게 스레드를 양보한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q3jQY/btsIEbqew5A/wKnwGtUKk8i7W5gPl90Zk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q3jQY/btsIEbqew5A/wKnwGtUKk8i7W5gPl90Zk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q3jQY/btsIEbqew5A/wKnwGtUKk8i7W5gPl90Zk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ3jQY%2FbtsIEbqew5A%2FwKnwGtUKk8i7W5gPl90Zk0%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;627&quot; height=&quot;277&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;277&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXcwXM/btsFvX9n6cr/Zg7q0mTwZKvBNF2uUjEwBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXcwXM/btsFvX9n6cr/Zg7q0mTwZKvBNF2uUjEwBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXcwXM/btsFvX9n6cr/Zg7q0mTwZKvBNF2uUjEwBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXcwXM%2FbtsFvX9n6cr%2FZg7q0mTwZKvBNF2uUjEwBk%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;1128&quot; height=&quot;348&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;348&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;&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;lsquo;조세영의 Kotlin World&amp;rsquo;를 통해 공부하면서 새롭게 알게 된 사실이 있다&amp;hellip;!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;난 코루틴을 사용하기 위해 항상 라이브러리를 추가해서 사용해왔다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 코루틴은 코틀린 언어에 기본적으로 내장되어 있는 기능이다. 따라서 별도의 라이브러리 설정 없이도 사용 가능하지만 그것은 저수준 API 라는 것이다. 그리고 UI 가 있는 앱의 경우 Main 스레드가 중요하다고 했는데 이 Main 스레드를 코루틴에서 사용하기 위해 필요한 라이브러리도 따로 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1721239452565&quot; class=&quot;clean&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// 고수준 코루틴을 사용하기 위한 라이브러리
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'

// 코루틴에서 Main 스레드를 사용하기 위한 라이브러리
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Android/Coroutine</category>
      <category>Android</category>
      <category>coroutine</category>
      <category>Kotlin</category>
      <category>비동기처리</category>
      <category>스레드</category>
      <category>안드로이드</category>
      <category>앱개발</category>
      <category>코루틴</category>
      <category>코틀린</category>
      <author>gangmini</author>
      <guid isPermaLink="true">https://studyroadmap-kkm.tistory.com/178</guid>
      <comments>https://studyroadmap-kkm.tistory.com/178#entry178comment</comments>
      <pubDate>Sun, 3 Mar 2024 03:06:32 +0900</pubDate>
    </item>
    <item>
      <title>[안드로이드/BottomSheet] Persistent Bottom Sheet 외부 화면 클릭 기능 구현</title>
      <link>https://studyroadmap-kkm.tistory.com/177</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;1164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XaiAv/btsFm33DiRG/9CbHKo1odbp5KJLzmMdTFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XaiAv/btsFm33DiRG/9CbHKo1odbp5KJLzmMdTFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XaiAv/btsFm33DiRG/9CbHKo1odbp5KJLzmMdTFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXaiAv%2FbtsFm33DiRG%2F9CbHKo1odbp5KJLzmMdTFk%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;686&quot; height=&quot;686&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;1164&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진과 같은 화면을 개발하기 위해 Persistent Bottom Sheet 를 개발하게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 액션에 따라 바텀시트를 펼치고 닫고 하는 동작이 중요한데 약간 까다로운? 다소 복잡한 요구사항을 만족시켜야 해서 구현하는데에 고민을 좀 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;✔️ 기능 요구사항&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;바텀시트가 열린 상태에서 외부를 클릭하면 바텀시트가 Hidden&lt;/li&gt;
&lt;li&gt;바텀시트가 열린 상태에서 외부를 드래그하면 바텀시트는 유지되고 드래그만 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바텀 시트 외부를 &lt;u&gt;클릭하게 되면 바텀시트 자체가 숨겨져&lt;/u&gt;야 하는데 &lt;u&gt;단순 클릭이 아닌 외부 화면을 드래그 하는 경우에는 바텀시트는 가만히&lt;/u&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;size18&quot;&gt;✔️ 문제상황&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바텀시트 외부 즉, 네이버 지도가 표시될 map_fragment를 사용자가 단순 터치를 했는지, 드래그를 했는지 구분해야 함&lt;/li&gt;
&lt;li&gt;&amp;nbsp;뷰바인딩을 통해 map_fragment에 터치 리스너 달아주었지만 전혀 감지/ 동작 하지 않음 =&amp;gt; 어떤&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: left;&quot;&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;span style=&quot;color: #333333; text-align: left;&quot;&gt;첫번째, 사용자가 외부화면을 단순히 '클릭' 한것인지 혹은 '드래그' 한 것인지를 구분해야 한다. 어렵긴 하지만 사용자 이벤트에 따라 클릭인지 드래그인지를 검사하는 로직에 대해 찾아보면 될 것 같았다.&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; text-align: left;&quot;&gt;두번째, 첫번째 문제를 해결하기 위해서는 일단 사용자의 이벤트를 감지하는 것이 중요하다. 그래야 이게 클릭인지 드래그인지 구분을 하지..그래서 일반적으로 사용할 법한 터치 이벤트를 감지하는 터치리스너를 맵 화면에 달아주고 OnTouch 메서드를 오버라이딩 하며 MotionEvent를 받아오려고 했으나 전혀 해당 함수가 호출되지 않는 문제가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709052421266&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;binding.mapFragment.setOnTouchListener(object : View.OnTouchListener {
            override fun onTouch(p0: View?, p1: MotionEvent?): Boolean {
                Log.d(&quot;mapFragment&quot;,&quot;onTouch&quot;)
                return true
            }

  })&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1709052442075&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;FramLayout
            android:id=&quot;@+id/map_fragment&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;match_parent&quot;
            android:clickable= &quot;true&quot;
            android:focusable= &quot;true&quot;
            android:onClick=&quot;@{() -&amp;gt; viewmodel.onClickContainer()}&quot;&amp;gt;
    &amp;lt;/FramLayout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(데이터바인딩 방식을 사용해 map 화면에 직접 클릭 메서드를 작성해주었는데 이또한 아무 반응이 없었다....ㅜ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;  원인/해결 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해서는 Touch Event의 내부적인 호출 경로를 알고 있어야 했다.&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;gt; 전달 -&amp;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;만약 자식뷰로 이벤트를 전달하지 않고 해당 뷰에 대해 터치이벤트를 처리해주고 싶다면 onInterceptTouchEvent() 메서드를 오버라이딩하여 true를 반환하게 만들도 onTouchEvent() 메서드 안에 원하는 동작을 처리해주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(* Fragment에서는 dispatchTouchEvent() 나 onInterceptTouchEvent() 같은 메서드를 오버라이딩 할 수가 없다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wpPXy/btsFmfiVK5Y/pZRK5fFTv4NoCp1gHlo9n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wpPXy/btsFmfiVK5Y/pZRK5fFTv4NoCp1gHlo9n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wpPXy/btsFmfiVK5Y/pZRK5fFTv4NoCp1gHlo9n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwpPXy%2FbtsFmfiVK5Y%2FpZRK5fFTv4NoCp1gHlo9n1%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;404&quot; height=&quot;537&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;1004&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Home Activity &amp;rarr; Map Fragment &amp;rarr; FramLayout(뷰그룹)(id : map_fragment) &amp;rarr; 네이버맵 객체(?)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;FramLayout 위에 누군가 있다?&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;위에서&lt;span&gt; xml 코드를 보면&amp;nbsp;&lt;/span&gt;&lt;/span&gt;FramLayout 은 자식뷰를 가지고 있지 않다. 그래서 나는 FramLayout에 터치리스너를 달아 이벤트를 받으려고 했지만 FramLayout은 맵을 표시하게 될 도화지이고, 실제 그 위에 올라가는 맵 객체가 존재하게 되면서 최종적으로 맵 객체로 이벤트가 들어오게 되는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(맵 객체에 터치 리스너를 달고 이벤트 전달 로그를 찍어본 결과 -&amp;gt; 최종적으로 맵 객체로 이벤트가 전달)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CgXdB/btsFqNfzcY1/E8PmhdkcBa0FWzZ68CKLt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CgXdB/btsFqNfzcY1/E8PmhdkcBa0FWzZ68CKLt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CgXdB/btsFqNfzcY1/E8PmhdkcBa0FWzZ68CKLt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCgXdB%2FbtsFqNfzcY1%2FE8PmhdkcBa0FWzZ68CKLt1%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;884&quot; height=&quot;114&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;114&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;그렇다면 무조건 FramLayout의 onInterceptTouchEvent() 가 true를 반환하게 해서 이벤트를 처리하는게 답일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NO!! 좀 더 복잡한 고민을 해줘야 한다....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무조건 true를 반환하게 되면 맵을 드래그해서 탐색하거나 마커를 클릭해서 바텀시트를 올라오게 하거나, 혹은 바텀시트의 내용만 바꿔주는 작업을 할 수 없게 된다. 이런 작업들은 맵 객체까지 이벤트가 전달되어야 동작할 수 있는데 FramLayout에서 가로채버리기 때문이다 ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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. 먼저 FramLayout의 onInterceptTouchEvnet() 코드에서 1차적으로 이 이벤트가 어떤 동작에 속하는지 체크해줘야 한다. 하지만 FramLayout의 경우에는 안드로이드에서 기본적으로 제공하고 있는 레이아웃 클래스이기 때문에 onInterceptTouchEvnet() 같은 내부 코드를 바꿀기는 어렵다. 따라서 FramLayout 를 implements 해서 Custom FramLayout을 만들고 여기서 onInterceptTouchEvent() 메서드를 오버라이딩 하면 된다.&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;만약 바텀시트가 up되어 있는 상태에서 터치 이벤트가 들어오게 된다면 일단 &lt;b&gt;viewmodel._isShowBottomSheetTab.value = false&lt;/b&gt; 로 바꿔준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트는 맵객체로 전달 되도록 &lt;b&gt;false를 반환&lt;/b&gt;한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709397705456&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CustomFragmentContainerView : FrameLayout {

    private lateinit var bottomSheetBehavior: BottomSheetBehavior&amp;lt;*&amp;gt;
    private lateinit var viewmodel: MapViewModel

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    private val TOUCH_THRESHOLD = 10
    private var startX = 0f
    private var startY = 0f

    private var preSelectedMarker : MarkerOfMap? = null

    fun setViewModel(viewmodel: MapViewModel) {
        this.viewmodel = viewmodel // 바텀시트 조작해줘야 하므로 바텀시트 BottomSheetBehavior를 이 커스텀 레아아웃 클래스에 셋팅해놔야 함
    }

    fun setBottomSheetBehavior(behavior: BottomSheetBehavior&amp;lt;*&amp;gt;) {
        this.bottomSheetBehavior = behavior // 바텀시트 조작해줘야 하므로 바텀시트 BottomSheetBehavior를 이 커스텀 레아아웃 클래스에 셋팅해놔야 함
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        // 터치 이벤트를 소비하여 ViewPager2로 전달하지 않음
        // 외부 영역을 클릭한 경우 바텀시트를 숨김

        when (ev!!.getAction()) {
            MotionEvent.ACTION_DOWN -&amp;gt; {
                // 화면에 손가락이 닿을 때 호출
                startX = ev!!.x
                startY = ev.y
            }
            MotionEvent.ACTION_MOVE -&amp;gt; {
                // 손가락이 화면에 닿은채로 움직일때 호출 (단순 터치를 할때도 호출 되기 때문에 이 이벤트로 뭔가를 판별하긴 어려움)
            }

            MotionEvent.ACTION_UP -&amp;gt; {
                // 화면에서 손가락이 떨어졌을 때 호출

                val endX = ev!!.getX()
                val endY = ev.getY()

                val distance = Math.sqrt(
                    ((endX - startX) * (endX - startX) + (endY - startY) * (endY - startY)).toDouble()
                ).toFloat()

                // 손가락이 화면이 닿았을 때 위치와 떨어질때 위치를 비교해 일정 이상 차이나면 단순 터치가 아닌 드래그로 간주
                if (distance &amp;lt; TOUCH_THRESHOLD) {
                    if (bottomSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN) { // 바텀시트가 up 되어 있다면
                        // 바텀시트를 숨기기 위한 플래그 설정
                        viewmodel._isShowBottomSheetTab.value = false
                        performClick()



                    }
                }
        }
        /* 만약 맵 탐색하는 드래그 동작이거나 바텀시트가 up 되어 있지 않는 상태에서 마커를 클릭한 경우에는
         * false를 반환하여 동작이 네이버맵으로 흘러가도록 해야 함
         */
    }
        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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 빈화면 터치가 아닌 다른 마커를 터치한 경우&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 viewmodel._isShowBottomSheetTab.value = false 로 바꿔주었지만 바텀시트를 숨기기 위해 빈 화면을 클릭한 것이 아닌 다른 마커를 선택하기 위한 터치일 수 있다. 이 경우 바텀시트는 그대로 두고 바텀시트의 내용만 바꿔줘야 하기 때문에 선택한 마커 데이터에 변화가 생겼을 경우 다시 &lt;b&gt;viewmodel._isShowBottomSheetTab.value = true&lt;/b&gt; 로 바꿔준다.&lt;/p&gt;
&lt;pre id=&quot;code_1709398772248&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  viewmodel.selectedMarker.observe(this, androidx.lifecycle.Observer {
            // 검색바 텍스트 설정
            if(it == null){
                binding.searchbarTextView.text = &quot;어디로 이동할까요?&quot;
                binding.searchbarTextView.setTextColor(ContextCompat.getColor(requireContext(), R.color.Gray3))
            }
            else {
                Log.d(&quot;selectedMarker&quot;, &quot;마커가 선택되었습니다&quot;)
                viewmodel.getRestaurantSummary()
                // 바텀시트 UP
                viewmodel._isShowBottomSheetTab.value = true
                persistenetBottomSheet.state = STATE_COLLAPSED
                viewmodel._BottomSheetStep1.value = true
                viewmodel._BottomSheetState.value = 1

            }
        })&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JNuGE/btsFobg0pu3/UzI4fBlcIUkSCPx98XRMwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JNuGE/btsFobg0pu3/UzI4fBlcIUkSCPx98XRMwk/img.png&quot; data-alt=&quot;바텀시트 up 상태에서 다른 마커를 선택한 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JNuGE/btsFobg0pu3/UzI4fBlcIUkSCPx98XRMwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJNuGE%2FbtsFobg0pu3%2FUzI4fBlcIUkSCPx98XRMwk%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;868&quot; height=&quot;158&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;바텀시트 up 상태에서 다른 마커를 선택한 경우&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;3. 빈화면을 터치해 바텀시트를 숨겨야 하는 경우&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 이벤트를 받게 될 맵객체에 클릭 리스너를 달아 최종적으로 &lt;b&gt;viewmodel._isShowBottomSheetTab.value &lt;/b&gt;결과에 따라바텀시트를 숨겨준다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709397935815&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; override fun onMapReady(naverMap: NaverMap) {

        naver_map = naverMap

        naver_map!!.setOnMapClickListener { pointF, latLng -&amp;gt;
            Log.d(&quot;NaverMap&quot;,&quot;NaverMap으로 터치 이벤트 전달&quot;)
            if(viewmodel.isShowBottomSheetTab.value == true) { //다른 마커 클릭

            } else {
                val bottomSheet = binding.bottomSheetLayout
                val persistenetBottomSheet = BottomSheetBehavior.from(bottomSheet)
                val density = resources.displayMetrics.density
                persistenetBottomSheet.peekHeight = (150 * density).toInt()
                persistenetBottomSheet.state = BottomSheetBehavior.STATE_HIDDEN

                viewmodel._selectedMarker.value = null
                viewmodel._BottomSheetStep1.value = true
                viewmodel._BottomSheetState.value = 0

            }

        }
}&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;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/445020493&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/35GmB/hyVum6NAKb/SMisDXo94ycgoIkIPKRGo0/img.jpg?width=864&amp;amp;height=1920&amp;amp;face=0_0_864_1920,https://scrap.kakaocdn.net/dn/Dq8F8/hyVqheYUVz/YjtJhFXqfKYZ6WrZ1F6311/img.jpg?width=864&amp;amp;height=1920&amp;amp;face=0_0_864_1920&quot; data-video-width=&quot;860&quot; data-video-height=&quot;1911&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1911&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/445020493?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;1911&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;** 코드 업로드 요청 받아서 올림 **&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[fragment_map.xml]&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;layout
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;&amp;gt;
    &amp;lt;data&amp;gt;
        &amp;lt;variable
            name=&quot;viewmodel&quot;
            type=&quot;com.aviro.android.presentation.home.ui.map.MapViewModel&quot; /&amp;gt;
    &amp;lt;/data&amp;gt;

    &amp;lt;androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id=&quot;@+id/bottom_sheet_coordinatorLayout&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;&amp;gt;

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


    &amp;lt;LinearLayout
        android:id=&quot;@+id/searchBar&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:minHeight=&quot;54dp&quot;
        android:layout_marginStart=&quot;16dp&quot;
        android:layout_marginTop=&quot;50dp&quot;
        android:layout_marginEnd=&quot;16dp&quot;
        android:paddingStart=&quot;16dp&quot;
        android:paddingEnd=&quot;16dp&quot;
        android:paddingTop=&quot;15dp&quot;
        android:paddingBottom=&quot;15dp&quot;
        android:elevation=&quot;10dp&quot;
        android:orientation=&quot;horizontal&quot;
        android:background=&quot;@drawable/base_roundsquare_white_10&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot;
        app:layout_constraintBottom_toTopOf=&quot;@+id/filteringScrollView&quot;
        app:visibilityChanged=&quot;@{(viewmodel.bottomSheetState == 0 || viewmodel.bottomSheetState == 1)}&quot;&amp;gt;

        &amp;lt;LinearLayout
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:background=&quot;@drawable/ic_search&quot;
            android:layout_gravity=&quot;center_vertical&quot;
            android:elevation=&quot;10dp&quot;
            android:orientation=&quot;vertical&quot;&amp;gt;
        &amp;lt;/LinearLayout&amp;gt;
        &amp;lt;TextView
            android:id=&quot;@+id/searchbarTextView&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:minHeight=&quot;21dp&quot;
            android:layout_marginStart=&quot;7dp&quot;
            android:fontFamily=&quot;@font/pretendard_medium&quot;
            android:textSize=&quot;18dp&quot;/&amp;gt;
    &amp;lt;/LinearLayout&amp;gt;

    &amp;lt;!-- 화면 크기를 지정해주지 않으면 버벅거리는 문제 발생 --&amp;gt;
    &amp;lt;com.aviro.android.presentation.home.ui.map.CustomFragmentContainerView
        android:id=&quot;@+id/map_fragment&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;0dp&quot;
        android:focusable= &quot;true&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot;&amp;gt;
    &amp;lt;/com.aviro.android.presentation.home.ui.map.CustomFragmentContainerView&amp;gt;

    &amp;lt;!-- 필터링 --&amp;gt;
  &amp;lt;HorizontalScrollView
          android:id=&quot;@+id/filteringScrollView&quot;
          android:layout_width=&quot;match_parent&quot;
          android:layout_height=&quot;wrap_content&quot;
          android:layout_marginStart=&quot;16dp&quot;
          android:layout_marginTop=&quot;10dp&quot;
          android:layout_marginEnd=&quot;16dp&quot;
          android:scrollbars=&quot;none&quot;
          app:visibilityChanged=&quot;@{(viewmodel.bottomSheetState == 0 || viewmodel.bottomSheetState == 1)}&quot;
          app:layout_constraintEnd_toEndOf=&quot;parent&quot;
          app:layout_constraintStart_toStartOf=&quot;parent&quot;
          app:layout_constraintTop_toBottomOf=&quot;@+id/searchBar&quot;&amp;gt;

    &amp;lt;LinearLayout
        android:id=&quot;@+id/filteringContainer&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;&amp;gt;


        &amp;lt;Button
            android:id=&quot;@+id/filter_cancel_btn&quot;
            android:layout_width=&quot;36dp&quot;
            android:layout_height=&quot;36dp&quot;
            android:paddingBottom=&quot;15dp&quot;
            android:elevation=&quot;10dp&quot;
            android:background=&quot;@drawable/ic_filter_cancel&quot;
            app:visibilityChanged=&quot;@{viewmodel.categoryFilter[0] || viewmodel.categoryFilter[1] || viewmodel.categoryFilter[2] || viewmodel.categoryFilter[3]}&quot;/&amp;gt;


        &amp;lt;LinearLayout
            android:id=&quot;@+id/filter_dish&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginStart=&quot;6dp&quot;
            android:elevation=&quot;10dp&quot;
            android:orientation=&quot;horizontal&quot;
            android:padding=&quot;9dp&quot;
            app:bgCategoryFilter=&quot;@{viewmodel.categoryFilter[0]}&quot; /&amp;gt;

        &amp;lt;LinearLayout
            android:id=&quot;@+id/filter_cafe&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginStart=&quot;6dp&quot;
            android:duplicateParentState=&quot;true&quot;
            android:elevation=&quot;10dp&quot;
            android:orientation=&quot;horizontal&quot;
            android:padding=&quot;9dp&quot;
            app:bgCategoryFilter=&quot;@{viewmodel.categoryFilter[1]}&quot; /&amp;gt;
        &amp;lt;LinearLayout
            android:id=&quot;@+id/filter_bakery&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginStart=&quot;6dp&quot;
            android:padding=&quot;10dp&quot;
            android:elevation=&quot;10dp&quot;
            android:orientation=&quot;horizontal&quot;
            app:bgCategoryFilter=&quot;@{viewmodel.categoryFilter[2]}&quot;/&amp;gt;
        &amp;lt;LinearLayout
            android:id=&quot;@+id/filter_bar&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginStart=&quot;6dp&quot;
            android:padding=&quot;10dp&quot;
            android:elevation=&quot;10dp&quot;
            android:orientation=&quot;horizontal&quot;
            app:bgCategoryFilter=&quot;@{viewmodel.categoryFilter[3]}&quot;/&amp;gt;
    &amp;lt;/LinearLayout&amp;gt;
&amp;lt;/HorizontalScrollView&amp;gt;

&amp;lt;/androidx.constraintlayout.widget.ConstraintLayout&amp;gt;



        &amp;lt;FrameLayout
            android:id=&quot;@+id/conteudo&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            app:layout_insetEdge=&quot;bottom&quot;
            app:layout_behavior=&quot;com.google.android.material.bottomsheet.BottomSheetBehavior&quot;/&amp;gt;
        &amp;lt;!--app:layout_behavior=&quot;com.google.android.material.bottomsheet.BottomSheetBehavior&quot;--&amp;gt;


        &amp;lt;com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id=&quot;@+id/favorites_floatingButton&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginEnd=&quot;21dp&quot;
            android:layout_marginBottom=&quot;80dp&quot;
            android:elevation=&quot;10dp&quot;
            android:padding=&quot;12dp&quot;
            android:onClick = &quot;@{() -&amp;gt; viewmodel.onClickFavorite()}&quot;
            android:src=&quot;@{viewmodel.isFavorite}&quot;
            android:theme=&quot;@style/Theme.AVIRO&quot;
            app:rippleColor=&quot;@android:color/transparent&quot;
            app:backgroundTint=&quot;@color/Gray7&quot;
            app:fabCustomSize=&quot;48dp&quot;
            app:fabSize=&quot;mini&quot;
            app:dynamicTint=&quot;@{viewmodel.isFavorite}&quot;
            app:layout_behavior=&quot;com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior&quot;
            app:layout_anchor=&quot;@+id/conteudo&quot;
            app:layout_anchorGravity=&quot;right|bottom&quot;
            app:visibilityChanged=&quot;@{(viewmodel.bottomSheetState == 0 || viewmodel.bottomSheetState == 1)}&quot;&amp;gt;
        &amp;lt;/com.google.android.material.floatingactionbutton.FloatingActionButton&amp;gt;


        &amp;lt;com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id=&quot;@+id/location_floatingButton&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginEnd=&quot;21dp&quot;
            android:layout_marginBottom=&quot;20dp&quot;
            android:elevation=&quot;10dp&quot;
            android:padding=&quot;12dp&quot;
            android:src=&quot;@drawable/ic_floating_location_default&quot;
            android:theme=&quot;@style/Theme.Material3.DayNight&quot;
            app:backgroundTint=&quot;@color/Gray7&quot;
            app:fabCustomSize=&quot;48dp&quot;
            app:fabSize=&quot;mini&quot;
            app:tint=&quot;@color/Gray1&quot;
            app:layout_behavior=&quot;com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior&quot;
            app:layout_anchor=&quot;@+id/conteudo&quot;
            app:layout_anchorGravity=&quot;right|bottom&quot;
            app:visibilityChanged=&quot;@{(viewmodel.bottomSheetState == 0 || viewmodel.bottomSheetState == 1)}&quot;&amp;gt;
        &amp;lt;/com.google.android.material.floatingactionbutton.FloatingActionButton&amp;gt;

        &amp;lt;com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id=&quot;@+id/action_down_floatingButton&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginStart=&quot;21dp&quot;
            android:layout_marginTop=&quot;51dp&quot;
            android:layout_gravity=&quot;start&quot;
            android:elevation=&quot;10dp&quot;
            android:padding=&quot;12dp&quot;
            android:src=&quot;@drawable/ic_arrow_down&quot;
            android:theme=&quot;@style/Theme.Material3.DayNight&quot;
            app:layout_behavior=&quot;com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior&quot;
            app:backgroundTint=&quot;@color/Gray7&quot;
            app:fabCustomSize=&quot;48dp&quot;
            app:fabSize=&quot;mini&quot;
            app:tint=&quot;@color/Gray1&quot;
            app:visibilityChanged=&quot;@{(viewmodel.bottomSheetState == 2)}&quot;&amp;gt;
        &amp;lt;/com.google.android.material.floatingactionbutton.FloatingActionButton&amp;gt;


        &amp;lt;LinearLayout
            android:id=&quot;@+id/bottom_sheet_layout&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;match_parent&quot;
            android:orientation=&quot;vertical&quot;
            app:behavior_hideable=&quot;true&quot;
            app:behavior_draggable=&quot;false&quot;
            app:behavior_peekHeight=&quot;160dp&quot;
            app:behavior_halfExpandedRatio=&quot;0.75&quot;
            app:layout_insetEdge=&quot;bottom&quot;
            app:layout_behavior=&quot;com.google.android.material.bottomsheet.BottomSheetBehavior&quot;&amp;gt;
            &amp;lt;!-- app:layout_behavior=&quot;com.google.android.material.bottomsheet.BottomSheetBehavior&quot;--&amp;gt;
            &amp;lt;include
                android:id=&quot;@+id/bottom_sheet&quot;
                layout=&quot;@layout/fragment_bottomsheet_step1&quot;/&amp;gt;
        &amp;lt;/LinearLayout&amp;gt;
    &amp;lt;/androidx.coordinatorlayout.widget.CoordinatorLayout&amp;gt;
&amp;lt;/layout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;[fragment_bottomsheet_step1.xml]&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;layout
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;&amp;gt;
    &amp;lt;data&amp;gt;
        &amp;lt;import type=&quot;androidx.core.content.ContextCompat&quot;/&amp;gt;
        &amp;lt;variable
            name=&quot;mapViewmodel&quot;
            type=&quot;com.aviro.android.presentation.home.ui.map.MapViewModel&quot; /&amp;gt;
        &amp;lt;variable
            name=&quot;bottomViewmodel&quot;
            type=&quot;com.aviro.android.presentation.bottomsheet.BottomSheetViewModel&quot; /&amp;gt;
    &amp;lt;/data&amp;gt;

    &amp;lt;LinearLayout
        android:id=&quot;@+id/summary_layout&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:orientation=&quot;vertical&quot;
        android:background=&quot;@drawable/base_top_roundsquare_white_20&quot;&amp;gt;

        &amp;lt;androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:background=&quot;@drawable/base_top_roundsquare_white_20&quot;&amp;gt;

        &amp;lt;LinearLayout
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginTop=&quot;5dp&quot;
            android:background=&quot;@drawable/bar_bottomsheet&quot;
            android:layout_marginStart=&quot;169dp&quot;
            android:orientation=&quot;horizontal&quot;
            app:layout_constraintStart_toStartOf=&quot;parent&quot;
            app:layout_constraintTop_toTopOf=&quot;parent&quot;
            app:visibilityChanged=&quot;@{!(mapViewmodel.bottomSheetState == 3)}&quot;&amp;gt;
        &amp;lt;/LinearLayout&amp;gt;
            &amp;lt;!--app:visibilityChanged=&quot;@{!(mapViewmodel.bottomSheetState == 3)}&quot;--&amp;gt;

        &amp;lt;LinearLayout
            android:id=&quot;@+id/restaurantInfo&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginStart=&quot;15dp&quot;
            android:layout_marginTop=&quot;30dp&quot;
            android:layout_marginEnd=&quot;78dp&quot;
            android:orientation=&quot;horizontal&quot;
            app:visibilityChanged=&quot;@{!(mapViewmodel.bottomSheetState == 3)}&quot;
            app:layout_constraintTop_toTopOf=&quot;parent&quot;
            app:layout_constraintEnd_toEndOf=&quot;parent&quot;
            app:layout_constraintStart_toStartOf=&quot;parent&quot;&amp;gt;
            &amp;lt;!--app:visibilityChanged=&quot;@{!(mapViewmodel.bottomSheetState == 3)}&quot;--&amp;gt;

            &amp;lt;LinearLayout
                android:id=&quot;@+id/veganTypeIcon&quot;
                android:layout_width=&quot;56dp&quot;
                android:layout_height=&quot;56dp&quot;
                android:orientation=&quot;horizontal&quot;
                app:bgVeganType2=&quot;@{mapViewmodel.selectedMarker}&quot;
                app:visibilityChanged=&quot;@{!(mapViewmodel.bottomSheetState == 3)}&quot;
                app:layout_constraintStart_toStartOf=&quot;parent&quot;
                app:layout_constraintTop_toTopOf=&quot;parent&quot;&amp;gt;
            &amp;lt;/LinearLayout&amp;gt;

            &amp;lt;LinearLayout
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_marginStart=&quot;15dp&quot;
                android:layout_marginBottom=&quot;40dp&quot;
                android:orientation=&quot;vertical&quot;&amp;gt;

                &amp;lt;!--바텀시트 2단계에서 표시될 비건 타입--&amp;gt;
                &amp;lt;TextView
                    android:id=&quot;@+id/typeTextView_step2&quot;
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:paddingTop=&quot;4dp&quot;
                    android:textSize=&quot;15dp&quot;
                    app:veganTypeColor=&quot;@{mapViewmodel.selectedMarker.veganTypeColor}&quot;
                    app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 2)}&quot;/&amp;gt;
                &amp;lt;!--app:veganTypeColor=&quot;@{mapViewmodel.selectedMarker.veganTypeColor}&quot;--&amp;gt;
                &amp;lt;!--app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 2)}&quot;--&amp;gt;
                &amp;lt;TextView
                    android:id=&quot;@+id/nameTextView&quot;
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:maxLines=&quot;1&quot;
                    android:ellipsize=&quot;end&quot;
                    android:textSize=&quot;20dp&quot;
                    android:textStyle=&quot;bold&quot;
                    android:textColor=&quot;@color/Gray0&quot;
                    android:text=&quot;@{mapViewmodel.restaurantSummary.title}&quot;/&amp;gt;
                &amp;lt;!--app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 1 || mapViewmodel.bottomSheetState == 2)}&quot;--&amp;gt;
                    &amp;lt;!-- android:layout_weight=&quot;0.7&quot;--&amp;gt;
                &amp;lt;!-- 바텀시트 1단계에서 표시될 카테고리, 비건타입 --&amp;gt;
                &amp;lt;TextView
                    android:id=&quot;@+id/typeTextView_step1&quot;
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:paddingTop=&quot;4dp&quot;
                    android:textSize=&quot;17dp&quot;
                    android:fontFamily=&quot;@font/pretendard_semibold&quot;
                    app:veganTypeColor=&quot;@{mapViewmodel.selectedMarker.veganTypeColor}&quot;
                    app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 1)}&quot;/&amp;gt;
                &amp;lt;!--app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 1)}&quot;--&amp;gt;
           &amp;lt;!-- app:veganTypeColor=&quot;@{mapViewmodel.selectedMarker.veganTypeColor}&quot;--&amp;gt;

                &amp;lt;LinearLayout
                    android:id=&quot;@+id/restaurantLoc&quot;
                    android:layout_width=&quot;match_parent&quot;
                    android:layout_height=&quot;wrap_content&quot;
                    android:layout_marginTop=&quot;7dp&quot;
                    android:orientation=&quot;horizontal&quot;
                    app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 1)}&quot;
                    app:layout_constraintTop_toBottomOf=&quot;@id/restaurantInfo&quot;&amp;gt;
                    &amp;lt;!--app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 1)}&quot;--&amp;gt;

                    &amp;lt;TextView
                        android:id=&quot;@+id/locTxtView&quot;
                        android:layout_width=&quot;match_parent&quot;
                        android:layout_height=&quot;wrap_content&quot;
                        android:maxLines=&quot;1&quot;
                        android:ellipsize=&quot;end&quot;
                        android:textSize=&quot;15dp&quot;
                        android:fontFamily=&quot;@font/pretendard_regular&quot;
                        android:text=&quot;@{mapViewmodel.restaurantSummary.address}&quot; /&amp;gt;
                &amp;lt;/LinearLayout&amp;gt;

            &amp;lt;!-- 바텀시트 1단계에서 보여지는 거리 및 후기수 --&amp;gt;
            &amp;lt;LinearLayout
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_marginTop=&quot;7dp&quot;
                android:orientation=&quot;horizontal&quot;
                app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 1)}&quot;&amp;gt;
                &amp;lt;!--app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 1)}&quot;--&amp;gt;
                &amp;lt;!--거리정보--&amp;gt;
                &amp;lt;LinearLayout
                    android:id=&quot;@+id/distanceIcon&quot;
                    android:layout_width=&quot;20dp&quot;
                    android:layout_height=&quot;20dp&quot;
                    android:orientation=&quot;horizontal&quot;
                    android:background=&quot;@drawable/ic_bottomsheet_loc&quot;&amp;gt;
                &amp;lt;/LinearLayout&amp;gt;
                &amp;lt;TextView
                    android:id=&quot;@+id/distanceTextView_step1&quot;
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:layout_marginStart=&quot;4dp&quot;
                    android:textSize=&quot;15dp&quot;
                    android:fontFamily=&quot;@font/pretendard_regular&quot;/&amp;gt;

                &amp;lt;!--후기정보--&amp;gt;
                &amp;lt;LinearLayout
                    android:id=&quot;@+id/numOfReviewIcon&quot;
                    android:layout_width=&quot;20dp&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:layout_marginStart=&quot;10dp&quot;
                    android:background=&quot;@drawable/ic_bottomsheet_review&quot;
                    android:orientation=&quot;horizontal&quot;&amp;gt;&amp;lt;/LinearLayout&amp;gt;
                &amp;lt;TextView
                    android:id=&quot;@+id/numOfReviewTextView&quot;
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:layout_marginStart=&quot;4dp&quot;
                    android:textSize=&quot;15dp&quot;
                    android:fontFamily=&quot;@font/pretendard_regular&quot;
                    android:text=&quot;@{String.valueOf(mapViewmodel.restaurantSummary.commentCount) + '개'}&quot; /&amp;gt;
            &amp;lt;/LinearLayout&amp;gt;


            &amp;lt;!-- 바텀시트 2단계시 보여지는 거리 및 카테고리 --&amp;gt;
            &amp;lt;LinearLayout
                android:layout_width=&quot;match_parent&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:layout_marginTop=&quot;7dp&quot;
                android:orientation=&quot;horizontal&quot;
                app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 2)}&quot;&amp;gt;
                &amp;lt;!--app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 2)}&quot;--&amp;gt;

                &amp;lt;TextView
                    android:id=&quot;@+id/distanceTextView_step2&quot;
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:fontFamily=&quot;@font/pretendard_regular&quot;
                    android:textSize=&quot;15dp&quot;/&amp;gt;

                &amp;lt;TextView
                    android:id=&quot;@+id/categoryTextView&quot;
                    android:layout_width=&quot;wrap_content&quot;
                    android:layout_height=&quot;match_parent&quot;
                    android:layout_marginStart=&quot;4dp&quot;
                    android:textSize=&quot;15dp&quot;
                    android:fontFamily=&quot;@font/pretendard_regular&quot;
                    android:text=&quot;@{mapViewmodel.restaurantSummary.category}&quot; /&amp;gt;

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


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


        &amp;lt;LinearLayout
            android:id=&quot;@+id/etcBtn&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_marginTop=&quot;30dp&quot;
            android:layout_marginEnd=&quot;20dp&quot;
            android:orientation=&quot;vertical&quot;
            app:visibilityChanged=&quot;@{!(mapViewmodel.bottomSheetState == 3)}&quot;
            app:layout_constraintEnd_toEndOf=&quot;parent&quot;
            app:layout_constraintTop_toTopOf=&quot;parent&quot;&amp;gt;
            &amp;lt;!-- app:visibilityChanged=&quot;@{!(mapViewmodel.bottomSheetState == 3)}&quot;--&amp;gt;

            &amp;lt;LinearLayout
                android:id=&quot;@+id/likeBtn&quot;
                android:layout_width=&quot;38dp&quot;
                android:layout_height=&quot;38dp&quot;
                android:padding=&quot;8dp&quot;
                android:orientation=&quot;horizontal&quot;
                android:onClick=&quot;@{() -&amp;gt; bottomViewmodel.updateBookmark()}&quot;&amp;gt;
                &amp;lt;LinearLayout
                    android:id=&quot;@+id/likeBtnSrc&quot;
                    android:layout_width=&quot;24dp&quot;
                    android:layout_height=&quot;24dp&quot;
                    android:orientation=&quot;horizontal&quot;
                    app:setBottomSheetLikeBtn=&quot;@{bottomViewmodel.isLike}&quot;&amp;gt;
                &amp;lt;/LinearLayout&amp;gt;
            &amp;lt;/LinearLayout&amp;gt;
            &amp;lt;LinearLayout
                android:id=&quot;@+id/shareBtn&quot;
                android:layout_width=&quot;38dp&quot;
                android:layout_height=&quot;38dp&quot;
                android:padding=&quot;8dp&quot;
                android:orientation=&quot;horizontal&quot;&amp;gt;
                &amp;lt;LinearLayout
                    android:layout_width=&quot;24dp&quot;
                    android:layout_height=&quot;24dp&quot;
                    android:orientation=&quot;horizontal&quot;
                    android:background=&quot;@drawable/ic_share&quot;&amp;gt;
                &amp;lt;/LinearLayout&amp;gt;
            &amp;lt;/LinearLayout&amp;gt;
        &amp;lt;/LinearLayout&amp;gt;
        &amp;lt;/androidx.constraintlayout.widget.ConstraintLayout&amp;gt;


        &amp;lt;androidx.appcompat.widget.Toolbar
            android:id=&quot;@+id/toolbar&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;50dp&quot;
            android:layout_marginTop=&quot;30dp&quot;
            android:layout_gravity=&quot;center_vertical&quot;
            android:background=&quot;@color/Gray7&quot;
            app:visibilityChanged=&quot;@{(mapViewmodel.bottomSheetState == 3)}&quot;
            app:layout_constraintTop_toBottomOf=&quot;@+id/summary_layout&quot;&amp;gt;

            &amp;lt;Button
                android:id=&quot;@+id/backBtn&quot;
                android:layout_width=&quot;24dp&quot;
                android:layout_height=&quot;24dp&quot;
                android:layout_gravity=&quot;start&quot;
                android:background=&quot;@drawable/ic_arrow_down&quot;
                android:orientation=&quot;horizontal&quot;
                app:layout_constraintTop_toTopOf=&quot;parent&quot;
                app:layout_constraintStart_toStartOf=&quot;parent&quot;/&amp;gt;

            &amp;lt;TextView
                android:layout_width=&quot;wrap_content&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:text=&quot;@{mapViewmodel.restaurantSummary.title}&quot;
                android:layout_gravity=&quot;center&quot;
                android:fontFamily=&quot;@font/pretendard_semibold&quot;
                android:textColor=&quot;@color/Gray0&quot;
                android:textSize=&quot;18dp&quot;
                app:layout_constraintTop_toTopOf=&quot;parent&quot;
                app:layout_constraintStart_toStartOf=&quot;parent&quot;
                app:layout_constraintEnd_toEndOf=&quot;parent&quot; /&amp;gt;
        &amp;lt;/androidx.appcompat.widget.Toolbar&amp;gt;


       &amp;lt;include
            android:id=&quot;@+id/fragment_bottomsheet_step2&quot;
            layout=&quot;@layout/fragment_bottomsheet_step2&quot;
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;match_parent&quot;
            android:visibility=&quot;visible&quot;
            app:layout_constraintEnd_toEndOf=&quot;parent&quot;
            app:layout_constraintStart_toStartOf=&quot;parent&quot;
            app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
            app:layout_constraintTop_toBottomOf=&quot;@+id/restaurantLoc&quot;&amp;gt;
        &amp;lt;/include&amp;gt;


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


&amp;lt;/layout&amp;gt;&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;/div&gt;</description>
      <category>Android</category>
      <category>Android</category>
      <category>BottomSheet</category>
      <category>Kotlin</category>
      <category>map</category>
      <category>Persistent Bottom Sheet</category>
      <category>안드로이드</category>
      <category>앱개발</category>
      <category>코틀린</category>
      <author>gangmini</author>
      <guid isPermaLink="true">https://studyroadmap-kkm.tistory.com/177</guid>
      <comments>https://studyroadmap-kkm.tistory.com/177#entry177comment</comments>
      <pubDate>Wed, 28 Feb 2024 01:49:32 +0900</pubDate>
    </item>
  </channel>
</rss>