<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발자 SEOBI</title>
    <link>https://gift123.tistory.com/</link>
    <description>&amp;ldquo;채용되는 개발자&amp;rdquo;보다 살아있는 서비스를 만드는 개발자를 지향합니다.
문제 해결을 위해 필요한 기술을 익히고, 팀 &amp;middot; 서비스의 성장을 끊임없이 탐구합니다.

- Kotlin, Java, Android, Spring boot, Jpa, sql</description>
    <language>ko</language>
    <pubDate>Tue, 21 Jul 2026 18:28:36 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>SEOBI서비</managingEditor>
    <image>
      <title>개발자 SEOBI</title>
      <url>https://tistory1.daumcdn.net/tistory/3319358/attach/65839eff301b498bb11225a8bc39f9a5</url>
      <link>https://gift123.tistory.com</link>
    </image>
    <item>
      <title>[포텐업] AI Agent &amp;amp; 언리얼 협업 과정 2기 후기</title>
      <link>https://gift123.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 기술 블로그를 작성하다 첫 교육과정 블로그를 포스팅 해봅니다.&lt;br /&gt;백엔드 -&amp;gt; AI 직군 변환을 선택하기 위해 선택한 교육과정 채용플랫폼 원티드가 진행하는 '포텐업'에서 국비교육을 받고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;(상세 링크)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;포텐업 2기&quot; href=&quot;https://www.work24.go.kr/hr/a/a/3100/selectTracseDetl.do?tracseId=AIG20240000459107&amp;amp;tracseTme=2&amp;amp;cstmConsTme=&amp;amp;crseTracseSe=C0061&amp;amp;trainstCstmrId=500041113426&amp;amp;tracseReqstsCd=&amp;amp;focusId=&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.work24.go.kr/hr/a/a/3100/selectTracseDetl.do?tracseId=AIG20240000459107&amp;amp;tracseTme=2&amp;amp;cstmConsTme=&amp;amp;crseTracseSe=C0061&amp;amp;trainstCstmrId=500041113426&amp;amp;tracseReqstsCd=&amp;amp;focusId=&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1760057621028&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;www.work24.go.kr&quot; data-og-source-url=&quot;https://www.work24.go.kr/hr/a/a/3100/selectTracseDetl.do?tracseId=AIG20240000459107&amp;amp;tracseTme=2&amp;amp;cstmConsTme=&amp;amp;crseTracseSe=C0061&amp;amp;trainstCstmrId=500041113426&amp;amp;tracseReqstsCd=&amp;amp;focusId=&quot; data-og-url=&quot;https://www.work24.go.kr/hr/a/a/3100/selectTracseDetl.do?crseTracseSe=C0061&amp;amp;cstmConsTme=&amp;amp;focusId=&amp;amp;tracseId=AIG20240000459107&amp;amp;tracseReqstsCd=&amp;amp;tracseTme=2&amp;amp;trainstCstmrId=500041113426&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.work24.go.kr/hr/a/a/3100/selectTracseDetl.do?tracseId=AIG20240000459107&amp;amp;tracseTme=2&amp;amp;cstmConsTme=&amp;amp;crseTracseSe=C0061&amp;amp;trainstCstmrId=500041113426&amp;amp;tracseReqstsCd=&amp;amp;focusId=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.work24.go.kr/hr/a/a/3100/selectTracseDetl.do?tracseId=AIG20240000459107&amp;amp;tracseTme=2&amp;amp;cstmConsTme=&amp;amp;crseTracseSe=C0061&amp;amp;trainstCstmrId=500041113426&amp;amp;tracseReqstsCd=&amp;amp;focusId=&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;훈련과정 개요 상세 표 이며 훈련목표, 훈련대상 요건 선수학습, 훈련대상 요건 직무경력, 훈련대상 요건 기취득자격, 훈련대상 요건 훈련과정의 강점 으로 구성되어 있습니다. 훈련대상자요건&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.work24.go.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img_01-2.jpg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDVSo9/btsQ3539F0s/dIAbEouUMXL6ghSJpliF80/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDVSo9/btsQ3539F0s/dIAbEouUMXL6ghSJpliF80/img.jpg&quot; data-alt=&quot;포텐업 수업 장소 '원티드 그라운드'&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDVSo9/btsQ3539F0s/dIAbEouUMXL6ghSJpliF80/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDVSo9%2FbtsQ3539F0s%2FdIAbEouUMXL6ghSJpliF80%2Fimg.jpg&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;1200&quot; height=&quot;800&quot; data-filename=&quot;img_01-2.jpg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;포텐업 수업 장소 '원티드 그라운드'&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;1. 협업 포트폴리오 제작의 목적이 컸습니다. 커리큘럼 상 프로젝트 기반 수업 진행 방식이었는데 매우 마음에 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 집단지성을 생각했습니다. 저같은 경우 AI 관련 공식 문서 및 여러가지 내용들로 충분히 독학을 한다면 할 수 있었지만 탄탄한 강사님의 지원 그리고 언리얼반과 협업 프로젝트 등 많은 개발 전문 스피커들의 의견을 들으면서 공부할 수 있을거라는 기대감이 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. GPU 제공 환경이 컸습니다. AI 개발을 개인이 연습하다보면 colab에서 작업을 했어야했는데 중형 AI 모델의 경우 GPU가 받춰지지 않으면 학습 연습하기에 매우 버거운 점이 있었습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떤 과정을 수강중?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트 반을 수강하고 있습니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 소문으로 들었던 평범한 국비교육은 강사가 1명 이었는데 포텐업은 3명이 매월마다 로테이션으로 돌아가면서 강사님이 알려주시는 방식이었습니다. 강의를 진행하지 않은 달의 강사님은 쉬는것이 아니라 학생들의 수업자료나 학습 환경을 만들어주는 베이스 작업을 따로 하고 계시기도 했습니다. 또한 강사님이 언제든지 면담을 받아주시면서 학생들을 위해 많은 시간과 노력을 투자해주시는 모습에 매우 감명 깊습니다.&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;두번째로 놀랐던 점은 강사님 뿐만아니라 운영진분들이 상주해계신다는 겁니다. 운영진분들이 수업외에 전체 프로세스를 관리해주시면서 체계적으로 잘 진행이 되고 있구나를 느꼈습니다. 또한 운영진분 중에 커리어 코칭 강사님도 계십니다. 커리어 코칭 강사님 6개월 내내 학생들의 이력서나 포트폴리오에 대한 면담 및 수업을 알려주십니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영진은 커리어코칭 상주(무제한 1:1 코칭) / 강사진이 6시 일과 종료 때까지 함께하는 점 / 운영진의 빠른 처리 등 많은 일을 해주십니다.&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-filename=&quot;img_10.jpg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbpFrv/btsQ4iWu96R/SBF09SZpP7PXoQuI9jaoa0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbpFrv/btsQ4iWu96R/SBF09SZpP7PXoQuI9jaoa0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbpFrv/btsQ4iWu96R/SBF09SZpP7PXoQuI9jaoa0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbpFrv%2FbtsQ4iWu96R%2FSBF09SZpP7PXoQuI9jaoa0%2Fimg.jpg&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;1200&quot; height=&quot;587&quot; data-filename=&quot;img_10.jpg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;587&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;커리큘럼 설명&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트 개발뿐 아니라 데이터분석, 머신러닝, 딥러닝, LLM 활용 등 AI 활용도에 대해 알아가면서 에이전트에 가까워 지는 방식입니다. 현업에 백엔드 개발하다가 ai 지식 없이 바로 에이전트 개발에 뛰어드시는 분들보다 훨씬 탄탄한 베이스를 가지고 접근할 수 있습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7월 데이터 분석,&lt;br /&gt;8월 머신러닝, 딥러닝,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9월 딥러닝(RNN, Transformer ..), LLM 파인튜닝, LLM 활용, yolo, meida pipe,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10월 AI Agent(LangChain, LangGraph 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11월 ~ 1일 언리얼 &amp;amp; AI 융합 프로젝트&lt;br /&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;/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;제 경우는 아래와 같은 프로젝트를 진행했었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;7월 데이터 분석 프로젝트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저품질 기사 솔루션 Cone&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;8월 머신러닝, 딥러닝 프로젝트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우울증 멀티모달 탐지기&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 음성, 얼굴 사진, 텍스트등 팀원 마다 각각 다른 모델을 맡아서 분석을 했습니다. 저는 음성 ai 모델 분석을 위주로 진행하였고 머신러닝의 군집화를 통해서 3개 모델의 지표를 통합해보려는 실험도 해봤었습니다.&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;9월 LLM 활용, 딥러닝 활용 프로젝트&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;- 버스 기사의 눈, 코, 입을 분석해서 졸음 상태에 가까워 지면 LLM이 이상한 말투로 말을 걸어서 잠을 확 깨는 서비스를 만들었습니다.&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;10월 AI Agent 프로젝트&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img_08.jpg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cI8gSN/btsQ3jogbFp/QX50S048qZhkJBkWU1m1N1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cI8gSN/btsQ3jogbFp/QX50S048qZhkJBkWU1m1N1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cI8gSN/btsQ3jogbFp/QX50S048qZhkJBkWU1m1N1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcI8gSN%2FbtsQ3jogbFp%2FQX50S048qZhkJBkWU1m1N1%2Fimg.jpg&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;1200&quot; height=&quot;806&quot; data-filename=&quot;img_08.jpg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;806&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;b&gt;주관적인 찐 후기 (아래부터 진짜입니다.)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 모두 밤 늦게까지 공부할만큼 쉬지 않고 달리십니다. 한명도 빠짐없이 모두 그렇습니다. 이러한 학습 분위기에 동기부여를 받을 수 있을 것 같습니다.&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. AI 수업은 4개월만 이루어지고 나머지 2개월은 프로젝트에 올인하는 방식입니다. 심지어 AI 수업 4개월에 한달마다 프로젝트를 진행하기 때문에 실질적인 수업양은 한달에 2~3주 사이 밖에 안됩니다. 국비 교육 특성상 단기간에 많은걸 해야하기 때문에 반드시 개발 처음 하시는 분들은 반드시 파이썬은 문법 공부를 많이 하시고 와야 수월 하실 겁니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 한달에 2~3주 밖에 AI 수업이 진행되지 않아서 강사님이 현업에서 사용되지 않은 내용을 많이 쳐내면서 수업을 진행중이신게 느껴집니다. 즉 현업에 중요한 키포인트만 위주로 주제를 뽑아서 알려주시려고 합니다. 그래서 AI 수업을 통해 AI 밑바닥부터 끝까지 다 배우시려는 분들은 많이 아쉬워할수도 있습니다.&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. 프로젝트를 통해 플젝 경험을 쌓으면서 할 수 있다는 점이 큰 강점이긴 합니다.&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. 비 개발자 출신, 개발자 출신이 많이 엮여있습니다. 그리고 매월 마다 팀이 이루어지기 때문에 사람 사이의 다양한 경험(?)을 해볼 수 있긴 합니다. 이로 인해 마음 아픈 경험, 즐거웠던 경험 등 다양한 경험을 해볼 수 있습니다.&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;6. 강사님 모두 질문을 받으면 대답을 너무 잘해주십니다. 저 같은 경우 수업 내용보다는 AI 현업 기술에 많이 궁금해서 강사님께 수업을 진행하지 않았던 질문도 많이 하는 편인데 모두다 성의 있게 대답해주시려고 노력합니다.&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;7. 비 개발자 출신 분들은 새로운 시작을 하기 때문에 모두 엄청난 열정을 가지고 시작하십니다. 하지만 생각보다 개발이 쉽지 않아서 처음에 많이 좌절 하는 모습이 있습니다. 그래도 기획면에서 많은 활약을 보여주기도 하고 결국 개발 실력도 물이 오르면서 나름 한 포지션을 하시기 시작합니다. 결국은 버티는자가 승자입니다.&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;8. (이 부분은 제 개인적인 얘기입니다.) 개발자 출신 입장에서 아쉬운 점은 프로젝트 기간(1~2주)이 너무 짧아서 아쉽습니다. 그러다 보니 현업에 비하면 너무 장난감 수준의 프로젝트만 할 수 밖에 없었습니다. 하지만 이 부분은 11월 ~ 1월 사이에 진행되는 2개월 간의 최종프로젝트에서 많이 채워질 것 같습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;9. 매월 프로젝트마다 발표를 시킵니다. 그래서 AI반 같은 경우 PPT에 어느정도 시간을 쏟아야 하는 점이 있습니다.&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;10. (이 부분은 제 개인적인 얘기입니다.) 개발자 출신인 저는 개발은 어렵지 않으나 기획이 너무 힘들었습니다. 저 같은 경우 기획 역할을 별로 좋아하지 않고 회사 도메인 적인 경험이나 개발 기술을 많이 좋아했었는데 여기서는 매월 프로젝트를 해야하다보니 기획에 대한 스트레스와 AI 개발 공부에 대한 스트레스를 같이 경험 해야 했습니다.&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;11. AI 수업 진행되는 4개월 동안은 멘탈이 많이 받쳐줘야합니다. 주로 팀 프로젝트 후 사람간의 관계에서 힘들수도 있고 아니면 AI 학습이 너무 어려워서 뒤쳐진다고 생각해서 스트레스 받을수가 있는데 별 생각하지 않고 묵묵히 버티는 자만이 해낼 수 있습니다.&lt;br /&gt;&lt;br /&gt;12. 위 1 ~ 11이 모두 모여서 다 경험이 되는 수업 입니다.&lt;/p&gt;</description>
      <category>인공지능 AI</category>
      <category>AI</category>
      <category>국비지원</category>
      <category>원티드</category>
      <category>포텐업</category>
      <category>포텐업 후기</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/93</guid>
      <comments>https://gift123.tistory.com/93#entry93comment</comments>
      <pubDate>Fri, 10 Oct 2025 11:03:47 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (46) Process, Activity, Compose LifeCycle</title>
      <link>https://gift123.tistory.com/92</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 개발에서 Process, Activity, Compose 의 세가지 LifeCycle은 안드로이드 개발자가 알아야할 필수 지식이며, 앱의 문제 해결에 있어서 매우 중요한 주제 입니다. 특히 실무 개발자라면 각 컴포넌트를 생성하고 파괴하는 과정과 그 사이에서 적절히 동작을 관리하는 방법을 잘 이해해야합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;love-8361649_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ybeGd/btsNGTM5Nmb/xMKaFH3kCxuYIWkCQWlk4k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ybeGd/btsNGTM5Nmb/xMKaFH3kCxuYIWkCQWlk4k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ybeGd/btsNGTM5Nmb/xMKaFH3kCxuYIWkCQWlk4k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FybeGd%2FbtsNGTM5Nmb%2FxMKaFH3kCxuYIWkCQWlk4k%2Fimg.jpg&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;525&quot; height=&quot;525&quot; data-filename=&quot;love-8361649_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 안드로이드 앱 생명주기 (Application LifeCycle)&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1746065320706&quot; class=&quot;kotlin&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;//Application Class
import android.app.Application

class CustomApplication: Application(){
    override fun onCreate() {
        super.onCreate()
        //.. 앱 초기화 시점 로직 실행..
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Application 클래스는 애플리케이션 전역에서 한 번 생성되는 객체로 앱의 전체 수명주기를 관리하는 역할을 합니다. 앱 프로세스가 시작될 때 가장 먼저 생성되며 앱이 종료될 때까지 존재하면서 전역 상태를 유지합니다. Application 클래스를 정의하면 앱 초기화 로직이나 전역 리소스 관리를 한 곳에 모아둘 수 있어 편리합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Application 의 역할&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;글로벌 상태 관리&lt;/b&gt;: Application 객체는 &lt;b&gt;애플리케이션 전역에서 참조 가능한 Context를 제공합니다.&lt;/b&gt; 이를 통해 다양한 컴포넌트(액티비티, 서비스 등)에서 공용으로 사용할 데이터나 자원을 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;초기화 작업&lt;/b&gt;: Application 의 onCreate() 는 앱이 시작될 때 한 번 호출되므로, 전역 초기화 작업을 수행하기에 적합합니다. 서드파티 라이브러리 초기화, DI 컨테이너 세팅 등을 여기서 진행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공용 리소스 제공&lt;/b&gt;: Application 은 ContextWrapper 를 상속 하므로 Context가 필요한 작업을 어디서든 수행할 수 있습니다. 그래서 Activity Context 를 사용하지 않는 사용처에서도 &lt;b&gt;Application Context를 통해 리소스 로드나 시스템 서비스 사용 등을 작업&lt;/b&gt;할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;Application 주요 메서드&lt;/h4&gt;
&lt;pre id=&quot;code_1746066986069&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CustomApplication: Application(){
    override fun onCreate() { }
    override fun onTerminate() { }
    override fun onLowMemory() { }
    override fun onConfigurationChanged(newConfig: Configuration) { }
	override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
        when(level){
            ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -&amp;gt;...
            else -&amp;gt; ...
        }
    }

}&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;onCreate()&lt;/b&gt; - 앱의 프로세스가 생성될 때 호출되며, 가장 먼저 실행됩니다. 앱의 전반적인 LifeCycle 에서 딱 한번만 호출됩니다. (멀티프로세스 환경이라면 각 프로세스마다 한 번씩 호출됨)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onTerminate()&lt;/b&gt; - 앱 종료시 호출됩니다. 하지만 이 메서드는 실제 단말기에서는 호출되지 않을 수 있습니다. &lt;b&gt;안드로이드 시스템은 일반적으로 프로세스를 강제로 종료하므로 onTermiante() 가 호출되지 않을 수 있습니다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onConfigurationChanged(newConfig: Configuration) -&amp;nbsp;&lt;/b&gt;앱의 구성 변경 시 호출됩니다. 구성 변경은 언어, 다크모드/라이트모드 변경, 화면 방향 등으로 인해 발생하는 환경 변경입니다. 현대 안드로이드 앱 개발에서 다크모드 &amp;lt;-&amp;gt; 라이트모드 변경에 따라 콜백 세팅을 수행하기 위해 사용하는 사례가 많습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onLowMemory() -&amp;nbsp; &lt;/b&gt;전체 시스템 메모리가 부족할&amp;nbsp; 때 호출됩니다. Android 4.0 이전에는 이 콜백을 통해 메모리 해제를 수동 처리했으나, 이후에는 더 세분화된 onTrimMemory() 가 도입되었습니다. 따라서 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;Android 4.0&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이상에는 이 메서드가 잘 호출되지 않으며, 하위 호환을 위해 남겨둔 fallback 정도로 간주합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onTrimMemory(level:Int)&lt;/b&gt; &lt;b&gt;-&lt;/b&gt; &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;Android 4.0&lt;span&gt; 이상부터 도입된 메서드로 메모리 트림 요청(trim memory)로 인해 호출이 됩니다. level를 android.content.ComponentCallbacks2의 상수값과 비교해서 &lt;b&gt;메모리 상태에 상황에 따라 다양한 작업을 수행 할 수 있습니다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;-&amp;gt; 메모리 트림 요청이란? 안드로이드 시스템이 &quot;지금 메모리가 필요하니 어느정도 반납해달라&quot; 라고 신호를 보내는 콜백입니다.&lt;br /&gt;-&amp;gt; ComponentCallbacks2 의 상수값 예시&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1746066783266&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ComponentCallbacks2 extends ComponentCallbacks {
    //멀티-윈도 / 픽처-인-픽처 일부 가려짐
    int TRIM_MEMORY_BACKGROUND = 40;

	// 앱이 백그라운드인데 시스템 메모리가 계속 부족
    int TRIM_MEMORY_COMPLETE = 80;
    int TRIM_MEMORY_MODERATE = 60;

    // 시스템이 전체적으로 메모리 여유가 부족
    int TRIM_MEMORY_RUNNING_CRITICAL = 15;
    int TRIM_MEMORY_RUNNING_LOW = 10;
    int TRIM_MEMORY_RUNNING_MODERATE = 5;

    // 앱 UI가 화면에서 완전히 사라짐
    int TRIM_MEMORY_UI_HIDDEN = 20;

    void onTrimMemory(int var1);
}&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;또한, Application 클래스 자체에 ComponenetCallbacks2 인터페이스를 구현하고 있기 때문에, Application의 onTrimMemory를 사용하지 않고도 registerComponentCallbacks { } 를 통해 별도 콜백을 등록해 메모리 이벤트를 수신할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746067794591&quot; class=&quot;scala&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;package android.app;

//ComponentCallbacks2 인터페이스를 구현한다.
public class Application extends ContextWrapper implements ComponentCallbacks2 { .. }&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_1746067969611&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CustomApplication : Application() {

    // ComponentCallbacks2 구현체를 람다로 간단히 준비
    private val memoryCallback = object : ComponentCallbacks2 {
        override fun onTrimMemory(level: Int) {
            // 필요에 따라 캐시 비우기, 리소스 해제
            if (level &amp;gt;= TRIM_MEMORY_BACKGROUND) {
                ImageCache.clear()
            }
        }
		//..기타 오버라이드 코드 생략..//
    }
	
    override fun onCreate() {
        super.onCreate()
        // 콜백 등록
        registerComponentCallbacks(memoryCallback)
    }

}&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;그리고 Application class의 추가 팁이 있습니다. 바로 registerActivityLifecycleCallbacks 를 사용하면 앱 내의 각 Activity LifeCycle 이벤트를 Application에서 전역적으로 모니터링 하거나 세팅이 가능합니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746068199599&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CustomApplication : Application() {

    private val callbacks = object : ActivityLifecycleCallbacks {
        override fun onActivityCreated(a: Activity, b: Bundle?) {}
        override fun onActivityStarted(a: Activity) {}
        override fun onActivityResumed(a: Activity) {}
        override fun onActivityPaused(a: Activity) {}
        override fun onActivityStopped(a: Activity) {}
        override fun onActivitySaveInstanceState(a: Activity, out: Bundle) {}
        override fun onActivityDestroyed(a: Activity) {}
    }

    override fun onCreate() {
        super.onCreate()
        registerActivityLifecycleCallbacks(callbacks)
    }

    override fun onTerminate() {
        unregisterActivityLifecycleCallbacks(callbacks)
        super.onTerminate()
    }
}&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;b&gt;팁:&lt;/b&gt; 전역 예외 처리 Thread.setDefaultUncaughtExceptionHandler를 Application에서 설정하여 전역 예외를 감지하고 처리(로그 전송 등)하는 패턴도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746082223413&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CrashHandler(
    private val context: Context,
    private val delegate: Thread.UncaughtExceptionHandler? = null
) : Thread.UncaughtExceptionHandler {

  	override fun uncaughtException(thread: Thread, throwable: Throwable) {
  	//...
  	}
    
}

class CustomApplication : Application() {

    override fun onCreate() {
        super.onCreate()
      
        val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()

        // 새 핸들러 등록
        Thread.setDefaultUncaughtExceptionHandler(
            CrashHandler(this, defaultHandler)
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;board-22098_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rveq9/btsNIgf1hhs/t6jXK0sZIsXcw7wDJuKAsK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rveq9/btsNIgf1hhs/t6jXK0sZIsXcw7wDJuKAsK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rveq9/btsNIgf1hhs/t6jXK0sZIsXcw7wDJuKAsK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frveq9%2FbtsNIgf1hhs%2Ft6jXK0sZIsXcw7wDJuKAsK%2Fimg.jpg&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;612&quot; height=&quot;853&quot; data-filename=&quot;board-22098_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2.&amp;nbsp; 안드로이드 프로세스 생명주기와 메모리 관리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드에서는 앱의 프로세스 생명주기가 앱 자체가 아닌&amp;nbsp; 시스템에 의해 관리 됩니다. 안드로이드 시스템은 앱의 &lt;b&gt;컴포넌트 활용 상태 및 현재 메모리 상황 등을 종합적으로 고려하여 프로세스를 생성하거나 종료&lt;/b&gt;합니다. 일반적으로 각 앱은 별도의 Linux 프로세스로 격리되어 실행되고, 해당 앱의 코드가 실행되어야 할 때(예: 액티비티 시작) 필요에 따라 프로세스가 생성 됩니다. 한 번 생성된 프로세스는 사용자가 앱을 계속 사용하거나 백그라운드에 남아 있는 한 유지되지만, 시스템 메모리가 부족해지만 더 중요한 작업을 수행중인 다른 앱에 메모리를 양보하기 위해 불필요한 프로세스를 종료시킵니다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로세스 생성 시점&amp;nbsp;&lt;/h3&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;: 해당 앱에 대한 새로운 프로세스가 Zygote로 부터 fork 되어 생성됩니다. 이 시점에 Application.onCreate가 호출됩니다. 새로운 프로세스를 완전히 생성하지 않고 이미 실행중인 씨앗 프로세스 Zygote로 부터 복제를 하기 때문에 새로운 프로세스를 빠르게 만들어 낼 수 있다고 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;백그라운드 서비스 시작: &lt;/b&gt;앱의&amp;nbsp;서비스가&amp;nbsp;예약(AlarmManager,&amp;nbsp;WorkManager&amp;nbsp;등)되어&amp;nbsp;있거나&amp;nbsp;백그라운드에서&amp;nbsp;동작을&amp;nbsp;시작해야&amp;nbsp;하는&amp;nbsp;경우,&amp;nbsp;기존&amp;nbsp;앱&amp;nbsp;프로세스가&amp;nbsp;살아&amp;nbsp;있다면&amp;nbsp;이를&amp;nbsp;재사용하고,&amp;nbsp;이미&amp;nbsp;종료된&amp;nbsp;상태라면&amp;nbsp;새로운&amp;nbsp;프로세스를&amp;nbsp;생성합니다.&amp;nbsp;두&amp;nbsp;경우&amp;nbsp;모두&amp;nbsp;프로세스가&amp;nbsp;처음&amp;nbsp;시작될&amp;nbsp;때는&amp;nbsp;Application.onCreate()가&amp;nbsp;호출된&amp;nbsp;후&amp;nbsp;서비스&amp;nbsp;컴포넌트의&amp;nbsp;시작&amp;nbsp;메서드(Service.onCreate())가&amp;nbsp;이어서&amp;nbsp;호출됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;BroadcastReceiver 실행: &lt;/b&gt;예를 들어 BOOT_COMPLETED 이나 특정 이벤트를 수신하도록 등록된 리시버가 있다면, 해당 인텐트를 기존 앱 프로세스가 살아 있다면 이를 재사용하고, 이미 종료된 상태라면 새로운 프로세스를 생성합니다. &amp;nbsp;두 경우 모두 프로세스가 처음 시작될 때는 Application.onCreate()가 호출된 후 Receiver.onReceive() 가 이어서 호출됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;메모리 부족과 프로세스 종료 (OOM Kill)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 메모리가 부족해지면, 안드로이드 시스템은 어떤 프로세스를 종료할지 결정하기 위해 프로세스의 중요도에 우선 순위 정리(LRU) 목록을 관리합니다. &lt;b&gt;중요도가 낮은 프로세스부터 차례로 종료&lt;/b&gt;하여 메모리를 회수하고, 꼭 필요한 프로세스는 최후까지 유지하는 전략입니다. 프로세스의 중요도 레벨과 특징은 아래와 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 86px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.217%; height: 20px;&quot;&gt;&lt;b&gt;프로세스 유형&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 61.7054%; height: 20px;&quot;&gt;&lt;b&gt;설명 (중요도 높은 순)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25.0775%; height: 20px;&quot;&gt;&lt;b&gt;메모리 정리 순위&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 13.217%; height: 18px;&quot;&gt;포그라운드 프로세스&lt;br /&gt;(Foreground)&lt;/td&gt;
&lt;td style=&quot;width: 61.7054%; height: 18px;&quot;&gt;사용자가 현재 &lt;b&gt;직접 상호작용 중인&lt;/b&gt; 컴포넌트를 포함하는 프로세스 입니다. 예를들어 전체 화면이보이는 액티비티(onResume()) , 현재 실행 중인 포그라운드 서비스(startForeground()) 또는 실행중인 리시버(onReceive() 리턴 전) 등을 포함 합니다.&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25.0775%; height: 18px;&quot;&gt;&lt;b&gt;최우선 유지 -&lt;/b&gt; 시스템이 극한의 메모리 부족 상태에 이르지 않는 한 종료되지 않습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 13.217%; height: 18px;&quot;&gt;가시적 프로세스&lt;br /&gt;(Visible)&lt;/td&gt;
&lt;td style=&quot;width: 61.7054%; height: 18px;&quot;&gt;사용자에게 화면상 보이지만 전체화면이 아닌 컴포넌트를 포함하는 프로세스 입니다. 예를들어 투명 액티비티나 다이얼로그 뒤에 보이는 액티비티(onPause()), 화면에 보이는 위젯 등이 있습니다.&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25.0775%; height: 18px;&quot;&gt;&lt;b&gt;매우 중요 -&amp;nbsp;&lt;/b&gt;포그라운드 상태 다음으로 중요하며, 메모리가 부족하면 다른 모든 캐시 프로세스를 종료해도 메모리가 모자를 때 종료됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 13.217%; height: 18px;&quot;&gt;서비스 프로세스&lt;br /&gt;(Service)&lt;/td&gt;
&lt;td style=&quot;width: 61.7054%; height: 18px;&quot;&gt;백그라운드 서비스를 실행중인 프로세스 입니다. 직접 UI를 보이진 않지만 사용자가 신경쓰는 동작을 수행중입니다. (예: 음악 재생, 데이터 동기화 등)&lt;/td&gt;
&lt;td style=&quot;width: 25.0775%; height: 18px;&quot;&gt;&lt;b&gt;중간 -&amp;nbsp;&lt;/b&gt;사용자가 인지하는 작업이므로 함부로 종료되지 않지만, 프로세스/가시적 상태를 유지 후에도 메모리가 부족하면 캐시 프로세스 다음으로 종료됩니다. 또한 장기간 실행 된 서비스는 캐시프로세스로 강등 되기도 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 12px;&quot;&gt;
&lt;td style=&quot;width: 13.217%; height: 12px;&quot;&gt;캐시 프로세스&lt;br /&gt;(Cached)&lt;/td&gt;
&lt;td style=&quot;width: 61.7054%; height: 12px;&quot;&gt;현재 필요하지 않은 프로세스들로 백그라운드에 대기 상태로 있는 앱들 입니다. 화면에 나타나는 컴포넌트가 없고 바인드된 서비스 등도 없는 프로세스가 이에 해당합니다. 시스템 입장에서 언제든 제거해도 무방한 프로세스 입니다.&lt;/td&gt;
&lt;td style=&quot;width: 25.0775%; height: 12px;&quot;&gt;&lt;b&gt;최우선 정리 - &lt;/b&gt;시스템은 메모리가 부족해지면 언제든지 프로세스를 종료하여 메모리를 확보합니다. &lt;br /&gt;최신 안드로이드 기기에서는 주로 메모리를 가장 많이 사용하는 프로세스부터 정리하며, 그렇지 않은 경우에는 오랫동안 사용되지 않은 순서(LRU)로 캐시 프로세스를 종료합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 설명하면 실제 사용자 눈에 UI가 눈으로 들어올 수록 가장 메모리 유지 가능성이 높고 UI 다음은 백그라운드 작업이고 마지막에는 사용되지 않는 프로세스 순으로 메모리 정리 우선 순위가 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;7814&quot; data-start=&quot;7778&quot; data-ke-size=&quot;size23&quot;&gt;OOM(Out-Of-Memory)과 메모리 모니터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 시스템은 Low Memory Kiler(LMK) 라는 메커니즘이 있습니다. 메모리 부족이 감지되면 위에 언급한 중요도 순서에 따라 자동으로 프로세스를 정리합니다. 개발 중에는 쉘을 통해 메모리 사용량을 확인하고, 어플리케이션 or ComponentCallbacks의&amp;nbsp; onTrimMemory(level:Int) 콜백을 활용하여 메모리 해제 로직이 잘 작동하는지 테스트 해볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746075000385&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//메모리 확인 
adb shell dumpsys meminfo&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;특히 onTrimMemory(level:Int) 콜백에 level 파라미터가 TRIM_MEMORY_UI_HIDDEN 로 호출될 때 앱이 차지하는 메모리를 크게 줄여두면 시스템 메모리 관리에 협조적이고 앱이 백그라운드에서 오래 살아 남을 가능성이 높아집니다.&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;- onTrimMemory(TRIM_MEMORY_UI_HIDDEN) 가 올때 메모리를 줄이면 앱이 오래 살아 남는 이유?&lt;/p&gt;
&lt;pre id=&quot;code_1746075283943&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌ 홈 버튼
│
├ onPause()       &amp;larr; 사용자에게 안 보임
├ onStop()
└ onTrimMemory(TRIM_MEMORY_UI_HIDDEN)   &amp;larr; 바로 여기!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;125&quot; data-start=&quot;83&quot; data-ke-size=&quot;size20&quot;&gt;호출 시점-&amp;mdash;&amp;ldquo;UI 안 보여, 이제&amp;nbsp; 캐시 취급할게!&amp;rdquo;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;313&quot; data-start=&quot;127&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;220&quot; data-start=&quot;127&quot;&gt;TRIM_MEMORY_UI_HIDDEN 은 &lt;b&gt;액티비티 화면이 전부 사라진 직후&lt;/b&gt;(예: 홈 버튼을 눌러 다른 앱으로 전환) 한번 호출됩니다.&lt;/li&gt;
&lt;li data-end=&quot;220&quot; data-start=&quot;127&quot;&gt;프로세스 유형 표에 따라 백그라운드 작업이 수행 중이지 않으면 캐시 프로세스로 전환되서 언제든지 시스템의 메모리 반환 요청으로 인해 정리 될 수 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;220&quot; data-start=&quot;127&quot;&gt;캐시 프로세스로 진입할 때 메모리 사용률을 최대한 줄인다면 LMK 에 의해 프로세스가 제거 되지 않도록 제거 대상 순위를 내릴 수 있습니다. 즉, &lt;b&gt;당장 화면에 필요 없는 리소스&lt;/b&gt;(불필요 서비스/리시버, WebView , Bitmap 캐시, OpenGL 텍스처, View 트리, 미디어 객체 등)를 정리를 해서 메모리 사용량을 확 줄이는 겁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1746076622236&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    if (level == TRIM_MEMORY_UI_HIDDEN) {

        // 1) 이미지 로더 캐시 줄이기
        Glide.get(this).trimMemory(TRIM_MEMORY_UI_HIDDEN)

        // 2) LruCache 비우기
        myBitmapCache.evictAll()

        // 3) 비활성 WebView 파괴
        webView?.run {
            loadUrl(&quot;about:blank&quot;)
            clearHistory(); destroy(); webView = null
        }
        
        // 4) 기타 작업 등등
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;7778&quot; data-end=&quot;7814&quot; data-ke-size=&quot;size23&quot;&gt;멀티 프로세스&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 하나의 앱은 하나의 프로세스에서 실행됩니다. 하지만 프로세스 분리의 필요성을 느끼면 멀티 프로세스 분리해서 사용이 가능합니다. 안드로이드에서는 AndroidManifest.xml 에서&amp;nbsp; android:process 속성을 활용해서 액티비티,서비스,리시버,프로바이더에 각 핵심 컴포넌트에 별도 프로세스 실행할 수 있도록 지정할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746078135255&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- Main 앱 프로세스 --&amp;gt;
&amp;lt;activity android:name=&quot;.MainActivity&quot;/&amp;gt;

&amp;lt;!-- 별도 프로세스로 실행되는 서비스 --&amp;gt;
&amp;lt;service
    android:name=&quot;.BackgroundService&quot;
    android:process=&quot;:background&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 프로세스 구조의 특징은 &lt;b&gt;프로세스 마다 별개의 Application 객체가 생성이 된다&lt;/b&gt;는 것입니다.&amp;nbsp; 프로세스 특징상 서로 다른 메모리를 공유하지 않으므로 스태틱이나 전역 변수등도 프로세스마다 별도로 존재합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;android:process 사용 시 주의사항&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;필요한 경우에만 사용하기: 멀티 프로세스는 앱 구조를 복잡하게 만들고 앱 자체의 메모리 사용량을 대폭 늘리므로 사용 여부를 잘 생각각 해야합니다. 이유 없이 단순 목적으로 프로세스를 나누면 메모리 사용량만 대폭 늘어 안드로이드 시스템의 LMK의 활동이 빈번해집니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;프로세스 간 통신 고려 : 분리된 프로세스 간에는 메모리를 공유하지 않으므로 데이터 공유가 필요하면 파일, DB, ContentProvider 또는 Binder(AIDL) 등을 사용해야 합니다. 단순히 전역 변수를 쓰는 것이 불가능 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;Application 초기화 분기 처리: 가장 중요한 점으로 Application.onCreate() 내에서 현재 프로세스가 어떤 프로세스 인지 확인하고 초기화 작업을 잘 생각해서 진행 해야합니다. 예를들어 UI 관련된 초기화는 메인 프로세스만 하면 되는데 백그라운드 관련 프로세스에서 진행을 하게되면&amp;nbsp; 불필요한 작업을 해버리는 것입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 프로세스 이름을 확인해서 분기 처리가 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746081212214&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CustomApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // 현재 프로세스 이름 가져오기 (API 28+ 간단하게)
        val processName = Application.getProcessName() ?: run {
            // API 28 미만 호환 방법: RunningAppProcessInfo에서 찾기
            val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
            am.runningAppProcesses?.find { it.pid == android.os.Process.myPid() }?.processName
        }
        
        // 메인 프로세스가 아닐 경우 조기 반환하여 불필요한 초기화 막기
        if (processName != null &amp;amp;&amp;amp; processName != packageName) {
            return  
        }
        
        // 이 아래는 메인 프로세스에서만 실행할 초기화 코드들
        initLibraries()    
        setupGlobalResources()
    }
}&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;7814&quot; data-start=&quot;7778&quot; data-ke-size=&quot;size23&quot;&gt;멀티 프로세스의 장단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 프로세스의 특징과 장단점을 잘 생각해서 앱에 적용할지 잘 생각 해야합니다. 다양한 장단점이 있지만 매우 큰 3가지를 정리했습니다. 아래와 같습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 88px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 16.0077%; height: 20px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 51.124%; height: 20px;&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.8682%; height: 20px;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 16.0077%; height: 18px;&quot;&gt;안전성 및 격리&lt;/td&gt;
&lt;td style=&quot;width: 51.124%; height: 18px;&quot;&gt;- 한 프로세스 OOM 및 Crash가 발생하더라도 다른 프로세스는 그대로 생존 &lt;br /&gt;(예:서비스 전용 프로세스가 제거되어도 UI전용 프로세스는 살아있어서 앱 UI는 유지됨 즉, 사용자에게 안정적인 앱이라고 느끼게끔 할수도 있음)&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp; 보안 취약 코드 및 플러그인을 안전하게 격리&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.8682%; height: 18px;&quot;&gt;- 컴포넌트가 서로 죽었다 살아날 때 상태 및 세션 유지 코드 필요 (예: A 프로세스에서 B프로세스의 Bind Serive 의존 할때 B프로세스 사망시 재바인드 필요 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 16.0077%; height: 18px;&quot;&gt;메모리 한도 우회&lt;/td&gt;
&lt;td style=&quot;width: 51.124%; height: 18px;&quot;&gt;- 프로세스 단위 별로 메모리 힙 상한 값이 적용이 되어있음, 하지만 멀티 프로세스를 사용하면 하나의 프로세스 보다 더 많은 메모리 힙을 각각 사용할 수 있게 됌&lt;br /&gt;(예: 프로세스 하나당 512mb 가 주어진다면, 서비스에서 무거운 작업을 한다했을때 액티비티 전용 프로세스와 서비스 전용 프로세스를 분리하면 서비스 전용 프로세스에서 512mb 사용 가능)&lt;/td&gt;
&lt;td style=&quot;width: 32.8682%; height: 18px;&quot;&gt;- 한 패키지 단위 앱의 총 RAM 사용량이 증가하므로 안드로이드 시스템 자체의 메모리 사용율 급증으로 인해서 LMK 활동이 매우 많아집니다. (예: 안드로이드 시스템 자체의 메모리 부족으로 앱 버벅임, 갑자기 음악재생 종료 및 다른 어플 종료 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 32px;&quot;&gt;
&lt;td style=&quot;width: 16.0077%; height: 32px;&quot;&gt;업데이트 및 모듈화&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 51.124%; height: 32px;&quot;&gt;- 프로세스 경계가 API 역활을 해버림 즉 팀별 독립 개발 및 버전 차등 적용하기에 편리함&lt;/td&gt;
&lt;td style=&quot;width: 32.8682%; height: 32px;&quot;&gt;- 실시간 프로세스간 통신이 필요할때 IPC를 사용해야할때, 꾸준한 인터페이스 유지보수 필요합니다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;7814&quot; data-start=&quot;7778&quot; data-ke-size=&quot;size23&quot;&gt;멀티 프로세스 사용 실제 사례&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예시로 카카오톡 어플이 있습니다. 카카오톡은 멀티 프로세스 구조를 사용하고 있는데 메인 UI 전용 프로세스와 푸시 서비스전용 프로세스가 함께 존재합니다. (그외에도 있지만 언급은 하지 않겠습니다.) UI 전용 프로세스가 죽어도 푸시 서비스 전용 프로세스로 인해서 메인 UI 전용 프로세스가 죽어도 푸시 서비스 로직이 잘 유지될 수 있도록 합니다. 그래서 푸시 서비스에 관한 OOM 및 crush 을 잘 유지하도록 한 것입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카카오톡도 푸시도 FCM으로 보낼텐데 왜 굳이 분리 프로세스를 했을까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FCM 은 내 앱이 죽어도 데이터 메시지에 저장해 둡니다. 하지만 바로 실행되지 않을 수 있으며, 엄격한 Doze 제한이 걸린 기기는 오랜 지연이 걸릴 수있습니다. 즉, 상용 레벨에서 요구하는 신뢰성을 만족하려면 별도의 프로세스를 유지해서 빠른 실시간성 및 소켓 및 알림은 살아 있도록 컨트롤 했어야 할겁니다.&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;7778&quot; data-end=&quot;7814&quot; data-ke-size=&quot;size23&quot;&gt;프로세스 추적 및 디버깅 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 프로세스 상태를 추적하고 생명주기 이벤트를 확인하면 많은 도움이 됩니다. 안드로이드에서는 ADB 명령어나 로그를 통해 프로세스 정보를 확인 할 수 있습니다. 현재 실행 중인 프로세스 나열 (abs shell ps): ADB 쉘에서 ps 명령으로 현재 실행중인 모든 프로세스를 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746081579412&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ adb shell ps | grep com.example.myapp
u0_a123   10932  ...  com.example.myapp
u0_a123   11389  ...  com.example.myapp:myservice&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;위 출력 예시에서, com.example.myapp (메인 프로세스)와 com.example.myapp:myservice (별도 서비스 프로세스) 두 개가 실행 중임을 알 수 있습니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;life-1662879_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;913&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFjPIW/btsNIRmtAea/LzQFcBjdhBwWGgfsw7SwT0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFjPIW/btsNIRmtAea/LzQFcBjdhBwWGgfsw7SwT0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFjPIW/btsNIRmtAea/LzQFcBjdhBwWGgfsw7SwT0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFjPIW%2FbtsNIRmtAea%2FLzQFcBjdhBwWGgfsw7SwT0%2Fimg.jpg&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;526&quot; height=&quot;375&quot; data-filename=&quot;life-1662879_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.&amp;nbsp; 액티비티 생명주기 (Activity Lifecycle)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity는 안드로이드 앱의 사용자와 상호 작용할 수 있는 진입 지점 입니다. (단순 UI 화면으로 보면 안된다 생각합니다.) 액티비티 생명주기는 앱이 상호작용하는 동안 화면 전환, 일시정지, 재개, 종료 등의 과정을 관리합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;onCreate() - &lt;/b&gt;액티비티 생성 초기화 단계 입니다. UI는 xml 및 View class 사용시에는 setContnetView() 를 설정해서 사용하고 Compose 사용 시&amp;nbsp; setContent {} 를 사용합니다.&amp;nbsp; savedInstanceState 를 통해 프로세스가 종료 시키기 이전 상태 복원도 가능합니다만 적은 용량만 가능한 한계가 있습니다. Compose 의 경우 rememberSaveable 를 통해 번들에 저장할 수 있습니다. aac ViewModel을 쓴다면 SavedStateHandle을 통해서 번들 값을 가져올 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onStart() -&amp;nbsp;&lt;/b&gt;액티비티가 화면에 보이기 시작하는 단계입니다. 아직 포커스를 갖지 않은 상태지만 사용자에게 UI가 보이게 되는 위치입니다. 이 단계에서는 UI를 갱신하거나 필요한 리소스를 확보하는 작업을 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onResume()&amp;nbsp;&lt;/b&gt;- 액티비티가 포그라운드가 되어 사용자와 상호작용하기 바로 직전 호출됩니다. 이호출이 끝나면 액티비티는 화면에서 포커스를 얻고 사용자 입력을 받을 수 있게 됩니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onPause()&amp;nbsp;&lt;/b&gt;- 액티비티가 다른 화면에 일부 가려지거나 포커스를 잃을 때 호출 됩니다. 예를 들어, 반투명한 새로운 액티비티가 뜨거나 현재 액티비티 위에 다이얼로그 창이 뜬 경우 입니다. 이 단계에서는 사용자 입력을 바로 받을 수 없습니다. (실무적인 관점에서 봤을때 UI 관련 리스너 등록을 이 단계에서 해제 하는것이 안전했습니다. )&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onStop() -&amp;nbsp;&lt;/b&gt;액티비티가 완전히 화면에서 사라질 때 호출됩니다. 사용자가 다른 액티비티로 완전히 넘어가거나 홈 화면으로 나가는 경우 등 UI가 더이상 보이지 않게 된 상태입니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onReStart() - &lt;/b&gt;기존 액티비티가 onStop() 이었다가 다시 재개(Resume) 되었을 경우 호출 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onDestory() &lt;/b&gt;- 액티비티가 종료되어 소멸되기 직전에 호출됩니다. finish() 를 통해 액티비티를 종료하거나 이전 액티비티로 돌아가기 위해 뒤로가기 버튼을 누르면 돌아가기전 액티비티의 onDestory()가 호출되기도 합니다. (시스템이 프로세스를 바로 죽여버리는 경우 onDestory()가 호출되지 않을 수 잇습니다. )&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ A &amp;rarr; B 로 화면 전환&lt;/p&gt;
&lt;pre id=&quot;code_1746084117231&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;A.onPause()        // A는 더 이상 입력 포커스를 갖지 않음
B.onCreate()
B.onStart()
B.onResume()       // B가 화면&amp;middot;포커스 모두 갖춤
A.onStop()         // A는 완전히 가려져 보이지 않음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A.onPause() - A.onStop() 사이에 오래 걸리는 I/O를 메인 스레드에서 수행 하면 UI 지연이 발생 합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;A가 singleInstance 등이 아니라면 프로세스 안에 그대로 남아 있으며, 메모리가 부족해야만 시스템이 A.onDestroy() 를 호출 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ B &amp;rarr; 뒤로가기 (Back) &amp;rarr; A 복귀&lt;/p&gt;
&lt;pre id=&quot;code_1746084343238&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;B.onPause()        // B는 포커스 상실
A.onRestart()      // 멈춰 있던 A가 다시 화면에 드러날 준비
A.onStart()
A.onResume()       // A가 다시 포커스 획득
B.onStop()
B.onDestroy()      // 기본 back stack 정책: B 인스턴스 제거&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;A.onResume() 이 호출이 되야만 B.onStop() 이 호출이 됩니다.&lt;/li&gt;
&lt;li&gt;A 액티비티가 onStop() 상태로 있었다가 B 액티비티의 뒤로가기로 인해 A가 다시 재개되서 onRestart() 가 호출됩니다. 즉, onRestart() 는 onStop() 에서 다시 재개 되어야만 호출 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Jetpack Lifecycle로 생명주기 관리 하는법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jetpack Lifecycle가 제공하는 세 가지 API를 통해 다양하게 생명주기를 관리 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746109858448&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation &quot;androidx.lifecycle:lifecycle-runtime-ktx:2.8.6&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;494&quot; data-start=&quot;448&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1️⃣ 클래스 단위로 관리 &amp;mdash; DefaultLifecycleObserver&lt;/p&gt;
&lt;p data-end=&quot;494&quot; data-start=&quot;448&quot; data-ke-size=&quot;size16&quot;&gt;원하는 생명주기 메서드를 override 해서 사용할 수 있습니다. 클래스 단위로 로직을 분리 할 수 있어서 Activity의 가독성을 해치지 않습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746109981871&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LifecycleObserverTest(
    context: Context
) : DefaultLifecycleObserver {                       
    override fun onResume(owner: LifecycleOwner) {   
        //..
    }
    override fun onPause(owner: LifecycleOwner) {
        //..
    }
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(..) {
        //..
        lifecycle.addObserver(LifecycleObserverTest(this))
    }
}&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;br /&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;2️⃣ 람다 한 줄로 끝 &amp;mdash; LifecycleEventObserver&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다로 심플하게 설정할 수 있고 람다안에서 원하는 생명주기를 분기처리 할 수 있습니다. (Lifecycle.Event... 사용)&lt;/p&gt;
&lt;pre id=&quot;code_1746110331272&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val quickLog = LifecycleEventObserver { _, event -&amp;gt;
    if (event == Lifecycle.Event.ON_CREATE) {
        Log.d(&quot;Lifecycle&quot;, &quot;Activity 방금 생성됨&quot;)
    }
}
lifecycle.addObserver(quickLog)&lt;/code&gt;&lt;/pre&gt;
&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;&lt;br /&gt;3️⃣ 코루틴&amp;middot;Flow와 함께 &amp;mdash; repeatOnLifecycle&lt;br /&gt;코루틴 Flow 를 이용해서 가능합니다. 특정 라이프 사이클에 맞게 Flow collect을 시작합니다. 아래 예제는 STARTED (onStart()) ~ STOPPED(onStop()) 사이에서 사용됩니다. STARTED 상황 일때 블록이 재실행 되고 STOPPED 이면 자동으로 취소 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746110548228&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lifecycleScope.launch {
// STARTED~STOPPED 사이만 실행
    repeatOnLifecycle(Lifecycle.State.STARTED) {     
        viewModel.uiState.collect { state -&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;4️⃣ &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Compose 에서의 처리 &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&amp;mdash; &lt;span&gt;LifecycleEventEffect, LifecycleResumeEffect, LifecycleStartEffect&lt;/span&gt;&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;&lt;span&gt;Composable 에서 쉽게 API 를 통해 라이프 사이클을 관리할 수 있습니다. 이 부분은 본문 4. Compose LifeCycle 관리에서 다뤄보도록 하겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;7778&quot; data-end=&quot;7814&quot; data-ke-size=&quot;size23&quot;&gt;상태 저장과 복원 (onSaveInstanceState &amp;amp; onRestoreInstansceState)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액티비티는 일시적으로 파괴되었다가 (onDestory()) 나중에 다시 생성될 수 있습니다. 대표적인 경우가 화면 회전이며 이때 기존 액티비티는 파괴되고 새로운 액티비티 인스턴스가 생성됩니다.&amp;nbsp; 이 과정에서 UI 상태가 모두 사라집니다. 이를 위해 별도의 상태저장/복원이 필요합니다.&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;onSaveInstanceState()&lt;/b&gt; &amp;ndash; 시스템이 액티비티를 파괴해야 할 때를 대비해서 앱이 백그라운드 상태에 진입하면 현재 상태를 Bundle에 저장합니다. 기본 View 계층의 상태(입력된 텍스트, 체크박스 체크 여부 등)를 자동 저장합니다. (컴포즈는 예외 입니다.) Custom View 나 별도의 상태는 이 메서드에서 outState.putXXX 형태로 직접 저장해야 합니다. (컴포즈는 rememberSaveable 사용) 이 메서드는 Android 8.1 이하에서는 onPause() Android 8.1 이상은 onStop() 이후에 호출 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 100px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff; text-align: start;&quot;&gt;OS 버전&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;b&gt;전형적 호출 순서&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;b&gt;&amp;nbsp;왜 이렇게?&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;height: 40px;&quot;&gt;&lt;b&gt;API 1 &amp;ndash; 27 (Android 8.1 Oreo&amp;darr;)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 40px;&quot;&gt;onPause() &amp;rArr; onSaveInstanceState() &amp;rArr; onStop()&lt;/td&gt;
&lt;td style=&quot;height: 40px;&quot;&gt;화면이 아직 Stopped 되기 전에 UI 트리에 접근할 기회를 주기 위해&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;height: 40px;&quot;&gt;&lt;b&gt;API 28 + (Android 9 Pie&amp;uarr;)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 40px;&quot;&gt;onPause() &amp;rArr; onStop() &amp;rArr; onSaveInstanceState()&lt;/td&gt;
&lt;td style=&quot;height: 40px;&quot;&gt;최신 다중-윈도우/제스처 전환 최적화. UI 가 이미 Stopped 상태라서 &amp;ldquo;UI 수정 금지 구간&amp;rdquo;이 짧아짐. oaicite:0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;시나리오&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;onSaveInstanceState() 발생 여부&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;홈 버튼 / 최근 앱으로 나가기&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;O (대부분)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;회전&amp;middot;다크모드 등 구성 변경&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;멀티 윈도우 전환&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;finish() 호출 / Back 버튼으로 완전 종료&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;X&lt;/b&gt; (돌아올 가능성이 없다고 판단) oaicite:1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;시스템이 kill 하기 직전&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;이미 호출돼 있었어야 함 (직전 호출 X)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;onRestoreInstanceState()&amp;nbsp;&lt;/b&gt;- 저장된 상태가 있을 경우 onStart() 직후에 호출 되어 onSaveInstanaceState 에 넣어둔 값을 꺼내와 뷰나 변수에 복원합니다. 이 메서드를 구현하지 않더라도 onCreate()의 savedInstanceState 파라미터를 통해 같은 Bundle을 받을 수 있도 있습니다. 일반적으로는 onCreate()에서 상태를 복원하거나 필요한 경우에만 onRestoreInstanceState를 별도로 오버라이드합니다.&amp;nbsp; (compose 같은 경우 rememberSaveable 에 있어서 개발자가 세팅한대로 UI와 상태가 자동 매칭 됩니다.)&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;lego-3602218_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xBauE/btsNIOceLqm/IipZCZCLarKJXAUaytztzk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xBauE/btsNIOceLqm/IipZCZCLarKJXAUaytztzk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xBauE/btsNIOceLqm/IipZCZCLarKJXAUaytztzk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxBauE%2FbtsNIOceLqm%2FIipZCZCLarKJXAUaytztzk%2Fimg.jpg&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;552&quot; height=&quot;368&quot; data-filename=&quot;lego-3602218_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. &lt;span&gt;&amp;nbsp;&lt;/span&gt;Compose&amp;nbsp; LifeCycle 관리&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-end=&quot;7814&quot; data-start=&quot;7778&quot;&gt;Composable 에서 Activity Lifecycle 관리법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 안드로이드 앱개발에서 Jetpack Compose 를 사용시 Activity = 셋업 + 네비게이션 호스트만 두고, 컴포저블은 Side effect API 로 컴포저블 내에서 자신의 수명에 맞춰 일을 하고 정리합니다.&amp;nbsp; 이 조합이면 Activity 의 onStart() 및 onStop() 콜백을 직접 건들일은 없습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API&amp;nbsp;&lt;/td&gt;
&lt;td&gt;언제 쓰나&amp;nbsp;&lt;/td&gt;
&lt;td&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LifecycleEventEffect(Lifecycle.Event)&lt;/td&gt;
&lt;td&gt;특정 라이프 사이클 이벤트에 맞춰 side-effect 실행&lt;/td&gt;
&lt;td&gt;LifecycleEventEffect(ON_START){ &amp;hellip; }&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LifecycleStartEffect / LifecycleResumeEffect&lt;/td&gt;
&lt;td&gt;(ON_START&amp;harr;ON_STOP / ON_RESUME&amp;harr;ON_PAUSE)을 다룰 때&lt;/td&gt;
&lt;td&gt;시작 블록 + onStopOrDispose{} 정리 블록 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DisposableEffect(key)&lt;/td&gt;
&lt;td&gt;컴포저블 진입/탈출 시 초기화&amp;middot;해제 코드를 한쌍으로&lt;/td&gt;
&lt;td&gt;전통적인 &amp;ldquo;add listener &amp;rarr; remove listener&amp;rdquo; 자리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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: #efefef; color: #333333; text-align: start;&quot;&gt;LifecycleEventEffect(Lifecycle.Event)&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;특정 라이프 사이클이 호출될 때 마다 composable 내에서 작업을 수행 하도록 합니다. Lifecycle.Event 의 상태를 파라미터로 넘겨서 원하는 라이프 사이클로 고정 해놓고 사용합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746087082057&quot; class=&quot;reasonml&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;@Composable
fun TestScreen() {
    // onStart() 일때만 호출
 	LifecycleEventEffect(Lifecycle.Event.ON_START) {
        Log.d(tag, &quot;ON_START observed at &quot; + System.currentTimeMillis())
    }
}&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;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;LifecycleStartEffect&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onStart() 가 호출될때마다 사용됩니다. 작업 구문 내부에 onStopOrDispose() 로 onStop() 이 호출할 때의 수행할 작업을 등록 해놓을 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746087170849&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun TestScreen() {
    val context = LocalContext.current

    // onStart() 일때 수행
    // key1 은 필수. 변화가 없으면 Unit 써도 됨
    LifecycleStartEffect(key1 = Unit) {            
        val receiver = object : BroadcastReceiver() {
            override fun onReceive(c: Context, i: Intent) {
                //...
            }
        }
        val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
        context.registerReceiver(receiver, filter)

		//onStop 일때 아래 작업 수행
        onStopOrDispose {                          
            context.unregisterReceiver(receiver)
        }
    }

    /* UI 내용 &amp;hellip; */
}&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;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;LifecycleResumeEffect&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;onResume() 가 호출될때마다 사용됩니다. 작업 구문 내부에 onPauseOrDispose() 로 onPause() 가 호출할 때의 수행할 작업을 등록 해놓을 수 있습니다.&lt;/span&gt;&lt;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746087249138&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun VideoPlayer(
    viewModel: PlayerViewModel = viewModel()
) {
    val context = LocalContext.current
    
    // ExoPlayer 인스턴스
    val player = remember(context) {             
        ExoPlayer.Builder(context).build()
    }

	//onResume() 일때 실행
    LifecycleResumeEffect(key1 = player) {       
        player.play()
		
         // onPasue() 일때 아래 작업 수행
        onPauseOrDispose {                      
            player.pause()
        }
    }

    AndroidView(factory = { PlayerView(context).apply { this.player = player } })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #efefef; color: #333333; 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;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;DisposableEffect(key)&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;인자로 받는 key 의 상태 변화를 기반으로 작업을 수행합니다.&amp;nbsp; 구문 내의 onDispose 로 인해 컴포저블이 해제 될때 수행할 작업도 같이 등록 해놓을 수 있습니다.&lt;/span&gt;&lt;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746087568190&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun CameraScreen(cameraEnabled: Boolean) {

    // key 의 상태가 바뀔 때마다 DisposableEffect 작업을 수행합니다.
    // onDispose 는 해당 컴포저블이 사라질 때 작업이 수행됩니다.
    DisposableEffect(cameraEnabled) {
        if (cameraEnabled) {
            camera.open()          
        }
        
        onDispose {                  
            camera.close()
        }
    }
}

...
//Unit 으로 키를 주면 &amp;ldquo;컴포저블이 사라질 때 한 번만 해제&amp;rdquo; 패턴으로 쓸 수 있습니다.
DisposableEffect(Unit) {
    onDispose {
        // 정리(Dispose) &amp;ndash; 등록 해제&amp;middot;리소스 닫기
    }
}&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Compose 의 네비게이션 (화면 이동, 백스택 등)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Compose 는 Single Activity 을 지향하고 UI는 Compose 로 구성하는 것을 구글에서 권장합니다. 그래서 액티비티 간의 화면 이동에 따른 LifeCycle 을 계산해야했던 것 과 달리 Compose 간의 화면 이동에 주로 신경을 쓰면 됩니다. 그리고 각 Composable 화면간의 생명주기는 NavHost가 관리 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746089672630&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun NavHost(
    navController: NavHostController,
    startDestination: String,
    modifier: Modifier = Modifier,
    route: String? = null,                 // 이 NavHost 자체를 중첩 그래프로 취급할 때
    builder: NavGraphBuilder.() -&amp;gt; Unit    // 이 블록 안에서 'composable', 'navigation' 호출
)&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;NavHost는&amp;nbsp; NavController 가 들고 있는 백스택을 관찰하면서 Composable를 실제로 화면에 렌더링 해주는 View Layer 입니다. 즉, 어떤 Route를 보여줄지 결정하고 Coposition 트리를 교체하는 역할을 맡습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아래는 예시 코드입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746089129212&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { AppRoot() }
    }
}

@Composable
fun AppRoot() {
    val navController = rememberNavController()
    val windowSize = calculateWindowSizeClass(LocalContext.current as Activity)
    val stateHolder = rememberSaveableStateHolder()

    MaterialTheme {
        // Bottom-Nav 예시: 화면마다 독립 State 보존
        Scaffold(
            bottomBar = { BottomBar(navController) }
        ) { innerPadding -&amp;gt;
            NavGraph(
                navController = navController,
                stateHolder = stateHolder,
                modifier = Modifier.padding(innerPadding)
            )
        }
    }
}&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 data-start=&quot;1431&quot; data-end=&quot;1479&quot;&gt;&lt;b&gt;calculateWindowSizeClass&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 태블릿/폴더블 대응.&lt;/li&gt;
&lt;li data-start=&quot;1480&quot; data-end=&quot;1592&quot;&gt;&lt;b&gt;rememberSaveableStateHolder&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;탭 간 전환&lt;/b&gt;에도 각 화면의 스크롤&amp;middot;폼 입력을 따로 보존&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NavController 는 화면 전환, 백스택, 딥링크, 상태 복원을 모두 맡는 싱글 인스턴스 객체입니다. NavController 가 현재 어떤 경로로 되어있는지에 따라 해당 화면을 렌더링 하고 NavController 를 이용해서 화면을 전환하기도 합니다. 위&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;코드는 구성 변경에도 같은 인스턴스가 유지될 수 있도록 rememberNavController() 를 통해 navController를 생성해서 navController를 NavGraph로 넘깁니다.&lt;br /&gt;&lt;br /&gt;그외 작업은 바텀 네비게이션 바를 세팅하거나 ui 규칙 Base를 만듭니다.&amp;nbsp;&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_1746089216984&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed interface Screen {
    val route: String
    data object Home : Screen { override val route = &quot;home&quot; }
    data object Detail : Screen { override val route = &quot;detail/{id}&quot; }
    // ...
}

@Composable
fun NavGraph(
    navController: NavHostController,
    stateHolder: SaveableStateHolder,
    modifier: Modifier = Modifier
) {
    NavHost(
        navController = navController,
        startDestination = Screen.Home.route,
        modifier = modifier
    ) {
        composable(Screen.Home.route) {
            // ─── key = route ───┐
            stateHolder.SaveableStateProvider(Screen.Home.route) {
                HomeScreen(
                    onOpenDetail = { id -&amp;gt; navController.navigate(&quot;detail/$id&quot;) }
                )
            }
        }
        composable(
            route = Screen.Detail.route,
            arguments = listOf(navArgument(&quot;id&quot;) { type = NavType.LongType })
        ) { backStackEntry -&amp;gt;
            val id = backStackEntry.arguments!!.getLong(&quot;id&quot;)
            DetailScreen(id = id)
        }
    }
}&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;NavHost 내에서 어떤 composable 의 렌더링을 세팅할지 정하고 navController 를 이용해 화면 전환 리스너 세팅을 해줍니다. 그리고 stateHolder 로 감싸 HomeScreen 을 호출하고 있는데 이는 탭 간 전환에도 화면의 스크롤 및 폼 입력을 보존하기 위함입니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;전체 ui 구조 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1746089076720&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MainActivity
   └─ setContent { AppRoot() }
          ├─ rememberNavController()
          └─ Scaffold
               ├─ BottomBar(navController)    &amp;larr; ③
               └─ NavHost(navController)
                    ├─ HomeScreen            &amp;larr; route &quot;home&quot;
                    └─ DetailScreen          &amp;larr; route &quot;detail/{id}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이렇게 네비게이션으로 Composable 간의 화면 전환을 관리하면서 액티비티 간 Lifecycle 에 대한 고민이 많이 사라졌고 컴포저블의 생명주기는 컴포지션 트리 진입 및 탈퇴에 따른 구조라서 매우 단순하기 때문에 ui 개발하기가 편리해졌습니다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Compose 의 라이프 사이클&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 탄생 (Composition)&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;Composable 함수가 처음 호출되는 부분 입니다.&lt;/li&gt;
&lt;li&gt;remember 로 초기값이 만들어지고, 코루틴 및 리스너를 담을 Effect훅이 준비됩니다.&lt;/li&gt;
&lt;li&gt;이때 Recomposer가 어떤 State를 읽었는지 의존 관계를 기록해둡니다.&lt;/li&gt;
&lt;li&gt;마지막 순서로 ui가 그려집니다.&amp;nbsp; &lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 변화 (Recomposition)&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;State 값이 바뀌면 Recomposer가 먼저 &quot;변한 State -&amp;gt; 영향을 받는 Composable&quot; 관계를 찾아냅니다.&lt;/li&gt;
&lt;li&gt;해당 Composable만 다시 실행해 레이아웃/드로우를 갱신하고 나머지를 건드리지 않습니다.&lt;/li&gt;
&lt;li&gt;remember 의 메모리 재할당 없이 블록이 실행되지 않으며 UI는 불필요한 재갱신을 하지 않아 오버헤드가 최소화됩니다.&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 퇴장 (Disposal)&lt;/b&gt; : 뒤로가기&amp;middot;탭 전환처럼 화면에서 사라질 때 정리한다.&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;외부 컴포저블의 recomposition 에 의한 상태 변화시 조건문 분기에서 제외 될때 Disposal 이 발생할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1746108938740&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun Counter() { 
//...
}

fun Test() {
	var show by remember { mutableStateOf(true) }

    //show 가 true 였다가 false 로 바뀌면 Disposal 상태 
	if (show) Counter()   
}&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개의 흐름을 가지고 있으며 [탄생 -&amp;gt; 퇴장] or [탄생 -&amp;gt; 여러번 변화 -&amp;gt; 퇴장] 등 초기 컴포지션 이후에 상태값에 따라 생명주기가 흘러갑니다. 그만큼 기존 View Class 와 달리 상태값 관리에 신경을 많이 써야 할겁니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;Compose 의 구성 변경을 대비한 Bundle 저장&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;언제 써야 하나?&lt;/td&gt;
&lt;td&gt;&amp;nbsp;대표 예시&lt;/td&gt;
&lt;td&gt;&amp;nbsp;권장 API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;한-화면(UI) 전용&amp;middot;일시적 상태&lt;/b&gt; (스크롤 위치, 입력 중 텍스트 등)&lt;/td&gt;
&lt;td&gt;LazyListState&amp;middot;TextField 내용&lt;/td&gt;
&lt;td&gt;rememberSaveable / rememberSaveableStateHolder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;화면 간 공유하거나 장수 해야 하는 상태&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;편집 중 폼, 페이지네이션 결과&lt;/td&gt;
&lt;td&gt;ViewModel (+ SavedStateHandle)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. UI 전용 상태 rememberSaveable 를 사용하면 SavedInstanceState 에&amp;nbsp; 저장하기 때문에 rememberSaveable 은 프로세스가 완전히 죽은 뒤 재시작할 때도 복원됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746090848027&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var query by rememberSaveable { mutableStateOf(&quot;&quot;) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1062&quot; data-start=&quot;934&quot; data-ke-size=&quot;size16&quot;&gt;2. 다중 화면(Navigation) 상황에서 rememberSaveableStateHolder 를 사용하면 route 키 별로 Bundle 을 관리해줘서 탭을 왕복해도 각 탭의 스크롤 위치 및 입력값이 유지됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1746091069522&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val holder = rememberSaveableStateHolder()
holder.SaveableStateProvider(route) {
    ScreenContent()   // 내부에서 rememberSaveable 사용 가능
}&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. viewModel에 번들 저장하기는 ViewModel + SavedStateHandle 을 통해 가능합니다. ViewModel은 재구성과 무관 하므로 프로세스가 살아있는 한 유지 되어야 할 상태에 적합합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746091087758&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DetailViewModel(state: SavedStateHandle) : ViewModel() {
    var uiState by mutableStateOf(state[&quot;ui&quot;] ?: UiState())
    override fun onCleared() { state[&quot;ui&quot;] = uiState }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Activity Lifecycle</category>
      <category>android 개발</category>
      <category>compose lifecycle</category>
      <category>안드로이드 compose</category>
      <category>안드로이드 개발</category>
      <category>안드로이드 생명주기</category>
      <category>안드로이드 액티비티</category>
      <category>안드로이드 컴포즈</category>
      <category>액티비티 생명주기</category>
      <category>컴포즈 생명주기</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/92</guid>
      <comments>https://gift123.tistory.com/92#entry92comment</comments>
      <pubDate>Fri, 2 May 2025 00:05:34 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (45) Alarm Manager 심층 분석</title>
      <link>https://gift123.tistory.com/91</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 알람 어플리케이션의 꽃 Alarm Manager 에 대해 알아보기전에 두가지에 대해 먼저 알아보겠습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Doze 모드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Doze 는 기기의 충전선을 뽑고 화면을 끈채 기기를 사용하지 않으면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;nbsp;Android가 배터리를 아끼기 위해 백그라운드 작업을 단계적으로 제한 하는 절전 모드&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;입니다. 배터리를 아끼려고 네트워크,CPU,일반 알람,Job/WorkManager 작업을 전부 잠시 미뤄둡니다. 시간이 지나면 잠깐 깨워 ( maintenance window ) 밀린 일만 몰아서 처리하고 다시 잠듭니다.&lt;br /&gt;&lt;br /&gt;Doze는 두가지 진입 단계가 있습니다.&lt;br /&gt;1)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Light Doze&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(화면 OFF 직후 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;+ 충전X&lt;/span&gt;)&amp;nbsp;&lt;br /&gt;Light Doze 는 네트워크 차단, Job/Sync 작업을 연기 하고 몇 분 간격으로 한꺼번에 처리하는 유지와 해제 주기를 반복 합니다.&amp;nbsp;&lt;br /&gt;즉, N 분 간격(기기별 1~15 분) 유지,&amp;nbsp; 스마트폰 화면을 켜면 Doze 가 해제가 됩니다. ​&amp;nbsp;&lt;br /&gt;&lt;br /&gt;2)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Deep Doze&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(보통 화면 OFF&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;30분 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+ 충전X&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+ 폰 이동 정지(가속도계 무동작)&lt;/span&gt;&lt;/span&gt;) -&amp;nbsp;&lt;br /&gt;위 Light Doze 제약을 가지고 있으면서 Doze 유지 - 해제 간격이 배수로 증가합니다. (30분 -&amp;gt; 60분 -&amp;gt; 120분..)&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 테스트 진행 시 Doze 진입 방법 (usb 연결 기기 or 에뮬레이터)&lt;/p&gt;
&lt;pre id=&quot;code_1745993416222&quot; class=&quot;dockerfile&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#1. 충전선 뽑힌 상태인지 확인
adb shell dumpsys battery unplug

#2. 강제로 Deep Doze 진입
adb shell cmd deviceidle force-idle

#3. 현재 상태 확인
adb shell dumpsys deviceidle | head -n 20

#4. 테스트 후 정상 복귀
adb shell cmd deviceidle unforce&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;위와 같은 방법으로 Android Studio에서 Doze 상태를 테스트할 수 있습니다.&lt;br /&gt;Logcat을 통해 Doze 진입 시점과 예약된 알람이 Doze를 해제하고 실제로 알람이 트리거된 시점을 확인하여, 예약한 알람 시간과의 오차 범위를 파악할 수 있습니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;igromania-1894847_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lI7Wd/btsNEnAKNs0/YgV0JZZWDUxNeymF7uZ8ik/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lI7Wd/btsNEnAKNs0/YgV0JZZWDUxNeymF7uZ8ik/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lI7Wd/btsNEnAKNs0/YgV0JZZWDUxNeymF7uZ8ik/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlI7Wd%2FbtsNEnAKNs0%2FYgV0JZZWDUxNeymF7uZ8ik%2Fimg.jpg&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;582&quot; height=&quot;388&quot; data-filename=&quot;igromania-1894847_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;제조사 별 제약&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 OS는 다양한 기기에서 사용이 됩니다. 하지만 안드로이드 OS 기반으로 하드웨어 제조사에 따라 프로세스 관리가 다른 경우가 있습니다. 대체로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;백그라운드 프로세스를 강제로 종료하거나 브로드캐스트를 차단 하는 경우&lt;/b&gt;가 있다고 합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;대외적으로 알려진 사실은 다음과 같습니다.&amp;nbsp;&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 style=&quot;text-align: center; width: 16.2791%;&quot;&gt;&lt;b&gt;제조사&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 39.5349%;&quot;&gt;&lt;b&gt;특징적인 제약&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 44.0698%;&quot;&gt;&lt;b&gt;해결을 위한 사용자 가이드&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.2791%;&quot;&gt;Xiaomi&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 39.5349%;&quot;&gt;&amp;ldquo;Battery saver&amp;rdquo;, &amp;ldquo;MIUI Optimization&amp;rdquo;, &amp;ldquo;AutoStart&amp;rdquo; 스위치가 3중으로 백그라운드 브로드캐스트를 막아 알람 미전달 사례 다수&lt;/td&gt;
&lt;td style=&quot;width: 44.0698%;&quot;&gt;앱 첫 실행 시 ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 인텐트로 화이트리스트 안내,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AutoStart 설정 페이지 딥링크&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;제공​&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.2791%;&quot;&gt;Huawei&lt;/td&gt;
&lt;td style=&quot;width: 39.5349%;&quot;&gt;PowerGenie&amp;middot;System Manager가 화면 OFF 상태 앱을 1~3 분 내 강종&lt;/td&gt;
&lt;td style=&quot;width: 44.0698%;&quot;&gt;배터리 사용량 최적화&amp;rdquo; &amp;rarr; &amp;ldquo;모든 앱&amp;rdquo; &amp;rarr; 앱 선택 후 &amp;lsquo;허용&amp;rsquo;을 유도. 푸시 알람은 FCM high-priority로 대체​&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.2791%;&quot;&gt;Samsung (One UI)&lt;/td&gt;
&lt;td style=&quot;width: 39.5349%;&quot;&gt;셋팅 -&amp;gt; 사용하지 않는 앱 관리로 인한 프로세스 사망&lt;/td&gt;
&lt;td style=&quot;width: 44.0698%;&quot;&gt;ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 외에도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;ldquo;앱 사용 중지 안함&amp;rdquo;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;설정 위치 안내 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;정확한 알람 기능이 어떤 상황에서든지 잘 동작하기 바란다면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Alarm Manager 가 Doze 를 어떻게 깨워야 할지 확실히 알고 대처를 하거나, 제조사에 따른 사용자 가이드를 제공하거나 Alarm Manager 세팅 외에 다양한 준비&lt;/b&gt;가 필요합니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1.&amp;nbsp; &amp;nbsp;AlarmManager 의 강력한 특징&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 data-start=&quot;113&quot; data-end=&quot;162&quot;&gt;앱의 생명주기를 완전히 벗어나도 알람이 가능합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;113&quot; data-end=&quot;162&quot;&gt;setAlarmClock() 및 setExactAndAllowWhileIdle() 사용시 앱이 죽어도 OS가 지정 시점에 인텐트를 전달이 가능합니다.&lt;/li&gt;
&lt;li data-start=&quot;113&quot; data-end=&quot;162&quot;&gt;다만, Android 13 이후 부터&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SCHEDULE_EXACT_ALARM 혹은 USE_EXACT_ALARM&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;권한을 허용해서 사용해야 Doze 상태에서 Doze 제한을 무시하는 알람 등록 (setAlarmClock(), setExactAndAllowWhileIdle() )을 사용하할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-start=&quot;238&quot; data-end=&quot;328&quot;&gt;시계/캘린더/타이머 같은 실제 시간 맞춤: 배터리,네트워크 제약 안에서 정확 해야 할 알람은 여전히 AlarmManager가 유일한 옵션 입니다.&lt;/li&gt;
&lt;li data-end=&quot;162&quot; data-start=&quot;113&quot;&gt;일반 앱이면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SCHEDULE_EXACT_ALARM&amp;nbsp;&lt;/b&gt;을 사용자에게 권한 허용을 얻어야 합니다. (정밀 알람 및 doze 에서 깨어나는 api 쓰고자 한다면)&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;162&quot; data-start=&quot;113&quot;&gt;알람 관련 앱이면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;USE_EXACT_ALARM&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;권한을 자동으로 세팅할 수 있습니다. (단, 구글 스토어에서 알람앱으로 인정 받을 경우 활성화 됩니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;clocks-257911_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;905&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKmmTO/btsNFY7k7C8/Ur5PaT7KYihmFYPVnS0Mg1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKmmTO/btsNFY7k7C8/Ur5PaT7KYihmFYPVnS0Mg1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKmmTO/btsNFY7k7C8/Ur5PaT7KYihmFYPVnS0Mg1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKmmTO%2FbtsNFY7k7C8%2FUr5PaT7KYihmFYPVnS0Mg1%2Fimg.jpg&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;458&quot; height=&quot;324&quot; data-filename=&quot;clocks-257911_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;905&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2.&amp;nbsp; 알림 타입 4종&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래는 알람 타입에 관한 내용 입니다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 95px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;height: 19px; text-align: center;&quot;&gt;&lt;b&gt;타입&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;절전 모드 무시&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;기준 시계&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;주 사용처&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ELAPSED_REALTIME&lt;/td&gt;
&lt;td&gt;&amp;nbsp;X&lt;/td&gt;
&lt;td&gt;부팅 이후 경과(ms)&lt;/td&gt;
&lt;td&gt;5 분 뒤 토큰 만료 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ELAPSED_REALTIME_WAKEUP&lt;/td&gt;
&lt;td&gt;O (디바이스 깨움)&lt;/td&gt;
&lt;td&gt;&amp;uarr;&lt;/td&gt;
&lt;td&gt;오프라인 백업 재시도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTC&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;UTC / 로컬 시간&lt;/td&gt;
&lt;td&gt;타이머(앱이 깨어있을 때만)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTC_WAKEUP&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;&amp;uarr;&lt;/td&gt;
&lt;td&gt;모닝콜 / 캘린더 알림&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;br /&gt;기기의 배터리를 생각하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;ELAPSED..&lt;/b&gt;&amp;nbsp;를 사용하고&amp;nbsp;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;정확한 시각이 정말 필요한 경우에만 RTC + WAKEUP 조합을 씁니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745938383937&quot; class=&quot;autohotkey&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;alarmManager.setInexactRepeating(
    RTC_WAKEUP,
    firstTriggerAt,
    AlarmManager.INTERVAL_DAY,

   // PendingIntent
    sender          
)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 setAlarmClock() 의 파라미터에 상수는 있지 않습니다. setAlarmClock 자체가 이미 RTC_WAKEUP 타입으로 동작하기 때문 입니다. 사용자에게 명확히 보이는 알람을 위한 API 이라서 이미 내부적으로 하나의 타입으로만 사용 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;folding-rule-1432941_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FDhFY/btsNFtfs7pS/DxScsB212fhn4ZsEq33QF0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FDhFY/btsNFtfs7pS/DxScsB212fhn4ZsEq33QF0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FDhFY/btsNFtfs7pS/DxScsB212fhn4ZsEq33QF0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFDhFY%2FbtsNFtfs7pS%2FDxScsB212fhn4ZsEq33QF0%2Fimg.jpg&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;706&quot; data-filename=&quot;folding-rule-1432941_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.&amp;nbsp; API 메서드 별 알람 정확도&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 API 메서드만 나열했습니다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 190px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 28.0233%; height: 19px;&quot;&gt;&lt;b&gt;메서드&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.9768%; height: 19px;&quot;&gt;정확도&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.7675%; height: 19px;&quot;&gt;Doze 지연 여부&lt;/td&gt;
&lt;td style=&quot;width: 7.32557%; height: 19px;&quot;&gt;반복 기능&lt;/td&gt;
&lt;td style=&quot;width: 19.4186%; height: 19px;&quot;&gt;대표 용도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 28.0233%; height: 38px;&quot;&gt;setExact()&lt;/td&gt;
&lt;td style=&quot;width: 11.9768%; height: 38px;&quot;&gt;&lt;b&gt;Exact&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.7675%; height: 38px;&quot;&gt;지연&lt;/td&gt;
&lt;td style=&quot;width: 7.32557%; height: 38px;&quot;&gt;✕&lt;/td&gt;
&lt;td style=&quot;width: 19.4186%; height: 38px;&quot;&gt;캘린더 리마인더(오차 수분 이내)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 28.0233%; height: 38px;&quot;&gt;setExactAndAllowWhileIdle()&lt;/td&gt;
&lt;td style=&quot;width: 11.9768%; height: 38px;&quot;&gt;&lt;b&gt;Exact&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.7675%; height: 38px;&quot;&gt;&lt;b&gt;즉시 실행&lt;/b&gt;(15개/24h 제한, android 12 ~ 14 인 경우 72회 /1h )&lt;/td&gt;
&lt;td style=&quot;width: 7.32557%; height: 38px;&quot;&gt;✕&lt;/td&gt;
&lt;td style=&quot;width: 19.4186%; height: 38px;&quot;&gt;모닝콜 등 시간 초 단위 보장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 28.0233%; height: 38px;&quot;&gt;setRepeating()&lt;/td&gt;
&lt;td style=&quot;width: 11.9768%; height: 38px;&quot;&gt;Inexact(19+)&lt;/td&gt;
&lt;td style=&quot;width: 14.7675%; height: 38px;&quot;&gt;지연&lt;/td&gt;
&lt;td style=&quot;width: 7.32557%; height: 38px;&quot;&gt;&lt;b&gt;O&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.4186%; height: 38px;&quot;&gt;매일 09:00 백업 &amp;rarr; WorkManager 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 28.0233%; height: 19px;&quot;&gt;setInexactRepeating()&lt;/td&gt;
&lt;td style=&quot;width: 11.9768%; height: 19px;&quot;&gt;Inexact&lt;/td&gt;
&lt;td style=&quot;width: 14.7675%; height: 19px;&quot;&gt;지연&lt;/td&gt;
&lt;td style=&quot;width: 7.32557%; height: 19px;&quot;&gt;&lt;b&gt;O&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.4186%; height: 19px;&quot;&gt;날씨 갱신&amp;middot;뉴스 동기화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 28.0233%; height: 38px;&quot;&gt;setAlarmClock()&lt;/td&gt;
&lt;td style=&quot;width: 11.9768%; height: 38px;&quot;&gt;&lt;b&gt;Exact&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.7675%; height: 38px;&quot;&gt;&lt;b&gt;즉시 실행&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 7.32557%; height: 38px;&quot;&gt;✕&lt;/td&gt;
&lt;td style=&quot;width: 19.4186%; height: 38px;&quot;&gt;사용자에게 &amp;ldquo;다음 알람&amp;rdquo; UI 노출&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;setAlaramClock() 이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;시간이 가장 정확하며 Doze 상관없이 즉시 실행 되는 특징&lt;/b&gt;이 있습니다. 하지만 그만큼 큰 단점이 존재합니다. 첫번째로 매우 강력한 api인 만큼 배터리 소모율이 매우 크기도 합니다. 두번째로 기본 UI 호출이 문제입니다. 상태바 아이콘이 자동으로 생겨버리거나 잠금화면에 다음 알림으로 생성이 됩니다. 심지어 일부 제조사에서는 시계 위젯에 까지 노출 됩니다. ( 사용자의 UX 를 중요시 하는 경우 골칫거리가 될 수 있습니다.)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setAlaramClock() 을 제외해서 다음 강력한 것은 setExactAndAllowWhileIdle() 입니다.&amp;nbsp; setExactAndAllowWhileIdle() 의 경우&lt;span&gt; &lt;/span&gt;&lt;b&gt;&amp;nbsp;1시간에 72회 호출로 제한&lt;/b&gt;되어 있습니다. 그리고&amp;nbsp; setAlarmClock() 과 달리 기본 ui가 생성되지 않습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;그외 지목할 점은 setRepeating 과 setInexactRepeating 입니다. 이 두가지 API 는 반복 기능이 있습니다. 하지만 정확한 시간 알람에 사용하기에는 정확도가 떨어집니다. 그래서 일정 주기 반복되는 알람을 만드려면 api 자체 기능을 쓰는 것이 아니라 1회 알람시 새로운 알람을 이어서 바로 예약해서 반복적으로 유지 시킬 수 있습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;the-eleventh-hour-3101625_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NtOOw/btsNFoSLD7S/bONJ2rKYFzt6yQDZxRe0OK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NtOOw/btsNFoSLD7S/bONJ2rKYFzt6yQDZxRe0OK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NtOOw/btsNFoSLD7S/bONJ2rKYFzt6yQDZxRe0OK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNtOOw%2FbtsNFoSLD7S%2FbONJ2rKYFzt6yQDZxRe0OK%2Fimg.jpg&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;517&quot; height=&quot;341&quot; data-filename=&quot;the-eleventh-hour-3101625_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.&amp;nbsp; 알람 반복 등록하기&lt;br /&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1745938383938&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class MorningAlarmReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        //1. 알람이 울렸을 때 할 일 // 예: 모닝콜 노티&amp;middot;사운드
        NotificationHelper.showMorningCall(context)  

        //2. 다음 알람 시각 계산
        val nextTriggerAt = ZonedDateTime.now()
            .plusDays(1)                         
            .withHour(8).withMinute(0)           
            .withSecond(0).withNano(0)
            .toInstant().toEpochMilli()

        //3. 같은 PendingIntent 재사용해 알람 재등록
        val alarmMgr = context.getSystemService(AlarmManager::class.java)

        val pi = PendingIntent.getBroadcast(
            context,
            Intent(context, MorningAlarmReceiver::class.java),
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        alarmMgr.setExactAndAllowWhileIdle(
            AlarmManager.RTC_WAKEUP,
            nextTriggerAt,
            pi
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;alarm manager&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;api 자체에 정확한 시간을 맞추는 반복 알람 기능이 없기 때문에 직접 알람을 재등록 해야 반복 알람 설정이 가능합니다&lt;/b&gt;. 그래서 일반 알람앱에 경우 onReceive 에서 첫번째로 사운드를 발생시키고 두번째로 notification 을 발생 시켜서 누르면 알람 화면으로 이동 시킵니다. 혹은 android.permission.SYSTEM_ALERT_WINDOW 을 사용자에게 허락 받아서&amp;nbsp; (사용자 설정 &amp;rarr; &amp;ldquo;다른 앱 위에 표시&amp;rdquo;) 잘 이용하는 방법 등등이 있을 겁니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;smartphone-2198559_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KrL5x/btsNFaOiahB/CnC2mVhphZR2yeoaKjOd0k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KrL5x/btsNFaOiahB/CnC2mVhphZR2yeoaKjOd0k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KrL5x/btsNFaOiahB/CnC2mVhphZR2yeoaKjOd0k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKrL5x%2FbtsNFaOiahB%2FCnC2mVhphZR2yeoaKjOd0k%2Fimg.jpg&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;441&quot; height=&quot;848&quot; data-filename=&quot;smartphone-2198559_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.&amp;nbsp; ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1745938383939&quot; class=&quot;dust&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;lt;!-- Manifest.xml --&amp;gt;
&amp;lt;uses-permission
    android:name=&quot;android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS&quot;/&amp;gt;

// 예시
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
    data = Uri.parse(&quot;package:$packageName&quot;)   
}
startActivity(intent)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 는 Android 6.0(API 23) 부터 추가된 시스템 Intent 액션입니다. 이 인텐트를 실행하면 OS가 배터리 사용량 최적화 제외(Doze&amp;middot;앱 대기 모드 무시) 권한을 사용자에게 직접 묻는 다이얼로그를 띄웁니다. 사용자가 &amp;ldquo;허용&amp;rdquo;을 탭하면 해당 앱이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Doze 절전 제약에서 제외&amp;nbsp;백그라운드 작업&amp;middot;정확한 알람&amp;middot;네트워크 I/O 등이 중단 없이 동작&lt;/b&gt;할 수 있습니다.&lt;br /&gt;&lt;br /&gt;하지만 ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 의 경우 구글 스토어는는 핵심 기능이 절전 차단이 없으면 제대로 작동 않음 인 앱만 허용합니다.&lt;/span&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;woman-7025093_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9bZLa/btsNEew8upq/XYPRohP5FE7kYRGWECapMk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9bZLa/btsNEew8upq/XYPRohP5FE7kYRGWECapMk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9bZLa/btsNEew8upq/XYPRohP5FE7kYRGWECapMk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9bZLa%2FbtsNEew8upq%2FXYPRohP5FE7kYRGWECapMk%2Fimg.jpg&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;471&quot; height=&quot;327&quot; data-filename=&quot;woman-7025093_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6.&amp;nbsp; 기기 환경 변경 대응&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1745938383939&quot; class=&quot;sqf&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;lt;receiver
    android:name=&quot;.AlarmRescheduler&quot;
    android:exported=&quot;false&quot;
    android:directBootAware=&quot;true&quot;&amp;gt;
    &amp;lt;intent-filter&amp;gt;
        &amp;lt;action android:name=&quot;android.intent.action.BOOT_COMPLETED&quot;/&amp;gt;
        &amp;lt;action android:name=&quot;android.intent.action.LOCKED_BOOT_COMPLETED&quot;/&amp;gt;
        &amp;lt;action android:name=&quot;android.intent.action.TIMEZONE_CHANGED&quot;/&amp;gt;
        &amp;lt;action android:name=&quot;android.intent.action.TIME_SET&quot;/&amp;gt;
    &amp;lt;/intent-filter&amp;gt;
&amp;lt;/receiver&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- ACTION_BOOT_COMPLETED&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 data-start=&quot;198&quot; data-end=&quot;223&quot;&gt;재부팅 뒤 한 번만 방송됩니다.&lt;/li&gt;
&lt;li data-start=&quot;224&quot; data-end=&quot;359&quot;&gt;재부팅 시 모든 알람이 소멸하므로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AlarmManager.set*()로 다시 등록&lt;/b&gt;해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- ACTION_LOCKED_BOOT_COMPLETED&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 data-start=&quot;452&quot; data-end=&quot;510&quot;&gt;&lt;b&gt;Direct Boot(기기 잠금 상태)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;도달 직후 발생합니다.&lt;/li&gt;
&lt;li data-start=&quot;511&quot; data-end=&quot;667&quot;&gt;비밀번호 해제 전에도 울려야 하는 알람(모닝콜 등)을 다시 세팅할 때 사용합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;570&quot; data-end=&quot;667&quot;&gt;
&lt;li data-start=&quot;570&quot; data-end=&quot;667&quot;&gt;&amp;lt;receiver android:directBootAware=&quot;true&quot;&amp;gt; 로 명시해야 수신 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;-&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ACTION_TIMEZONE_CHANGED&lt;/span&gt;&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 data-start=&quot;702&quot; data-end=&quot;750&quot;&gt;사용자가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;타임존을 바꾸거나 DST(서머타임) 전환&lt;/b&gt;이 일어나면 방송됩니다.&lt;/li&gt;
&lt;li data-start=&quot;751&quot; data-end=&quot;910&quot;&gt;알람을 &amp;ldquo;오전 9시&amp;rdquo; 같은 로컬 시각 기준으로 저장해 두었다면,
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot; data-start=&quot;797&quot; data-end=&quot;910&quot;&gt;
&lt;li data-start=&quot;797&quot; data-end=&quot;810&quot;&gt;기존 알람 취소&lt;/li&gt;
&lt;li data-start=&quot;813&quot; data-end=&quot;910&quot;&gt;새 타임존 기준 시각으로 재계산 &amp;rarr; 재등록&lt;br /&gt;를 해 주어야 다음 날에도 정확히 9시에 울립니다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;-&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ACTION_TIME_SET&lt;/span&gt;&lt;/span&gt;&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 data-start=&quot;937&quot; data-end=&quot;1000&quot;&gt;사용자가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;시계를 수동으로 조정&lt;/b&gt;하거나 네트워크 시간 동기화로 시스템 시각이 크게 변할 때 방송됩니다.&lt;/li&gt;
&lt;li data-start=&quot;1001&quot; data-end=&quot;1056&quot;&gt;로직은 TIMEZONE_CHANGED와 동일: 저장해 둔 알람 시각을 다시 계산해서 재세팅 필요합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;smartphone-7063769_1280.jpg&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpM836/btsND1Y2chM/Pk2516KO84ggEhgMt05i5k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpM836/btsND1Y2chM/Pk2516KO84ggEhgMt05i5k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpM836/btsND1Y2chM/Pk2516KO84ggEhgMt05i5k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpM836%2FbtsND1Y2chM%2FPk2516KO84ggEhgMt05i5k%2Fimg.jpg&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;223&quot; height=&quot;335&quot; data-filename=&quot;smartphone-7063769_1280.jpg&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7.&amp;nbsp; 알람 누락 대비 전략&lt;br /&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 알람이 누락 됬다면 혹시 모를 대비 전략도 준비할 수 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Backup 알람 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;메인 알람(정확) + 5 분 뒤 setAndAllowWhileIdle() 예비 알람 실행&lt;br /&gt;&lt;b&gt;WorkManager Fallback :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;OneTimeWorkRequest(Expedited)로 같은 시각 예약하기 &amp;rarr; 정확 알람 실패 시 최소 몇 분 내 작동하기&lt;br /&gt;&lt;b&gt;누락감지 :&amp;nbsp;&lt;/b&gt;마지막 울림 시각을 기록하고 앱 실행 시 누락 여부 점검 &amp;rarr; 가이드 재노출하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FCM&lt;/b&gt; &lt;b&gt;Fallback :&amp;nbsp;&lt;/b&gt;서버에서 FCM 을 기기로 보내서 예정 시간에 미리 doze 를 깨워놓고&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;samsung-galaxt-note-edge-1283897_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;803&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oQo46/btsNGrVAVTO/KnOH51qWnMFXocNyBWmFxK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oQo46/btsNGrVAVTO/KnOH51qWnMFXocNyBWmFxK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oQo46/btsNGrVAVTO/KnOH51qWnMFXocNyBWmFxK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoQo46%2FbtsNGrVAVTO%2FKnOH51qWnMFXocNyBWmFxK%2Fimg.jpg&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;383&quot; height=&quot;240&quot; data-filename=&quot;samsung-galaxt-note-edge-1283897_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;803&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8.&amp;nbsp; 제조사별 알람 살리기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알람 앱에서 알람이 울리지 않는다는 불만은&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;제조사 배터리 관리(앱 킬러) 설정&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;원인이 꽤 있을거라고 생각합니다. 다행히도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://dontkillmyapp.com/&quot;&gt;https://dontkillmyapp.com/&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;과 같은 이슈 정리 사이트가 있어서 많은 도움을 얻을 수 있습니다. 대표적인 예시로 삼성 기기의 경우&amp;nbsp;&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;▸&amp;nbsp;배터리&amp;nbsp;▸&amp;nbsp;백그라운드&amp;nbsp;사용&amp;nbsp;제한&amp;nbsp;&amp;rarr;&amp;nbsp;(1)&amp;nbsp;깊은&amp;nbsp;절전&amp;nbsp;앱&amp;nbsp;/&amp;nbsp;(2)&amp;nbsp;절전&amp;nbsp;앱&amp;nbsp;목록에서&amp;nbsp;제거&lt;br /&gt;② 설정 ▸ 앱 ▸ (앱) ▸ 배터리 &amp;rarr; 백그라운드 사용 허용&lt;br /&gt;③ 오래된 모델은 배터리 ▸ 앱 절전 &amp;rarr; &amp;ldquo;사용하지 않는 앱 절전&amp;rdquo; 끔&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;scissors-634045_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RMLgj/btsNFtT33Tj/S7twWrM3p4JMHF8gxgDYO0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RMLgj/btsNFtT33Tj/S7twWrM3p4JMHF8gxgDYO0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RMLgj/btsNFtT33Tj/S7twWrM3p4JMHF8gxgDYO0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRMLgj%2FbtsNFtT33Tj%2FS7twWrM3p4JMHF8gxgDYO0%2Fimg.jpg&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;411&quot; height=&quot;960&quot; data-filename=&quot;scissors-634045_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;9.&amp;nbsp; 정리&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 data-start=&quot;4138&quot; data-end=&quot;4221&quot;&gt;&lt;b&gt;정확한 알람 및 doze 깨우기는&amp;nbsp; setExactAndAllowWhileIdle() / setAlarmClock() 가 강력하다.&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;4221&quot; data-start=&quot;4138&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;SCHEDULE_EXACT_ALARM or USE_EXACT_ALARM 가 있어야 Alarm Manager 100% 활용&lt;/b&gt;&lt;/li&gt;
&lt;li data-start=&quot;4138&quot; data-end=&quot;4221&quot;&gt;&lt;b&gt;반복 알람은 &amp;ldquo;정확 알람 &amp;rarr; 다음 날 재예약&amp;rdquo; 패턴&lt;/b&gt;&lt;/li&gt;
&lt;li data-start=&quot;4260&quot; data-end=&quot;4310&quot;&gt;&lt;b&gt;BOOT / TIMEZONE 변경 시 재등록 + Direct Boot 대응&lt;/b&gt;&lt;/li&gt;
&lt;li data-start=&quot;4311&quot; data-end=&quot;4336&quot;&gt;&lt;b&gt;제조사별 절전 예외 UX 필수&lt;/b&gt;&lt;/li&gt;
&lt;li data-start=&quot;4337&quot; data-end=&quot;4375&quot;&gt;&lt;b&gt;Backup 알람 &amp;amp; 누락 감지로 위험도 보강&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>alarammanger deep dive</category>
      <category>AlarmManager</category>
      <category>alarmmanager setting</category>
      <category>android alarm manager</category>
      <category>안드로이드</category>
      <category>안드로이드 alarmmanager</category>
      <category>안드로이드 알람매니저</category>
      <category>알람매니저</category>
      <category>알람매니저 분석</category>
      <category>알람매니저 심층분석</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/91</guid>
      <comments>https://gift123.tistory.com/91#entry91comment</comments>
      <pubDate>Tue, 29 Apr 2025 23:54:22 +0900</pubDate>
    </item>
    <item>
      <title>서버개발 (2) 실제 운영 경험담: Spring Boot 로그인 PessimisticLockException</title>
      <link>https://gift123.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 개발자 SEOBI 입니다. 전 회사에서 프로젝트 전반을 책임졌던 서버 리딩 경험은 제 커리어의 가장 큰 설렘이었습니다. 사용자와 거래 데이터를 지켜내는 그 긴장감과 성취감을 다시 느끼고 싶어, 공부한 것들을 블로그에 아낌없이 공유하려 합니다.&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;a href=&quot;https://gift123.tistory.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://gift123.tistory.com/89&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745367522560&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;서버 개발 (1) MySQL - 트랜잭션 잠금 관리&quot; data-og-description=&quot;안녕하세요, 개발자 SEOBI 입니다. 전 회사에서 프로젝트 전반을 책임졌던 서버 리딩 경험은 제 커리어의 가장 큰 설렘이었습니다.사용자와 거래 데이터를 지켜내는 그 긴장감과 성취감을 다시 &quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/89&quot; data-og-url=&quot;https://gift123.tistory.com/89&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJOYuO/hyYFGV7mZY/WEznJe8ubhgsEXz7MpPme1/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/PUESp/hyYH8qU75T/3VmmN05xHbLfKlkQIH1RC1/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/fcmqg/hyYFBN20py/kVpua9dhsjk6dqZseZiEBk/img.jpg?width=1280&amp;amp;height=964&amp;amp;face=0_0_1280_964&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/89&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJOYuO/hyYFGV7mZY/WEznJe8ubhgsEXz7MpPme1/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/PUESp/hyYH8qU75T/3VmmN05xHbLfKlkQIH1RC1/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/fcmqg/hyYFBN20py/kVpua9dhsjk6dqZseZiEBk/img.jpg?width=1280&amp;amp;height=964&amp;amp;face=0_0_1280_964');&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;서버 개발 (1) MySQL - 트랜잭션 잠금 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요, 개발자 SEOBI 입니다. 전 회사에서 프로젝트 전반을 책임졌던 서버 리딩 경험은 제 커리어의 가장 큰 설렘이었습니다.사용자와 거래 데이터를 지켜내는 그 긴장감과 성취감을 다시&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;사실 위 공부를 했었던 이유는 과거 운영환경에서 일어난 Pessimistic Lock 장애가 있었기 때문 입니다. 당시 재현이 힘들었던 그 Lock 을 어떻게 해결해갔는지 제가 겪은 현업에서의 실제 사례를 꺼내와 보도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;nbsp;로그인 API PessmisticLockException&amp;nbsp; 발생&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image Apr 23, 2025, 01_37_03 PM.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cafzZt/btsNwKnSeoe/yd7HxCtwDehh99wLFjED9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cafzZt/btsNwKnSeoe/yd7HxCtwDehh99wLFjED9k/img.png&quot; data-alt=&quot;긴급 장애 발생 하..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cafzZt/btsNwKnSeoe/yd7HxCtwDehh99wLFjED9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcafzZt%2FbtsNwKnSeoe%2Fyd7HxCtwDehh99wLFjED9k%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;437&quot; height=&quot;437&quot; data-filename=&quot;ChatGPT Image Apr 23, 2025, 01_37_03 PM.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;긴급 장애 발생 하..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느날 앱에서 사용하는 로그인 api에 대한 급격한 에러로그와 함께 많은 고객 문의와 구글 스토어의 부정적인 리뷰가 쏟아지기 시작했습니다. 운영 환경에서 사용자의 로그인이 간헐적으로 안된다는 긴급장애가 발견 되었습니다. 상황을 재현하기 위해 개발계 환경에서 테스트를 통해 증상을 재현하기 매우 힘들었습니다. 확인 해보니, 구체적으로, 여러개의 쓰레드가 같은 사용자 ID로 동시에 로그인 시도를 하면 &lt;b&gt;Lock wait timeout exceeded; try restarting transaction&lt;/b&gt; 에러와 함께 Hibernate의 PessimisticLockException이 발생했습니다. 아래는 해당 오류 로그의 일부입니다:&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;org.springframework.dao.PessimisticLockingFailureException:&amp;nbsp;could&amp;nbsp;not&amp;nbsp;execute&amp;nbsp;statement&lt;br /&gt;Caused&amp;nbsp;by:&amp;nbsp;org.hibernate.PessimisticLockException:&amp;nbsp;could&amp;nbsp;not&amp;nbsp;execute&amp;nbsp;statement&lt;br /&gt;Caused&amp;nbsp;by:&amp;nbsp;co&lt;a href=&quot;http://m.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException:&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://m.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException:&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Lock&amp;nbsp;wait&amp;nbsp;timeout&amp;nbsp;exceeded;&amp;nbsp;try&amp;nbsp;restarting&amp;nbsp;transaction&lt;br /&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;/b&gt;이 일어날 때 나타납니다. 즉, 하나의 트랜잭션이 특정 Row에 대한 &lt;b&gt;Exclusive Lock&lt;/b&gt;(배타적 잠금)을 오래 쥐고 놓지 않으면, 동일한 데이터를 업데이트하려는 다른 트랜잭션은 &lt;b&gt;지정된 시간 내에 락을 얻지 못하고 시간 초과&lt;/b&gt;됩니다. Hibernate에서는 이러한 DB 락 타임아웃 상황을 JPA 는&amp;nbsp;&lt;b&gt;&amp;ldquo;요청한 락을 획득하지 못했을 때 발생하는 예외&lt;/b&gt;&amp;rdquo;로 PessimisticLockException을 발생 시킵니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;digital-marketing-1725340_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAsTjS/btsNxvqsoPu/rkKAjA47yhMiMrLcmVfTy1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAsTjS/btsNxvqsoPu/rkKAjA47yhMiMrLcmVfTy1/img.jpg&quot; data-alt=&quot;파악해보자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAsTjS/btsNxvqsoPu/rkKAjA47yhMiMrLcmVfTy1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAsTjS%2FbtsNxvqsoPu%2FrkKAjA47yhMiMrLcmVfTy1%2Fimg.jpg&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;468&quot; height=&quot;853&quot; data-filename=&quot;digital-marketing-1725340_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;파악해보자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실제 원인 분석&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1745389279564&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * (문제 코드) 하나의 트랜잭션에 모든 일을 몰아넣은 상태
 */
@Transactional
fun signIn(...) {
	//..생략
    //dirty 마킹//
    member.detail.lastLoginAt = now   
    member.detail...
    member.detail...
    member.detail...
    ...
    
	// 1. save() 함수로 인해 flush() -&amp;gt; update member.. X락 획득!
    loginLogRepository.save(LoginLog(member, ip))     
    
    
    //2. Redis I/O  50 ms, 외부 HTTP 호출 300 ms 
    redisTokenSvc.saveToken(...)
    amplitudeClient.sendEvent(...)
                     
    /*
     * 4. member 가 PESSIMISTIC_WRITE로 잠금 
     * member행을 기준으로 거래목록 로드 한뒤 관련 엔티티 필드 수정
     */
     val txList:List&amp;lt;Tx&amp;gt; = txRepository.findBy..(member.id)
     txList.forEach { 
     ... 
     }
}&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;동시 트랜 잭션간에 락 충돌 =&lt;span&gt; &quot;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;락 대상이 겹쳤다. + 락을 오래 쥐었다.&lt;/b&gt;&quot; 을 기억하고 위 트랜잭션 내부 코드를 하나씩 살펴보겠습니다. &lt;br /&gt;&lt;br /&gt;첫번째 작업은&amp;nbsp; loginLogRepository.save(..) 을 사용합니다.&amp;nbsp; spring 의 repository.save() 는 flush() 후 insert or update 작업을 수행합니다.&amp;nbsp; save() 를 호출하기 전 회원 정보를 갱신하는 작업을 했습니다. hibernate 는 setXXX() 로 바로 commit 하지 않고, 캐시처리 하고 save() 지점에 기존 캐시 작업들을&amp;nbsp; flush() 합니다. 쓰기 작업을 하기 때문에 위 트랜잭션은 이 때 &lt;b&gt;X 락을 획득하게 됩니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; 회원 상태 갱신 + X락 획득&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;두번째 작업은 redis 연동이나 외부 서비스 호출과 같이 DB락과 관계 없는 작업도 같은 트랜잭션 안에서 처리 되고 있었습니다. 트랜잭션이 해당 회원 행의 Lock 시간이 조금더 길어집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;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;세번째 작업은 해당 회원 행이 이미 잠금이 걸린 상태에서 txRepository.findBy(member.id)로 여러 엔티티를 로드한 뒤, 루프를 돌며 다른 여러 엔티티 정보를 수정합니다. 즉, &lt;b&gt;연쇄 쿼리가 트랜잭션 안에서 쏟아지기 때문에 작업량이 상당히 많아집니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;-&amp;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;위 3가지 작업을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;락을 오래 쥐었다.&amp;nbsp;&lt;/b&gt;라는 조건이 완성됩니다. 그리고 첫번째 작업으로 인해 같은 사용자가 login 을 또 시도하게 되면 작업 초반부에 이전 트랜잭션과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;락 대상이 겹쳤다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;조건이 생깁니다. 그렇게 락 충돌 = 락 대상이 겹쳤다. + 락을 오래 쥐었다. 로 인해서 두번째 트랜잭션은 lock wait 상태에 빠지게 되고 특정 시간이 지나면 hibernate 의&lt;span&gt;&amp;nbsp;&lt;/span&gt;PessimisticLockException이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745392204980&quot; class=&quot;routeros&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;sequenceDiagram
    participant T1 as 로그인 요청 #1
    participant T2 as 로그인 요청 #2
    T1-&amp;gt;&amp;gt;DB: UPDATE member ...  (X-Lock 확보)
    Note over T1,DB: 1,2,3 단계까지 트랜잭션 길게 수행
    T2-&amp;gt;&amp;gt;DB: UPDATE member ...  (락 대기)
    Note over T2,DB: 50초 내 락 못얻으면 TimeOut
    T1--&amp;gt;&amp;gt;DB: COMMIT (락 해제)
    T2--&amp;gt;&amp;gt;DB: 락 획득 OR TimeOut&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;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; 인증 토큰 발급, 로그 저장, 이벤트 발행까지 모두 한 트랜잭션으로 처리하여 트랜잭션 유지 시간이 불필요하게 길었다. 그로 인해 DB 락이 오래 유지되었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;부수 작업과 핵심 로직의 결합&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;: &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로그 저장, 외부 이벤트 발행,&amp;nbsp; 다른 엔티티 정보 갱신 등 첫번째와 세번째 작업은 로그인 결과와 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원자적으로 처리될 필요는 없는 작업들&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;임에도, 모두 한 트랜잭션에 묶여 있었습니다. 이 때문에 만약 부가 작업에서 예외가 발생하면 주 작업까지 롤백되는 문제도 잠재되어 있었습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 구조에서는 가능한 짧게 유지해야 할 트랜잭션을 불필요하게 오래 유지하고 있었고, 그 결과 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동시 트랜잭션 간에 락 충돌과 타임아웃&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 발생했던 것입니다.&lt;/span&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;scissors-634045_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SYTO6/btsNwa2IxBg/FRkZUmjyJdTJbVbJTiEHv0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SYTO6/btsNwa2IxBg/FRkZUmjyJdTJbVbJTiEHv0/img.jpg&quot; data-alt=&quot;싹둑 분리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SYTO6/btsNwa2IxBg/FRkZUmjyJdTJbVbJTiEHv0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSYTO6%2FbtsNwa2IxBg%2FFRkZUmjyJdTJbVbJTiEHv0%2Fimg.jpg&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;447&quot; height=&quot;960&quot; data-filename=&quot;scissors-634045_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;싹둑 분리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜 잭션 분리 REQUIRES_NEW 적용&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제의 핵심이 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 경합&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;과 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;과도한 락 보유 시간&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이었으므로, 해결 전략은 자연스럽게 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션을 더 세분화&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하여 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심 로직과 부가 로직의 수행 시점을 분리&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하는 것이었습니다. Spring의 트랜잭션 전파 속성 중 &lt;/span&gt;&lt;b&gt;REQUIRES_NEW&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 이용하면 이러한 분리가 가능합니다 REQUIRES_NEW 는 현재 실행 중인 트랜잭션이 있다면 그 트랜잭션을 잠시 보류(suspend)시키고 별도의 새로운 트랜잭션을 시작합니다. &lt;span style=&quot;color: #000000;&quot;&gt;이를 통해 하나의 요청 내에서 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;둘 이상의 독립적인 트랜잭션&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을 사용할 수 있게 됩니다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745395779220&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
fun signIn(...) {
	//..생략
    //dirty 마킹//
    member.detail.lastLoginAt = now   
    member.detail...
    member.detail...
    member.detail...
    ...

    logService.handle(member.id, req.ip)
    
}
    
data class SignInEvent(
    val memberId: Long, // memberId만 전달
    val ip: String
)

@Component
class LogSerivice
    private val memberRepository: MemberRepository,
    private val loginLogRepository: LoginLogRepository,
    private val txRepository: TxRepository,
    private val amplitudeClient: AmplitudeClient,
    private val redisTokenSvc: RedisTokenService
) {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    fun handle(memberId:Long, ip:String){
        val member = memberRepository.findById(event.memberId).orElseThrow { MemberNotFoundException() }

        loginLogRepository.save(LoginLog(member,ip))

       	redisTokenSvc.saveToken(...)
    	amplitudeClient.sendEvent(...)
                     
     	val txList:List&amp;lt;Tx&amp;gt; = txRepository.findBy..(member.id)
     	txList.forEach { 
     	... 
     	}
    }
}&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; 부분 실패를 하더라도 주 트랜잭션(signIn) 과 격리했기 때문에 새 트랜잭션에서 예외가 발생해도 주 트랜잭션이 롤백되지 않습니다. 즉 서로 결과가 독립적이며 락‧데드락 전파를 줄입니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;train-3384786_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzqUs2/btsNwJDBigP/GoL9yGufNd4ODVQkHGc341/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzqUs2/btsNwJDBigP/GoL9yGufNd4ODVQkHGc341/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzqUs2/btsNwJDBigP/GoL9yGufNd4ODVQkHGc341/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzqUs2%2FbtsNwJDBigP%2FGoL9yGufNd4ODVQkHGc341%2Fimg.jpg&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;509&quot; height=&quot;339&quot; data-filename=&quot;train-3384786_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-end=&quot;36&quot; data-start=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;TransactionPhase.AFTER_COMMIT이란?&lt;/b&gt;&lt;/h2&gt;
&lt;div&gt;주 트랜잭션(signin)이 정상적으로 커밋된 직후에 DB 락이 모두 해제된 뒤 실행이 됩니다. 코드단으로 예시를 들면 signIn 의 DB 작업이 끝나고 나서 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 의 작업이 후처리로 진행이 됩니다. 이 리스너 안쪽 코드는 이미 락이 풀린 상태에서 돌기 때문에 X락 충돌이 없습니다.&lt;/div&gt;
&lt;div&gt;하지만 AFTER_COMMIT 만 사용하게 된다면 예외 발생시 부분 저장이 될 가능성이 있습니다. 이를 방지하기 위해 위에 언급한 REQUIRES_NEW를 같이 사용 한다면 이부분을 방지 할 수 있습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AFTER_COMMIT + REQUIERS_NEW 조합이 필요한 이유&amp;nbsp;&lt;br /&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div data-message-author-role=&quot;assistant&quot; data-message-id=&quot;29c0369a-03a8-470f-ac69-b24f0b72968e&quot; data-message-model-slug=&quot;o3&quot;&gt;두가지는 아래와 같은 특성을 가지고 있습니다.&amp;nbsp;&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot; data-start=&quot;54&quot; data-end=&quot;261&quot;&gt;
&lt;li data-start=&quot;54&quot; data-end=&quot;111&quot;&gt;AFTER_COMMIT &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;락을 가장 빨리 푼다&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(주 트랜잭션 커밋 직후에만 실행)&lt;/li&gt;
&lt;li data-start=&quot;114&quot; data-end=&quot;261&quot;&gt;REQUIRES_NEW &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;원자성&amp;middot;롤백 격리&lt;/b&gt;를 확보한다 (핸들러 내부 쿼리를 하나의 새 물리 트랜잭션으로 묶음)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘을 함께 붙여야 &quot;락 해제 최소화 + DB 무결성 + 부분 실패 방지&quot; 라는 세마리 토끼를 한꺼번에 잡을 수 있습니다.&lt;/p&gt;
&lt;div&gt;(Spring 6.1 이상에서는 @TransactionalEventListener 메서드에 @Transactional를 붙일 때 전파 옵션이 REQUIRES_NEW 또는 NOT_SUPPORTED 가 아니면 부팅 단계에서 오류를 던지기 때문에 필수로 같이 사용해야 합니다.)&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;AFTER_COMMIT 과 REQUIERS_NEW 조합을 통해 아래 상황에 적용 해보도록 하겠습니다.&lt;/div&gt;
&lt;pre id=&quot;code_1745393348437&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
fun signIn(...) {
	//..생략
    //dirty 마킹//
    member.detail.lastLoginAt = now   
    member.detail...
    member.detail...
    member.detail...
    ...

    // ★ 커밋 후 처리할 이벤트 준비
    eventPublisher.publishEvent(SignInEvent(member.id, req.ip))
    
    //Commit X락 해제
}
    
data class SignInEvent(
    val memberId: Long, 
    val ip: String
)

@Component
class SignInEventHandler(
    private val memberRepository: MemberRepository,
    private val loginLogRepository: LoginLogRepository,
    private val txRepository: TxRepository,
    private val amplitudeClient: AmplitudeClient,
    private val redisTokenSvc: RedisTokenService
) {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    fun handle(event: SignInEvent) {
        
        val member = memberRepository.findById(event.memberId).orElseThrow { MemberNotFoundException() }

        loginLogRepository.save(LoginLog(member,ip))

       	redisTokenSvc.saveToken(...)
    	amplitudeClient.sendEvent(...)
                     
     	val txList:List&amp;lt;Tx&amp;gt; = txRepository.findBy..(member.id)
     	txList.forEach { 
     	... 
     	}
    }
}&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; member.set.. 으로 인한 DB Update 작업이 flush 되어 한번에 처리 되면서 X락이 해제가 되면서 주 트랜잭션의 작업이 먼저 Commit 됩니다. 주 트랜잭션의 작업이 끝나서야 AFTER_COMMIT 리스너 실행이 됩니다.&amp;nbsp; 리스너에서 회원 정보를 재조회 합니다. 리스너 메서드는 별도의 트랜잭션 컨텍스트에서 동작하며 member 를 select로 조회(S락)합니다.&amp;nbsp; 그다음 LoginLog의 save() 작업이 진행합니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;LoginLog 삽입과 FK 검증&lt;/b&gt;: 새로운 트랜잭션에서 LoginLog 엔티티를 INSERT할 때, 외래키 제약조건(FK)으로 인해 &lt;b&gt;회원(parent) 테이블에 대한 참조 무결성 검증&lt;/b&gt;이 이루어집니다. 이 과정에서 InnoDB는 해당 Member 레코드에 공유 락(S-락)을 획득하여 부모 레코드의 존재를 확인하고 유지합니다​. &amp;nbsp;외래키 체크 시 참조되는 부모 또는 자식 레코드에 대해 &lt;b&gt;row-level S-락&lt;/b&gt;을 겁니다.&lt;br /&gt;&lt;br /&gt;요약하면 주 트랜잭션에서 회원 정보 수정 작업을 이미 끝냈고, 두번째 트랜잭션에서 회원을 수정하지 않기 때문에 X락은 걸리지 않았기 때문에 &lt;b&gt;LoginLog 삽입 시에도 회원에 걸리는 것은 일시적인 S락일 뿐입니다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745399232614&quot; class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;signIn()  &amp;larr; @Transactional(REQUIRED, 기존 TX #A)
 ├─ member..
 ├─ eventPublisher.publishEvent()
 └─ 커밋  ───────────╮
                    │  (X-락 해제)
AFTER_COMMIT 리스너  │
   └─ handle() &amp;larr; @Transactional(REQUIRES_NEW) &amp;rArr; 새 TX #B
         ├─ SELECT member
         ├─ INSERT login_log
         ├─ Redis / 외부 HTTP
         └─ 커밋   (TX #B 종료)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 179px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-start=&quot;67&quot; data-end=&quot;576&quot;&gt;
&lt;tbody data-start=&quot;137&quot; data-end=&quot;576&quot;&gt;
&lt;tr data-start=&quot;137&quot; data-end=&quot;195&quot;&gt;
&lt;td data-start=&quot;137&quot; data-end=&quot;141&quot;&gt;①&lt;/td&gt;
&lt;td data-start=&quot;141&quot; data-end=&quot;165&quot;&gt;signIn() 메서드&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;진입&lt;/b&gt;&lt;/td&gt;
&lt;td data-start=&quot;165&quot; data-end=&quot;195&quot;&gt;스프링 AOP 프록시가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;트랜잭션 A 시작&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;196&quot; data-end=&quot;256&quot;&gt;
&lt;td data-start=&quot;196&quot; data-end=&quot;200&quot;&gt;②&lt;/td&gt;
&lt;td data-start=&quot;200&quot; data-end=&quot;237&quot;&gt;member&amp;hellip; 수정 &amp;middot; publishEvent() 호출&lt;/td&gt;
&lt;td data-start=&quot;237&quot; data-end=&quot;256&quot;&gt;아직&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;트랜잭션 A 안&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;257&quot; data-end=&quot;323&quot;&gt;
&lt;td data-start=&quot;257&quot; data-end=&quot;261&quot;&gt;③&lt;/td&gt;
&lt;td data-start=&quot;261&quot; data-end=&quot;286&quot;&gt;signIn()&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;리턴( } )&lt;/b&gt;&lt;/td&gt;
&lt;td data-start=&quot;286&quot; data-end=&quot;323&quot;&gt;프록시가 commit() 호출 &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;DB 커밋 수행 (member update 작업 모두 종료)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;324&quot; data-end=&quot;422&quot;&gt;
&lt;td data-start=&quot;324&quot; data-end=&quot;328&quot;&gt;④&lt;/td&gt;
&lt;td data-start=&quot;328&quot; data-end=&quot;344&quot;&gt;&lt;b&gt;커밋이 끝난 직후&lt;/b&gt;&lt;/td&gt;
&lt;td data-start=&quot;344&quot; data-end=&quot;422&quot;&gt;afterCommit() 콜백 실행 &amp;rarr; @TransactionalEventListener(AFTER_COMMIT) 리스너 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;423&quot; data-end=&quot;499&quot;&gt;
&lt;td data-start=&quot;423&quot; data-end=&quot;427&quot;&gt;⑤&lt;/td&gt;
&lt;td data-start=&quot;427&quot; data-end=&quot;468&quot;&gt;리스너 안에서 @Transactional(REQUIRES_NEW)&lt;/td&gt;
&lt;td data-start=&quot;468&quot; data-end=&quot;499&quot;&gt;&lt;b&gt;새 트랜잭션 B 시작&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 후처리 쿼리 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;500&quot; data-end=&quot;534&quot;&gt;
&lt;td data-start=&quot;500&quot; data-end=&quot;504&quot;&gt;⑥&lt;/td&gt;
&lt;td data-start=&quot;504&quot; data-end=&quot;512&quot;&gt;리스너 끝&lt;/td&gt;
&lt;td data-start=&quot;512&quot; data-end=&quot;534&quot;&gt;트랜잭션 B 커밋 &amp;rarr; 리스너 종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;535&quot; data-end=&quot;576&quot;&gt;
&lt;td data-start=&quot;535&quot; data-end=&quot;539&quot;&gt;⑦&lt;/td&gt;
&lt;td data-start=&quot;539&quot; data-end=&quot;561&quot;&gt;컨트롤러&amp;middot;서비스 호출자에게 값 반환&lt;/td&gt;
&lt;td data-start=&quot;561&quot; data-end=&quot;576&quot;&gt;전체 호출 스택 복귀&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;fantasy-2861107_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chEP6h/btsNw2I2pzG/Vu2TgEXKkhdwjnNJkxMFjK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chEP6h/btsNw2I2pzG/Vu2TgEXKkhdwjnNJkxMFjK/img.jpg&quot; data-alt=&quot;가장 떨리는 순간&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chEP6h/btsNw2I2pzG/Vu2TgEXKkhdwjnNJkxMFjK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchEP6h%2FbtsNw2I2pzG%2FVu2TgEXKkhdwjnNJkxMFjK%2Fimg.jpg&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;518&quot; height=&quot;724&quot; data-filename=&quot;fantasy-2861107_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가장 떨리는 순간&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;운영 반영 &amp;amp; 효과&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;307&quot; data-start=&quot;60&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;307&quot; data-start=&quot;88&quot;&gt;
&lt;tr data-end=&quot;150&quot; data-start=&quot;88&quot;&gt;
&lt;td data-end=&quot;104&quot; data-start=&quot;88&quot;&gt;&lt;b&gt;1) 코드만 교체&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;150&quot; data-start=&quot;104&quot;&gt;DB 스키마 수정이 없어서 &lt;br /&gt;&amp;rarr; 최악의 경우 &lt;b&gt;즉시 롤백&lt;/b&gt; 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;244&quot; data-start=&quot;151&quot;&gt;
&lt;td data-end=&quot;176&quot; data-start=&quot;151&quot;&gt;&lt;b&gt;2) 소규모 카나리 (&amp;asymp; 5 %)&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;244&quot; data-start=&quot;176&quot;&gt;API 게이트웨이에서 /v1/.../signin 일부 호출을 &lt;br /&gt;/v2/.../signin으로 우회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;307&quot; data-start=&quot;245&quot;&gt;
&lt;td data-end=&quot;271&quot; data-start=&quot;245&quot;&gt;&lt;b&gt;3) 2시간 모니터링 후 전면 전환&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;307&quot; data-start=&quot;271&quot;&gt;오류율&amp;middot;응답시간이 &lt;b&gt;정상&lt;/b&gt;임을 확인하고 100 % 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;24 시간 동안&lt;/b&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;h4 data-end=&quot;643&quot; data-start=&quot;623&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;회고 &amp;amp; 체크리스트&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;896&quot; data-start=&quot;644&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;726&quot; data-start=&quot;644&quot;&gt;UPDATE &amp;rarr; Commit &amp;rarr; 후처리&amp;rdquo; 순서로만 바꿔도 락 충돌이 급감했습니다.&lt;/li&gt;
&lt;li data-end=&quot;896&quot; data-start=&quot;820&quot;&gt;외부 I/O가 길면 @Async 고려해볼 생각입니다.&lt;br /&gt;(이번 로그인은 I/O가 짧아 동기 유지. 필요 시 별도 스레드로 분리)&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>서버 Server</category>
      <category>hibernate</category>
      <category>jpa</category>
      <category>MySQL</category>
      <category>pessimisticlockexception</category>
      <category>Spring</category>
      <category>springboot</category>
      <category>transaction</category>
      <category>서버</category>
      <category>스프링</category>
      <category>스프링부트</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/90</guid>
      <comments>https://gift123.tistory.com/90#entry90comment</comments>
      <pubDate>Wed, 23 Apr 2025 18:45:55 +0900</pubDate>
    </item>
    <item>
      <title>서버개발 (1) MySQL - 트랜잭션 잠금 관리</title>
      <link>https://gift123.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 개발자 SEOBI 입니다. 전 회사에서 프로젝트 전반을 책임졌던 서버 리딩 경험은 제 커리어의 가장 큰 설렘이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자와 거래 데이터를 지켜내는 그 긴장감과 성취감을 다시 느끼고 싶어, 공부한 것들을 블로그에 아낌없이 공유하려 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;heart-lock-4921885_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5ftgj/btsNuumAJ5G/rRXPnivB2yfDHYXDqrWa40/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5ftgj/btsNuumAJ5G/rRXPnivB2yfDHYXDqrWa40/img.jpg&quot; data-alt=&quot;못들어오게 잠그다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5ftgj/btsNuumAJ5G/rRXPnivB2yfDHYXDqrWa40/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5ftgj%2FbtsNuumAJ5G%2FrRXPnivB2yfDHYXDqrWa40%2Fimg.jpg&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;550&quot; height=&quot;366&quot; data-filename=&quot;heart-lock-4921885_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;못들어오게 잠그다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;b&gt;1. 잠금(lock) 이란?&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;잠금은 비유하자면 트랜잭션이 데이터(행&amp;middot;범위&amp;middot;테이블)를 자신만 안전하게 쓰거나 읽기 위해 &amp;lsquo;예약표&amp;rsquo;를 꽂아 두는 것입니다. &lt;/b&gt;다른 트랜잭션이 같은 구역에 들어오면, 꽂혀&amp;nbsp;있는 예약표(잠금)와 &lt;b&gt;호환되는지&lt;/b&gt;를 보고 &amp;mdash; 호환되면 통과,&amp;nbsp; 안 되면 대기(LOCK WAIT) 시킵니다. LOCK WAIT 된 트랜잭션은 원래 트랜잭션의 COMMIT 이 끝나야 다시 쿼리가 실행됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;'왜 잠근다고 표현할까?'&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공유(Shared, S) 잠금 - &quot;읽기만 할 테니 너도 읽어&quot;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SELECT ... &lt;b&gt;FOR SHARE&lt;/b&gt; (또는 LOCK IN SHARE MODE) 처럼 다 같이 조회할 때 건다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;독점(Exclusive, X) 잠금 - &quot;나혼자 쓰고 읽겠다.&quot;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UPDATE&amp;middot;DELETE&amp;middot;INSERT, SELECT &amp;hellip; &lt;b&gt;FOR UPDATE&lt;/b&gt; 등을 실행할 때 InnoDB가 자동으로 건다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠금을 잡는다(획득한다) = 트랜잭션이 그 구역에 대해 S&amp;middot;X 같은 잠금 모드를 &lt;b&gt;성공적으로 등록&lt;/b&gt;했다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 에서 FOR SHARE 는 S잠금, FOR UPDATE 는 X 잠금 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745302714010&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--1. 트랜잭션 A
--&amp;nbsp;id&amp;nbsp;=&amp;nbsp;42 레코드에 X&amp;nbsp;잠금 획득 
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 42;
-- COMMIT; 을 하지 않을때..

--2. 트랜잭션 B
-- 같은 레코드에 S&amp;nbsp;잠금 시도
-- X&amp;nbsp;잠금과 호환 안 됨 &amp;rArr; LOCK&amp;nbsp;WAIT
START TRANSACTION;
SELECT * FROM accounts WHERE id = 42 FOR SHARE;&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;같은 행에 대하여 &amp;ldquo;B가 잡으려는 잠금&amp;rdquo;이 S 잠금이고, 이미 &amp;ldquo;A가 잡아 둔 잠금&amp;rdquo;이 X 잠금입니다. 둘이 호환되지 않으니 B는 기다립니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745302999007&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 트랜 잭션 A
-- id = 42 에 X 잠금 획득
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 42;
-- COMMIT; 을 하지 않을때..

-- 트랜 잭션 B  (거의 동시에 실행)
-- 같은 행에 X 잠금 시도 &amp;rarr; 세션 A 가 끝날 때까지 LOCK WAIT
START TRANSACTION;
UPDATE accounts SET balance = balance + 100 WHERE id = 42;&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;위 예제는 같은 행을 둘 다 Update 하는 경우 입니다. 두 X 잠금은 절대 함께 존재 할 수 없으므로, 나중에 온 B가 기다립니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745308728361&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 트랜 잭션 A
-- id = 42 에 S 잠금 획득
START TRANSACTION;
SELECT * FROM accounts WHERE id = 42 FOR SHARE;
-- COMMIT; 을 하지 않을때..

-- 트랜 잭션 B  (거의 동시에 실행)
-- 같은 행에 S 잠금 시도 S 잠금 끼리는 읽을 수 있어서 LOCK WAIT 발생 X
START TRANSACTION;
SELECT * FROM accounts WHERE id = 42 FOR SHARE;&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;위 예제는 같은 행을 둘 다 SELETE 하는 경우 입니다. 두 S 잠금은 서로 읽을 수 있음으로 잠금 대기는 발생하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;'서로 다른 행을 잡으면 데드락 발생 가능성이 있다.'&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데드락은 잠긴 대상을 서로 교차로 기다리는 순환 대기가 있을 때 발생합니다. 행이 다르더라도 트랜잭션 A가 행 1을 잡은 뒤 행2를 기다리고 반대로 트랜잭션 B가 행2를 잡은 뒤 행1을 기다리면 두 트랜잭션은 서로 풀어 주지 않는 '막힌 고리'를 만들게 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745304119378&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;t0  세션 A  START TRANSACTION;
t1  세션 A  UPDATE accounts SET &amp;hellip; WHERE id = 1;   -- id1 에 X 잠금 획득

t2  세션 B  START TRANSACTION;
t3  세션 B  UPDATE accounts SET &amp;hellip; WHERE id = 2;   -- id2 에 X 잠금 획득

t4  세션 A  UPDATE accounts SET &amp;hellip; WHERE id = 2;   -- id2 잠금 요청 &amp;rarr; LOCK WAIT
t5  세션 B  UPDATE accounts SET &amp;hellip; WHERE id = 1;   -- id1 잠금 요청 &amp;rarr; LOCK WAIT

▶  A는 id2, B는 id1 잠금을 서로 기다리는 순환 대기 &amp;rarr; InnoDB가 데드락 감지
   ERROR 1213 (40001) 중 한 트랜잭션이 롤백됨&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;- 트랜잭션 AB 둘다 항상 같은 순서(id 오름차순 등)로 행을 업데이트&lt;br /&gt;- 트랜잭션을 짦게 유지하고 즉시 COMMIT&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ERROR 1213 발생 시&amp;nbsp; 재시도 로직 적용&amp;nbsp;&lt;br /&gt;같은 방지가 필요합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;berlin-key-96225_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y6xXp/btsNudFrcwB/dWgVDIEfcOLHnTZb07cadK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y6xXp/btsNudFrcwB/dWgVDIEfcOLHnTZb07cadK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y6xXp/btsNudFrcwB/dWgVDIEfcOLHnTZb07cadK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY6xXp%2FbtsNudFrcwB%2FdWgVDIEfcOLHnTZb07cadK%2Fimg.jpg&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;439&quot; height=&quot;329&quot; data-filename=&quot;berlin-key-96225_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;b&gt;2. Record Lock 과 Next-key Lock 란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 53px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;무엇을 잠그나?&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;왜 필요하나&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px; text-align: center;&quot;&gt;&lt;b&gt;충돌 (대기 상황)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;Record Lock&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;인덱스 행 (Record) 하나&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;같은 행(프라미머리 키, 보조 인덱스)등 수정 충돌 방지&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;트랜잭션&amp;nbsp;A가 UPDATE &amp;hellip; WHERE id = 10 &amp;rarr; &lt;b&gt;X&lt;/b&gt;(배타)&amp;nbsp;잠금. &lt;br /&gt;&lt;br /&gt;트랜잭션 B가 같은 id = 10 업데이트 시도 &amp;rarr; &lt;b&gt;대기&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;Next - Key Lock&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;Record Lock + Gap Lock&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;해레코드 읽기 + 범위 내 INSERT 모두 제어&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;트랜잭션&amp;nbsp;A가 SELECT &amp;hellip; WHERE score BETWEEN 80 AND 90 FOR UPDATE&lt;br /&gt;&amp;rarr; 80 &amp;le; score &amp;lt; 다음 레코드까지 범위 잠금.&lt;br /&gt;&lt;br /&gt;트랜잭션 B가 score 85 INSERT 시도 &amp;rarr; &lt;b&gt;대기&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Record Lock 은 같은 행을 동시에 수정, 삭제를 못하도록 합니다. Next - key Lock 은 아래에서 상세 설명하겠습니다.&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;'Next‑key Lock = Record Lock ＋ Gap Lock'&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Gap Lock 은 두 행 사이 '빈 구간' 입니다. (레코드 앞,뒤 공간)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) 트랜잭션&amp;nbsp;A가 &lt;b&gt;WHERE pk BETWEEN 5 AND 9 FOR UPDATE&lt;/b&gt; 를 실행&lt;br /&gt;&amp;rarr;&amp;nbsp;인덱스상 &lt;b&gt;(5,&amp;nbsp;9)&lt;/b&gt;&amp;nbsp;구간 전체가 &lt;b&gt;Gap&amp;nbsp;Lock&lt;/b&gt; 으로 잠김&lt;br /&gt;&lt;br /&gt;반면 Next-key Lock은 Record Lock 과 Gap Lock 합쳐졌다고 보시면 됩니다. 즉,&lt;br /&gt;따라서 &lt;b&gt;범위 안에 이미 존재하는 행&lt;/b&gt;과 &lt;b&gt;범위 안에 아직 없는 공간(갭)&lt;/b&gt;&amp;nbsp;을 둘 다 잠급니다.&lt;/p&gt;
&lt;div&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;/span&gt;
&lt;div&gt;범위 안에서 다른 트랜잭션이 하려는 작업가능 여부
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;409&quot; data-start=&quot;112&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;409&quot; data-start=&quot;211&quot;&gt;
&lt;tr data-end=&quot;287&quot; data-start=&quot;211&quot;&gt;
&lt;td data-end=&quot;240&quot; data-start=&quot;211&quot;&gt;&lt;b&gt;UPDATE / DELETE&amp;nbsp;(기존 행)&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;249&quot; data-start=&quot;240&quot;&gt;&lt;b&gt;불가&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;287&quot; data-start=&quot;249&quot;&gt;그 행 자체가 &lt;b&gt;Record&amp;nbsp;Lock(X)&lt;/b&gt; 에 걸려 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;353&quot; data-start=&quot;288&quot;&gt;
&lt;td data-end=&quot;307&quot; data-start=&quot;288&quot;&gt;&lt;b&gt;INSERT&amp;nbsp;(새 행)&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;316&quot; data-start=&quot;307&quot;&gt;&lt;b&gt;불가&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;353&quot; data-start=&quot;316&quot;&gt;행과 행 사이 공간이 &lt;b&gt;Gap&amp;nbsp;Lock&lt;/b&gt; 으로 잠겨 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;409&quot; data-start=&quot;354&quot;&gt;
&lt;td data-end=&quot;376&quot; data-start=&quot;354&quot;&gt;&lt;b&gt;UPDATE&amp;nbsp;(범위 밖 행)&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;381&quot; data-start=&quot;376&quot;&gt;가능&lt;/td&gt;
&lt;td data-end=&quot;409&quot; data-start=&quot;381&quot;&gt;Next‑key&amp;nbsp;Lock 영역과 겹치지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;550&quot; data-start=&quot;411&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;Insert뿐 아니라, 범위 안 기존 레코드를 건드리는 Update&amp;middot;Delete도 모두 대기&lt;/b&gt;합니다.&lt;br /&gt;Next‑key&amp;nbsp;Lock이 &amp;ldquo;레코드 수정+새 행 삽입&amp;rdquo; 두 가지를 동시에 막는 이유는 &lt;b&gt;팬텀 리드&lt;/b&gt;를 완전히 차단하기 위해서입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745306452228&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 준비: 한 번만 (어느 세션이든)
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (id INT PRIMARY KEY, price INT) ENGINE=InnoDB;
INSERT INTO orders VALUES (1,80),(5,120),(9,220);

───────────── SESSION A ─────────────
START TRANSACTION;
 -- Next‑key&amp;nbsp;Lock (price 100~200)
SELECT * FROM orders           
WHERE  price BETWEEN 100 AND 200
FOR UPDATE;

───────────── SESSION B ─────────────
START TRANSACTION;
-- WAIT
UPDATE orders SET price = 130 WHERE id = 5; 
-- WAIT   
INSERT INTO orders VALUES (6,150);  
-- OK            
INSERT INTO orders VALUES (10,250);             

-- ───────────── SESSION A ─────────────
-- 잠금 해제
COMMIT;&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-end=&quot;813&quot; data-start=&quot;803&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;485&quot; data-start=&quot;460&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1039&quot; data-start=&quot;487&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;627&quot; data-start=&quot;487&quot;&gt;&lt;b&gt;SELECT &amp;hellip; WHERE price&amp;nbsp;BETWEEN&amp;nbsp;100&amp;nbsp;AND&amp;nbsp;200&amp;nbsp;FOR&amp;nbsp;UPDATE&lt;/b&gt;&lt;br /&gt;범위 조건 &amp;rarr; &lt;b&gt;레코드&amp;nbsp;id&amp;nbsp;5&lt;/b&gt; + &lt;b&gt;둘러싼 갭(100&amp;nbsp;~&amp;nbsp;200)&lt;/b&gt; 가 &lt;b&gt;Next‑key&amp;nbsp;Lock(X)&lt;/b&gt; 로 잠김.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-end=&quot;738&quot; data-start=&quot;629&quot;&gt;&lt;b&gt;SESSION&amp;nbsp;B &amp;ndash; UPDATE id&amp;nbsp;=&amp;nbsp;5&lt;/b&gt;&lt;br /&gt;레코드&amp;nbsp;5는 이미 X&amp;nbsp;잠금이라 &lt;b&gt;LOCK WAIT&lt;br /&gt;&lt;br /&gt;&lt;/b&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;/li&gt;
&lt;li data-end=&quot;862&quot; data-start=&quot;740&quot;&gt;&lt;b&gt;SESSION&amp;nbsp;B &amp;ndash; INSERT 150&lt;/b&gt;&lt;br /&gt;값&amp;nbsp;150은 갭&amp;nbsp;(100&amp;nbsp;~&amp;nbsp;200) 안 &amp;rarr; 갭&amp;nbsp;락에 막혀 &lt;b&gt;LOCK WAIT&lt;br /&gt;&lt;br /&gt;&lt;/b&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;/li&gt;
&lt;li data-end=&quot;946&quot; data-start=&quot;864&quot;&gt;&lt;b&gt;SESSION&amp;nbsp;B &amp;ndash; INSERT&amp;nbsp;250&lt;/b&gt;&lt;br /&gt;값&amp;nbsp;250은 (200&amp;nbsp;~&amp;nbsp;&amp;infin;) 갭 &amp;rarr; 범위 밖, 잠금 없음 &amp;rarr; &lt;b&gt;즉시 성공&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-end=&quot;1039&quot; data-start=&quot;948&quot;&gt;&lt;b&gt;SESSION&amp;nbsp;A &amp;ndash; COMMIT&lt;/b&gt;&lt;br /&gt;Next‑key&amp;nbsp;Lock 해제 &amp;rarr; B의 대기 쿼리(UPDATE&amp;nbsp;5&amp;middot;INSERT&amp;nbsp;150) 순차 실행 완료.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ai-generated-8661949_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c35S6o/btsNun85OOI/MnFmJl6jvfcV81uq1NBKkk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c35S6o/btsNun85OOI/MnFmJl6jvfcV81uq1NBKkk/img.jpg&quot; data-alt=&quot;팬텀을 설명하기 위한 유령 사진&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c35S6o/btsNun85OOI/MnFmJl6jvfcV81uq1NBKkk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc35S6o%2FbtsNun85OOI%2FMnFmJl6jvfcV81uq1NBKkk%2Fimg.jpg&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;553&quot; height=&quot;416&quot; data-filename=&quot;ai-generated-8661949_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;964&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;팬텀을 설명하기 위한 유령 사진&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;b&gt;3. 왜 잠금(Lock)을 알아야 하나?&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;실무에서 다음과 같은 문제가 발생할 위험이 있기 때문 입니다.&lt;/span&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;서비스 지연: 평소 주요 API 응답이 잠금 충돌로 인해 100ms -&amp;gt; 1.5s 로 늘어 날때, 대부분 Record/Next-Key Lock 충돌 입니다.&lt;br /&gt;&lt;/b&gt;- 동시성 높은 UPDATE/DELETE/SELECT &amp;hellip; FOR UPDATE 구문이 같은 인덱스 레코드(Record lock)나 범위(Next‑key lock = Record + Gap)를 건드리면 대기 시간이 선형 이상으로 늘어날 수 있다는 것이 MySQL 매뉴얼과 여러 운영 사례에서 확인됩니다&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;운영 해결 난이도:잠금 이슈는 재현이 까다로워, 일반 장애보다 2‑3배 긴 분석‑해결 시간이 필요합니다.&lt;br /&gt;&lt;/b&gt;- 락 대기는 트랜잭션 타이밍 의존성이 크고 재현 로드가 필요해 디버깅 난이도가 높은 편입니다. 공식 지표는 없지만, 여러 사례에서 일반 성능 문제보다 수 배의 분석 시간이 든다고 보고됩니다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySql 8 이후에는 Percona&amp;nbsp;PMM&amp;middot;Datadog&amp;nbsp;DB&amp;nbsp;Monitoring 같은 SaaS APM이 실시간으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;잠금 대기&amp;middot;데드락&amp;middot;블로킹 트리&lt;/b&gt;를 보여 줍니다. 그래서 장애 원인이 잠금이라는 사실은 예전처럼 수 시간 걸리지 않고 몇&amp;nbsp;분 안에 확인하는 경우가 많습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 무결성: 잘못된 트랜잭션 격리는 중복 결제&amp;middot;포인트 이중 적립 같은 오류를 야기합니다.&lt;/b&gt;&lt;br /&gt;- 낮은 격리 수준이거나 적절한 잠금이 없으면 &lt;b&gt;팬텀&amp;nbsp;리드&amp;middot;Dirty&amp;nbsp;Write&lt;/b&gt;가 발생합니다.&amp;nbsp; MySQL&amp;nbsp;InnoDB는 기본 격리 수준인 &lt;b&gt;Repeatable&amp;nbsp;Read&lt;/b&gt;에서 &lt;b&gt;Next‑key&amp;nbsp;Lock(Record&amp;nbsp;+&amp;nbsp;Gap)&lt;/b&gt; 을 자동으로 사용해 이러한 문제를 방지합니다. 단, 격리를 &lt;b&gt;READ&amp;nbsp;COMMITTED&lt;/b&gt;로 낮추면 갭 락이 꺼져 팬텀 리드를 막지 못합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션 격리 수준(lsolation Level) 이란?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동시에 실행되는 트랜잭션 사이에서 &amp;lsquo;어디까지 서로를 보지 못하게 할지&amp;rsquo; 규칙입니다. SQL 표준은 4단계(높음&amp;rarr;낮음)로 정의합니다. &amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-start=&quot;122&quot; data-end=&quot;612&quot;&gt;
&lt;tbody data-start=&quot;233&quot; data-end=&quot;612&quot;&gt;
&lt;tr data-start=&quot;233&quot; data-end=&quot;285&quot;&gt;
&lt;td data-start=&quot;233&quot; data-end=&quot;252&quot;&gt;&lt;b&gt;SERIALIZABLE&lt;/b&gt;&lt;/td&gt;
&lt;td data-start=&quot;252&quot; data-end=&quot;261&quot;&gt;완전 직렬화&lt;/td&gt;
&lt;td data-start=&quot;261&quot; data-end=&quot;265&quot;&gt;&amp;mdash;&lt;/td&gt;
&lt;td data-start=&quot;265&quot; data-end=&quot;285&quot;&gt;모든 SELECT도 공유 잠금&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;286&quot; data-end=&quot;423&quot;&gt;
&lt;td data-start=&quot;286&quot; data-end=&quot;319&quot;&gt;&lt;b&gt;&lt;br /&gt;REPEATABLE&amp;nbsp;READ&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(MySQL 기본)&lt;/td&gt;
&lt;td data-start=&quot;319&quot; data-end=&quot;339&quot;&gt;같은 쿼리 두 번 ‑ 결과 동일&lt;/td&gt;
&lt;td data-start=&quot;339&quot; data-end=&quot;352&quot;&gt;팬텀 리드 차단 O&lt;/td&gt;
&lt;td data-start=&quot;352&quot; data-end=&quot;423&quot;&gt;&lt;b&gt;Next‑key&amp;nbsp;Lock&lt;/b&gt;(Record＋Gap)&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://dev.mysql.com/doc/refman/9.1/en/innodb-locking.html?utm_source=chatgpt.com&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;MySQL Developer Zone&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;424&quot; data-end=&quot;551&quot;&gt;
&lt;td data-start=&quot;424&quot; data-end=&quot;445&quot;&gt;&lt;b&gt;READ&amp;nbsp;COMMITTED&lt;/b&gt;&lt;/td&gt;
&lt;td data-start=&quot;445&quot; data-end=&quot;459&quot;&gt;&amp;ldquo;커밋된 것만 본다&amp;rdquo;&lt;/td&gt;
&lt;td data-start=&quot;459&quot; data-end=&quot;481&quot;&gt;팬텀 리드 O, 전/후 값 차이 O&lt;/td&gt;
&lt;td data-start=&quot;481&quot; data-end=&quot;551&quot;&gt;&lt;b&gt;Record&amp;nbsp;Lock만&lt;/b&gt;, 갭&amp;nbsp;락 대부분 생략&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://dev.mysql.com/doc/refman/8.4/en/innodb-locks-set.html?utm_source=chatgpt.com&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;MySQL Developer Zone&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start=&quot;552&quot; data-end=&quot;612&quot;&gt;
&lt;td data-start=&quot;552&quot; data-end=&quot;575&quot;&gt;&lt;b&gt;READ&amp;nbsp;UNCOMMITTED&lt;/b&gt;&lt;/td&gt;
&lt;td data-start=&quot;575&quot; data-end=&quot;590&quot;&gt;언커밋 데이터까지 노출&lt;/td&gt;
&lt;td data-start=&quot;590&quot; data-end=&quot;603&quot;&gt;Dirty&amp;nbsp;Read&lt;/td&gt;
&lt;td data-start=&quot;603&quot; data-end=&quot;612&quot;&gt;최소 잠금&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;팬텀 리드란?&lt;br /&gt;(&lt;b&gt;next-key locking과 갭락이 비활성화 일경우&lt;/b&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1745307439581&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 트랜잭션 A
SELECT * FROM orders WHERE amount BETWEEN 100 AND 200; 

-- 트랜잭션 B (다른 트랜잭션)
INSERT INTO orders VALUES (999, 150); 

-- 트랜잭션 A - 팬텀 리드 발생
SELECT * FROM orders WHERE amount BETWEEN 100 AND 200;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;A가 같은 쿼리를 실행했는데, 중간에 B가 끼워 넣은 150원 주문이 팬텀(새 행)처럼 나타나 개수가 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;팬텀 리드 결과.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 82px;&quot; border=&quot;1&quot; data-end=&quot;868&quot; data-start=&quot;759&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;868&quot; data-start=&quot;789&quot;&gt;
&lt;tr style=&quot;height: 18px;&quot; data-end=&quot;803&quot; data-start=&quot;789&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-end=&quot;795&quot; data-start=&quot;789&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-end=&quot;803&quot; data-start=&quot;795&quot;&gt;120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-end=&quot;818&quot; data-start=&quot;804&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-end=&quot;810&quot; data-start=&quot;804&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-end=&quot;818&quot; data-start=&quot;810&quot;&gt;140&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-end=&quot;853&quot; data-start=&quot;819&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-end=&quot;825&quot; data-start=&quot;819&quot;&gt;999&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-end=&quot;832&quot; data-start=&quot;825&quot;&gt;150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot; data-end=&quot;868&quot; data-start=&quot;854&quot;&gt;
&lt;td style=&quot;height: 18px;&quot; data-end=&quot;860&quot; data-start=&quot;854&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot; data-end=&quot;868&quot; data-start=&quot;860&quot;&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;</description>
      <category>서버 Server</category>
      <category>MySQL</category>
      <category>mysql transaction lock</category>
      <category>mysql trsanction</category>
      <category>mysql 트랜잭션</category>
      <category>SQL</category>
      <category>transaction lock</category>
      <category>트랜잭션</category>
      <category>트랜잭션 잠금</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/89</guid>
      <comments>https://gift123.tistory.com/89#entry89comment</comments>
      <pubDate>Tue, 22 Apr 2025 17:24:33 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (44) Room @Transaction 에 대해 알아보자</title>
      <link>https://gift123.tistory.com/88</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;puzzle-91233_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nK9uM/btsNslJAkGM/VKZhyGxXWmzSMsoHRFdkk1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nK9uM/btsNslJAkGM/VKZhyGxXWmzSMsoHRFdkk1/img.jpg&quot; data-alt=&quot;로컬 데이터베이스에 대해 마스터를 해보자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nK9uM/btsNslJAkGM/VKZhyGxXWmzSMsoHRFdkk1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnK9uM%2FbtsNslJAkGM%2FVKZhyGxXWmzSMsoHRFdkk1%2Fimg.jpg&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;556&quot; height=&quot;960&quot; data-filename=&quot;puzzle-91233_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로컬 데이터베이스에 대해 마스터를 해보자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;@Upsert&lt;/span&gt;&lt;span&gt;로 INSERT 또는 UPDATE 한 줄에 사용법&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Upsert               // Room 2.7.0+
suspend fun save(user: User): Long&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;br /&gt;위와 같이 편리하게 사용하는 법이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동작 원리:&amp;nbsp; Room이 생성한 EntityUpsertionAdapter가 내부적으로 기본 키(PK)나 고유 제약조건(unique)&amp;nbsp; 예외가 없으면 INSERT, 예외 발생 시 (SQLiteConstraintException) UPDATE를 실행합니다.&amp;nbsp; 그래서 같은 트랜잭션 안에서 UPDATE.. WHERE pk = ? 실행 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Insert 가 실행된 경우 리턴 값은 row id ,&amp;nbsp; Update 가 실행된 경우 -1 입니다. 성공 여부나 업데이트된 컬럼 수를 알기 위해서는 추가 쿼리가 필요합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745150355096&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  
    @Upsert
    suspend fun save(user: User): Long

 
    @Query(&quot;SELECT changes()&quot;)
    suspend fun changes(): Int

    /**
     *  하나의 트랜잭션으로 묶어야 같은 커넥션에서 실행됨
     *  changes() 결과가 save()의 영향 행 수를 정확히 반환
     */
    @Transaction
    suspend fun saveAndGetAffected(user: User): Int {
        save(user)
        // INSERT면 1, UPDATE면 1, WHERE 조건 불일치면 0
        return changes()     
    }&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;Room DataBase 인스턴스는 기본적으로 커넥션 풀을 들고 있고, DAO 메서드를 호출할 때마다 거기서 &lt;b&gt;빈 커넥션 하나를 빌려&lt;/b&gt; 쿼리를 실행합니다. 그래서 위와 같이 트랜잭션으로 하나의 커넥션으로 묶어서 안전하게 사용할 수 있습니다. &lt;br /&gt;(추가 설명으로 changes() 는 현재 커넥션에서 직전에 실행된 삽입/수정/삭제가 영향을 준 행 수를 돌려줍니다. )&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;needle-4854847_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;867&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daT9lC/btsNsUqX0FZ/tgqItnKu2zGdchEepesiCk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daT9lC/btsNsUqX0FZ/tgqItnKu2zGdchEepesiCk/img.jpg&quot; data-alt=&quot;묶다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daT9lC/btsNsUqX0FZ/tgqItnKu2zGdchEepesiCk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaT9lC%2FbtsNsUqX0FZ%2FtgqItnKu2zGdchEepesiCk%2Fimg.jpg&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;507&quot; height=&quot;343&quot; data-filename=&quot;needle-4854847_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;867&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;묶다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-pm-slice=&quot;1 3 []&quot;&gt;&lt;b&gt;트랜잭션(Transaction) 이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여러 SQL 작업을 &amp;ldquo;한 덩어리&amp;rdquo;로 묶어  전부 성공 &amp;rarr; 모두 반영, 하나라도 실패 &amp;rarr; 전부 취소(rollback) 되도록 보장하는 기능입니다.&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;이해를 쉽게 하기 위해서 SQL로 예시를 들었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;예시. 주문과 재고를 동시에 수정 &lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[트랜잭션 X 일 경우]&lt;/p&gt;
&lt;pre id=&quot;code_1745151900862&quot; class=&quot;sql&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 트랜잭션 없이 실행
INSERT INTO orders(id, item_id, qty) VALUES (1001, 42, 1);

-- 어플리 케이션 예외!!!
-- 아래 UPDATE는 실행되지 못함
UPDATE stock SET qty = qty - 1 WHERE item_id = 42;&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;orders 테이블에 insert 한 구문만 실행되서 DB에 반영 됩니다.&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;[트랜잭션 O 일 경우]&lt;/p&gt;
&lt;pre id=&quot;code_1745152111668&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- BEGIN 이 트랜잭션 사용 입니다.
BEGIN;                                       
INSERT INTO orders(id, item_id, qty) VALUES (1001, 42, 1);

-- 어플리케이션 에러 발생!!
UPDATE stock SET qty = qty - 1 WHERE item_id = 42;

-- 성공시 반영, 에러 일땐 자동 ROLLBACK
COMMIT;&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;중간에 어플리케이션이 발생 했기 때문에 BEGIN; 아래에 있던 모든 구문은 실행이 되지 않고 DataBase에 변경사항이 없습니다. 즉 order 테이블에 값을 Insert&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가지가 있습니다. 첫번째 Atomicity(전부 성공 또는 전부 실패한다.), 두번째 Consistency(트랜잭션 전후에 DB 제약조건(무결성)이 유지된다.), 세번째 lsolation(동시에 실행되는 트랜잭션끼리 간섭 최소화), 네번째 Durability (커밋된 내용은 OFF 후에도 유지된다.)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;fiber-4814456_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rWb8x/btsNsSUdmMt/YyuSZ6gf7aTkHDA3yNEQl1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rWb8x/btsNsSUdmMt/YyuSZ6gf7aTkHDA3yNEQl1/img.jpg&quot; data-alt=&quot;안드로이드의 SQLite 특성으로 인해 주의할 점이 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rWb8x/btsNsSUdmMt/YyuSZ6gf7aTkHDA3yNEQl1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrWb8x%2FbtsNsSUdmMt%2FYyuSZ6gf7aTkHDA3yNEQl1%2Fimg.jpg&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;569&quot; height=&quot;379&quot; data-filename=&quot;fiber-4814456_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;안드로이드의 SQLite 특성으로 인해 주의할 점이 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;안드로이드 Room 트랜 잭션 (Transaction)&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1745152683051&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.beginTransaction()
try {
    dao.insertOrder(order)
    dao.insertOrderItems(items)

    // 트랜 잭션 성공
    db.setTransactionSuccessful() 
} finally {
	// 성공 플래그 있으면 COMMIT, 없으면 ROLLBACK
    db.endTransaction()             
}&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;안드로이드 Room 에서 메서드에 @Transaction 을 사용하면 사실상 SQLite 에서 위와 같이 이루어집니다. Room이 생성한 구현체가db.beginTransaction() &amp;rarr; 메서드 본문(또는 SELECT 쿼리) 실행 &amp;rarr; 예외 없으면 setTransactionSuccessful() &amp;rarr; endTransaction() 순서로 처리합니다. 메서드 안에서 발생한 예외가 전파되면 전부 롤백됩니다. &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Room에서는 위 패턴을 &lt;/span&gt;&lt;b&gt;@Transaction&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 애너테이션 하나로 감싸서 자동으로 처리합니다.&lt;br /&gt;&lt;/span&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;knot-5448108_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqPLTK/btsNtgN46tX/4DT1A4A5AiJBIUaiK9FOV1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqPLTK/btsNtgN46tX/4DT1A4A5AiJBIUaiK9FOV1/img.jpg&quot; data-alt=&quot;하나의 커넥션?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqPLTK/btsNtgN46tX/4DT1A4A5AiJBIUaiK9FOV1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqPLTK%2FbtsNtgN46tX%2F4DT1A4A5AiJBIUaiK9FOV1%2Fimg.jpg&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;527&quot; height=&quot;853&quot; data-filename=&quot;knot-5448108_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;하나의 커넥션?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;b&gt;&lt;br /&gt;&quot;트랜잭션을 사용하면 하나의 커넥션을 잡는다. &quot; 라는 뜻&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1745153354351&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//androidx.sqlite:sqlite 
public interface SQLiteConnection&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;커넥션(connection)은 SQLite 엔진 사이의 통신 채널을 뜻합니다. 안드로이드 내부 클래스는 SQLiteConnection(C 레벨 핸들 + 잠금 정보)로 사용 됩니다. Room DataBase 인터페이스는 커넥션 풀을 들고 있습니다. DAO 메서드를 호출 할때 마다 거기서 빈 커넥션 하나를 빌려서 쿼리를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;304&quot; data-start=&quot;273&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;트랜잭션(@Transaction)&amp;nbsp;&lt;/b&gt;을 선언하면&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;569&quot; data-start=&quot;307&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;364&quot; data-start=&quot;307&quot;&gt;Room이 db.beginTransaction()을 호출하며 하나의 커넥션을 잡습니다.&lt;/li&gt;
&lt;li data-end=&quot;411&quot; data-start=&quot;367&quot;&gt;메서드 본문(여러 DAO 호출 포함)을 &lt;b&gt;모두 그 커넥션에서&lt;/b&gt; 실행됩니다.&lt;/li&gt;
&lt;li data-end=&quot;569&quot; data-start=&quot;414&quot;&gt;끝나면 위에서 언급한대로 setTransactionSuccessful() &amp;rarr; endTransaction() &amp;rarr; 커밋 또는 롤백&lt;br /&gt;따라서 쿼리&amp;middot;DML이 &lt;b&gt;한 커넥션&amp;middot;한 트랜잭션 안에서 원자적으로&lt;/b&gt; 실행되는 것 입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1132&quot; data-start=&quot;1128&quot; data-ke-size=&quot;size16&quot;&gt;요약하자면&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1243&quot; data-start=&quot;1135&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1174&quot; data-start=&quot;1135&quot;&gt;&lt;b&gt;커넥션&lt;/b&gt; = DB와 대화하는 파이프(여러 개 존재 가능).&lt;/li&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1177&quot;&gt;&lt;b&gt;@Transaction&lt;/b&gt; = 한 파이프를 잠그고 그 안에서 모든 쿼리를 처리 &amp;rarr; 원자성 + 동일 커넥션 보장&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;안드로이드 SQLite는 하나의 DB 파일에 대해 여러 커넥션을 동시에 열수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;876&quot; data-start=&quot;652&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;876&quot; data-start=&quot;724&quot;&gt;
&lt;tr data-end=&quot;787&quot; data-start=&quot;724&quot;&gt;
&lt;td data-end=&quot;737&quot; data-start=&quot;724&quot;&gt;&lt;b&gt;Writer&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;781&quot; data-start=&quot;737&quot;&gt;INSERT/UPDATE/DELETE 실행 시 &lt;b&gt;단일&lt;/b&gt;로 lock 획득&lt;/td&gt;
&lt;td data-end=&quot;787&quot; data-start=&quot;781&quot;&gt;1개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;838&quot; data-start=&quot;788&quot;&gt;
&lt;td data-end=&quot;801&quot; data-start=&quot;788&quot;&gt;&lt;b&gt;Reader&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;820&quot; data-start=&quot;801&quot;&gt;SELECT 전용, 병행 읽기&lt;/td&gt;
&lt;td data-end=&quot;838&quot; data-start=&quot;820&quot;&gt;최대 3개 (구버전은&amp;nbsp;2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;876&quot; data-start=&quot;839&quot;&gt;
&lt;td data-end=&quot;844&quot; data-start=&quot;839&quot;&gt;합계&lt;/td&gt;
&lt;td data-end=&quot;866&quot; data-start=&quot;844&quot;&gt;한 앱 프로세스 안에서 1 ~ N개&lt;/td&gt;
&lt;td data-end=&quot;876&quot; data-start=&quot;866&quot;&gt;4 개 내외&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 Write는 커넥션을 1개, Reader 는 최대 3개까지 가지고 있습니다. 하나의 앱 프로세스 안에서 4개 내외로 여러 커넥션이 동시에 연결 될 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745996635854&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Dao
interface PersonDao {
    @Query(&quot;UPDATE person SET age = age + 1 WHERE id = :id&quot;)
    suspend fun inc(id: Long)

    @Query(&quot;SELECT changes()&quot;)
    suspend fun chg(): Int

    /** 트랜잭션 없음 */
    suspend fun incAndGetWrong(id: Long): Int {
    	// 커넥션 A
        inc(id)          
        
        // 커넥션 A or B (커넥션이 달라질 가능성 있음 결과 1 or 0)
        return chg()     
    }
}&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;&quot;Select changes()&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; 테스트를 하게 되면&amp;nbsp;처음 Update 할때 Writer 가 A 커넥션에서 실행됬지만 곧바로 이어진 &quot;Select changes()&quot; 가 커넥션 B에서 실행 될 수 있어서 개발자가 예측한 결과랑 다를 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Room에서 트랜잭션을 썼다고 해서 DB 접근을 '싱글스레드'로 하는 것이 아니고, 트랜잭션 구간 동안에는 다른 write가 잠시 대기한다 정도로 이해하면 됩니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;node-2032759_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdPtBU/btsNsUxIaHI/NyP4aGI1Sal1mcfgTEXKik/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdPtBU/btsNsUxIaHI/NyP4aGI1Sal1mcfgTEXKik/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdPtBU/btsNsUxIaHI/NyP4aGI1Sal1mcfgTEXKik/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdPtBU%2FbtsNsUxIaHI%2FNyP4aGI1Sal1mcfgTEXKik%2Fimg.jpg&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;482&quot; height=&quot;321&quot; data-filename=&quot;node-2032759_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;두 군데에서 동시에 트랙잭션(Transaction)을 사용한다면 일어나는 일&lt;br /&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설명 기반으로 안드로이드 SQLite 큐칙상 write 1개, read 3개 커넥션 연결이 가능 함으로 아래 표와 같은 결과가 생깁니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;866&quot; data-start=&quot;40&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;866&quot; data-start=&quot;151&quot;&gt;
&lt;tr data-end=&quot;483&quot; data-start=&quot;151&quot;&gt;
&lt;td data-end=&quot;205&quot; data-start=&quot;151&quot;&gt;&lt;b&gt;① 두 트랜잭션 모두 쓰기&lt;/b&gt;&lt;br /&gt;(INSERT&amp;middot;UPDATE&amp;middot;DELETE 포함)&lt;/td&gt;
&lt;td data-end=&quot;396&quot; data-start=&quot;205&quot;&gt;-실제 동작-&lt;br /&gt;SQLite는 &lt;b&gt;&amp;ldquo;동시 writer 1명&amp;rdquo;&lt;/b&gt; 규칙. &lt;br /&gt;첫 번째 트랜잭션이 writer 락을 잡고 진행 &amp;rarr; 두 번째 트랜잭션은 &lt;b&gt;대기&lt;/b&gt;. &lt;br /&gt;락 대기 시간이 busy_timeout(기본 5 s, Room에서 0 s) 를 넘기면 &lt;b&gt;SQLiteBusyException(database is locked)&lt;/b&gt; 발생.&lt;/td&gt;
&lt;td data-end=&quot;483&quot; data-start=&quot;396&quot;&gt;-보이는 현상-&lt;br /&gt;&amp;bull; 첫 번째 트랜잭션이 금방 끝나면 문제없음.&lt;br /&gt;&amp;bull; 길어지면 두 번째 호출 스레드/코루틴이 멈칫하거나 BUSY 예외가 터짐.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;644&quot; data-start=&quot;484&quot;&gt;
&lt;td data-end=&quot;508&quot; data-start=&quot;484&quot;&gt;&lt;b&gt;② 읽기(SELECT) &amp;times; 쓰기&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;605&quot; data-start=&quot;508&quot;&gt;-실제 동작-&lt;br /&gt;WAL 모드(안드 9+ Room 기본)에서는 &lt;b&gt;동시 읽기 OK&lt;/b&gt;. &lt;br /&gt;쓰기 트랜잭션이 커밋 직전 WAL &amp;rarr; DB&amp;nbsp;파일 합치기 단계에서 잠깐 읽기도 BLOCK.&lt;/td&gt;
&lt;td data-end=&quot;644&quot; data-start=&quot;605&quot;&gt;-보이는 현상- &lt;br /&gt;SELECT가 아주 짧게 지연될 수 있지만 대부분 체감 안 됨.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;726&quot; data-start=&quot;645&quot;&gt;
&lt;td data-end=&quot;668&quot; data-start=&quot;645&quot;&gt;&lt;b&gt;③ 둘 다 읽기 전용 트랜잭션&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;713&quot; data-start=&quot;668&quot;&gt;-실제 동작-&lt;br /&gt;제한 없음. reader 커넥션이 3개라면 최대 3곳에서 병렬 SELECT.&lt;/td&gt;
&lt;td data-end=&quot;726&quot; data-start=&quot;713&quot;&gt;-보이는 현상- &lt;br /&gt;정상 동시 실행.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;866&quot; data-start=&quot;727&quot;&gt;
&lt;td data-end=&quot;785&quot; data-start=&quot;727&quot;&gt;&lt;b&gt;④ 같은 스레드&amp;middot;코루틴 안에서 중첩(@Transaction ➜ 내부 @Transaction)&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;845&quot; data-start=&quot;785&quot;&gt;-실제 동작-&lt;br /&gt;Room이 &lt;b&gt;&amp;ldquo;이미 열린 트랜잭션에 합류&amp;rdquo;&lt;/b&gt; &amp;rarr; 실제로는 하나의 트랜잭션으로 취급되어 원자성 보장.&lt;/td&gt;
&lt;td data-end=&quot;866&quot; data-start=&quot;845&quot;&gt;-보이는 현상- &lt;br /&gt;중첩이라도 BUSY 문제 없음.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;span style=&quot;letter-spacing: 0px;&quot;&gt; SQLite는 원래 &amp;ldquo;&lt;/span&gt;&lt;b&gt;한 순간에 writer 1명&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;rdquo; 모델이므로, &lt;/span&gt;트랜잭션을 두 군데서 동시에 열어도 &lt;b&gt;직렬(Queue) 처리&lt;/b&gt;될 뿐입니다. 만약 버벅임이 있다면, 버벅임 여부는 첫 트랜잭션이 얼마나 오래 writer 락을 잡느냐에 달려 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screw-1924174_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dm4jDR/btsNtstK385/Uzpq7dBbbLYysTX7zZiuu0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dm4jDR/btsNtstK385/Uzpq7dBbbLYysTX7zZiuu0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dm4jDR/btsNtstK385/Uzpq7dBbbLYysTX7zZiuu0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdm4jDR%2FbtsNtstK385%2FUzpq7dBbbLYysTX7zZiuu0%2Fimg.jpg&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;584&quot; height=&quot;306&quot; data-filename=&quot;screw-1924174_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;트랜잭션 (Transaction) 사용시 주의할 점&amp;nbsp;&lt;/b&gt;&lt;/h3&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;안드로이드 SQLite 는 동시 write 1명 규칙으로 인해서 트랜잭션이 write 커넥션을 오래 점유하면 그 시간 동안 새로 들어온 write 작업들은 락이 풀릴때까지 대기합니다.&amp;nbsp;&lt;br /&gt;- Update 작업용 코루틴이 길게 블로킹 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &quot;database is locked&quot; 로그가 발생하기도 합니다.&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;br /&gt;&lt;br /&gt;&lt;/b&gt;1. 트랜작션은 짦고 굵게 쓰는것이 중요합니다. DB I/O or 네트워크 or Cpu가 무거울만한 작업 등 이러한 작업들은 @Transaction 사용을 고려하는것이 중요해보입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 잦은 단건 INSERT를 메모리 큐에 모아서 50~100건씩 한 트랜잭션 처리 (또한, Room 에서 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;@Insert fun insert(list: List&amp;lt;Order&amp;gt;)&lt;/span&gt;&quot; 을 만들어도 자동으로 트랜잭션 처리가 되긴 합니다.)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. busy_timeout 설정을 통해서 한 트랜잭션이 writer 락을 오래 잡고 있어도 SQLiteBusyException 가 곧바로 발생하지 않고 대기 할 수 있도록 시간을 설정 해두는 것도 하나의 방법 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- busy_timeout 이란? SQLite 가 lcok 에 걸려서 SQLITE_BUSY(&quot;database is locked) 를 내보내려 할 때, 그 예외를 던지지 않고 최대한&amp;nbsp;설정한 시간(ms) 동안 lock 해제를 기다렸다가, 그래도 풀리지 않으면 그때 예외를 던지게 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745156183358&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Database(entities = [Order::class, Stock::class], version = 1)
abstract class AppDb : RoomDatabase() {

    companion object {
        fun build(context: Context) =
            Room.databaseBuilder(context, AppDb::class.java, &quot;app.db&quot;)
                .addCallback(object : Callback() {
                    override fun onConfigure(db: SupportSQLiteDatabase) {
                        // 3초 동안 BUSY 대기
                        db.execSQL(&quot;PRAGMA busy_timeout = 3000;&quot;)
                    }
                })
                .build()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;이상 Android 트랜잭션에 대해서 상세히 알아봤습니다~&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>android jetpack room</category>
      <category>android room</category>
      <category>android sqlite</category>
      <category>Room</category>
      <category>room transaction</category>
      <category>안드로이드 jetpack</category>
      <category>안드로이드 room</category>
      <category>안드로이드 룸</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/88</guid>
      <comments>https://gift123.tistory.com/88#entry88comment</comments>
      <pubDate>Sun, 20 Apr 2025 22:51:47 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (43) Compose Recomposition 최적화</title>
      <link>https://gift123.tistory.com/87</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;church-1815342_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;946&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgFFgb/btsNrvZwnTI/hCPL2XxYEaaiqyrpMs5a1k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgFFgb/btsNrvZwnTI/hCPL2XxYEaaiqyrpMs5a1k/img.jpg&quot; data-alt=&quot;잘 만들어 보자..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgFFgb/btsNrvZwnTI/hCPL2XxYEaaiqyrpMs5a1k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgFFgb%2FbtsNrvZwnTI%2FhCPL2XxYEaaiqyrpMs5a1k%2Fimg.jpg&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;599&quot; height=&quot;443&quot; data-filename=&quot;church-1815342_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;946&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;잘 만들어 보자..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Compose 화면이 그려지는 그려지는 과정&lt;br /&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1744984583111&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun CounterDemo() {
    // 1. State 읽기
    var count by remember { mutableStateOf(0) }


    Counter(
        count = count,
    	// 2. 값 변경 Snapshot 이 변경 사실 기록
        onPlus = {                         
            count++                         
        }
    )
}

@Composable
private fun Counter(count: Int, onPlus: () -&amp;gt; Unit) {
    // 3. Recompose 될 때마다 여기 로그가 한 번씩 찍힌다.
    LaunchedEffect(count) {
        Log.d(&quot;RECOMPOSE&quot;, &quot;Counter recomposed with count = $count&quot;)
    }

	// 4. Applier 가 View 에 패치
    Button(onClick = onPlus) {              
        Text(&quot;Count = $count&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1) &lt;b&gt;MutableState 읽기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composable 함수가 count 같은 State 값을 읽으면, 런타임은 &quot;애가 이 값에 의존하고 있구나&quot; 하고 메모해 둡니다.&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) &lt;b&gt;사용자가 값 변경&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 눌러 &lt;b&gt;count ++&amp;nbsp;&lt;/b&gt;가 실행 되면, 그 순간 스냅샷 시스템이 &quot;관찰된 값이 바뀌었다&quot; 는 신호를 Recomposer 에게 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;3)&lt;b&gt; Recompose 예약 &amp;rarr; 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Recomposer 는 다음 프레임에서 해당 함수만 다시 호출합니다. 이때 이전 호출 때 만들어 둔 &lt;b&gt;SlotTable&lt;/b&gt;(UI 노드 설계도)을 들고 와서 &quot;바뀐 부분만&quot; 비교 하고 적용합니다.&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) &lt;b&gt;Applier가 실제 View 트리에 패치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 androidx.compose.runtime:runtime.Applier 가 변화된 Slot을 View 계층(또는 Compose UI 레이아웃 노드)에 반영하고 끝납니다. &quot;&lt;b&gt;핵심은 읽은 값 감시한다&lt;/b&gt;&quot; 라는 점입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;man-362150_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QFTLy/btsNsl2UDDK/6IrOdKO05CdNKeFH5BsPK0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QFTLy/btsNsl2UDDK/6IrOdKO05CdNKeFH5BsPK0/img.jpg&quot; data-alt=&quot;확인 해보자!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QFTLy/btsNsl2UDDK/6IrOdKO05CdNKeFH5BsPK0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQFTLy%2FbtsNsl2UDDK%2F6IrOdKO05CdNKeFH5BsPK0%2Fimg.jpg&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;579&quot; height=&quot;386&quot; data-filename=&quot;man-362150_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;확인 해보자!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Recomposition 낭비 중인지 확인 법?&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 컴파일러 메트릭 켜보기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744980760223&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android.compose.compiler.report=true
android.compose.compiler.metricsDestination=./compose-metrics&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gradle.properties 에 위를 적고 Release 빌드를 돌립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(빨리 확인용으로는 debug 빌드를 가끔 쓰기도 하지만.. )&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) skip.txt 파일을 열어봅니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;skip.txt 생성 디렉토리를 별도로 지정하지 않았다면 root 디렉토리 바로 밑에 compose-metrics/skip.txt&amp;nbsp; 가 있을겁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744980843528&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Counter.kt:25 restartable:3 skippable:10 restartSkips: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;restartable 은 재컴포지션 가능한 함수의 개수 입니다. skippable 은 그중 스킵 검사를 한 횟수 입니다. restartSkips 는 실제로 스킵이 일어난 횟수 입니다. 즉, &lt;b&gt;skippable &lt;/b&gt;숫자가 크고 &lt;b&gt;restartSkips&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;가 작다면 낭비가 있는 겁니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제는 스킵된 비율이 (skippable)10/ (restartSkips) 2&amp;nbsp; = 20%&amp;nbsp; 라면 검사 10번 중 8 번은 다시 그렸다는 말이므로 낭비가 일부 존재 합니다.&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;3) 실시간 프로퍼일러&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android Studio &amp;rarr; Layout Inspector &amp;rarr; Compose 탭을 열고, Recompose&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screw-4929711_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;851&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cY5aIh/btsNrXnVzyt/fff0R9369mLyF2WSpULPD1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cY5aIh/btsNrXnVzyt/fff0R9369mLyF2WSpULPD1/img.jpg&quot; data-alt=&quot;고쳐 보자!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cY5aIh/btsNrXnVzyt/fff0R9369mLyF2WSpULPD1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcY5aIh%2FbtsNrXnVzyt%2Ffff0R9369mLyF2WSpULPD1%2Fimg.jpg&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;645&quot; height=&quot;429&quot; data-filename=&quot;screw-4929711_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;851&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;고쳐 보자!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Recomposition 낭비 방지법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 람다(익명 함수) 새로 만들기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744980284364&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Button(onClick = { count++ })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 같은 람다를 매 프레임 새로 만들면, Compose는 람다도 UI 트리 일부라고 생각해 다시 비교하게 됩니다.&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;rarr; 아래와 같이rememberUpdateState 로 람다 내용만 갱신하고 람다 객체는 재사용하면 해결 합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1744983569915&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   // 1) 최신 count 값을 상태로 보관
    val currentCount by rememberUpdatedState(count)

    // 2) 람다 인스턴스는 remember 블록에서 단 한 번만 생성
    val plusOne = remember {
        {
            count = currentCount + 1
        }
    }

    Button(onClick = plusOne) {
        Text(&quot;Count = $count&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;&lt;b&gt;2) State 규칙 깨뜨리기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744981769942&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class User(var name: String)&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;var 프로퍼티&lt;/b&gt;가 있는 데이터 클래스를 파라미터로 넘기면 &lt;b&gt;불안정(Unstable) 타입&lt;/b&gt;으로 간주돼 모든 하위 UI가 재구성 됍니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp;&lt;b&gt;프로퍼티를 val&lt;/b&gt;로 하거나 &lt;b&gt;@Stable 및 @Immutable 애노테이션&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;3) key 규칙 안 주는 LazyColumn&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744982121966&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun ChatsScreen(chats: List&amp;lt;Chat&amp;gt;) {
    // 키 없이 사용 &amp;ndash; 스크롤 시 매 행을 새 항목으로 오해
    LazyColumn {
        items(chats) { chat -&amp;gt;                   
            ChatRow(chat)                        
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트 아이템에 고유 Key 를 안 주면 스크롤할 때 매 행이 새 데이터로 바뀌었다고 착각합니다. 그렇게 전부 재컴포지션이 일어납니다.&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;rarr;&amp;nbsp; 아래와 같이 꼭 key 를 써야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1744982180921&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; items(chats, key = { chat -&amp;gt; chat.id }) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;4) remeber 없이 새 객체를 계속 만들 때&amp;nbsp;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744982648113&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Button(
    onClick = { /* &amp;hellip; */ },
    // recomposition마다 새 객체 생성
    interactionSource = MutableInteractionSource()
) {
    Text(&quot;Click&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;br /&gt;위 예제 처럼 MutableInteractionSource() 로 새 인스턴스를 생성하면, Compose는 &lt;b&gt;프로퍼티가 바뀌었다고 판단&lt;/b&gt;해 Button&amp;nbsp;자체 + 내부 UI를 전부 다시 그립니다.&lt;br /&gt;&lt;br /&gt;&amp;rarr;&amp;nbsp; 아래와 같이 꼭 remember { &amp;hellip; } 처럼 객체를 만들어서 사용해야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744982731312&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val source = remember { MutableInteractionSource() }

Button(
    onClick = { /* &amp;hellip; */ },
    interactionSource = source           
) {
    Text(&quot;Click&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;5) mutableListOf&amp;nbsp;직접 수정으로 Snapshot&amp;nbsp;불일치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744982856698&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun BadListScreen() {
    // Compose가 변경을 추적하지 못하는 일반 MutableList
    val items = remember { mutableListOf&amp;lt;String&amp;gt;() }


    fun add(item: String) {
        items.add(item)          
    }

    Column {
        // 스크롤&amp;middot;다시 빌드 시 전체 행 재컴포지션 가능
        LazyColumn {
            items(items) { Text(it) }
        }
    
        // add 함수가 Button 클릭에서 호출된다고 가정
        Button(onClick = { add(&quot;Item ${items.size}&quot;) }) {
            Text(&quot;Add&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제 처럼 하면 문제가 생깁니다. 왜냐하면 &lt;b&gt;mutableListOf 는 Compose Snapshot 시스템이 변경을 관찰하지 못합니다.&lt;/b&gt; 즉, Recomposer는 리스트 전체가 새로 바뀌었다고 오해하고 모든 행을 재컴포지션&amp;middot;재측정 해버립니다.&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;rarr;&amp;nbsp; 두가지 방법이 있습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 1. 상태형 리스트 사용&lt;/p&gt;
&lt;pre id=&quot;code_1744983098351&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val items = remember { mutableStateListOf&amp;lt;Item&amp;gt;() }

fun add(item: Item) {
    items += item            
}&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_1744983126756&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var items by remember { mutableStateOf(emptyList&amp;lt;Item&amp;gt;()) }

fun add(item: Item) {
    items = items + item    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(+ 연산자는 plus() 역할이라서 새로운 리스트를 copy 함)&lt;br /&gt;&lt;br /&gt;두 방법 모두 Compose 가 변경을 추적해서 바뀐 행만 리컴포지션 합니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;tool-384740_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKwJxF/btsNsMFIYUz/Mtfa4NFA5EDwGAv5kRCrQ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKwJxF/btsNsMFIYUz/Mtfa4NFA5EDwGAv5kRCrQ1/img.jpg&quot; data-alt=&quot;정비 완료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKwJxF/btsNsMFIYUz/Mtfa4NFA5EDwGAv5kRCrQ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKwJxF%2FbtsNsMFIYUz%2FMtfa4NFA5EDwGAv5kRCrQ1%2Fimg.jpg&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;617&quot; height=&quot;411&quot; data-filename=&quot;tool-384740_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정비 완료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2253&quot; data-start=&quot;2163&quot;&gt;&lt;b&gt;Recomposition&lt;/b&gt;&amp;nbsp;은 &amp;ldquo;읽은 State 가 바뀌었을 때만&amp;rdquo; 발생한다&amp;mdash;그러나 람다&amp;middot;키&amp;middot;안정성 규칙을 놓치면 불필요한 재컴포지션이 폭증한다.&lt;/li&gt;
&lt;li data-end=&quot;2327&quot; data-start=&quot;2254&quot;&gt;&lt;b&gt;컴파일러 메트릭&lt;/b&gt;과 &lt;b&gt;Layout&amp;nbsp;Inspector&lt;/b&gt; 를 켜 두면 어느 함수가 낭비를 만드는지 금방 찾을 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;2403&quot; data-start=&quot;2328&quot;&gt;람다 객체 재사용, Stable 데이터, Lazy List key 사용, remember 사용, Lazy List - mutableList 사용 주의 등으로&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>Android Compose</category>
      <category>recompsition</category>
      <category>안드로이드 개발</category>
      <category>안드로이드 컴포즈</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/87</guid>
      <comments>https://gift123.tistory.com/87#entry87comment</comments>
      <pubDate>Fri, 18 Apr 2025 22:48:07 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (42) Kotlin In Action 정리 - 6</title>
      <link>https://gift123.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(이전편 다시보기)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/77&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/77&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676795511139&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;안드로이드 개발 (41) Kotlin In Action 정리 - 5&quot; data-og-description=&quot;(이전편 다시보기) 안드로이드 개발 (40) Kotlin In Action 정리 - 4 (이전편 다시보기) 안드로이드 개발 (38) Kotlin In Action 정리 - 2 (이전 편 다시보기) 안드로이드 개발 (37) Kotlin In Action 정리 - 1 현업에서&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/77&quot; data-og-url=&quot;https://gift123.tistory.com/77&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdA1pA/hyRF6nfl5S/kFC7ZyNn9LXLvvfInrwsS0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bzt8dz/hyRF3jJ55J/4RiUyR6HTfROvQcmZXIHy0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/co2e63/hyRFZPbwlD/Zrt2EQDGxLK4SKijWdYQh1/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/77&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/77&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdA1pA/hyRF6nfl5S/kFC7ZyNn9LXLvvfInrwsS0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bzt8dz/hyRF3jJ55J/4RiUyR6HTfROvQcmZXIHy0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/co2e63/hyRFZPbwlD/Zrt2EQDGxLK4SKijWdYQh1/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (41) Kotlin In Action 정리 - 5&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;(이전편 다시보기) 안드로이드 개발 (40) Kotlin In Action 정리 - 4 (이전편 다시보기) 안드로이드 개발 (38) Kotlin In Action 정리 - 2 (이전 편 다시보기) 안드로이드 개발 (37) Kotlin In Action 정리 - 1 현업에서&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;어느덧 6장 요약 이네요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;참고로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;kotlin in action을 전체 요약본이 아니라 개인적으로 필요한 부분을 위주로 정리했습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6장 코틀린 타입 시스템&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;abstract-1867937_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQn69W/btrZR1ZkSwF/l0okFCnEY5TYKpSOTmuyXK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQn69W/btrZR1ZkSwF/l0okFCnEY5TYKpSOTmuyXK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQn69W/btrZR1ZkSwF/l0okFCnEY5TYKpSOTmuyXK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQn69W%2FbtrZR1ZkSwF%2Fl0okFCnEY5TYKpSOTmuyXK%2Fimg.jpg&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;636&quot; height=&quot;424&quot; data-filename=&quot;abstract-1867937_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kotlin 만의 타입 시스템은 타입이 널 허용하는지 안하는지에 따른 안전성과 가변과 불변 타입으로 나누거나 자바에서 신경써야 했던 래퍼 타입과 원시 타입의 구분이 스마트하게 된다는점 등등의 특징을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각: kotlin은 위같은 장점으로 굉장히 좋은 안전성을 보여줍니다.*&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;1) 널 가능성&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;예시로 if(s != null) s.toUpperCase() else null를 s?.toUpperCase() 간결하게 표현이 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677160943951&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//?. 처리가 없다면 아래와 같은 코드로 해야한다.
if(s != null) s.toUpperCase() else null

//?. 를 사용한다면 아래처럼 매우 간결한 코드로 null 처리를 매우 효율적으로 할 수 있다.
s?.toUpperCase()&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;- T? 에서 T가 null 일 경우에 null를 return 하지 않고 다른 값을 return하고 싶다면 엘비스 연산자 ?: 를 사용할 수 있다.&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;- T as? R 은 안전한 캐스트로 T가 R 타입으로 바꿀 수 없다면 null 를 return 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1677162157798&quot; class=&quot;dart&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//아래 두개의 코드는 같은 동작을 한다.

//1)
foo as?Type

//2)
if(foo is Type) foo as Type else null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그외에 null처리는 let?.{} , lateini, 확장함수를 이용한 방식 ex: Collection&amp;lt;T&amp;gt;.isNullOrEmpty()&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;- 모든 타입 파라미터는 기본적으로 null 이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677161607547&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt;test(t:T){
//위에서 T? 를 명시하지 않았음에도 불구하고 T는 될 수 있어서
// 안전한 호출자 ?. 를 사용해도 Ide의 조언이 없다.
    t?.hashCode()
}&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677161791548&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//T에 Any로 타입 상한을 하면 T는 null이 될 수 없으므로, 안전한 호출자 ?. 를 쓸 이유가 없다.
fun &amp;lt;T:Any&amp;gt;test(t:T){
    t.hashCode()
}&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;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각: 문법적으로 null 관리에 있어서 매우 뛰어나다고 생각합니다. 특히 저는 엘비스 연산자는 책에서 짦게 언급 되었지만 현업에서 실용적으로 잘 사용하고 있습니다.*&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;- 플랫폼 타입은 kotlin이 null 관련 정보를 알 수 없는 타입이기 때문에 굉장히 위험하기 때문에 본래 플랫폼 타입에 null 지정 여부 처리를 하던가(Java로 치면 @Nullable 애노테이션이 예시) 문서를 정리해서 공유해야 할 것이다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2023-02-23 232211.png&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AjuUH/btr0w1rXEV3/MdJm5Vf5KLst9Nv5HZ86kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AjuUH/btr0w1rXEV3/MdJm5Vf5KLst9Nv5HZ86kK/img.png&quot; data-alt=&quot;count:Int! 임에도 불구하고 null을 넣어도 컴파일 상 문제가 없다..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AjuUH/btr0w1rXEV3/MdJm5Vf5KLst9Nv5HZ86kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAjuUH%2Fbtr0w1rXEV3%2FMdJm5Vf5KLst9Nv5HZ86kK%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;677&quot; height=&quot;132&quot; data-filename=&quot;화면 캡처 2023-02-23 232211.png&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;count:Int! 임에도 불구하고 null을 넣어도 컴파일 상 문제가 없다..&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;- kotlin이 플랫폼 타입을 모두 null 허용 타입으로 처리하지 않고 플랫폼 타입이라는 개념을 사용한 이유는 외부에서 오는 모든 타입이 null 이라면 null을 검사하는 비용이 상당히 발생하기 때문에 플랫폼 타입이라는 가능성을 열어둔 것 이다. 대표적으로 ArraryList&amp;lt;String?&amp;gt;? 를 사용할 때 배열의 원소를 접근할 떄마다 null 검사 및 안전한 캐스트를 일일히 수행해야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각: Java와 Kotlin을 혼용한 프로젝트에서 눈에 띄게 실수 할 수 있는 문제라서 두개의 언어를 혼용하여 사용한다고 하면 플랫폼 타입에 대한 관리를 철저히 해야할 것 입니다.*&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;- Kotlin에서 자바 메서드를 override 할 때 메서드의 파라미터나 반환 타입을 null 허용할 타입으로 선언할지 아닌지 결정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677162909153&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//아래와 같은 자바 클래스가 있다면
public abstract class JavaTest {
    public abstract Integer addValue (Integer addValue);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1677163084971&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class JavaTestImpl() : JavaTest() {
//1) addVlue의 파라미터가 널을 허용하지 않도록 지정 가능
    override fun addValue(addValue: Int): Int {
        return addValue + 1
    }
}


class JavaTestImpl() : JavaTest() {
//2) addVlue의 파라미터가 널을 허용하도록 지정 가능
    override fun addValue(addValue: Int?):Int {
        return addValue + 1
    }
}

class JavaTestImpl() : JavaTest() {
//3) addVlue의 return 타입이 널을 허용하도록 지정 가능 
    override fun addValue(addValue: Int):Int? {
        return addValue + 1
    }
}


// 등등.. 상속받은 자바 클래스의 파라미터 나 리턴 타입을 언제든지 널 허용/비허용을 바꿀 수 있다.&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;2) 코틀린의 원시 타입&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kotlin은 개발자가 직접 원시 타입과 참조 타입을 구분하지 않는다. 문법 상황에 따라 원시 타입을 쓸지, 참조 타입을 쓸 지 Kotlin 컴파일러가 스마트하게 내부적으로 상황에 맞게 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Kotlin으로 개발하는 개발자는 Java처럼 원시타입 대신 참조 타입을 써야할지 고민할 필요가 없이 항상 한가지 타입만 사용하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677165557624&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//코틀린에서 아래와 같이 사용한다면,, 
val i:Int = 1
val list:List&amp;lt;Int&amp;gt; = listOf(1,2,3)&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_1677165732608&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; // 자바로 변환하면 아래와 같이 변한다. 
 // 아래는 원시 타입인 int를 사용하고
 int i = true;
 // Collection&amp;lt;T&amp;gt; 와 같이 참조 타입이 필요한 경우 Integer 타입을 사용하는것을 확인할 수 있다.
 List list = CollectionsKt.listOf(new Integer[]{1, 2, 3});&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Java 원시 타입을 코틀린에서 사용할 때 (플랫폼 타입이 아니라) 널이 될 수 없는 타입으로 취급이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- null 이 될 수 있는 Kotlin 타입은 자바 원시 타입으로 표현할 수 없기 때문에 kotlin 에서 null 이 될 수 있는 원시 타입을 사용하게 되면 그 타입은 Java의 래퍼 타입으로 컴파일 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Java와 Kotlin 모두 제네릭 클래스는 박스 타입을 사용하는 공통점이 있다.&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;- kotlin의 잊을만한 원시 타입 리터럴은 double 타입에서 1.2e10, 1.2e-10 표현이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin의 잊을만한 원시 타입 리터럴은 0x나 0X 접두사가 붙은 16진 리터럴: 0xCAFEBABE, 0xbcdL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin의 잊을만한 원시 타입 리터럴은 0b나 0B 접두사가 붙은 2진 리터럴: 0b000000101&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin는 숫자 리터럴 중간에 밑줄을 넣을 수 있다.(1_123, 1_000_000)&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;- Any는 Kotlin의 널이 될 수 없는 타입의 최상위 타입이다. (null을 허용하려면 Any?) 원시 타입이던, 참조 타입이던 모든 타입의 조상 타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677167118471&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Any는 아래 3가지 메서드를 가지고 있다.
public open class Any {

    public open operator fun equals(other: Any?): Boolean

    public open fun hashCode(): Int

    public open fun toString(): String
}&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;- Java에서 Any와 비슷한 것은 Object 인데, 참조타입만 Object가 정점으로 하는 타입 계층이 된다.&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;- Unit 타입은 void와 비슷하지만 다르다. Unit은 타입인자로 사용할 수 있다. 코틀린의 함수는 반환하는 타입을 명시하지 않으면 묵시적으로&amp;nbsp; Unit을 반환하고 있고, 별도의 return 을 명시할 필요 없이 Unit을 반환 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1677167330934&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Unit은 싱글톤 으로 구현 되어 있음
public object Unit {
    override fun toString() = &quot;kotlin.Unit&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;- Nothing은 정상적으로 끝나지 않음을 의미 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677167773923&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Nothing private constructor()&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;span style=&quot;color: #ee2323;&quot;&gt;/*Loner 생각: Nothing 은 정말 위 한줄이 끝입니다. class로 만들었는데, 생성자가 private이라서 생성할 수가 없습니다. 내부에 companion object가 있는것도 아니라서 호출할 내부 인스턴스도 없으며, object가 아닌 class라서 단일 인스턴스로 사용할 방법이 없습니다.&amp;nbsp; 그래서 함수에 반환 타입으로 사용하면 Nothing 새롭게 생성하거나 호출할 단일 인스턴스가 없어서, 함수내에 오직 throw와 같은 프로그램의 종료를 적는 수밖에 없습니다.*/&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_1677168099508&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun nothingTest(): Nothing {
    throw RuntimeException(&quot;이 함수의 끝에 throw말고 적을 수 있는게 없다..&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 컬렉션과 배열&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- null 가능성에 관해서 크게 4가지 경우가 있다. Collection&amp;lt;T?&amp;gt;? , Collection&amp;lt;T?&amp;gt; , Collection&amp;lt;T&amp;gt;?, Collection&amp;lt;T&amp;gt; 어떤 null 가능성을 사용하는 가에 따라 널 검사 하는 빈도나 처리하는 문법 등등이 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;/*Loner 생각:&lt;span&gt; 개인적으로 Collection&amp;lt;T&amp;gt;? 와 같이 Collection이 아예 빈 경우를 나타낼거라면 대안으로 Collection&amp;lt;T&amp;gt;로 타입을 지정한 뒤 초기 값으로 emptyList()을 활용하는 경우가 많은 것 같습니다.&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;- Collection 타입은 각각 Collection과 MutableCollection 으로, 읽기 전용 혹은 변경 가능한 컬렉션으로 나뉘어 있고, 상황에 따라 효율적으로 변경 가능, 읽기 전용을 사용해서 변경할 지점과 읽기 지점을 확실히 명시 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;/*Loner 생각:&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span&gt; 아래 코드는 Kotlin.collections 최상위 파일에 적혀있는 Collection의 핵심이 되는 코드들 입니다. &lt;/span&gt;*/&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_1677168858564&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package kotlin.collections

import kotlin.internal.PlatformDependent

public interface Iterable&amp;lt;out T&amp;gt; {
    public operator fun iterator(): Iterator&amp;lt;T&amp;gt;
}

public interface MutableIterable&amp;lt;out T&amp;gt; : Iterable&amp;lt;T&amp;gt; {
    override fun iterator(): MutableIterator&amp;lt;T&amp;gt;
}

public interface Collection&amp;lt;out E&amp;gt; : Iterable&amp;lt;E&amp;gt; {
    public val size: Int

    public fun isEmpty(): Boolean

    public operator fun contains(element: @UnsafeVariance E): Boolean

    override fun iterator(): Iterator&amp;lt;E&amp;gt;

    public fun containsAll(elements: Collection&amp;lt;@UnsafeVariance E&amp;gt;): Boolean
}


public interface MutableCollection&amp;lt;E&amp;gt; : Collection&amp;lt;E&amp;gt;, MutableIterable&amp;lt;E&amp;gt; {
    override fun iterator(): MutableIterator&amp;lt;E&amp;gt;
    
    public fun add(element: E): Boolean

    public fun remove(element: E): Boolean

    public fun addAll(elements: Collection&amp;lt;E&amp;gt;): Boolean

    public fun removeAll(elements: Collection&amp;lt;E&amp;gt;): Boolean

    public fun retainAll(elements: Collection&amp;lt;E&amp;gt;): Boolean

    public fun clear(): Unit
}

public interface List&amp;lt;out E&amp;gt; : Collection&amp;lt;E&amp;gt; {

    override val size: Int
    override fun isEmpty(): Boolean
    override fun contains(element: @UnsafeVariance E): Boolean
    override fun iterator(): Iterator&amp;lt;E&amp;gt;

    override fun containsAll(elements: Collection&amp;lt;@UnsafeVariance E&amp;gt;): Boolean
    
    public operator fun get(index: Int): E

    public fun indexOf(element: @UnsafeVariance E): Int
    public fun lastIndexOf(element: @UnsafeVariance E): Int

    public fun listIterator(): ListIterator&amp;lt;E&amp;gt;

    public fun listIterator(index: Int): ListIterator&amp;lt;E&amp;gt;

    public fun subList(fromIndex: Int, toIndex: Int): List&amp;lt;E&amp;gt;
}

public interface MutableList&amp;lt;E&amp;gt; : List&amp;lt;E&amp;gt;, MutableCollection&amp;lt;E&amp;gt; {

    override fun add(element: E): Boolean
    override fun remove(element: E): Boolean
    override fun addAll(elements: Collection&amp;lt;E&amp;gt;): Boolean
    public fun addAll(index: Int, elements: Collection&amp;lt;E&amp;gt;): Boolean

    override fun removeAll(elements: Collection&amp;lt;E&amp;gt;): Boolean
    override fun retainAll(elements: Collection&amp;lt;E&amp;gt;): Boolean
    override fun clear(): Unit

    public operator fun set(index: Int, element: E): E

    public fun add(index: Int, element: E): Unit
    
    public fun removeAt(index: Int): E

    override fun listIterator(): MutableListIterator&amp;lt;E&amp;gt;

    override fun listIterator(index: Int): MutableListIterator&amp;lt;E&amp;gt;

    override fun subList(fromIndex: Int, toIndex: Int): MutableList&amp;lt;E&amp;gt;
}

public interface Set&amp;lt;out E&amp;gt; : Collection&amp;lt;E&amp;gt; {
    override val size: Int
    override fun isEmpty(): Boolean
    override fun contains(element: @UnsafeVariance E): Boolean
    override fun iterator(): Iterator&amp;lt;E&amp;gt;

    override fun containsAll(elements: Collection&amp;lt;@UnsafeVariance E&amp;gt;): Boolean
}

public interface MutableSet&amp;lt;E&amp;gt; : Set&amp;lt;E&amp;gt;, MutableCollection&amp;lt;E&amp;gt; {

    override fun iterator(): MutableIterator&amp;lt;E&amp;gt;

    override fun add(element: E): Boolean

    override fun remove(element: E): Boolean

    override fun addAll(elements: Collection&amp;lt;E&amp;gt;): Boolean
    override fun removeAll(elements: Collection&amp;lt;E&amp;gt;): Boolean
    override fun retainAll(elements: Collection&amp;lt;E&amp;gt;): Boolean
    override fun clear(): Unit
}

public interface Map&amp;lt;K, out V&amp;gt; {

    public val size: Int

    public fun isEmpty(): Boolean

    public fun containsKey(key: K): Boolean

    public fun containsValue(value: @UnsafeVariance V): Boolean

    public operator fun get(key: K): V?

    @SinceKotlin(&quot;1.1&quot;)
    @PlatformDependent
    public fun getOrDefault(key: K, defaultValue: @UnsafeVariance V): V {
        return null as V
    }
    
    public val keys: Set&amp;lt;K&amp;gt;
    public val values: Collection&amp;lt;V&amp;gt;
    public val entries: Set&amp;lt;Map.Entry&amp;lt;K, V&amp;gt;&amp;gt;
    public interface Entry&amp;lt;out K, out V&amp;gt; {
        public val key: K
        public val value: V
    }
}

public interface MutableMap&amp;lt;K, V&amp;gt; : Map&amp;lt;K, V&amp;gt; {

    public fun put(key: K, value: V): V?

    public fun remove(key: K): V?
    
    @SinceKotlin(&quot;1.1&quot;)
    @PlatformDependent
    public fun remove(key: K, value: V): Boolean {
        return true
    }

    public fun putAll(from: Map&amp;lt;out K, V&amp;gt;): Unit

    public fun clear(): Unit

    override val keys: MutableSet&amp;lt;K&amp;gt;

    override val values: MutableCollection&amp;lt;V&amp;gt;

    override val entries: MutableSet&amp;lt;MutableMap.MutableEntry&amp;lt;K, V&amp;gt;&amp;gt;

    public interface MutableEntry&amp;lt;K, V&amp;gt; : Map.Entry&amp;lt;K, V&amp;gt; {
        public fun setValue(newValue: V): V
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;/*Loner 생각:&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span&gt;&lt;span&gt; 모든 Collection 타입은 인터페이스로 구현이 되어있고, Iterable로 부터 시작해 Collection에서 구현하기 시작해서 계층구조를 만들어 왔다는 것을 확인할 수 있습니다. 사용 되는 class는 따로 있습니다. 대표적인 예시로 ArrayList, HashSet, LinkedHashMap 입니다. ArrayList 은 최종적으로 MutableList&amp;lt;T&amp;gt;를 구현하고 HashSet은 MutableSet&amp;lt;T&amp;gt; 를 최종 구현하고 LinkedHashMap는 MutableMap&amp;lt;T&amp;gt;를 구현합니다. &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;pre id=&quot;code_1677169335615&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//MutableList를 구현 한다.
expect class ArrayList&amp;lt;E&amp;gt; : MutableList&amp;lt;E&amp;gt;, RandomAccess {
 /*...*/
}

//MutableSet을 구현 한다.
expect class HashSet&amp;lt;E&amp;gt; : MutableSet&amp;lt;E&amp;gt; {
/*...*/
}

//MutableMap을 구현 한다.
expect class LinkedHashMap&amp;lt;K, V&amp;gt; : MutableMap&amp;lt;K, V&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;&lt;span style=&quot;color: #ee2323;&quot;&gt;/*Loner 생각:&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;그렇기 때문에 Kotlin이 Java 와 똑같은 Collection을 쓸 수 있는 이유가 위와 같이 인터페이스로 정의한 타입에 따라 필요한 기능만 쓰면서 제한을 두거나 사용하기 때문에 Java와 같은 Collection 을 쓰면서 Kotlin만의 특징을 살릴 수 있었던 이유가 Collection 전용 확장함수와 함께 위 같은 계층을 가졌기 때문 입니다.*/&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: #000000;&quot;&gt;- 읽기 전용 컬렉션이 항상 멀티스레딩에서 안전하다는 보장은 없다. kotlin의 collection은 읽기 전용과 변경 가능 전용 타입을 사용하는데 상황에 따라 같은 컬렉션 객체를 읽기전용과 변경 가능 전용 타입이 동시에 참조한다면 문제가 발생할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&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;color: #000000;&quot;&gt;- Java와 kotlin을 같이 쓰는 프로젝트에서 Java는 읽기 전용, 변경 가능 개념이 없기 때문에 Java 메서드 인자에 Kotlin의 Collection을 넘길때 이를 유의해야한다. (kotlin에서 읽기 전용 컬렉션으로 Java 메서드에 인자를 넘겼지만, Java 메서드에서 변경 가능으로 쓰고 있으면 해당 읽기 전용 컬렉션 내용이 변경된다.)&lt;/span&gt;&lt;span style=&quot;color: #000000;&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;- 플랫폼 타입에서 Java 상위 클래스를&amp;nbsp; Kotlin이 구현해서 메서드를 override해서 구현 할때 collection 타입인 파라미터도 읽기 전용이나 변경 가능 전용으로 개발자가 원하는 의도대로 바꿀 수 있다.&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;- 배열에서 원시 타입을 쓰고 싶다면 IntArray , ByteArrary, CharArray 등등 원시타입을 사용할 수 있도록 하는 배열들을 지원한다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5WAaW/btrZJcuIKuT/5VLY6FKukQlrFkuiyRPPsk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5WAaW/btrZJcuIKuT/5VLY6FKukQlrFkuiyRPPsk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5WAaW/btrZJcuIKuT/5VLY6FKukQlrFkuiyRPPsk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5WAaW%2FbtrZJcuIKuT%2F5VLY6FKukQlrFkuiyRPPsk%2Fimg.jpg&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;663&quot; height=&quot;853&quot; data-filename=&quot;다운로드.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>androidkotlin</category>
      <category>Kotlin</category>
      <category>kotlin개발</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>안드로이드코틀린</category>
      <category>자바</category>
      <category>코틀린</category>
      <category>코틀린개발</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/78</guid>
      <comments>https://gift123.tistory.com/78#entry78comment</comments>
      <pubDate>Fri, 24 Feb 2023 01:43:15 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (41) Kotlin In Action 정리 - 5</title>
      <link>https://gift123.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(이전편 다시보기)&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676690442706&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;안드로이드 개발 (40) Kotlin In Action 정리 - 4&quot; data-og-description=&quot;(이전편 다시보기) 안드로이드 개발 (38) Kotlin In Action 정리 - 2 (이전 편 다시보기) 안드로이드 개발 (37) Kotlin In Action 정리 - 1 현업에서 일을 하다보면, Back To The Basic 을 통하여 디버깅 추적 및 안전&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/75&quot; data-og-url=&quot;https://gift123.tistory.com/75&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eAJijW/hyREHBmolA/kDMBBpIpwGBbY8KLQqfjG1/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/bdaHIJ/hyRF9C2ipT/WTRQISAzO7QKc7KlVhpJbK/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/4TuAQ/hyRELX2EjC/jdkSJP6gzoBWuLV4FhTsJ1/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/75&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/75&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eAJijW/hyREHBmolA/kDMBBpIpwGBbY8KLQqfjG1/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/bdaHIJ/hyRF9C2ipT/WTRQISAzO7QKc7KlVhpJbK/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/4TuAQ/hyRELX2EjC/jdkSJP6gzoBWuLV4FhTsJ1/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (40) Kotlin In Action 정리 - 4&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;(이전편 다시보기) 안드로이드 개발 (38) Kotlin In Action 정리 - 2 (이전 편 다시보기) 안드로이드 개발 (37) Kotlin In Action 정리 - 1 현업에서 일을 하다보면, Back To The Basic 을 통하여 디버깅 추적 및 안전&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전편들을 총 요약해보면, 1장은 kotlin의 탄생 배경,철학, 특징을 알아보았습니다.&amp;nbsp; 2장은 기본적인 문법(if, when,try,for 등등..)을 설명하였습니다. 3장은 함수 호출 관련, collection 의 설명, 문자열 치환 등등을 설명하였고 4장은 kotlin의 class,interface, object, companion object, 가시성 등등을 알아보았습니다.&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&gt;그리고 이어서 kotlin in action 5장에서 정리할만한 내용들을 설명합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로, &lt;b&gt;kotlin in action을 읽으면서 잊혀지기 쉽거나 중요한 내용을 위주로 정리를 하였습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5장 - 람다로 프로그래밍&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;math-3999637_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAboVP/btrZNJLz9A9/IWCcErj6y9trM92butrKF1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAboVP/btrZNJLz9A9/IWCcErj6y9trM92butrKF1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAboVP/btrZNJLz9A9/IWCcErj6y9trM92butrKF1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAboVP%2FbtrZNJLz9A9%2FIWCcErj6y9trM92butrKF1%2Fimg.jpg&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;524&quot; height=&quot;350&quot; data-filename=&quot;math-3999637_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다 식은 기본적으로 다른 함수에 넘길 수 있는 작은 코드 조각을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각:&lt;span&gt; 인자로 람다를 넘길 수 있는 부분이 매우 중요하다 생각합니다. 상속보다 합성으로 코드 구조를 설계할 수 있으며, (대표적으로 compose) 표준 라이브러리를 사용해서 선언형 방식의 코드 작성을 돕게 합니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;kotlin 표준 라이브러리는 람다를 아주 많이 사용하고 있습니다. 과거 자바 프로그래머들은 자바에 람다의 도입을 오랫동안 기다려왔고, 자바 8의 람다는 그 기다림의 끝이었습니다. 그만큼 람다는 중요하게 생각되는 기능 입니다.&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;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 아래는 람다를 사용하지 않고 이벤트를 넘길 때&amp;nbsp; (Java)&lt;/p&gt;
&lt;pre id=&quot;code_1676691448035&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;람다 사용전.. 이벤트를 정의할 때
public class LamdaTest {
    public static void main(String[] args) {
        Button button = new Button();
        button.setOnClickListener(new OnClickListener() {
            public void onClick() {
                //....//
            }
        });
    }
}

class Button{
    public void setOnClickListener(OnClickListener listener) {
        listener.onClick();
    }
}


interface OnClickListener{
    void onClick();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 람다를 사용한 koltin의 이벤트처리&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676691649130&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val button = Button()
    button.setOnClickListener{
        //..//
    }
}
class Button {
    fun setOnClickListener(onClick:()-&amp;gt;Unit) {
        onClick()
    }
}&lt;/code&gt;&lt;/pre&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;- collection에서 표준 라이브러리 함수는 기본적으로 람다를 활용합니다. 람다를 통해&amp;nbsp; collection의 원소 찾기, copy 등등을 쉽게 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1676692428952&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  val test = listOf(1,2,3,4)
    println(test.filter { it% 2==0 }.maxBy { it })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 멤버 참조를 통해 깔끔한 코드 구성도 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1676692626777&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val people = listOf(
        Person(&quot;로너&quot;, 31),
        Person(&quot;누나&quot;, 34)
    )
    println(people.maxBy(Person::age))
}

data class Person(val name: String, val age: Int)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 개인적으로 멤버 참조는 직관적인 가독성을 보여주기 때문에 매우 유용하기 쓰고 있습니다.&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;/p&gt;
&lt;pre id=&quot;code_1676692982955&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // 아래 두개로도 사용할 수 있으나 깔끔한 문법은 아니다.
  people.maxBy(){ it.age }
  people.maxBy({ it.age })
    
    // 아래가 가장 깔끔하다.
    people.maxBy{ it.age }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: kotlin으로 프로젝트를 진행하다보면 보통 함수 파러미터 정의 규약에 람다는 맨 마지막에 두는 경우가 많은데 위와 같이 깔끔하게 { }로 마지막을 꾸밀 수 있기 때문 입니다.&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;- 자바의 익명클래스 본문 안에서 외부 변수는 final 변수만 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 요즘 kotlin으로만 개발하다보니, 잊고 있었던 지식이네요..&quot;&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;- kotlin의 람다에서 캡쳐링한 변수가 final 변수가 아닐 때 Ref&amp;lt;T&amp;gt; 로 감싸서 래퍼에 대한 참조를 람다 코드와 함께 저장한다. java로 디컴파일한 코드는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676779729166&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//kotlin의 예시가 아래와 같다면..
var counter = 0 
val inc = {counter++}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1676779756131&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*자바에서 
아래와 같이 InfRef() 를 생성해서 counter.element에 value를 대입하여 사용합니다. */
final IntRef counter = new IntRef();
      counter.element = 0;
      Function0 inc = (Function0)(new Function0() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke() {
            return this.invoke();
         }

         public final int invoke() {
         // counter라는 IntRef() 참조 타입을 내부적으로 가지고 사용 합니다. 
            IntRef var10000 = counter;
            int var1;
            // counter++ 는 -&amp;gt; element + 1 을 counter 내부 element에 대입하는 것을 확인 할 수 있습니다.
            var10000.element = (var1 = var10000.element) + 1;
            return var1;
         }
      });&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;- kotlin의 멤버 참조, Person::age&amp;nbsp; -&amp;gt; Person은 클래스, ::은 구분&amp;nbsp; age는 멤버(프로퍼티나 메서드)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Person::age는 {person:Person -&amp;gt; person.age} 를 간략하게 쓴것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 즉, 람다식 입니다. 그렇기 때문에 람다를 인자로 받는 함수에서 중괄호 없이 {} 소괄호 ()안에 간단히 사용 할 수 있는 것입니다. ex: collection&amp;lt;T&amp;gt;.map{ it.age } 을 collection&amp;lt;T&amp;gt;.map(Person::age) 로 사용 가능한 이유입니다.&quot;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;- 함수 언어에서 에타 변환은 함수 f와 람다 {x -&amp;gt; f(x)}를 서로 바꿔쓰는 것을 뜻하는데,&amp;nbsp; 멤버참조가 대표적인 예시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin의 멤버 참조에서 클래스명만 생략한 것은 최상위에 선언된 멤버를 호출하는 것을 뜻한다. (::age, ::name)&lt;/p&gt;
&lt;pre id=&quot;code_1676777498646&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Person(
    val name:String,
    val age:Int
)
    init {
    //::멤버명은 최상위의 함수나 프로퍼티를 참조한다.
        println(run(::age))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 위에서 run없이 println(::age) 를 사용하면 property age ~ 라는 문구만 출력될 뿐이다. ::age는 기본적으로 람다이기 때문에 println의 인자에서 바로 읽지는 못하는 것이다. 그래서 run(::age)을 통해 age를 return 받아 사용한다. &quot;&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;/p&gt;
&lt;pre id=&quot;code_1676778815132&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val createPerson =::Person    
val p = createPerson(&quot;아기&quot;,1)&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_1676779383851&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; val p = Person(&quot;로너&quot;,31)
    val getAgeByPerson = Person::age
    println(getAgeByPerson(p))

// {p.age} 를 쓰지 않고 바운드 멤버 참조를 통해 바로 age를 return 해준다. 
    val boundExample = p::age
    println(boundExample())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 멤버 참조는 람다식을 에타변환을 하는 것이라서, Person::age 는 {Person -&amp;gt; it.age} 와 같고 마지막 age를 반환해서 int를 얻는다 그래서 getAgeByPerson()은 인자로 Person을 받고 age를 return 하는 것이다.&quot;&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;2) 컬렉션 함수형 API&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- collection의 map을 사용할 때 참조 멤버를 쓰면 문법이 명확해보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- collection의 filter() 혹은 map() 을 쓸때 반복 작업으로 최종값을 여러번 구하는 것을 조심 하자&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676781147169&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//아래 코드는 100번 최댓값 연산을 수행한다.
people.filter { it.age == people.maxBy(Person::age)!!.age }

//아래와 같이 개선 하면 한번만 연산을 수행한다.
    val maxAge = people.maxBy(Person::age)?.age?:throw NullPointerException(&quot;Is Null&quot;)
    people.filter { it.age == maxAge }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각:&amp;nbsp; 사이드 이펙트를 최대한 줄인다 생각하고 코딩한다보면 위와 같은 실수를 한번쯤은 할 수 있겠네요. 배열의 반복 계산은 상당히 주의해야할 문제라 생각하고 때에 따라 객체를 따로 선언해서 쓰는것이 가독성면에서 더 plus가 되는 경우가 많은 경우가 있었습니다.&quot;&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&gt;- collection의 any, all 을 사용할때 !any,!all을 반대로 쓰지 않는것을 권장한다. !는 가독성이 떨어지기 때문이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1676781789825&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    println(people.all { it.age&amp;lt;=27 })
    println(people.any { it.age&amp;lt;=27 })
    
    // 아래 처럼 !를 쓰면 가독성이 떨어진다. !는 보기 놓치기 쉽다.
    println(!people.any { it.age&amp;lt;=27 })
    println(!people.all { it.age&amp;lt;=27 })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- collection에서 filter().size 보다, count를 사용하는 것이 효율적이다. 이유는 filter는 collection을 중간에 만들고 반면 count는 조건을 만족하는 원수의 개수만을 추적하기만 하고 collection을 만들지 않기 떄문에 효율적이다.&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;- find는 firstOrNull 과 같다. null이 나온다는 사실을 더 명확하게 하고 싶다면 firstOrNull을 사용하는 것이 나을 수도 있다.&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;- groupBy는 인자로 넘겨주는 값을 기준으로 key를 만들고 분류해서 map으로 변환 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1676782869043&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;println(people.groupBy(Person::age))

/* 
31=[Person(name=로너, age=31),Person(name=로너쌍둥이, age=31)], 
26=[Person(name=로너동생, age=26)],
33=[Person(name=로너형, age=33)],
34=[Person(name=로너누나, age=34)]
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각:&amp;nbsp; groupBy는 인자를 2개를 받는다. 두번쨰 인자는 valueTransform이라는 인자인데 그룹화 된 map의 &amp;lt;T&amp;gt;를 새로운 형식으로 바꾸거나 값을 바꿀 수 있다. 예시는 아래와 같다.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1676785977771&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public inline fun &amp;lt;T, K, V&amp;gt; Iterable&amp;lt;T&amp;gt;.groupBy(keySelector: (T) -&amp;gt; K,valueTransform: (T) -&amp;gt; V): Map&amp;lt;K, List&amp;lt;V&amp;gt;&amp;gt; {
    return groupByTo(LinkedHashMap&amp;lt;K, MutableList&amp;lt;V&amp;gt;&amp;gt;(), keySelector, valueTransform)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1676786167334&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;println(people.groupBy(Person::age,Person::name))
{31=[로너, 로너쌍둥이], 26=[로너동생], 33=[로너형], 34=[로너누나]}

println(people.groupBy(Person::age){it.age * 2 })
{31=[62, 62], 26=[52], 33=[66], 34=[68]}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- flatMap 은 map -&amp;gt; flatten 순으로 이루어지는 함수다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 개인적으로 배열&amp;lt;T&amp;gt;에서 T가 collection을 프로퍼티로 포함하고 있다면, 모든 원소의 이 collection을 전체로 묶어 하나의 리스트로 추출하기 위해 많이 사용하는 편 입니다. 아래 코드와 같은 상황에서 많이 씁니다.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1676786874676&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    val rooms = listOf(
        MyRoom(&quot;로너&quot;, listOf(&quot;asd&quot;, &quot;asd&quot;, &quot;asd&quot;)),
        MyRoom(&quot;로너&quot;, listOf(&quot;asd&quot;, &quot;asd&quot;, &quot;asd&quot;))
    )
    println(rooms.flatMap { it.books })
    //[asd, asd, asd, asd, asd, asd]&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) 지연 계산 컬렉션 연산&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 즉시 연산은 filter().map() 처럼 collection을 중간과정에 만들고 최종값을 얻는 방식이고, 지연 계산은 각 원소마다 filter의 계산식과 map의 계산식을 거쳐서 최종값을 얻는 방식이다. sequence를 이용해 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676793322321&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//collection을 .asSequence() 를 통해 시퀀스로 변환 시킨다.
people.asSequence()
    //아래 filter와 map은 중간 연산
            .filter { it.name.endsWith(&quot;너&quot;) }
            .map(Person::name)
    // 아래는 최종 연산이다. 마지막에 sequence로 반환 되기 때문에 필요한 collection으로 변환 해야한다.
            .toList()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 sequence의 내부 동작 설명용 코드다.&lt;/p&gt;
&lt;pre id=&quot;code_1676793503918&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;listOf(1, 2, 3, 4).asSequence().map {
        print(&quot;map($it)&quot;)
        it * it
    }.filter {
        print(&quot;filter($it)&quot;)
        it % 2 == 0
    }.toList()
    
    // 결과 : map(1)filter(1)map(2)filter(4)map(3)filter(9)map(4)filter(16)&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;- sequence는 최종 계산이 끝나면 더 이상 계산하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676793732032&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    listOf(1, 2, 3, 4).asSequence().map {
        print(&quot;map($it)&quot;)
        it * it
    }.find {
        if (it &amp;gt; 3) {
            print(&quot;find($it)&quot;)
            true
        } else {
            false
        }
    }
    
    //결과 map(1)map(2)find(4)
    //시퀀스를 쓰지 않았다면 map(3),map(4) 까지도 진행 되었을 것이다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- sequence 에서 filter와 map을 쓸때 filter를 먼저 쓰는것이 불필요한 연산을 덜하게 만든다. map을 먼저 사용하면 모든 원소를 변환하지만 filter를 먼저 하면 부적절한 원소를 먼저 제외하기 때문에 그런 원소는 변환되지 않기 때문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1676794158992&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  val people = listOf(
        Person(&quot;로너&quot;, 31),
        Person(&quot;로너동생&quot;, 26),
    )

    people.asSequence()
        .filter {
            it.name.endsWith(&quot;너&quot;)
        }
        .map {
            print(&quot;이 상황에서 map은 1번만 호출한다.&quot;)
            Person::name
        }
        .toList()

//결과: 이 상황에서 map은 1번만 호출한다.&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;- generateSequnce 함수로 시퀀스를 생성해서 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676794403662&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; val result = generateSequence { i ++ }.takeWhile { i &amp;lt;= 10 }.last()
    println(result)
    결과: 9&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: generateSequnce의 다양한 사용법은 아래 링크에서 확인이 가능하다.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/generate-sequence.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/generate-sequence.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676794492742&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;generateSequence - Kotlin Programming Language&quot; data-og-description=&quot; &quot; data-og-host=&quot;kotlinlang.org&quot; data-og-source-url=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/generate-sequence.html&quot; data-og-url=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/generate-sequence.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bk3PGy/hyRF08os6y/o2L4ybKCkqlcNVCELLb5LK/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/generate-sequence.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/generate-sequence.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bk3PGy/hyRF08os6y/o2L4ybKCkqlcNVCELLb5LK/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400');&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;generateSequence - Kotlin Programming Language&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;kotlinlang.org&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;b&gt;4) 자바 함수형 인터페이스 활용&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;- (SAM을 인자를 요구하는 메서드에 람다를 넘겨서 만든 객체일 경우) object: ~ 로 만든 무명객체와 람다는 차이가 있다. 객체를 명시적으로 선언하는 경우 메서드을 호출 할 때마다 새로운 객체가 생성된다. 람다는 주변 영역의 변수를 포획하지 않을 경우 무명 객체를 호출할 떄마다 반복 사용한다. &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&gt;&amp;nbsp;&lt;/span&gt;(SAM을 인자를 요구하는 메서드에 람다를 넘겨서 만든 객체일 경우) &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; 리스너 등록 / 해제를 위해서라면 무명객체를 써서 this을 활용해서 도중 취소가 가능하게끔 하는 방식도 있다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYsQN4/btrZKxrI4qs/xxeHZOFa5C3p4SrSJatvK0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYsQN4/btrZKxrI4qs/xxeHZOFa5C3p4SrSJatvK0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYsQN4/btrZKxrI4qs/xxeHZOFa5C3p4SrSJatvK0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYsQN4%2FbtrZKxrI4qs%2FxxeHZOFa5C3p4SrSJatvK0%2Fimg.jpg&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;618&quot; height=&quot;853&quot; data-filename=&quot;다운로드.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다에 대해 다시 살펴볼 수 있는 시간이라서 좋았습니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>androidkotlin</category>
      <category>Kotlin</category>
      <category>kotlin개발</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>안드로이드코틀린</category>
      <category>자바</category>
      <category>코틀린</category>
      <category>코틀린개발</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/77</guid>
      <comments>https://gift123.tistory.com/77#entry77comment</comments>
      <pubDate>Sun, 19 Feb 2023 17:30:25 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (38) Kotlin In Action 정리 - 2</title>
      <link>https://gift123.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;이전의 순서 1편에서 2편까지 모두 한꺼번에 정리하였었으나, 포스팅 글 제목으로 인해서 2라고 표시된 순서가 3장을 요약하는 글이 되버려서 인식하기에 불편하다는 생각이 들었습니다. 그래서 1편에서 2장을 분리하여 글제목과 요약 장이 일치하도록 재작성 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;(이전 편 다시보기)&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1676689365185&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;안드로이드 개발 (37) Kotlin In Action 정리 - 1&quot; data-og-description=&quot;현업에서 일을 하다보면, Back To The Basic 을 통하여 디버깅 추적 및 안전한 코드 작성 능력을 강화 할 수 있다는 것을 알게 됩니다. 이 글은 J brains 에서 일하는 Dmitry Jemerov, Kotlin 개발팀의 일원인 Sv&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/73&quot; data-og-url=&quot;https://gift123.tistory.com/73&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/X25GW/hyRGcsO33D/cuX9WZ81jkZA7KcLS5VYYk/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/eaYUqI/hyRETn98d2/HwQZQq9a1fnpeZwTK7lXt1/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/VCEdF/hyRGb1KCnL/WVOXrm4I3My4nvUB83FeQK/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/73&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/73&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/X25GW/hyRGcsO33D/cuX9WZ81jkZA7KcLS5VYYk/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/eaYUqI/hyRETn98d2/HwQZQq9a1fnpeZwTK7lXt1/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/VCEdF/hyRGb1KCnL/WVOXrm4I3My4nvUB83FeQK/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (37) Kotlin In Action 정리 - 1&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;현업에서 일을 하다보면, Back To The Basic 을 통하여 디버깅 추적 및 안전한 코드 작성 능력을 강화 할 수 있다는 것을 알게 됩니다. 이 글은 J brains 에서 일하는 Dmitry Jemerov, Kotlin 개발팀의 일원인 Sv&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2장 - 코틀린 기초&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;architecture-1857175_1280 (1).jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oH6uK/btrZNt9aBLK/wDfyQyAVxFd92OnNtd7Vm0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oH6uK/btrZNt9aBLK/wDfyQyAVxFd92OnNtd7Vm0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oH6uK/btrZNt9aBLK/wDfyQyAVxFd92OnNtd7Vm0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoH6uK%2FbtrZNt9aBLK%2FwDfyQyAVxFd92OnNtd7Vm0%2Fimg.jpg&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;510&quot; height=&quot;853&quot; data-filename=&quot;architecture-1857175_1280 (1).jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;2장에서 다루는 내용은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2장은 기본적인 내용은 어느정도 스킵하고 복습할만하다고 생각 되는 부분만 적었습니다.&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;- 함수, 변수, 클래스, enum, 프로퍼티 선언하는 방법&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;- when, if, try&amp;nbsp; 조건문 대부분 Expression 으로 되어있어서 값을 return할 수 있다. (Java는 Statement라서 반대)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;상황에 따라 코드가 간결해질 수 있고, Side effect를 방지할 수 있도록 도움 줄 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1676689325955&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//조건문을 통해서 값을 return 한다.
fun main() {
    val result = when (0) {
        1 -&amp;gt; &quot;1&quot;
        2 -&amp;gt; &quot;2&quot;
        else -&amp;gt; &quot;0&quot;
    }
    println(result)
}&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;- 반면 대입문은 Java에서는 Expression 이나, Kotlin에서는 Statement 로 되어 있어서 대입식과 비교식을 잘못 바꿔 써서 버그가 생기는 경우가 드뭅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각:&lt;span&gt; 즉 when,if,try는 expression , = 는 Statement 입니다. Java 같은 경우는 정반대로 되어 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;&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;- koltin에서 if문의 3항 연산자를 지원하지 않는 이유는 expression라서 따로 지원하지 않습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( kotlin의 if( a&amp;gt;b) b else a 이 a&amp;gt;b ? 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;- 긴 함수의 return문이 여럿 들어있는 경우를 생각(어떤 값을 return하는지 모두 명시하기 위해) 해서 식이 본문인 함수의 반환 타입만 생략이 가능합니다.&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;- val은 value에서 따왔고, var는 variable에서 따온 축약어 입니다.&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;-한글을 사용한 String 의 경우 상황에따라 unresolved reference 오류가 일어 날 수 있다. 그래서 한글은 ${} 써야하는 경우가 종종 있습니다.&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;- 기본 이름 규칙에 따라 is로 시작하는 변수명의 property 는 기본적으로 getter, setter 도 is로 시작하는 이름이 됩니다.&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;- Kotlin 에서 Smart Casting은 is 로 이루어지는데, 컴파일러에서 캐스팅을 해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- map collection을 Iteration 해서 쓸 수 있습니다다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 범위 비교는 in, !in 을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각: value in 1..5 와 같은 범위 수열 비교는 한눈에 보기에도 좋습니다. 매우 가독성이 좋다 생각합니다.&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;&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;- Kotlin은 unchecked와 check Exception을 구분하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Kotlin의 for문에서 down to 와 같이 다양한 방식을 활용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각:&lt;span&gt; 하지만 collection에 대한 공식 라이브러리 함수들이 너무 유용해서 kotlin을 오래 사용하다보면 for를 사용할일이 매우 드물게 되는것 같습니다. 또한 for는 st를 사용하다보면 사이드 이펙트를 일으키는 실수를 할 수 있기 때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;employees-2452807_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cT8y4H/btrZNv6YdFz/Kkmr92KbMb7CvPsa7ubHo1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cT8y4H/btrZNv6YdFz/Kkmr92KbMb7CvPsa7ubHo1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cT8y4H/btrZNv6YdFz/Kkmr92KbMb7CvPsa7ubHo1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcT8y4H%2FbtrZNv6YdFz%2FKkmr92KbMb7CvPsa7ubHo1%2Fimg.jpg&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;642&quot; height=&quot;428&quot; data-filename=&quot;employees-2452807_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;이상 Kotlin In Action 아주 짦은 2장 정리 였습니다.&amp;nbsp; 사실 아는 내용이 중복적으로 있어서 많은 내용을 담지 않았습니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>#Android</category>
      <category>#AndroidDev</category>
      <category>#androidkotlin</category>
      <category>#Kotlin</category>
      <category>#안드로이드</category>
      <category>#안드로이드Kotlin</category>
      <category>#안드로이드개발</category>
      <category>#안드로이드코틀린</category>
      <category>#코틀린</category>
      <category>#코틀린개발</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/76</guid>
      <comments>https://gift123.tistory.com/76#entry76comment</comments>
      <pubDate>Sat, 18 Feb 2023 12:14:02 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (40) Kotlin In Action 정리 - 4</title>
      <link>https://gift123.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;(이전편 다시보기)&lt;/p&gt;
&lt;figure id=&quot;og_1676655446532&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;안드로이드 개발 (38) Kotlin In Action 정리 - 2&quot; data-og-description=&quot;(이전 편 다시보기) 안드로이드 개발 (37) Kotlin In Action 정리 - 1 현업에서 일을 하다보면, Back To The Basic 을 통하여 디버깅 추적 및 안전한 코드 작성 능력을 강화 할 수 있다는 것을 알게 됩니다. 이&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/74&quot; data-og-url=&quot;https://gift123.tistory.com/74&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bMxdfy/hyRFYOSmyU/WUmAcq14V1pQLvG9eHDlc0/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/bOSwlO/hyRGejSlVs/8SZeN4Kzwadl3tREjRmDo0/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/cYIdK0/hyRF7dYJW2/8iTgpUD7Oyd68jEUWB1ex1/img.png?width=1097&amp;amp;height=427&amp;amp;face=0_0_1097_427&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/74&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/74&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bMxdfy/hyRFYOSmyU/WUmAcq14V1pQLvG9eHDlc0/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/bOSwlO/hyRGejSlVs/8SZeN4Kzwadl3tREjRmDo0/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/cYIdK0/hyRF7dYJW2/8iTgpUD7Oyd68jEUWB1ex1/img.png?width=1097&amp;amp;height=427&amp;amp;face=0_0_1097_427');&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;안드로이드 개발 (38) Kotlin In Action 정리 - 2&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;(이전 편 다시보기) 안드로이드 개발 (37) Kotlin In Action 정리 - 1 현업에서 일을 하다보면, Back To The Basic 을 통하여 디버깅 추적 및 안전한 코드 작성 능력을 강화 할 수 있다는 것을 알게 됩니다. 이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kotlin in action 요약 4번째 편 4장 요약입니다. kotlin 의 간단한 문법 설명 보다, 잊기 쉬울 만한 내용을 위주로 골라 정리하였습니다.&amp;nbsp;&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;4장 - 클래스, 객체, 인터페이스&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;geometry-1023846_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bN1aNB/btrZJc8BCXF/F4ZQq8sMavxKdSgywq49G0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bN1aNB/btrZJc8BCXF/F4ZQq8sMavxKdSgywq49G0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bN1aNB/btrZJc8BCXF/F4ZQq8sMavxKdSgywq49G0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbN1aNB%2FbtrZJc8BCXF%2FF4ZQq8sMavxKdSgywq49G0%2Fimg.jpg&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;673&quot; height=&quot;378&quot; data-filename=&quot;geometry-1023846_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java와 kotlin은 비슷하면서도 사뭇 다릅니다. 예를들어, kotlin의 interface에 프로퍼티 선언이 들어갈수 있고, class는 기본적으로 final 이며 public 입니다. 그외에 kotlin의 익명 클래스 사용이나, koltin의 compainon object , kotlin Object (싱글톤 클래스) 등등 자바와 차별점 되는 점들이 있습니다.&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;p data-ke-size=&quot;size16&quot;&gt;- kotlin의 interface는 default 구현 기능을 제공하며, 구현 클래스에서 default 구현 기능을 사용할때 override를 강제하지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1676656173050&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LonerImpl : Loner {
 // ide에서 구현을 강제하지 않는다.
}

interface Loner {
    fun blogPost() = print(&quot;블로그 작성&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;- kotlin은 interface를 다중 구현 할때, 두개의 인터페이스의 메서드 명이 같다면 구현할 클래스에서 반드시 중복된 override를 해야하며 어느 interface의 메서드를 사용할지 super&amp;lt;T&amp;gt; 로 명시가 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676656259337&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class LonerImpl : Loner, Mom {
// default 구현이라도 해도 인터페이스가 중복 된다면, ide에서 override를 강제 한다.
    override fun sleep() {
        super&amp;lt;Loner&amp;gt;.sleep()
        super&amp;lt;Mom&amp;gt;.sleep()
    }
}

interface Loner {
    fun blogPost() = print(&quot;블로그 작성&quot;)
    fun sleep() = println(&quot;로너가 잠을 자다&quot;)
}

interface Mom {
    fun sleep() = println(&quot;엄마가 잠을 자다&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin 1.5v 부터 interface의 default 구현을 사용할때 kotlin complier가 java interface의 default 메서드를 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676656875212&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; kotlin -&amp;gt; java 디컴파일 코드

public interface Mom {
   void sleep();

   @Metadata(
      mv = {1, 1, 18},
      bv = {1, 0, 3},
      k = 3
   )
   public static final class DefaultImpls {
      public static void sleep(@NotNull Mom $this) {
         String var1 = &quot;엄마가 잠을 자다&quot;;
         System.out.println(var1);
      }
   }
}&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) 기본적으로 final&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;또한, 정확한 설계나 지침으로 만들지 않은 class는 상속하지 않는 것이 하는 것이 낫다고 Effective Java 책에서 언급 합니다.그래서 상속을 생각하지 않는 class는 java에서 기본적으로 final로 하는것을 권장한다. kottlin은 위 같은 문제를 생각하여 class에 앞에 명시가 없다면 기본적으로 final 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 중요한 내용이라 생각되서 길게 기술했습니다. 그래서 상속보다 합성 방식이 더 낫기도 합니다. 사례로 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;android View 기반 class들 (Button,TextVeiw..)의 구조와 android Jetpack Compose를 비교해볼 수 있겠습니다.*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;- class의 기본적인 상속 가능 상태를 final의 장점으로 다양한 경우에서 kotlin의 smart Cast가 가능하다는 점이 생깁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, final을 가진 타입의 property가 final 이 아니라면 그 property를 다른 클래스가 상속하면서 커스텀 접근자를 정의하면서 스마트 캐스트의 요구 사항을 꺨 수 있습니다.&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;- java와 달리 kotlin은 패키지를 name space를 관리하기 위한 용도로 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin은 module 내부 가시성을 제공 합니다. (internal)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin은 최상위 선언(클래스, 함수 프로퍼티) 에 대해 private 가시성을 허용합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676658720927&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//최상위 파일 안에서 1개의 클래스와 1개의 확장함수

internal class Test{
    private fun test01() = print(&quot;test01&quot;)
    protected fun test02() = print(&quot;test02&quot;)
}
//Test에 빨간줄이 그어짐, 위에 internal이기 때문
fun Test.test(){

  //test01에 빨간줄이 그어짐 private 이기 때문
    test01()
    //test02에 빨간줄이 그어짐 protected 이기 때문
    test02()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- java는 같은 패키지 안에서 protected 멤버에 접근할 수 있지만, kotlin은 그렇지 않다. protected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 클래스 안에서만 보입니다.&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;- kotlin의 public, protected, private 변경자는 컴파일 된 자바 바이트코드 안에서도 그대로 유지 됩니다. 예외적으로 private class는 java의 패키지 전용 클래스로 컴파일 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676659537021&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//private은 아래와 같이 된다. 
final class PrivateTest {
   public PrivateTest() {
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin -&amp;gt; java 바이트코드 변경에서, internal 변경자는 java의 패키지 전용 internal과 다르고 모듈은 보통 여러 패키지로 이뤄지며, 서로 다른 모듈에 같은 패키지 속한 선언이 들어 있을수도 있어서, internal은 바이트 코드상 public으로 변합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676659602070&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 변경전 internal class InternalTest.. 

public final class InternalTest {

}&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;- kotlin -&amp;gt; java 바이트코드 변경상에서 internal 의 멤버 이름을 보기 나쁘게(?) 변경하는 이유는 두가지 입니다. 첫번째는 한 모듈에 속한 어떤 클래스를 모듈 밖에서 상속한 경우 그 하위 클래스 내부의 메서드 이름이 우연히 상위 클래스의 internal 메서드와 같아져서 내부 메서드를 override 하는 경우를 방지하기 위함이고, 두번째는 internal 클래스를 모듈 외부에서 사용하는 일을 막기 위함 입니다.&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) 내부 클래스와 중첩된 클래스&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- java와 정반대로 kotlin은 클래스 본문 안에 새로운 클래스를 선언해도 내부 클래스는 바깥 클래스를 기본적으로 참조할 수 없습니다. 내부 클래스가 바깥 클래스를 참조하면서 생기는 단점을 생각해서 기본적으로 바깥 클래스를 참조를 할 수 없게 되었습니다. 바깥 클래스를 참조하기 위해서 내부클래스에 inner를 사용 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676660074021&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class OuterClass{
    val test = 1
    class InnerClass{
        init {
        //참조 할 수 없다고 뜹니다.
            test
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1676660118101&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class OuterClass{
    val test = 1
    inner class InnerClass{
        init {
        //inner를 추가해서 바깥 클래스 참조 가능해졌습니다.
            test
        }
    }
}&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;5) sealed class&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sealed class는 본문 안쪽에서만 하위 클래스를 선언 할 수 있는 확장성이 막혀있는 class 입니다. 본문 외부에서 하위 클래스를 만들어 낼 수 없기 때문에 타입이 보장되는 장점이 있습니다. 대표적으로아래 코드 처럼, when 식이 모든 하위 클래스를 검사 하기 때문에 별도의 else가 필요 없어집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1676660524941&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun test(sealedClassTest: SealedClassTest){
    when(sealedClassTest){
        is SealedClassTest.Test01 -&amp;gt; {}
        is SealedClassTest.Test02 -&amp;gt; {}
    }
}

sealed class SealedClassTest{
    object Test01:SealedClassTest()
    data class Test02(val test:Int):SealedClassTest()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6) 클래스 선언&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin은 class 생성자는&amp;nbsp; primary 생성자와 secondary 생성자, init 블록 크게 3가지를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- secondary 생성자는 주로 자바와의 상호 운용성 및 자바 프레임워크 클래스를 확장해야할 때 사용하는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- secondary 생성자는 super() 를 통해 상위 클래스 생성자에게 객체를 위임 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- secondary 생성자는 this를 통해서 자신의 다른 생성자를 호출 할 수 있습니다.&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;7) 접근자의 가시성 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- custom getter setter도 가시성 변경이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1676661318196&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    //아래 set은 오류 난다.
    BlogExample.postCount++
    //아래 get은 문제 없이 사용 가능하다.
    println(BlogExample.postCount)
}

object BlogExample{
    var postCount = 0
    private set
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8) 클래스위임 by 키워드 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin에서 위임 패턴을 돕는 by 키워드가 있다. by 키워드를 통해서 불필요한 override fun를 쓰지 않고 필요한 것만 override를 해서 언어 차원에서 불필요한 코드를 줄일 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676661721666&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val testList = TestList&amp;lt;Int&amp;gt;()
    println(testList.add(0))
}

class TestList&amp;lt;T&amp;gt;(
    val innerTestList:MutableCollection&amp;lt;T&amp;gt; = mutableListOf()
): MutableCollection&amp;lt;T&amp;gt; by innerTestList{
    override fun add(element: T): Boolean {
        return false 
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: by 키워드가 없었다면.. MutableCollection에 있는 미구현 함수들을 모두 override 해야했을 것이고, 상속을 하지 않고 외부 객체 위임을 통해서 기능을 확장하니, 상속에 대한 단점을 두려워 하지 않아도 되서 좋다.&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;9) companion object&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- companion object의 함수도 확장함수로 이용 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- companion object의 이름을 붙일 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1676662223866&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    companion object Loner{
        ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- companion object를 통해서 class의 primary 생성자를 비공개로 만들고 팩토리 메서드를 활용해주면 매우 유용하다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676662195690&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CompanionObjectTest private constructor(val test:Int){
    companion object{
        ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- companion object&amp;nbsp; 에서 인터페이스 구현이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1676662360322&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface TestInterface{
    fun test()
}
class CompanionObjectTest {
    companion object :TestInterface{
        override fun test() {
            ...
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUfR30/btrZISbzylW/HFYkfM3ztMXY18UgGq3G70/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUfR30/btrZISbzylW/HFYkfM3ztMXY18UgGq3G70/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUfR30/btrZISbzylW/HFYkfM3ztMXY18UgGq3G70/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUfR30%2FbtrZISbzylW%2FHFYkfM3ztMXY18UgGq3G70%2Fimg.jpg&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;1280&quot; height=&quot;853&quot; data-filename=&quot;다운로드.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4장의 경우 내용이 꽤나 방대하게 느껴졌습니다. 아직은 크게 어렵지 않는 내용이었습니다. 잊기 쉬운 내용들을 위주로 정리를 하였고 기본기를 다시 리마인드 하는 느낌으로 꾸준히 진행할 예정입니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>androidkotlin</category>
      <category>Kotlin</category>
      <category>kotlin개발</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>안드로이드코틀린</category>
      <category>자바</category>
      <category>코틀린</category>
      <category>코틀린개발</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/75</guid>
      <comments>https://gift123.tistory.com/75#entry75comment</comments>
      <pubDate>Sat, 18 Feb 2023 04:36:25 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (39) Kotlin In Action 정리 - 3</title>
      <link>https://gift123.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;(이전 편 다시보기)&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1676655273877&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;안드로이드 개발 (37) Kotlin In Action 정리 - 1&quot; data-og-description=&quot;현업에서 일을 하다보면, Back To The Basic 을 통하여 디버깅 추적 및 안전한 코드 작성 능력을 강화 할 수 있다는 것을 알게 됩니다. 이 글은 J brains 에서 일하는 Dmitry Jemerov, Kotlin 개발팀의 일원인 Sv&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/73&quot; data-og-url=&quot;https://gift123.tistory.com/73&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/X25GW/hyRGcsO33D/cuX9WZ81jkZA7KcLS5VYYk/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/eaYUqI/hyRETn98d2/HwQZQq9a1fnpeZwTK7lXt1/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/VCEdF/hyRGb1KCnL/WVOXrm4I3My4nvUB83FeQK/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/73&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/73&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/X25GW/hyRGcsO33D/cuX9WZ81jkZA7KcLS5VYYk/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/eaYUqI/hyRETn98d2/HwQZQq9a1fnpeZwTK7lXt1/img.jpg?width=800&amp;amp;height=1066&amp;amp;face=0_0_800_1066,https://scrap.kakaocdn.net/dn/VCEdF/hyRGb1KCnL/WVOXrm4I3My4nvUB83FeQK/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (37) Kotlin In Action 정리 - 1&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;현업에서 일을 하다보면, Back To The Basic 을 통하여 디버깅 추적 및 안전한 코드 작성 능력을 강화 할 수 있다는 것을 알게 됩니다. 이 글은 J brains 에서 일하는 Dmitry Jemerov, Kotlin 개발팀의 일원인 Sv&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;Kotlin In Action 요약 세번째 편 입니다.&lt;/b&gt; 이전에 읽었던 책 이었지만, 다시 복습 겸 블로그 포스팅을 통해 내용 정리도 함께 하고 있습니다. 이번 편은 3장 요약 내용 입니다. 개인적으로 널리 알려진 내용 정리는 스킵 하였고, 다시 잊어 먹거나 반복 암기하면 좋을 내용들 위주로 정리하였습니다.&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;3장 - 함수 정의와 호출&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;pi-1453836_1280 (1).jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PNWZI/btrZIfyebhq/gYw9s4i7IsDDGtounVt8LK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PNWZI/btrZIfyebhq/gYw9s4i7IsDDGtounVt8LK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PNWZI/btrZIfyebhq/gYw9s4i7IsDDGtounVt8LK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPNWZI%2FbtrZIfyebhq%2FgYw9s4i7IsDDGtounVt8LK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;370&quot; data-filename=&quot;pi-1453836_1280 (1).jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 코틀린에서 컬렉션 만들기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kotlin 은 java의 표준 collection을 사용합니다. 이로써, 자바에서 코틀린 함수를 호출 하거나 코틀린에서 자바코드를 호출할 때 서로 변환할 필요가 없으므로 상호 작용하기가 훨씬 쉽습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1097&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6N16k/btrYThwn7gr/t5QSDbqfo46PPDkYZv3O90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6N16k/btrYThwn7gr/t5QSDbqfo46PPDkYZv3O90/img.png&quot; data-alt=&quot;java.util 에서 사용되는 collection 들을 사용하는편 이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6N16k/btrYThwn7gr/t5QSDbqfo46PPDkYZv3O90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6N16k%2FbtrYThwn7gr%2Ft5QSDbqfo46PPDkYZv3O90%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;427&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1097&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;java.util 에서 사용되는 collection 들을 사용하는편 이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 함수를 호출하기 쉽게 만들기&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;- kotlin 은 인자의 이름 붙여 사용할 수 있고, default Argment를 넣어서 활용 할 수 있습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 매우 강력한 기능 입니다. 인자를 붙임으로써 별도의 빌더 패턴을 없앨 수 있고, 중복된 오버로딩 코드를 제거함으로써, 코틀린의 가치관에서 언급한 중요하지 않는 코드 중복 제거 목적에 맞는 기능 입니다.*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;pre id=&quot;code_1676189177615&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    person(
        name = &quot;asd&quot;,
    )
}

fun person(name:String = &quot;asd&quot;,arg:Int = 123) = Unit&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1676189289607&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 자바로 변환하면 아래와 같은 함수로 바뀝니다.
   public static void person$default(String var0, int var1, int var2, Object var3) {
      if ((var2 &amp;amp; 1) != 0) {
         var0 = &quot;asd&quot;;
      }

      if ((var2 &amp;amp; 2) != 0) {
         var1 = 123;
      }

      person(var0, var1);
   }&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;- default Argment 를 자바에서 활용하려면 @JvmOverloads 애노테이션을 붙이면 자동적으로 오버로딩한 함수들이 생성됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1676189318448&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@JvmOverloads
fun person(name:String = &quot;asd&quot;,arg:Int = 123) = Unit&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1676189360976&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   //아래와 같이 변환 합니다.
   @JvmOverloads
   public static final void person(@NotNull String name) {
      person$default(name, 0, 2, (Object)null);
   }

   @JvmOverloads
   public static final void person() {
      person$default((String)null, 0, 3, (Object)null);
   }&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;- koltin 에서 클래스 없이 파일에 함수만 만들면 , java 로 디컴 파일 시에 클래스명이 파스칼 케이스로 변경되고 + Kt 접두사가 붙는 클래스가 생성되고 클래스 안에 함수가 생성되는것을 확인 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676189676396&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//파일명은 util
fun testUtil() = Unit&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1676189705415&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;자바에서 아래와 같이 변환 된다.
public final class UtilKt {
   public static final void testUtil() {
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위 처럼 최상위 함수를 사용할 때 자바에서 생성되는 클래스명을 바꾸고 싶다면 @file:JvmName을 활용 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 실무에서 여러 util 관련해서 최상위 함수를 쓰는 경우가 많아서 파일명을 고심해서 짓기 때문에 이미 고려한 파일명을 java 만 따로 다르게 만드는 경우가 거의 없을 것 같습니다. 있다면 kotlin 와 java를 같이 쓰는 프로덕션의 경우, 각 언어마다 Util 네이밍 규칙이 다를 경우 사용할 일이 있을 것 같습니다.*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1676189872664&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@file:JvmName(&quot;TestUtil&quot;)
fun testUtil() = Unit&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1676189849880&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//자바에서 아래와 같이 변환 된다.
@JvmName(
   name = &quot;TestUtil&quot;
)
public final class TestUtil {
   public static final void testUtil() {
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 확장 함수와 확장 프로퍼티&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;kotlin은 기존 java를 사용하는 곳에서 새롭게 도입 하는 곳들이 많기 때문에 기존 레거시나 혹은 java로 만들어진 라이브러리 및 프레임워크를 사용하는 경우가 대부분 입니다. 이런 기존 자바 API 를 재작성하지 않고도 확장 함수 및 확장 프로퍼티를 활용해서 기존 API에서 추가적인 기능을 만들 수 있습니다.&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;- 수신객체의 비공개 method 나 비공개 property (Java라면 비공개 Field) 등등 외부로 노출되지 않는 것들은 사용할 수 없으며, 확장 함수는 오버라이드 할 수 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 확장 프로퍼티는 아무 상태도 가질 수 없이 getter만 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 둘다. 확장성이 매우 뛰어나고 기존 객체에 영향을 받지 않으며, 선언형/함수형 코딩 방식을 쉽게 돕기도하며, private를 잘 활용해서 쓰면 필요한 부분에서만 가져다 쓸 수 있기 때문에 실전에서도 매우 유용하게 쓸 곳이 많습니다.*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&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_1676190452367&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    println(&quot;Hi&quot;.lastChar())
    println(&quot;Hi&quot;.firstChar)
    println(1.intPlusTest)
    println(1.intPlusTest())
}

fun String.lastChar() = last()
val String.firstChar get() = first()

val Int.intPlusTest get() = this +1
fun Int.intPlusTest() = this +1&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1676190879034&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//자바로 변환하면 다음과 같습니다.
// 특이점은 kotlin에서 &quot;Hi&quot;.lastChar() 이었던 호출 부가 
// Java에서 lastChar(&quot;Hi&quot;) 로 바뀌어 사용하게 됩니다. 
// java에서 확장함수는 static final 이며, 첫번째 인자로 수신객체를 받습니다.
public static final void main() {
      char var0 = lastChar(&quot;Hi&quot;);
      System.out.println(var0);
      var0 = getFirstChar(&quot;Hi&quot;);
      System.out.println(var0);
      int var1 = getIntPlusTest(1);
      System.out.println(var1);
      var1 = intPlusTest(1);
      System.out.println(var1);
   }

   public static void main(String[] var0) {
      main();
   }

   public static final char lastChar(@NotNull String $this$lastChar) {
      Intrinsics.checkParameterIsNotNull($this$lastChar, &quot;$this$lastChar&quot;);
      return StringsKt.last((CharSequence)$this$lastChar);
   }

   public static final char getFirstChar(@NotNull String $this$firstChar) {
      Intrinsics.checkParameterIsNotNull($this$firstChar, &quot;$this$firstChar&quot;);
      return StringsKt.first((CharSequence)$this$firstChar);
   }

   public static final int getIntPlusTest(int $this$intPlusTest) {
      return $this$intPlusTest + 1;
   }

   public static final int intPlusTest(int $this$intPlusTest) {
      return $this$intPlusTest + 1;
   }&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;b&gt;4) 컬렉션 처리&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;kotlin의 collection 은 java.util.~ 로 기존 java의 것을 사용하지만 공식적으로 지원해주는 collection의 확장 함수를 통해서 확장성 높은 collection으로 사용 되고 있습니다.&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;- vararg 는 Java랑 달리 * 를 쓰는 spread 연산자를 이용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1676192623460&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val temp = arrayOf(1,2,3)
    val list = listOf(1,*temp)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 중위 호출 infix call 로 아래와 같은 문법을 만들 수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1676192903884&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    println(&quot;로너&quot; age 31)
}

//좋은 코드는 아닙니다;; 
infix fun String.age(age:Int) = Person(this,age)
data class Person(
    val name:String,
    val age:Int
)&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;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 공식 지원해주는 infix fun 과 혼잡이 올 수 있어서 커스텀한 infix call을 사용했을때 보장되는 효율이 있을 때만 사용하면 좋을 것 같습니다. *&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;/p&gt;
&lt;pre id=&quot;code_1676193203508&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val (name,age) = &quot;로너&quot; to 31
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* Loner 생각: 개인적으로 when문에서 효율적으로 사용하는 경우가 많았습니다.*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;b&gt;5) 문자열 나누기&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- java에서 문자열의 split 메서드를 사용할 때 split은 기본적으로 구분 문자열을 정규식으로 사용 되기 때문에 혼동이 되는 경우가 있어서 kotlin은 split 대신에 여러가지 조합의 파라미터를 받는 split의 확장 함수를 제공함으로써 혼동을 야기하는 메서드를 감춥니다.&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;또한 정규식의 혼동을 막기 위해 toRegex() 확장 함수를 통해 Regex 타입을 따로 받기도 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1676194264599&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val name = &quot;로 너&quot;.replace(&quot; &quot;.toRegex(),&quot;&quot;)
    println(name)
    //로너&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;- kotlin 은 Regiex 타입을 넘기지 않아도 문자열 치환 하는 확장함수들을 많이 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &quot;&quot;&quot; &quot;&quot;&quot; 를 사용하면 별도의 escape 할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &quot;&quot;&quot; &quot;&quot;&quot; 는 프로그램 텍스트를 쉽게 문자열로 바꾸는 데도 사용한다.&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;6) 로컬 함수와 확장&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kotlin은 로컬 함수를 지원한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676195352836&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun function(){
    fun localFun(){
        //..//
    }
    localFun()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;Loner 생각: 책에서 나온 첫번째 예시는 좋으나.. 두번째 예시에서 함수 외부 객체를 통해 로컬 함수 내부의 코드량을 줄이던데, 함수형을 추구하는 코드를 짜고 있다면 두번째 예시가 꼭 좋다고만 볼 수는 없어보인다.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPFf2y/btrZIlrKASm/bfHCUDSd1p18HAvB9VHdKK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPFf2y/btrZIlrKASm/bfHCUDSd1p18HAvB9VHdKK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPFf2y/btrZIlrKASm/bfHCUDSd1p18HAvB9VHdKK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPFf2y%2FbtrZIlrKASm%2FbfHCUDSd1p18HAvB9VHdKK%2Fimg.jpg&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;608&quot; height=&quot;405&quot; data-filename=&quot;다운로드.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kotlin 기본적인 함수 호출,문자열, collection 등등을 알아보았습니다. 시간이 지나 다시 해석하니 감회가 새롭고, 당시에 지나쳤던 부분을 다시 음미할 수 있어서 좋네요&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>AndroidDev</category>
      <category>androidkotlin</category>
      <category>Kotlin</category>
      <category>안드로이드</category>
      <category>안드로이드 코틀린</category>
      <category>안드로이드개발</category>
      <category>안드로이드코틀린</category>
      <category>코틀린</category>
      <category>코틀린개발</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/74</guid>
      <comments>https://gift123.tistory.com/74#entry74comment</comments>
      <pubDate>Sun, 12 Feb 2023 19:20:05 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (37) Kotlin In Action 정리 - 1</title>
      <link>https://gift123.tistory.com/73</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20230130_174133328.jpg&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2f1ky/btrX2nrgzpX/BYSQ6vrNw5LJ6cJMdORKl0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2f1ky/btrX2nrgzpX/BYSQ6vrNw5LJ6cJMdORKl0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2f1ky/btrX2nrgzpX/BYSQ6vrNw5LJ6cJMdORKl0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2f1ky%2FbtrX2nrgzpX%2FBYSQ6vrNw5LJ6cJMdORKl0%2Fimg.jpg&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;418&quot; height=&quot;557&quot; data-filename=&quot;KakaoTalk_20230130_174133328.jpg&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1400&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;현업에서 일을 하다보면, Back To The Basic 을 통하여 디버깅 추적 및 안전한 코드 작성 능력을 강화 할 수 있다는 것을 알게 됩니다. 이 글은 J brains 에서 일하는&amp;nbsp; Dmitry Jemerov, Kotlin 개발팀의 일원인 Svetlana lsakova 이 두명이 집필한 책인 &lt;b&gt;Kotlin In Action 내용을 정리한 내용&lt;/b&gt; 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;Drimitry jemerov 는 대표적으로 최초의 Kotlin ByteCode Generator를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Svetlana Isakova는 대표적으로 Kotlin Compiler의 Type Inference 과 Overload resolution 부분을 개발 하였다.&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 Effective Kotlin 과 Kotlin In Action을 다시 정독하기 시작했습니다. 예전에 이미 읽었던 책이지만, 바쁜 현업 생활에 기본기가 떨어지지 않을까 걱정하는 생각 때문에 이번엔 블로그에 글을 남겨 Basic과 best Pratice를 나의 생각에 뿌리 깊게 박히길 원하는 마음으로 적었습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Intro) 탄생 비화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;galaxy-11098_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cluRtA/btrYdjz6X7Q/wt8skc5WRgtYvTk5LAWta1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cluRtA/btrYdjz6X7Q/wt8skc5WRgtYvTk5LAWta1/img.jpg&quot; data-alt=&quot;kotlin 의 시작..!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cluRtA/btrYdjz6X7Q/wt8skc5WRgtYvTk5LAWta1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcluRtA%2FbtrYdjz6X7Q%2Fwt8skc5WRgtYvTk5LAWta1%2Fimg.jpg&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;1280&quot; height=&quot;800&quot; data-filename=&quot;galaxy-11098_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kotlin 의 시작..!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin 의 탄생 비화는 Java 를 쓰는&lt;b&gt; J Brain 팀이 다른 팀에서 C# 를 이용하여 모던한 방식으로 개발 하는 것을 보며 부러워하게 됬던 점에서 시작하였습니다.&lt;/b&gt; 그래서 Java 팀은 대체할만한 언어를 찾거나 대안을 찾아내려는 노력을 하면서 다음과 같은 해결점을 고려하였습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;기존 Java팀이 쓰던 Java 라이브러리나 프레임워크를 호환이 가능해야함 그렇지 않으면 처음부터 만들거나 유지보수 해야하는 불상사가 발생&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&amp;nbsp;1번을 해결하기 위해 Java와 상호 호환성을 할 수 있는 언어가 없음&lt;/li&gt;
&lt;li&gt;&amp;nbsp;모던한 방식&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 J Brain은 &lt;b&gt;자바와 상호호환성이 좋고 모던함을 갖추고 J Brain 팀의 실무 경험을 토대로 실용적인 언어를 새롭게 만들기&lt;/b&gt;로 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;Kotlin은 코틀린 개발 팀이 대부분 살고 있는 러시아의 상트페테르부르크 근처에 있는 섬이름을 따서 만든 이름입니다. Java나 Ceylon 같이 언어 기원의 전통을 따른 것인데, 고향에 가까운 섬의 이름을 따왔습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1장 - Kotlin이 무엇이고, 왜 써야하는가?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;programming-593312_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9TkUD/btrX4ZCL4NC/Alg3OZIEYque43wVL4pnWk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9TkUD/btrX4ZCL4NC/Alg3OZIEYque43wVL4pnWk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9TkUD/btrX4ZCL4NC/Alg3OZIEYque43wVL4pnWk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9TkUD%2FbtrX4ZCL4NC%2FAlg3OZIEYque43wVL4pnWk%2Fimg.jpg&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;570&quot; height=&quot;380&quot; data-filename=&quot;programming-593312_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;위 에서 설명했다시피 Java 보다 더 모던한 방식이 필요하면서 동시에 Java로 만든 라이브러리,SDK, 프레임 워크를 이용할 수 있는 언어가 필요하였고 이 두가지 요구 충족을 목표로 하여 만들어진 것이 Koltin 입니다.&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;아래는 Kotlin 코드 맛보기 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675575944832&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val persons = listOf(
        Person(&quot;영희&quot;),
        Person(&quot;철수&quot;, 29)
    )
    
    val oldPerson = persons.maxBy { it.age?:0 }
    println(&quot;나이가 가장 많은 사람: $oldPerson&quot;)
}

data class Person(val name: String, val age: Int? = null)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Kotlin은 다음과 같은 특성들이 생깁니다.&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;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 자바가 실행되는 모든 곳에서 사용이 가능합니다. 대표적으로 Spring , Android 등등이 있습니다.&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;ex) 인텔의 멀티OS 엔진을 통한 IOS디바이스에서 실행, 자바스크립트에서 사용 등등..&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) 정적 타입 지정 언어&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 정적 언어가 가지는 안정성을 그대로 가져간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 정적 언어지만 동시에 타입 추론이 가능하기 때문에 간결한 코드 작성이 가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각: 하지만 타입 추론을 잘못 사용하면 상위 타입과 매칭이 안되는 경우도 있으며, 당장 타입을 알지 못해 직접 확인해야하는 비용이 발생 합니다. 이는 effective 코틀린에서 언급되는 내&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;용이기도 합니다.*&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 함수 타입을 지원한다.&amp;nbsp;예제 코드를 보면 다음과 같다.&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 style=&quot;color: #ee2323;&quot;&gt;*Loner 생각: Kotlin의 모든 함수는 기본적으로 Unit이라는 Type을 반환 합니다. 사실 실무에서 아래 예제처럼 쓰는 경우는 거의 없고 람다를 주로 쓰겠지만.. Unit Type 의 특징을 예시로 듭니다.*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;pre id=&quot;code_1675577041748&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    test2(test1())
}
fun test1(){
    println(&quot;asd&quot;)
}

fun test2(test1:Unit){
    test1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#자바에서는 &lt;span style=&quot;color: #000000;&quot;&gt;오류난다. (비교가 이상하지만.. void는 타입이 아니기 때문 )&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각: 당연히 Unit이 없어서 사용불가 하다.*&lt;/span&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2023-02-05 150316.png&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clbCg8/btrX6nXwlOK/hVZ2tugKu5KCM0go9y1HS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clbCg8/btrX6nXwlOK/hVZ2tugKu5KCM0go9y1HS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clbCg8/btrX6nXwlOK/hVZ2tugKu5KCM0go9y1HS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclbCg8%2FbtrX6nXwlOK%2FhVZ2tugKu5KCM0go9y1HS1%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;711&quot; height=&quot;182&quot; data-filename=&quot;화면 캡처 2023-02-05 150316.png&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;182&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;b&gt;3) 객체지향 + 함수형의 장점을 가진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Kotlin은 객체지향과 함수형을 가지는 멀티 패러다임 언어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 함수형을 간단히 설명하자면.. (1) 일급 시민인 함수를 변수로 취급하거나 다른 함수로 인자로 보낼 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 불변성을 가진다. 코드 작성시 불변인 상태를 사용해서 많은 이점을 가져간다. (3) 사이드 이펙트가 없다. 사이드 이펙트는 함수 내부에서 함수 외부의 상태 값을 건드는 일인데, Pure Function 이라는 개념으로 함수 외부 객체의 값을 변경하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각: Kotlin은 상황에 따라 가변 객체를 사용할 수 있어서 매우 활용성이 좋으나 기본 권장으로 객체가 불변상태인것이 디버깅 할때 마음 편하고, 안정적이긴 합니다.&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;- Kotlin은 람다와 공식지원 라이브러리가 함수형으로 코드 작성 할 수 있도록 도와준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 함수형의 특징을 가지기 때문에 고차함수 활용이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- data class의 copy로 불변 객체 활용을 도와준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Kotlin은 객체지향, 함수형 프로그래밍의 장점을 상황에 따라 자유롭게 활용할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각: 실무에서 가능하면 선언형 프로그래밍 방식과 안정성을 위해 불변성 응용과 사이드 이펙트와 람다를 활용하려는 경우가 많은것 같고,&amp;nbsp; &lt;span style=&quot;color: #ee2323;&quot;&gt;큰 설계 그림에서 객체지향적인 설계 위주로 생각하게 되는 것 같습니다. &lt;/span&gt;물론 상황에 따라 가변 변수나 명령형 방식을 쓰는게 좋은 경우가 간혹 있습니다.&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&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;b&gt;4) 안드로이드 프로그래밍&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;- 안드로이드의 Jetpack Compose가 Kotlin Api를 이용한 대표적인 좋은 사례이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각:&lt;span&gt; 책에서 Anko를 예시를 들지만, Anko는 이제 사용되지 않는 라이브러리 임으로 정리에서 제외하였습니다.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Kotlin의 Null 방지 시스템은 컴파일 단계에서 Null PointerException 방지가 가능하므로 앱 도중에 중지되는 일을 많이 방지 할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-Koltin으로 작성한 앱은 Java와 작성된 앱과 비교했을때 성능이 차이가 없다. 용량은 Kotlin 런타임 시스템의 용량이 작기 때문에 조금만 차이가 있을 뿐이다. 또한 코틀린 표준 라이브러리 함수는 인자로 받은 람다를 Inlining 하기 때문에 새로운 객체가 만들어지지 않아서 GC 이 늘어나 프로그램이 자주 Colding 되는일이 없다.&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;5) 코틀린의 철학&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Koltin은 Java와 호환성을 초점으로 맞추면서 실용적이고 간결하고 안전한 언어를 목표로 만들어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실용성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 연구를 목적을 위한 언어가 아니며, 실제 문제 해결을 위한 언어라서 기존에 검증된 해법과 기능을 효율적으로 활용 할 수 있도록 한다. 또한 Kotlin은 특정 프로그래밍만 강조하는 것이 아니므로 내가 익숙한 프로그래밍 기법으로 바로 코딩할 수 있다. 뿐만 아니라 Kotlin은 인텔리J의 IDE 지원을 강력하게 받을 수 있어서 인텔리J사용시 매우 편한 개발환경을 만들어준다.&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;#IDE 지원예시&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2023-02-05 154755.png&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIF97O/btrX2qO5dKc/lqKPkHEJcqJvWTjekLDlZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIF97O/btrX2qO5dKc/lqKPkHEJcqJvWTjekLDlZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIF97O/btrX2qO5dKc/lqKPkHEJcqJvWTjekLDlZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIF97O%2FbtrX2qO5dKc%2FlqKPkHEJcqJvWTjekLDlZk%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;545&quot; height=&quot;263&quot; data-filename=&quot;화면 캡처 2023-02-05 154755.png&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;263&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2023-02-05 154740.png&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CslCe/btrX3sMq0XG/3c0obksNAKQcaRkJLsQxT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CslCe/btrX3sMq0XG/3c0obksNAKQcaRkJLsQxT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CslCe/btrX3sMq0XG/3c0obksNAKQcaRkJLsQxT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCslCe%2FbtrX3sMq0XG%2F3c0obksNAKQcaRkJLsQxT1%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;287&quot; height=&quot;415&quot; data-filename=&quot;화면 캡처 2023-02-05 154740.png&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;415&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;p data-ke-size=&quot;size16&quot;&gt;여기서 간결하다는 말은 작성된 코드를 읽을때 의도가 쉽게 파악되기 쉬운 구문 구조를 제공하고, 그 의도를 달성하는 방법을 이해할때 방해가 될 수 있는 부가적인 코드가 적다는 뜻이다.&lt;br /&gt;그래서 Kotlin은 게터,세터,생성자 파라미터 등등.. 부가적인 코드작성 비용이 발생하는 부분들은 최대한 간소화 되었으며, Collection&amp;lt;T&amp;gt; 관련 함수와 같이 공식 라이브러리 함수를 통하여 핵심 로직만 코드 작성 할 수 있는 구문 구조를 만들 수 있다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각:&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span&gt; 소스코드를 가능한 짦게 만드는 것과는 다른 내용입니다. 그래서 Kolin은 연산자 오버로딩은 지원하지만, 연산자 자체를 커스텀할 수 있는 기능을 제공하지 않습니다. 예시로 test{ 1+1/2} 를 더 짦게 만들고자.. test{ (1)2 } 이런식으로 커스텀 하는 예제가 있을 것 같네요&lt;/span&gt;&quot;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램에서 발생할 수 있는 오류중에서 프로그램 설계가 원천적으로 방지하려는 노력을 뜻합니다. 기본적으로 JVM위 에서 돌아가므로 메모리 안정성과 버퍼 오버 플로우를 방지할 수 있고, 문법 및 구조 적으로는 안전한 작성 방식을 유도합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예시로 객체를 다른 타입으로 cast하기 위해 반드시 if(value is String) 을 사용하거나, Null 방지 및 컨트롤 시스템으로 충분한 안전성을 가질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각:&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;더 나아가서 공격적 프로그래밍을 하기에도 좋은 것들이 있습니다. 또한&amp;nbsp; runCatching을 통한 getOrNull, getOrDefault&amp;nbsp; 등등 활용할게 많습니다.&lt;/span&gt;&quot;&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: #000000;&quot;&gt;- 상호 운용성&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트 내에 Java와 Kotlin으로 작성된 클래스가 각각 있을때 서로 의존해도 아무런 호환문제가 없다. 별도의 처리해야될 문제가 없으므로 쉽게 사용할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각: &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;Kotlin에서 Java의 플랫폼 타입을 쓸때 주의할 점으로 Java가 Nullable 처리를 따로 하지 않는다면 문제가 생길 수 있습니다. &lt;/span&gt;&lt;/span&gt;&quot;&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: #000000;&quot;&gt;- Kotlin 빌드 과정&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDMYjO/btrX7Zvr9sO/2drCHoqWvr6VwDKJS8K8I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDMYjO/btrX7Zvr9sO/2drCHoqWvr6VwDKJS8K8I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDMYjO/btrX7Zvr9sO/2drCHoqWvr6VwDKJS8K8I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDMYjO%2FbtrX7Zvr9sO%2F2drCHoqWvr6VwDKJS8K8I1%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;640&quot; height=&quot;238&quot; data-filename=&quot;img.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.kt 파일 생성 -&amp;gt; Koltin Complier에서 컴파일링 시작 -&amp;gt; .class 로 변환 -&amp;gt; .jar 파일 생성 -&amp;gt; Application (with. Kotlin Runtime 에 의존)&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;2장 - 코틀린 기초&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;architecture-1857175_1280 (1).jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWFcg9/btrX2nks4uM/hAiEhMzc3cukhyLiJ7vTGK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWFcg9/btrX2nks4uM/hAiEhMzc3cukhyLiJ7vTGK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWFcg9/btrX2nks4uM/hAiEhMzc3cukhyLiJ7vTGK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWFcg9%2FbtrX2nks4uM%2FhAiEhMzc3cukhyLiJ7vTGK%2Fimg.jpg&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;510&quot; height=&quot;853&quot; data-filename=&quot;architecture-1857175_1280 (1).jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;2장에서 다루는 내용은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2장은 기본적인 내용은 어느정도 스킵하고 복습할만하다고 생각 되는 부분만 적었습니다.&lt;/b&gt;&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;- 함수, 변수, 클래스, enum, 프로퍼티 선언하는 방법&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;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- when, if, try&amp;nbsp; 조건문 대부분 Expression 으로 되어있어서 값을 return할 수 있다. (Java는 Statement라서 반대)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*Loner 생각:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 상황에 따라 코드가 간결해질 수 있고, Side effect를 방지할 수 있도록 도움 줄 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1675590361611&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//조건문을 통해서 값을 return 한다.
fun main() {
    val result = when (0) {
        1 -&amp;gt; &quot;1&quot;
        2 -&amp;gt; &quot;2&quot;
        else -&amp;gt; &quot;0&quot;
    }
    println(result)
}&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;- 반면 대입문은 Java에서는 Expression 이나, Kotlin에서는 Statement 로 되어 있어서 대입식과 비교식을 잘못 바꿔 써서 버그가 생기는 경우가 드물다.&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;- koltin에서 if문의 3항 연산자를 지원하지 않는 이유는 expression라서 따로 지원하지 않는다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( kotlin의 if( a&amp;gt;b) b else a 이 a&amp;gt;b ? 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;- 긴 함수의 return문이 여럿 들어있는 경우를 생각(어떤 값을 return하는지 모두 명시하기 위해) 해서 식이 본문인 함수의 반환 타입만 생략이 가능하다.&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;- val은 value에서 따왔고, var는 variable에서 따온 축약어이다.&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;-한글을 사용한 String 의 경우 상황에따라 unresolved reference 오류가 일어 날 수 있다. 그래서 한글은 ${} 써야하는 경우가 종종 있다.&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;- 기본 이름 규칙에 따라 is로 시작하는 변수명의 property 는 기본적으로 getter, setter 도 is로 시작하는 이름이 된다.&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;- Kotlin 에서 Smart Casting은 is 로 이루어지는데, 컴파일러에서 캐스팅을 해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- map collection을 Iteration 해서 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 범위 비교는 in, !in 을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Kotlin은 unchecked와 check Exception을 구분하지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;employees-2452807_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DQOGe/btrX6g5lKdk/ed9JwIPzk6hGlqk5DXaT50/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DQOGe/btrX6g5lKdk/ed9JwIPzk6hGlqk5DXaT50/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DQOGe/btrX6g5lKdk/ed9JwIPzk6hGlqk5DXaT50/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDQOGe%2FbtrX6g5lKdk%2Fed9JwIPzk6hGlqk5DXaT50%2Fimg.jpg&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;642&quot; height=&quot;428&quot; data-filename=&quot;employees-2452807_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;이상 Kotlin In Action 1장 정리 였습니다.&amp;nbsp; 앞으로도 Back to the Basic을 외치며 탄탄해지도록 노력해야겠네요.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Kotlin</category>
      <category>KotlinInAction</category>
      <category>안드로이드</category>
      <category>안드로이드 개발</category>
      <category>안드로이드개발</category>
      <category>안드로이드자바</category>
      <category>자바</category>
      <category>코틀린</category>
      <category>코틀린기초</category>
      <category>코틀린인액션</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/73</guid>
      <comments>https://gift123.tistory.com/73#entry73comment</comments>
      <pubDate>Sun, 5 Feb 2023 19:20:15 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (36) 심플한 Flow Sample</title>
      <link>https://gift123.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;마침, 최근에 리딩중인 사이드 프로젝트에서 과제로 Flow 를 사용하는 법을 간단하게 정리하기로 하여서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 작성하게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;smartphone-5207836_1920.jpg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;875&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bokstF/btrLMyrbASz/L4Y2LAwgSKQnTFF38U7Ftk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bokstF/btrLMyrbASz/L4Y2LAwgSKQnTFF38U7Ftk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bokstF/btrLMyrbASz/L4Y2LAwgSKQnTFF38U7Ftk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbokstF%2FbtrLMyrbASz%2FL4Y2LAwgSKQnTFF38U7Ftk%2Fimg.jpg&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;638&quot; height=&quot;875&quot; data-filename=&quot;smartphone-5207836_1920.jpg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;875&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[개요]&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전 Jatpack LiveData가 등장하였을 때 더 이상 observe 한 객체의 라이프사이클 처리를 개발자가 직접적으로 처리할 필요가 없어서 매우 편해짐에 따라 이후 안드로이드 개발에서 LiveData를 거의 필수적으로 사용하게 되었습니다. 하지만 LiveData 의 경우 LifeCycle 종속적이라는 문제가 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;대표적으로 따라 data Layer, Domain Layer 에서 Room 라이브러리를 사용할 때 반환하는 객체가 LiveData&amp;lt;T&amp;gt; 일 경우 import androidx. 종속이 생깁니다.&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;또한 LiveData는 간편함을 제공하는 훌륭한 data holder 지만, Reactive programming 스럽게 코드를 짤 때 데이터를 변환하는 것이 map, switchMap이 다라고 할정도로 간단한 기능만을 제공하였습니다. (LiveData는 안드로이드 개발자가 LifeCycle 처리를 직접적으로 하지 않는것에 더 집중하였기 때문이라고 생각합니다.)&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에서 only Kotlin을 추구하고 있고, Kotlin에서 지원하는 스마트한 비동기 처리인 Coroutine 을 활용하는 방법을 공식적으로 추가로 지원하기 시작합니다. (단점으로 Java의 활용도를 생각하지 않는 점에서 Java는 많이 배제가 되고 있습니다.)&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coroutine에 데이터 스트림을 만드는 Flow 가 있습니다. 생산자, 중개자(데이터 변환), 소비자 3단계로 나뉘어서 사용할 수 있게되며 기본적으로 Cold Stream 으로 되어있어 생산자, 중개자(옵션) 에서 방출하는 데이터를 준비시켜놓고, 1명의 소비자가 발생시 그 소비자에게만 데이터가 방출 됩니다.&lt;br /&gt;&lt;br /&gt;이런 식으로 수도꼭지를 틀고 닫듯이 사용하는것이 Flow 입니다. 하지만 Cold Stream 특성이 사실 고정된 값을 보내기만 합니다. 그래서 State Pattren(State에 따라 UI에 반영하는) 을 주로 사용하는 클라단에서 사용하기 매우 불편한 상황입니다. 그래서 Flow를 Hot Stream 으로 사용할 수 있도록 State Flow,&amp;nbsp; Shared Flow를 별도로 지원 합니다.&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;size16&quot;&gt;&lt;b&gt;공통 사항&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 팀 개발 인원 각자 개인 블로그에 &lt;b&gt;Android&lt;/b&gt; &lt;b&gt;Flow 관련 코드를 작성하여, &lt;/b&gt;&amp;nbsp;때 제출합니다. 공통적으로 아래 상황에서 3가지 주제를 진행합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;//Blog &amp;lt;- data ,domain 에서 사용
data class Blog(
   val title:String ,
   val content:String,
   // 블로그 출처
   val type:???
)

UiBlog &amp;lt;- view, viewModel 사용
data class UiBlog(
   val blog:Blog,
   val isSelected:Boolean
)

/**data/domainModule 에서 ui 모듈로 데이터 이동시 
*ViewModel에서 UiBlog로 변환하여 사용하기*/&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;b&gt;1) Flow를 이용하여 데이터 상태에 따라 버튼 활성화 비활성화 변경하기&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;//예시: 맛집을 추가할 때 필수 항목을 적어야 완료버튼이 활성화 한다는 가정
/**예시는 여러분들이 마음대로 만들어내셔서 하면 좋을것 같습니다.*/
class ViewModel(val useCase:UseCase...){
   val requireDatas:StateFlow&amp;lt;T&amp;gt;()..
   val enableBtn = requireDatas.map{..}.filter{..}
or 
   val title:StateFlow&amp;lt;String&amp;gt;()..
   val image:StateFlow&amp;lt;Image&amp;gt;()...
   val enableBtn = title.combine(image){...}...
}
&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) Flow에서 Error Catch 및 onCompletion() 를 이용한 예제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// 예시: 맛집 후기(블로그) 목록을 조회할 때 페이징처리를 할때, 페이징 도중에 에러가 났는지 체크
// 와 마지막 페이지의 마지막 데이터를 조회할때
class ViewModel(val useCase:UseCase ...){
   val blog:StateFlow&amp;lt;UiBlog&amp;gt;() = 
useCase.toUiBlogModel.error{..}
                     .onCompetion{ 
                       if(isLast){ 
                         showToast(&quot;마지막 페이지 입니다.&quot;)
                       }
                    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) StateFlow, SharedFlow, Chnnel 이 3가지를 활용한 코드 작성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;//예시: StateFlow 를 통해서 아이템 목록 Ui 보여주려고 할때 등등.
val uiState:StateFlow&amp;lt;T&amp;gt;...
Activity{ viewModel.uiState.collect { ... }}

//예시: SharedFlow 를 이용해서 이벤트 처리 하려고 할때 등등..
val uiEvent:SharedFlow&amp;lt;T&amp;gt;...

//예시: Channel 를 이용해서 이벤트 처리 하려 할때 등등..
val event:Channel&amp;lt;T&amp;gt;...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3가지 숙제를 위해 추가 설정을 임의로 붙이셔도 상관 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/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;전체 코드: &lt;a href=&quot;https://github.com/LonerStayle/SimpleFlowSample&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/LonerStayle/SimpleFlowSample&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1662966053128&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - LonerStayle/SimpleFlowSample: 사이드 프로젝트 과제 제출용&quot; data-og-description=&quot;사이드 프로젝트 과제 제출용. Contribute to LonerStayle/SimpleFlowSample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/LonerStayle/SimpleFlowSample&quot; data-og-url=&quot;https://github.com/LonerStayle/SimpleFlowSample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cAsZJF/hyPJ64tc71/HqUVfVaJKS5Mmg5r4wcW7K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/LonerStayle/SimpleFlowSample&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/LonerStayle/SimpleFlowSample&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cAsZJF/hyPJ64tc71/HqUVfVaJKS5Mmg5r4wcW7K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - LonerStayle/SimpleFlowSample: 사이드 프로젝트 과제 제출용&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트 과제 제출용. Contribute to LonerStayle/SimpleFlowSample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) Flow를 이용하여 데이터 상태에 따라 버튼 활성화 비활성화 변경하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제출: 네이버 , 다음 Api를 통해 각각 데이터를 불러와서 UI로 보여주고, 각각 아이템마다 선택 할 수 있는 체크박스가 있습니다. 어떤 아이템을 클릭했는지에 따라 버튼 활성화 여부가 달라집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1662965874949&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//1번째 숙제 화면에서만 사용하는 FirstHomeWorkUiBlogState ui모델을 정의하여 사용했습니다.
private val _uiBlogState = MutableStateFlow(FirstHomeWorkUiBlogState.initData)
val firstHomeWorkUiBlogState: StateFlow&amp;lt;FirstHomeWorkUiBlogState&amp;gt; = _uiBlogState&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2222.png&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZKS03/btrLSuHN41V/SQp8pNgsQJZNkgM9ue7bk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZKS03/btrLSuHN41V/SQp8pNgsQJZNkgM9ue7bk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZKS03/btrLSuHN41V/SQp8pNgsQJZNkgM9ue7bk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZKS03%2FbtrLSuHN41V%2FSQp8pNgsQJZNkgM9ue7bk1%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;254&quot; height=&quot;499&quot; data-filename=&quot;2222.png&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1662965971141&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  val completeOnlyNaverSelect: StateFlow&amp;lt;Boolean&amp;gt; = firstHomeWorkUiBlogState.map {
        val resultList = it.uiBlogGroup.value.filter { uiBlog -&amp;gt;
            uiBlog.isSelected &amp;amp;&amp;amp; uiBlog.blog.type == BlogType.Naver
        }
        resultList.isNotEmpty()
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)&lt;/code&gt;&lt;/pre&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;- 다음 블로그를 선택 했을 때&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2022-09-12 154324.png&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;761&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cK63rf/btrLNwGFomd/542Hl3sngoRAebGInmQabk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cK63rf/btrLNwGFomd/542Hl3sngoRAebGInmQabk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cK63rf/btrLNwGFomd/542Hl3sngoRAebGInmQabk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcK63rf%2FbtrLNwGFomd%2F542Hl3sngoRAebGInmQabk%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;248&quot; height=&quot;431&quot; data-filename=&quot;화면 캡처 2022-09-12 154324.png&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;761&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1662966102973&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val completeOnlyDaumSelect: StateFlow&amp;lt;Boolean&amp;gt; = firstHomeWorkUiBlogState.map {
        val resultList = it.uiBlogGroup.value.filter { uiBlog -&amp;gt;
            uiBlog.isSelected &amp;amp;&amp;amp; uiBlog.blog.type == BlogType.Daum
        }
        resultList.isNotEmpty()
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)&lt;/code&gt;&lt;/pre&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;- 다음, 네이버 블로그 둘다 선택했을 때&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3333.png&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x1dxQ/btrLYlwzpoJ/usv6ymK98KyjLPenRdHHGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x1dxQ/btrLYlwzpoJ/usv6ymK98KyjLPenRdHHGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x1dxQ/btrLYlwzpoJ/usv6ymK98KyjLPenRdHHGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx1dxQ%2FbtrLYlwzpoJ%2Fusv6ymK98KyjLPenRdHHGK%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;243&quot; height=&quot;436&quot; data-filename=&quot;3333.png&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1662966358108&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  val completeComplexSelect: StateFlow&amp;lt;Boolean&amp;gt; = firstHomeWorkUiBlogState.map { uiBlog -&amp;gt;
        val selectUiBlogs = uiBlog.uiBlogGroup.value.filter { it.isSelected }
        selectUiBlogs.find { it.blog.type == BlogType.Naver } != null
                &amp;amp;&amp;amp; selectUiBlogs.find { it.blog.type == BlogType.Daum } != null
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)&lt;/code&gt;&lt;/pre&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;- 블로그 전체 4개이상 선택 했을 때,&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;111.png&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRo9cc/btrLPdNpl78/QK7758ptKqRP1IXUwhY6G0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRo9cc/btrLPdNpl78/QK7758ptKqRP1IXUwhY6G0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRo9cc/btrLPdNpl78/QK7758ptKqRP1IXUwhY6G0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRo9cc%2FbtrLPdNpl78%2FQK7758ptKqRP1IXUwhY6G0%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;225&quot; height=&quot;430&quot; data-filename=&quot;111.png&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;805&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1662966361601&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val completeFourCountSelect: StateFlow&amp;lt;Boolean&amp;gt; = firstHomeWorkUiBlogState.map {
        it.uiBlogGroup.value.filter { it.isSelected }.size &amp;gt;= 4
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) Flow에서 Error Catch 및 onCompletion() 를 이용한 예제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;제출:&lt;span&gt; onCompletion()은 Job이 완료 되어야 실행 됩니다. flow는 collect를 하고 있어서 잠시 테스트를 위해 onPause()에서 job.cancle 처리를 해주었습니다.&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2022-09-12 155430.png&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCcnaT/btrLYjFwwHB/KRT27B6hgkbLLolz1bqH91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCcnaT/btrLYjFwwHB/KRT27B6hgkbLLolz1bqH91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCcnaT/btrLYjFwwHB/KRT27B6hgkbLLolz1bqH91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCcnaT%2FbtrLYjFwwHB%2FKRT27B6hgkbLLolz1bqH91%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;256&quot; height=&quot;495&quot; data-filename=&quot;화면 캡처 2022-09-12 155430.png&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2022-09-12 155325.png&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmK9RT/btrLQmXD5GG/nnS3SHoi3U7BS23mmN7aT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmK9RT/btrLQmXD5GG/nnS3SHoi3U7BS23mmN7aT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmK9RT/btrLQmXD5GG/nnS3SHoi3U7BS23mmN7aT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmK9RT%2FbtrLQmXD5GG%2FnnS3SHoi3U7BS23mmN7aT1%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;253&quot; height=&quot;497&quot; data-filename=&quot;화면 캡처 2022-09-12 155325.png&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1662966953887&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   private val job = lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.blogList.onCompletion {
                viewModel.showToast(SecondHomeWorkViewModel.ShowMsg(&quot;Job 처리 완료.&quot;))
            }.collect {
                binding.rvBlogs.adapter = SecondHomeWorkAdapter(it)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        setAdapter()
        ..
    }
    
     private fun setAdapter() {
        job.start()
    }
    
     override fun onPause() {
        ...
        job.cancel()
    }&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;&lt;b&gt;3)&lt;span&gt;&amp;nbsp;&lt;/span&gt;StateFlow, SharedFlow, Chnnel 이 3가지를 활용한 코드 작성&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제출: 1,2번 과제에 녹아들어있으므로 따로 작성하지 않았습니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>Coroutine</category>
      <category>coroutineFlow</category>
      <category>flow</category>
      <category>Kotlin</category>
      <category>kotlin coroutine</category>
      <category>안드로이드 coroutine flow</category>
      <category>안드로이드 flow</category>
      <category>안드로이드 개발</category>
      <category>안드로이드플로우</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/72</guid>
      <comments>https://gift123.tistory.com/72#entry72comment</comments>
      <pubDate>Mon, 12 Sep 2022 16:20:12 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발(35) 안드로이드 설계 - Layered Architecture</title>
      <link>https://gift123.tistory.com/69</link>
      <description>&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.23.42.png&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddt0JM/btrwlRlkL1E/IKcWHKj3YzhzWHGp9KFdA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddt0JM/btrwlRlkL1E/IKcWHKj3YzhzWHGp9KFdA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddt0JM/btrwlRlkL1E/IKcWHKj3YzhzWHGp9KFdA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fddt0JM%2FbtrwlRlkL1E%2FIKcWHKj3YzhzWHGp9KFdA0%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;658&quot; height=&quot;445&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.23.42.png&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;안드로이드 설계&lt;/b&gt;&lt;/span&gt; 관한 이야기를 하기전에, 안드로이드 개발을 이야기 해보자면 안드로이드 개발은 &lt;b&gt;Activity, Service, ContentProvider, BroadCastReceiver&lt;/b&gt; 크게 4개의 컴포넌트가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 네가지는 공통적으로 Context를 제공해주며 앱에 진입할 수 있는 진입점이 됩니다.&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-filename=&quot;스크린샷 2022-03-18 오후 11.30.03.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOqwv4/btrwkp3Vcde/aGj0fpJxxnDVvOtTLJ8PqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOqwv4/btrwkp3Vcde/aGj0fpJxxnDVvOtTLJ8PqK/img.png&quot; data-alt=&quot;Activity는 Context를 상속 받고 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOqwv4/btrwkp3Vcde/aGj0fpJxxnDVvOtTLJ8PqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOqwv4%2Fbtrwkp3Vcde%2FaGj0fpJxxnDVvOtTLJ8PqK%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;1068&quot; height=&quot;362&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.30.03.png&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Activity는 Context를 상속 받고 있다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.26.08.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cznQ3x/btrwmakEi1n/R6OcYRfJTDL4cLx8DH2Wn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cznQ3x/btrwmakEi1n/R6OcYRfJTDL4cLx8DH2Wn0/img.png&quot; data-alt=&quot;서비스는 Context를 상속 받고 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cznQ3x/btrwmakEi1n/R6OcYRfJTDL4cLx8DH2Wn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcznQ3x%2FbtrwmakEi1n%2FR6OcYRfJTDL4cLx8DH2Wn0%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;866&quot; height=&quot;264&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.26.08.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서비스는 Context를 상속 받고 있다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.31.04.png&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kQlBE/btrwlReA8lE/nykImdZk5gaScwEIb49Ld1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kQlBE/btrwlReA8lE/nykImdZk5gaScwEIb49Ld1/img.png&quot; data-alt=&quot;BroadcastReceiver는 onReceive 라는 추상 함수로 context를 인자로 제공한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kQlBE/btrwlReA8lE/nykImdZk5gaScwEIb49Ld1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkQlBE%2FbtrwlReA8lE%2FnykImdZk5gaScwEIb49Ld1%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;1444&quot; height=&quot;374&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.31.04.png&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BroadcastReceiver는 onReceive 라는 추상 함수로 context를 인자로 제공한다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.28.28.png&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/INm54/btrwmpazkcQ/rJ2QgDwyycQOtyrV1ci271/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/INm54/btrwmpazkcQ/rJ2QgDwyycQOtyrV1ci271/img.png&quot; data-alt=&quot;ContentProvider는 getContext() 를 제공한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/INm54/btrwmpazkcQ/rJ2QgDwyycQOtyrV1ci271/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FINm54%2FbtrwmpazkcQ%2FrJ2QgDwyycQOtyrV1ci271%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;1478&quot; height=&quot;284&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.28.28.png&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ContentProvider는 getContext() 를 제공한다.&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;b&gt;1개&amp;nbsp; 이상의 Activity가 필요합니다. &lt;/b&gt;Activity가 없는 어플리케이션은 실행 할 수 가 없습니다. 그로므로 manifest에 최소 1개의 Activity 가 있어야 하며&amp;nbsp; 앱 실행의 시작점을 Activity intent-filter안에서 명시 해놔야 합니다. &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-filename=&quot;스크린샷 2022-03-18 오후 11.44.01.png&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGoyFS/btrwnqmHZrK/0xlKa3idPaTrOStOyHzWj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGoyFS/btrwnqmHZrK/0xlKa3idPaTrOStOyHzWj1/img.png&quot; data-alt=&quot;액티비티 없이 브로드 캐스트 리시버만 존재한다는 가정했을 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGoyFS/btrwnqmHZrK/0xlKa3idPaTrOStOyHzWj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGoyFS%2FbtrwnqmHZrK%2F0xlKa3idPaTrOStOyHzWj1%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;1420&quot; height=&quot;852&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.44.01.png&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;액티비티 없이 브로드 캐스트 리시버만 존재한다는 가정했을 때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.45.37.png&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eFlyLL/btrwlRMpOsZ/p6W1svl3Pk5ai8vIaVKfD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eFlyLL/btrwlRMpOsZ/p6W1svl3Pk5ai8vIaVKfD1/img.png&quot; data-alt=&quot;컴포넌트가 존재하더라도 액티비티가 없어서 실행을 할 수 없다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eFlyLL/btrwlRMpOsZ/p6W1svl3Pk5ai8vIaVKfD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeFlyLL%2FbtrwlRMpOsZ%2Fp6W1svl3Pk5ai8vIaVKfD1%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;1090&quot; height=&quot;326&quot; data-filename=&quot;스크린샷 2022-03-18 오후 11.45.37.png&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;컴포넌트가 존재하더라도 액티비티가 없어서 실행을 할 수 없다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Activity는 사용자와 상호 작용할 수 있는 인터페이스를 제공합니다. 사용자와 직접적으로 상호작용 하기 때문에 UI를 보여주거나 유저 입력을 받아 처리하기 합니다. 그렇기 때문에&lt;/span&gt; 처음으로 안드로이드 개발 할때 기본적으로 Activity를 자주 접하게 되고 Activity를 중심으로 개발을 하게 됩니다. 그래서&amp;nbsp;&lt;b&gt;개발을 하다보면 Activity에 많은 로직들을 포함하게 되는 데 아래와 같은 문제가 발생합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;telephone-1822040_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boIprE/btrwms6o7bL/dVViqzThbKtuzSmNS2p9TK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boIprE/btrwms6o7bL/dVViqzThbKtuzSmNS2p9TK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boIprE/btrwms6o7bL/dVViqzThbKtuzSmNS2p9TK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboIprE%2Fbtrwms6o7bL%2FdVViqzThbKtuzSmNS2p9TK%2Fimg.jpg&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;1280&quot; height=&quot;726&quot; data-filename=&quot;telephone-1822040_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;726&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Activity 로직 몰빵&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 안드로이드 설계를 생각하지 않는다면 UI를 동작하기 위한 로직과&amp;nbsp; Model, View 사이에 상호작용 하는 로직이 Activity 한곳에서 진행됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-19 오전 12.20.47.png&quot; data-origin-width=&quot;3624&quot; data-origin-height=&quot;2168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBxlSE/btrwkwBsSLO/CORkwzzGsbQLq1MYkgQ4Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBxlSE/btrwkwBsSLO/CORkwzzGsbQLq1MYkgQ4Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBxlSE/btrwkwBsSLO/CORkwzzGsbQLq1MYkgQ4Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBxlSE%2FbtrwkwBsSLO%2FCORkwzzGsbQLq1MYkgQ4Sk%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;3624&quot; height=&quot;2168&quot; data-filename=&quot;스크린샷 2022-03-19 오전 12.20.47.png&quot; data-origin-width=&quot;3624&quot; data-origin-height=&quot;2168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) XML이 정적으로 선언되어 뼈대가 되는 UI의 모습이 구성 되지만 런타임에서 XML을 통해 UI를 변경할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 위 이유로 모델에서 제공 받은 데이터를 UI에 맞게 데이터를 재구성하고 재구성된 데이터로 UI를 그리는 일까지 Activity가 처리하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) Activity를 통해 &lt;span&gt;사용자 입력을&lt;span&gt;&amp;nbsp;받고&lt;/span&gt;&lt;/span&gt; 입력 반응에 필요한 데이터를 모델에서 찾습니다. 그 다음 사용자 요구에 맞는 View(xml inflate,혹은 Android View Class)를 골라서 데이터와 조합한 후 View를 보여주는일을 Activity가 하게 됩니다. (mvc의 컨트롤러와 완전히 같지는 않지만 비슷한 모습을 가지게 됩니다. 심지어 View의 역활을 Activity가 함께 하기도 합니다.)&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;위와 같은 이유로 Activity는 매우 비대해지는데 이는 곧 안드로이드 앱 환경에서 큰 문제가 됩니다.&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-filename=&quot;android-199225_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y8xan/btrwnr63QwH/TyKbimaKC1V561DA1D1JBk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y8xan/btrwnr63QwH/TyKbimaKC1V561DA1D1JBk/img.jpg&quot; data-alt=&quot;철저하게 감시중이다. 흒먼-&amp;amp;amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y8xan/btrwnr63QwH/TyKbimaKC1V561DA1D1JBk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy8xan%2Fbtrwnr63QwH%2FTyKbimaKC1V561DA1D1JBk%2Fimg.jpg&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;1280&quot; height=&quot;800&quot; data-filename=&quot;android-199225_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;철저하게 감시중이다. 흒먼-&amp;amp;amp;amp;amp;amp;nbsp;&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;앱 컴포넌트는 기존에 실행중인 것이 없다면 처음 실행시에 Android OS의 쓰레드로 Linux 프로세스를 생성합니다.&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;Android OS의 쓰레드로 실행하는 것이기 때문에 Android OS 관리하에 기기의 메모리 부족시 앱의 프로세스를 kill 시킬 수 있습니다. Activity의 경우 Process 가 kill이 되면 보유중인 데이터가 사라집니다. (별도의 saveInstance 처리를 하지 않았다면) Activity로 돌아온다면 다시 onCreate 부터 라이프사이클이 시작되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뿐만 아니라, 실행중인 앱은 여러 컴포넌트가 비순차적으로 실행 될 수 있고 (Activity와 함께 Service, BroadCastReceiver 동작중 등등.. ) 사용자가 다른앱과 상호작용을 하는 경우가 많고 (Android OS가 여러앱을 통해 메모리가 부족하게 되면 내 어플의 Process를 종료) 사용자가 강제적으로 프로세스를 종료할 수도 있습니다.&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;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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;bridge-2483498_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IEKZc/btrwhx10Nz6/4AShZYYR1S03lKZvacQznK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IEKZc/btrwhx10Nz6/4AShZYYR1S03lKZvacQznK/img.jpg&quot; data-alt=&quot;크게 바라보자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IEKZc/btrwhx10Nz6/4AShZYYR1S03lKZvacQznK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIEKZc%2Fbtrwhx10Nz6%2F4AShZYYR1S03lKZvacQznK%2Fimg.jpg&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;1280&quot; height=&quot;854&quot; data-filename=&quot;bridge-2483498_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;크게 바라보자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;앱 컴포넌트로부터 독립된 설계&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity는 화면 UI를 가지기 때문에 앱이 소유한 화면처럼 느껴질수는 있으나, 사실상 &lt;b&gt;Activity는 Android OS와 어플리케이션 간의 상호작용 하는 클래스입니다.&lt;/b&gt; 그래서 Activity는 UI 기반 로직 및 OS 와 상호작용을 우선적으로 생각해서 사용해야 합니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 UI기반 로직과 OS 상호작용을 Activity에서 분리한다면 다른곳에서 처리해야할 것이 몇가지가 있습니다.&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. 뷰의 상태를 보관하는 역활&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 앱 데이터를 &lt;span&gt;UI를 표시 하기위한 UI 상태로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;재구성 하는 로직을 담당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 앱 데이터를 제공 및 가공해주는 Model(data, domain Layer) 에 의존&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity가 관심을 가질 필요가 없는 위 역활들은 다른 클래스로 책임을 위임 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위와 같이 클래스의 관심을 분리하는 것을 안드로이드 권장 설계에서 가장 중요한 최우선 원칙으로 생각합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;관심사 분리&lt;/b&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;&lt;b&gt;AAC ViewModel&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/topic/libraries/architecture/viewmodel&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647618831045&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;ViewModel 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel&quot; data-og-url=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OIGSu/hyNKbSKkrI/MvNJtJuRK3tsvnAgRGHLVK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OIGSu/hyNKbSKkrI/MvNJtJuRK3tsvnAgRGHLVK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ViewModel 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드는 공식적으로 Activity로 부터 분리된 책임을 맡아주기에 최적의 환경을 자랑하는 AAC ViewModel이 제공됩니다.&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;AAC ViewModel은 데이터와 상태를 특정 View의 Lifecycle에 보다 더 오래 유지합니다. Destory보다 더 오래 데이터와 상태를 유지하기 때문에&amp;nbsp; &lt;a href=&quot;https://developer.android.com/guide/topics/resources/runtime-changes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;configration change&lt;/a&gt; 에 있어서 안정성이 있으며 View LifeCycle에 아무런 영향이 받지 않으면서 데이터와 상태를 보존 할수 있습니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;avocado-1838785_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAoC8K/btrwooWlnvU/IRY08cKmJpKHankjkf6u51/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAoC8K/btrwooWlnvU/IRY08cKmJpKHankjkf6u51/img.jpg&quot; data-alt=&quot;앱 데이터를 UI 상태로 가공하는 State Holder&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAoC8K/btrwooWlnvU/IRY08cKmJpKHankjkf6u51/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAoC8K%2FbtrwooWlnvU%2FIRY08cKmJpKHankjkf6u51%2Fimg.jpg&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;553&quot; height=&quot;1280&quot; data-filename=&quot;avocado-1838785_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;앱 데이터를 UI 상태로 가공하는 State Holder&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;StateHolder&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StateHolder은 Model(data, domain Layer)에서 앱 데이터를 받아오거나 상호작용 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 데이터를 가지고 UI를 표시 하기 위한 UI상태로 변환 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 StateHolder는 Model(data, domain Layer)에 의존 하게 됩니다.&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;StateHolder는 UI상태를 관리하기 때문에&amp;nbsp; StateHolder에서 발생하는 이벤트 결과는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 UI상태 업데이트가 이루어집니다.&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가지를 StateHolder (ViewModel)가 모두 책임을 맡아 처리하면 딱 알맞는 모습이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 뷰의 상태를 보관하는 역활&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. UI 상태에 맞게 앱 데이터를 재구성 하는 로직 담당&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 앱 데이터를 제공 및 가공해주는 Model(data, domain Layer) 에 의존&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;그리고 안드로이드 공식 설계는 위에서 설명한 AAC ViewModel을&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;StateHolder 개념으로 사용하기를 권장합니다.&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;b&gt;Activity는 UI, ViewModel은 StateHolder로 관심을 분리 합니다.&lt;/b&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;sign-2854067_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wGpOd/btrwmsS0Ca3/f8Ovkkyudsx2qf0lUK2Ve1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wGpOd/btrwmsS0Ca3/f8Ovkkyudsx2qf0lUK2Ve1/img.jpg&quot; data-alt=&quot;잠깐!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wGpOd/btrwmsS0Ca3/f8Ovkkyudsx2qf0lUK2Ve1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwGpOd%2FbtrwmsS0Ca3%2Ff8Ovkkyudsx2qf0lUK2Ve1%2Fimg.jpg&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;606&quot; height=&quot;853&quot; data-filename=&quot;sign-2854067_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;잠깐!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;- AAC ViewModel은 Owner로 설정된 View 보다 더 오래 유지되기 때문에 View Type의 context 를 AAC ViewModel에 가지고 있으면 Activity가 소멸 되어도 ViewModel에서 참조가 유지 되고 있기 때문에 메모리 누수가 일어날 수 있습니다.&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;그래서 UI로직이 View Type의 context 를 필요로 하는가에 따라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 로직이 UI와 StateHolder중에 어디에 있을지는 상황에 따라 달라집니다.&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 입력은 여전히 Activity(UI)에서 받습니다. 주시할점은 Activity를 바라보는 시선이 이전과 달라야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity를 UI로 바라보고 이 UI는 사용자로 부터 입력을 받아야 한다는 개념으로 생각해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity는 사용자 입력을 받고 UI Element 와 관련된 휘발성 데이터(ex: Animation, RecyclerView Adapter 등등.. )와 같이 특별한 경우가 아니라면 UI 상태를 들고 있는 ViewModel에 이벤트를 넘깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity는 오로지 UI 기반 로직과 OS상호작용만 생각해야 합니다.&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;br /&gt;&lt;/b&gt;ViewModel은 다시 Activity 에게 결과를 알립니다. 여기서 결과를 알리는 방식이 중요한데, 아래에서 상세히 설명하겠습니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;recycle-734878_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;871&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ck0wMf/btrwnqHad3F/pUQw45hkVojrdECS7F5Xak/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ck0wMf/btrwnqHad3F/pUQw45hkVojrdECS7F5Xak/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ck0wMf/btrwnqHad3F/pUQw45hkVojrdECS7F5Xak/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fck0wMf%2FbtrwnqHad3F%2FpUQw45hkVojrdECS7F5Xak%2Fimg.jpg&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;557&quot; height=&quot;871&quot; data-filename=&quot;recycle-734878_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;871&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;단방향 데이터 흐름&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 이벤트를 UI(ex:Activity) 가 받고 StateHolder(ex:ViewModel) 로 전달하고 Model 쪽으로 이벤트를 전달합니다. 이벤트로 인해 변경된 상태는 반대순서로 이어지며 UI로 전달 됩니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;state-unidirectional-flow.png&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKB4JO/btrwnOA0QPu/T9D6KkTKR2cyNoKiBvxZO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKB4JO/btrwnOA0QPu/T9D6KkTKR2cyNoKiBvxZO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKB4JO/btrwnOA0QPu/T9D6KkTKR2cyNoKiBvxZO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKB4JO%2FbtrwnOA0QPu%2FT9D6KkTKR2cyNoKiBvxZO1%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;429&quot; height=&quot;527&quot; data-filename=&quot;state-unidirectional-flow.png&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;527&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;Event: 저수준 -&amp;gt; 고수준의 계층순으로 이벤트가 이동해서 이벤트가 위로 흐른다라고 이야기 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State: 위에서 처리한 이벤트에 의해 업데이트 된 State을 다시&amp;nbsp; StateHolder에게 전달하고 UI상태에 맞게 데이터를 재구성 한 뒤 UI로 전달합니다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&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;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;그래서 하나의 흐름을 이루게 됩니다.&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;mad-arch-ui-udf.png&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ2aZ7/btrwnCtHWnp/DUMXg3j4dUMIRjOPlF6xik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ2aZ7/btrwnCtHWnp/DUMXg3j4dUMIRjOPlF6xik/img.png&quot; data-alt=&quot;출처: 안드로이드 UI 레이어&amp;amp;amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ2aZ7/btrwnCtHWnp/DUMXg3j4dUMIRjOPlF6xik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJ2aZ7%2FbtrwnCtHWnp%2FDUMXg3j4dUMIRjOPlF6xik%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;545&quot; height=&quot;383&quot; data-filename=&quot;mad-arch-ui-udf.png&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: 안드로이드 UI 레이어&amp;amp;amp;amp;amp;amp;nbsp;&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;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;UI -&amp;gt; StateHolder -&amp;gt; UI 혹은&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt; UI -&amp;gt; StateHolder -&amp;gt; Model(data,domain Layer) -&amp;gt; StateHolder -&amp;gt; UI&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&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&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;b&gt;사용자 입력의 &lt;/b&gt;&lt;b&gt;결과는 어떻게 받지?&lt;/b&gt;&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;&lt;span&gt;- 단방향 흐름에 따라서 데이터 스트림에 의해 UI상태가 변화시 UI도 변화 해야 합니다. 그래서 UI 상태에 따라 UI를 변화 시키기 위해 Observable data holder를 사용하게 됩니다. &lt;b&gt;UI상태를 Observeable data Holder 타입으로 저장한다면 UI상태 변화시 UI가 자동 업데이트 하게 됩니다.&lt;/b&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;안드로이드 개발에서 자주쓰이는 Observeable data Hodler 는 대표적으로&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; Flow&lt;/b&gt;&lt;/span&gt; 와 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;LiveData&lt;/b&gt;&lt;/span&gt;가 있습니다. 안드로이드 개발자는 Observeable data Holder 를 지원하는 여러 방식 중에 각각 장단점을 파악하여 상황에 맞는 것을 사용하면 됩니다.&lt;/span&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;b&gt;&lt;span&gt;Flow&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://developer.android.com/kotlin/flow&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/kotlin/flow&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647623688700&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Android의 Kotlin 흐름 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;Android의 Kotlin 흐름 코루틴에서 흐름은 단일 값만 반환하는 정지 함수와 달리 여러 값을 순차적으로 내보낼 수 있는 유형입니다. 예를 들면 흐름을 사용하여 데이터베이스에서 실시간 업데이트&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/kotlin/flow&quot; data-og-url=&quot;https://developer.android.com/kotlin/flow?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/o8tym/hyNKg7CQo8/aQWYZylckAyjSdSksi9qq0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/kotlin/flow&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/kotlin/flow&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/o8tym/hyNKg7CQo8/aQWYZylckAyjSdSksi9qq0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Android의 Kotlin 흐름 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Android의 Kotlin 흐름 코루틴에서 흐름은 단일 값만 반환하는 정지 함수와 달리 여러 값을 순차적으로 내보낼 수 있는 유형입니다. 예를 들면 흐름을 사용하여 데이터베이스에서 실시간 업데이트&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Live Data&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/livedata&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/topic/libraries/architecture/livedata&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647623711250&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;LiveData 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata&quot; data-og-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pToxo/hyNKdXl3J1/n1HAHY7xIPKmerNJCwy7h1/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/livedata&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pToxo/hyNKdXl3J1/n1HAHY7xIPKmerNJCwy7h1/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;LiveData 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;단방향 흐름의 장점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;단방향 흐름을 사용함으로써 상태가 위에서 아래 방향으로 흐르기 때문에 다른 액션으로 인해 View의 State이 꼬일일이 줄어들며,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt; State이 변경되는 위치, 변환되는 위치, 사용되는 위치가 확실히 구분 됨으로 좋은 유지보수 환경이나 좋은 테스트 가능성을 만들어 낼 수 있습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ensemble-619260_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUSErn/btrwmaZlawd/yloeWB1gNQtHjj1WkdEpNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUSErn/btrwmaZlawd/yloeWB1gNQtHjj1WkdEpNk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUSErn/btrwmaZlawd/yloeWB1gNQtHjj1WkdEpNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUSErn%2FbtrwmaZlawd%2FyloeWB1gNQtHjj1WkdEpNk%2Fimg.jpg&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;1280&quot; height=&quot;853&quot; data-filename=&quot;ensemble-619260_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Layered Architeture&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층을 확실히 구분하는 아키텍처 입니다. 안드로이드 권장 설계는 Layered Architeture와 흡사한 형태를 가지고 있습니다. 안드로이드에서 공식적으로 설명하는 아키텍처 가이드는 다음과 같습니다.&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;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/jetpack/guide?hl=ko&quot;&gt;https://developer.android.com/jetpack/guide?hl=en&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647625266246&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;앱 아키텍처 가이드 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;앱 아키텍처 가이드 이 가이드에는 고품질의 강력한 앱을 빌드하기 위한 권장사항 및 권장 아키텍처가 포함되어 있습니다. 참고: 이 페이지는 Android 프레임워크 기본을 잘 아는 사용자를 대상으&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/jetpack/guide?hl=ko&quot; data-og-url=&quot;https://developer.android.com/jetpack/guide?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bBOhBi/hyNKa0DXcb/rcuPHPKTzpdVOQB0P4lUZK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/jetpack/guide?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/jetpack/guide?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bBOhBi/hyNKa0DXcb/rcuPHPKTzpdVOQB0P4lUZK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;앱 아키텍처 가이드 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;앱 아키텍처 가이드 이 가이드에는 고품질의 강력한 앱을 빌드하기 위한 권장사항 및 권장 아키텍처가 포함되어 있습니다. 참고: 이 페이지는 Android 프레임워크 기본을 잘 아는 사용자를 대상으&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;mad-arch-ui-overview.png&quot; data-origin-width=&quot;1697&quot; data-origin-height=&quot;1503&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLZQC0/btrwlQ085lG/Vo5s6uAhyREyD3GCficZBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLZQC0/btrwlQ085lG/Vo5s6uAhyREyD3GCficZBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLZQC0/btrwlQ085lG/Vo5s6uAhyREyD3GCficZBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLZQC0%2FbtrwlQ085lG%2FVo5s6uAhyREyD3GCficZBk%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;446&quot; height=&quot;395&quot; data-filename=&quot;mad-arch-ui-overview.png&quot; data-origin-width=&quot;1697&quot; data-origin-height=&quot;1503&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;UI Layer &lt;/b&gt;(UI와 StateHolder)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 UI와 StateHolder 포함 합니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 와 State Holder 는 함께 UI 기반 로직을 수행합니다. 이 둘은 UI element + UI State 을 조합 하여 사용자에게 보여줄 UI를 보여줍니다. 이 둘을 묶거나 UI기반 관련된 로직을 수행하는 계층을 Presentataion Layer 혹은 UI Layer라고 부릅니다. UI Layer는 앱 데이터를 UI로 표시할 수 있는 형식으로 바꿔서 최종적으로 사용자에게 시각적으로 데이터를 보여주는 역활을 합니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;mad-arch-overview-data.png&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;1511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1CeSz/btrwkw2x3xp/BnDiwAazGw2Ao0U5ZwYTq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1CeSz/btrwkw2x3xp/BnDiwAazGw2Ao0U5ZwYTq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1CeSz/btrwkw2x3xp/BnDiwAazGw2Ao0U5ZwYTq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1CeSz%2Fbtrwkw2x3xp%2FBnDiwAazGw2Ao0U5ZwYTq0%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;485&quot; height=&quot;433&quot; data-filename=&quot;mad-arch-overview-data.png&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;1511&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;b&gt;Data Layer&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;Data Layer 는 앱 데이터에 대한 비즈니스 로직을 처리합니다. 안드로이드 설계에서 권장하는 Data Layer는 여러 출처의 데이터 소스를 한곳에서 관리하는 Repository를 가지고 있고 Repositoty는 외부 계층에서 접근할 수 있는 유일한 방법 입니다. 외부 계층은 Repository를 통해 Data Layer 에 접근 해서 데이터 소스를 가져오거나 데이터 소스의 업데이트를 요청 합니다.&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;StateHolder(ex:ViewModel) 가 직접 DataLayer 에 Repository 접근 해서 앱 데이터를 사용하게 할 수 있습니다. 하지만 하나의 StateHolder에 DataLayer의 여러 Repository를 다루다 보면 StateHolder 가 가지는 책임이 매우 커지게 됩니다.&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;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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;mad-arch-overview-domain.png&quot; data-origin-width=&quot;1687&quot; data-origin-height=&quot;979&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBxaD6/btrwmr0OIyb/Q2cLKULNKONcYaAKkPZioK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBxaD6/btrwmr0OIyb/Q2cLKULNKONcYaAKkPZioK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBxaD6/btrwmr0OIyb/Q2cLKULNKONcYaAKkPZioK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBxaD6%2Fbtrwmr0OIyb%2FQ2cLKULNKONcYaAKkPZioK%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;314&quot; data-filename=&quot;mad-arch-overview-domain.png&quot; data-origin-width=&quot;1687&quot; data-origin-height=&quot;979&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;b&gt;Domain Layer&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Domain Layer 는 여러 비즈니스 로직을 캡슐화 합니다. 단일 원칙 책임을 따르는 &lt;span&gt;UseCase라는 명칭으로 하나의 비즈니스 로직을 하나의 클래스로 관리 합니다. 여러 Repository 를 통해 하나의 로직을 만들어낼 수 있으며 이미 만들어낸 UseCase와 다른 Repository를 결합하여 하나의 로직을 만드는 등등.. 재사용성을 가진 클래스를 만들어 낼 수 있으며,&amp;nbsp; UseCase는 하나의 책임만 맡으므로 책임이 큰 클래스가 생기지 않습니다. 또한 StateHolder는 재사용성을 위해 만들어진 UseCase를 사용함으로써 StateHolder에 보일러 플레이트 코드가 많이 사라집니다.&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;안드로이드&amp;nbsp; 공식 설계는 Domain Layer를 필수 계층으로 지정하지 않습니다. 비즈니스 로직의 재사용 필요성 및 StateHolder의 부담에 따라 필요하면 만들어 사용하라고 권장합니다.&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;UseCase는 변경할 수 있는 이유가 하나여야 한다는 원칙에 따라서 만들어지는 Class입니다. 그로므로, UseCase 마다 하나의 비즈니스 로직만 가지는데 즉, 비즈니스 로직 갯수 만큼 많은 클래스를 생성해야 한다는 뜻입니다. 앱 제작에 있어서 비즈니스 로직의 재사용의 필요성을 느끼지 못했다면 UI Layer 와 Data Layer 두가지로 충분할 것 입니다.&amp;nbsp; &amp;nbsp;&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;mad-arch-overview.png&quot; data-origin-width=&quot;1725&quot; data-origin-height=&quot;1005&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0QO7R/btrwonwofSI/2zb6FYUl4lSkYmSsUHyu8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0QO7R/btrwonwofSI/2zb6FYUl4lSkYmSsUHyu8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0QO7R/btrwonwofSI/2zb6FYUl4lSkYmSsUHyu8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0QO7R%2FbtrwonwofSI%2F2zb6FYUl4lSkYmSsUHyu8K%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;568&quot; height=&quot;1005&quot; data-filename=&quot;mad-arch-overview.png&quot; data-origin-width=&quot;1725&quot; data-origin-height=&quot;1005&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;b&gt;Android Recommend Architeture&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ui -&amp;gt; domain -&amp;gt; data 순으로 의존 흐름을 확인 할 수 있고 각각 계층에 따라 관심사 분리를 준수했고 의존의 끝에 Data Layer가 있습니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-19 오전 3.15.23.png&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;1976&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yv3Tp/btrwiwIzIql/DNIkAxuHnUurhI7NFnWxf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yv3Tp/btrwiwIzIql/DNIkAxuHnUurhI7NFnWxf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yv3Tp/btrwiwIzIql/DNIkAxuHnUurhI7NFnWxf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyv3Tp%2FbtrwiwIzIql%2FDNIkAxuHnUurhI7NFnWxf0%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;632&quot; height=&quot;669&quot; data-filename=&quot;스크린샷 2022-03-19 오전 3.15.23.png&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;1976&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;b&gt;Layered Architeture&amp;nbsp;&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;안드로이드에서 공식으로 권장하는 설계는 Layered Architeture 라고 볼 수 있습니다.&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;b&gt;왜 안드로이드 권장 설계는 Layered 형태를 가지게 되었는가?&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.jpeg&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;567&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bno0fA/btrwlJ8rNUK/8KgIuOHuD5hyKo15RiyJg1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bno0fA/btrwlJ8rNUK/8KgIuOHuD5hyKo15RiyJg1/img.jpg&quot; data-alt=&quot;클린 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bno0fA/btrwlJ8rNUK/8KgIuOHuD5hyKo15RiyJg1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbno0fA%2FbtrwlJ8rNUK%2F8KgIuOHuD5hyKo15RiyJg1%2Fimg.jpg&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;658&quot; height=&quot;483&quot; data-filename=&quot;다운로드.jpeg&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;567&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;클린 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1_2mp_thUsKpFRLZhrGj8qKQ.jpeg&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHjH2P/btrwk37qUHh/7fhSTw7ymY5kBIrHVFkw3k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHjH2P/btrwk37qUHh/7fhSTw7ymY5kBIrHVFkw3k/img.jpg&quot; data-alt=&quot;육각형 아키텍처&amp;amp;amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHjH2P/btrwk37qUHh/7fhSTw7ymY5kBIrHVFkw3k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHjH2P%2Fbtrwk37qUHh%2F7fhSTw7ymY5kBIrHVFkw3k%2Fimg.jpg&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;681&quot; height=&quot;383&quot; data-filename=&quot;1_2mp_thUsKpFRLZhrGj8qKQ.jpeg&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;육각형 아키텍처&amp;amp;amp;amp;amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1_0Pg6_UsaKiiEqUV3kf2HXg.png&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5AEA7/btrwmr0O2zL/8qYdI1k2aubJ2uK7XaeCo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5AEA7/btrwmr0O2zL/8qYdI1k2aubJ2uK7XaeCo1/img.png&quot; data-alt=&quot;어니언 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5AEA7/btrwmr0O2zL/8qYdI1k2aubJ2uK7XaeCo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5AEA7%2Fbtrwmr0O2zL%2F8qYdI1k2aubJ2uK7XaeCo1%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;621&quot; height=&quot;610&quot; data-filename=&quot;1_0Pg6_UsaKiiEqUV3kf2HXg.png&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어니언 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hexagonal Architecture, onion Architecture, clean Architecture의 경우&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Domain Driven Development 로 의존성의 끝은 Domain 으로 향하면서 Domain 을 중심으로 구성됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 아키텍처들은 domain이 정책을 담당하므로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;domain이 상당히 중요한 레이어가 됩니다.&lt;/b&gt;&lt;/span&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-filename=&quot;스크린샷 2022-03-19 오전 2.39.32.png&quot; data-origin-width=&quot;3754&quot; data-origin-height=&quot;1368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE0oPd/btrwnC1zyP1/k9k6bybgZA3Gl4614a9GJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE0oPd/btrwnC1zyP1/k9k6bybgZA3Gl4614a9GJk/img.png&quot; data-alt=&quot;안드로이드 권장 설계가 위 3개 아키텍처 기반이었다면 위의 모습을 할 것이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE0oPd/btrwnC1zyP1/k9k6bybgZA3Gl4614a9GJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE0oPd%2FbtrwnC1zyP1%2Fk9k6bybgZA3Gl4614a9GJk%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;3754&quot; height=&quot;1368&quot; data-filename=&quot;스크린샷 2022-03-19 오전 2.39.32.png&quot; data-origin-width=&quot;3754&quot; data-origin-height=&quot;1368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;안드로이드 권장 설계가 위 3개 아키텍처 기반이었다면 위의 모습을 할 것이다.&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: #ee2323;&quot;&gt;&lt;b&gt;Layered Architeture 는 DataBase Driven Development 를 따릅니다.&lt;/b&gt;&lt;/span&gt; 의존 방향은 가장 영구적인 것으로 향하기 때문에 데이터 베이스 기반으로 설계가 됩니다.&amp;nbsp; 그렇기 때문에 Layered Architeture와 같은 형식을 보이는 안드로이드 권장 설계에서 domain보다 data 계층의 중요성을 더 크게 판단하기 때문에 &lt;b&gt;domain Layer는 필수가 아닌 선택이 가능한겁니다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;안드로이드 권장 설계에서 앱 데이터를 앱 컴포넌트에 저장하는 것을 가능하면 피해야한다고 말합니다. 그래서 가장 영속적인 데이터를 기반으로 UI를 그려내는 것을 원칙으로 합니다.&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;영구적인 데이터를 기반으로 UI를 그리는 것이 Android OS 부터 완전히 독립 되어 어떤 상황에서도 데이터의 안전을 보장 할 수 있기 때문에 영구 적인 Database로 부터 데이터를 가져와 사용하는 설계를 우선적으로 생각 했을 것이라 예상해봅니다. 또한, 안드로이드 개발 특성상 clean Architeture 처럼 완벽하게 프레임워크로 부터 독립하려면 타협이 필요할 수도 있고, cleanArchiteture 를 도입 하는 순간 여러 비용이 발생합니다.&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;반면, Layered Architeture 가 아키텍처는 개발자가 학습 하기 쉬운 비용를 가지고 있으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관심사 분리를 충분히 준수하고 있으면서 앱 데이터 기반으로 UI를 도출해야하는 원칙을 상당히 중요한 부분으로 권장하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;안드로이드 권장 설계는 Layered Architeture의 모습을 선택한 것 같습니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;0_1ZrS8t3HvPzRAuqg.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1hs2I/btrwpuB3EfV/l2eKbXbrpuDcmQAMYTCzoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1hs2I/btrwpuB3EfV/l2eKbXbrpuDcmQAMYTCzoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1hs2I/btrwpuB3EfV/l2eKbXbrpuDcmQAMYTCzoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1hs2I%2FbtrwpuB3EfV%2Fl2eKbXbrpuDcmQAMYTCzoK%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;593&quot; height=&quot;264&quot; data-filename=&quot;0_1ZrS8t3HvPzRAuqg.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MV 시리즈 응용하기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 안드로이드 개발자들이 clean Architeture 혹은 Layered Architeture 기반을 두고&amp;nbsp; Mv시리즈 패턴을 적용하려고 합니다. &lt;br /&gt;이 경우 data ~ domain을 model로 취급하고 ui Layer의 컨트롤러/프레젠터/뷰모델 을 둬서 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히나 요즘은 mvvm패턴을 많이 사용합니다. mvvm 패턴을 응용한다면 가능한 ViewModel 에&amp;nbsp; 비즈니스로직이 포함 되지 않도록 하면서 프레젠테이션 로직을 위주로 사용해야하고&amp;nbsp; 단방향 흐름의 데이터 스트림 개념보다 ViewModel의 상태의 변화를 View가 관찰해서 View가 업데이트 되고 ViewModel은 아예 View를 몰라야 하는 개념을 더 중점으로 생각하게 됩니다.&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;하지만 아쉽게도 AAC ViewModel 를 Mvvm의 ViewModel 로써 사용하려 한다면 Mvvm의 ViewModel의 장점을 모두 활용하기는 어렵습니다.&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;AAC ViewModel 은 View Owner이 무엇인가에 따라 수명주기가 정해집니다. 그래서 특정 View에 대한 종속성이 생김으로 Mvvm&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 특징인 View 와 ViewModel n:1을 활용하기 힘듭니다. 그래서 &lt;b&gt;대부분 1:1로 사용합니다.&lt;/b&gt;&amp;nbsp;&lt;br /&gt;n:1로 사용하는 경우는 parentFragmentOwner 또는 상위 Activity Owner를 AAC ViewModel에 지정해놓고 Fragment 간의 데이터 공유로 활용하는 경우가 대부분 입니다.&lt;br /&gt;&lt;br /&gt;또한 이미 AAC ViewModel은 View의 Owner를 가지고 있는데 이는 ViewModel은 View를 알고 있다는 이야기가 됩니다. 이러한&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 타협점을 감안하고 사용하는것이 AAC ViewModel를 사용한 안드로이드의 MVVM입니다.&amp;nbsp; &lt;/b&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;그래서 만약 완벽한 Mvvm 구현하고 싶다면 AAC ViewModel를 사용하지 않고 별도의 ViewModel Class를 자체적으로 만들어서 AAC ViewModel이 주는 장점을 포기한채 필요한 부분과 불편한점을 커버한 것들을 스스로가 직접 구현해야 합니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;steampunk-3791039_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MXCmV/btrwmZiJJ47/bBQCBrySYU6yurmJBmKGp1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MXCmV/btrwmZiJJ47/bBQCBrySYU6yurmJBmKGp1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MXCmV/btrwmZiJJ47/bBQCBrySYU6yurmJBmKGp1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMXCmV%2FbtrwmZiJJ47%2FbBQCBrySYU6yurmJBmKGp1%2Fimg.jpg&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;572&quot; height=&quot;322&quot; data-filename=&quot;steampunk-3791039_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&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;마무리&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;- Activity 는 컨트롤러 역활을 기본적으로 수행하면서 뷰의 역활을 동시에 필요로하는 경우가 있기 때문에 설계없이 코딩을 한다면 Activity가 매우 비대해지면서 나중에 유지 보수하기 힘들어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 또한 Activity와 같은 앱 컴포넌트 류에 앱 데이터나 상태를 저장하기에는 Android OS 상호작용이나 사용자 강제 종료 등등 위험성이 많으므로 철저하게 관심사 분리를 해야합니다.&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;- 안드로이드에서 제공하는 AAC ViewModel를 StateHolder로 사용하고, UI Layer 에서 Activity는 ui기반 로직 및 OS 상호작용에만 집중할 수 있도록 하는 것을 권장 합니다.&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;- AAC ViewModel에&amp;nbsp; ApplicationContext를 제외한 Context 류의 타입이 존재하면 메모리 누수의 위험이 있습니다. 로직에 Context 필요에 따라 UI 로직의 위치가 바뀌기도 합니다.&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UDF(단방향 흐름)으로 이벤트 반응을 단방향으로 제어합니다. Event 는 올라가고 State은 내려옵니다.&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;- 안드로이드 권장 설계는 DataBase Driven Devolpment 를 띄는 Layerd Architeture 를 권장 합니다. data 계층에서 제공되는&amp;nbsp; 앱 데이터를 기반으로 UI 계층의 StateHolder에서 UI를 표시 할수 있는 상태로 변환하고 UI에 나타냅니다. 여기서 StateHolder 너무 비대해지는 것을 대비하여 domain Layer를 추가합니다. 관심사 분리를 중요시 여기며 앱 데이터 기반으로 ui 표시를 중요시 합니다.&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;- 안드로이드 권장 설계에서 Domain은 필수가 아닌 선택입니다. 또한 의존성의 끝은 Domain이 아닌 Data 계층 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 Clean Architeture, Hexagonal architecture, Onion Archteture 등등 과 같은 Domain 기반 설계와 안드로이드 공식 문서에서 설명한 설계와 다름을 이야기 합니다.&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;- 개발자는 필요에 따라 data , domain을 Model로 취급하고 ui레이어에서 Mv패턴 시리즈를 사용하기도 합니다.&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; MVVM ViewModel 과 AAC ViewModel는 이름만 같을 뿐 입니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이상 잠 못드는 밤 생각을 정리해본 글이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android Architeture</category>
      <category>clean Architeture</category>
      <category>Layered Architeture</category>
      <category>MVVM</category>
      <category>설계</category>
      <category>안드로이드</category>
      <category>안드로이드 개발</category>
      <category>안드로이드 개발 설계</category>
      <category>안드로이드 권장 설계</category>
      <category>안드로이드 설계</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/69</guid>
      <comments>https://gift123.tistory.com/69#entry69comment</comments>
      <pubDate>Sat, 19 Mar 2022 03:09:59 +0900</pubDate>
    </item>
    <item>
      <title>홍드방 입문자님을 위한 파이어베이스 그레들리 셋팅 방법(gradle 7.x 이상)</title>
      <link>https://gift123.tistory.com/68</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;a href=&quot;https://open.kakao.com/o/gWIiEKWc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://open.kakao.com/o/gWIiEKWc&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1654654352685&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;#유튜브구독후1:1인증 #홍드로이드 #안드로이드 #개발자 #취준생 #앱개발 #android #개발공부 #java # kotlin #프로그래밍&quot; data-og-host=&quot;open.kakao.com&quot; data-og-source-url=&quot;https://open.kakao.com/o/gWIiEKWc&quot; data-og-url=&quot;https://open.kakao.com/o/gWIiEKWc&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qBdEX/hyOGQNGa99/wUvD2nyw6y48WSqXf0KLF0/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628&quot;&gt;&lt;a href=&quot;https://open.kakao.com/o/gWIiEKWc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://open.kakao.com/o/gWIiEKWc&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qBdEX/hyOGQNGa99/wUvD2nyw6y48WSqXf0KLF0/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;왕초보 안드로이드 개발 질문 답변&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;#유튜브구독후1:1인증 #홍드로이드 #안드로이드 #개발자 #취준생 #앱개발 #android #개발공부 #java # kotlin #프로그래밍&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;open.kakao.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;b&gt;(1)build.gradle(Project:플젝명)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645582009864&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;buildscript {
  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository

  }
  dependencies {
    ...
    // Add this line
    classpath 'com.google.gms:google-services:4.3.10'

  }
}

allprojects {
  ...
  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository

    ...
  }
}&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;(2)build.gradle(Module:플젝명.app)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1645581680545&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apply plugin: 'com.android.application'

// Add this line
apply plugin: 'com.google.gms.google-services'


dependencies {
  // Import the Firebase BoM
  implementation platform('com.google.firebase:firebase-bom:29.1.0')


  // Add the dependency for the Firebase SDK for Google Analytics
  // When using the BoM, don't specify versions in Firebase dependencies
  implementation 'com.google.firebase:firebase-analytics'


  // Add the dependencies for any other desired Firebase products
  // https://firebase.google.com/docs/android/setup#available-libraries
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 classPath는 사라졌으며 그렇다고 비슷해보이는 plugin에 적기만해도 안될겁니다.&lt;br /&gt;&lt;br /&gt;그래서 입문자분들은 어렵겠지만,, 공식문서를 보는 습관을 만드셔야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고링크는 아래입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/studio/releases/gradle-plugin?hl=ko#updating-plugin&quot;&gt;https://developer.android.com/studio/releases/gradle-plugin?hl=ko#updating-plugin&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1645581845754&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Android Gradle 플러그인 출시 노트 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;Android 스튜디오 빌드 시스템은 Gradle을 기반으로 하며 Android Gradle 플러그인에는 Android 앱을 빌드하는 데 사용하는 몇 가지 추가 기능이 있습니다.&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/studio/releases/gradle-plugin?hl=ko#updating-plugin&quot; data-og-url=&quot;https://developer.android.com/studio/releases/gradle-plugin?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bgr2Ic/hyNv9UBNkf/3GeLBv4vK684wnIWM7pWZk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/studio/releases/gradle-plugin?hl=ko#updating-plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/studio/releases/gradle-plugin?hl=ko#updating-plugin&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bgr2Ic/hyNv9UBNkf/3GeLBv4vK684wnIWM7pWZk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Android Gradle 플러그인 출시 노트 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Android 스튜디오 빌드 시스템은 Gradle을 기반으로 하며 Android Gradle 플러그인에는 Android 앱을 빌드하는 데 사용하는 몇 가지 추가 기능이 있습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;어쨋든 결론인 코드를 보여드리겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 classPath는 없어졌고, &lt;b&gt;build.gradle(Project:플젝명)과 &lt;b&gt;build.gradle(Project:플젝명.app)에 plugins 두곳다 존재하는 것이 보일겁니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;즉, classPath에 적힌 'com.google.gms:google-services:4.3.10' (예시) 를&amp;nbsp;&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;build.gradle(Project:플젝명)&lt;/b&gt; plugins 에 다음과 같이 포함하면 됩니다.&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645582077206&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '7.1.1' apply false
    id 'com.android.library' version '7.1.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
    // 이게 핵심!!!
    id 'com.google.gms.google-services' version '4.3.10' apply false
}

task clean(type: Delete) {
    delete rootProject.buildDir
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&amp;nbsp;주의할점은 &lt;b&gt;&lt;b&gt;om.google.gms:google-services:4.3.10에서&amp;nbsp; com.google.gms.google-services + 버전명 으로 바뀌었다는것인데 자세히 보면 gms:google 를 gms.google 로 바꾼것을 볼 수 있습니다. 즉&amp;nbsp;&lt;/b&gt;&lt;/b&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;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt; &lt;b&gt;build.gradle(Module:플젝명.app)에 plugins에 명시된 내용을 적고 version을 따로 적어주는 형식입니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;현재 예시의 해결 코드.&amp;nbsp;&lt;/b&gt;&lt;/b&gt;&lt;/b&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;(1) build.gradle(Module:플젝명) (바로 위에 사용한것과 같은 코드)&lt;/p&gt;
&lt;pre id=&quot;code_1645582286578&quot; class=&quot;pgsql&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '7.1.1' apply false
    id 'com.android.library' version '7.1.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
    id 'com.google.gms.google-services' version '4.3.10' apply false
}

task clean(type: Delete) {
    delete rootProject.buildDir
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2)&amp;nbsp;build.gradle(Module:플젝명.app)&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645582340023&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'com.google.gms.google-services'
}

android {
....
}

dependencies {
...

    //파이어 베이스
    implementation 'com.google.firebase:firebase-analytics'
    implementation platform('com.google.firebase:firebase-bom:29.1.0')
}&lt;/code&gt;&lt;/pre&gt;</description>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/68</guid>
      <comments>https://gift123.tistory.com/68#entry68comment</comments>
      <pubDate>Wed, 23 Feb 2022 11:13:13 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (34) RecyclerView 성능 향상</title>
      <link>https://gift123.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;847&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NHXKi/btrp3VutIa4/s2XWPLUB1RGtuWAdfLjng0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NHXKi/btrp3VutIa4/s2XWPLUB1RGtuWAdfLjng0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NHXKi/btrp3VutIa4/s2XWPLUB1RGtuWAdfLjng0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNHXKi%2Fbtrp3VutIa4%2Fs2XWPLUB1RGtuWAdfLjng0%2Fimg.jpg&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;441&quot; height=&quot;847&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;847&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Android 개발을 하다보면 불가피 하게 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;를 사용해야 하는 순간이 대부분 옵니다. 요새 Compose열풍이 휩쓸고 있지만 아직은 회사에서 사용하는 목록형 UI는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;를 통해 만들었을 겁니다. &lt;br /&gt;&lt;br /&gt;저 또한 마찬가지로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;를 활용하여 목록형 UI를 만들던 도중이었습니다. 하지만 테스트 기기중에 저사양 기기는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;를 단순하게 사용하면 버벅거림을 피할 수 없었습니다. 저사양 기기에서 최상의 퍼포먼스를 내기 위해 연구했습니다.&lt;br /&gt;&lt;br /&gt;내가 만든 소프트웨어는 기기에 차별받지 않고 항상 사랑받고 싶은 욕심으로.. 연구했던 내용을 블로그에 정리하려고 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l25j4/btrp7jOp2EU/XTtsbVCyJqgzTEVBmeHjH1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l25j4/btrp7jOp2EU/XTtsbVCyJqgzTEVBmeHjH1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l25j4/btrp7jOp2EU/XTtsbVCyJqgzTEVBmeHjH1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl25j4%2Fbtrp7jOp2EU%2FXTtsbVCyJqgzTEVBmeHjH1%2Fimg.jpg&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;588&quot; height=&quot;392&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;0. 느린 렌더링&lt;/b&gt;&lt;/span&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;i&gt;&quot;&lt;/i&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;i&gt;UI 렌더링은 앱에서 프레임을 생성하여 화면에 표시하는 작업입니다. 사용자와 앱의 상호작용이 원활하게 이루어지도록 하려면 앱이 16ms 미만으로 프레임을 렌더링하여 초당 60프레임을&lt;/i&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;i&gt; 달성해야 합니다. 앱의 UI 렌더링 속도가 느리면 시스템에서 프레임을 건너뛰게 되고 사용자는 앱에서 끊김을 인식합니다. 이런 현상을&lt;/i&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;i&gt; &lt;/i&gt;&lt;/span&gt;&lt;/span&gt;&lt;i&gt;&lt;b&gt;버벅거림 &lt;/b&gt;&lt;/i&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;i&gt;이라고 합니다.&lt;/i&gt;&lt;/span&gt;&lt;/span&gt;&lt;i&gt;&quot;&lt;/i&gt;&lt;br /&gt;- 안드로이드 공식문서 &lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;안드로이드에서 말하는 성능이란 무엇일까요?&lt;/b&gt;&lt;/span&gt;&lt;/span&gt; 안드로이드는 사용자와 직접적으로 맞닿아 있기 때문에 사용자가 느낄 때 UI 변경시 끊김 현상을 눈치채지 못하는 것이 가장 중요합니다.&lt;br /&gt;안드로이드는 16ms 미만으로 프레임을 렌더링해서 초당 60프레임을 달성해야 사용자 눈에는 전혀 버벅거림을 느낄 수 없다고 합니다. &lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;UI Thread 와 Render Thread(롤리팝 이후) 의 최상의 컨디션으로 초당 60프레임을 달성 해야합니다. &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt; &lt;/span&gt;&lt;br /&gt;위 조건을 위해 우리 안드로이드 개발자는 최선의 방법을 항상 찾아야 합니다. &lt;br /&gt;&lt;br /&gt;추천 링크 - &lt;br /&gt;&lt;a href=&quot;https://medium.com/google-developers/recyclerview-prefetch-c2f269075710&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://medium.com/google-developers/recyclerview-prefetch-c2f269075710&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;RecyclerView Prefetch&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Smoother Flings and Scrolls by Doing Stuff Sooner&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/google-developers/recyclerview-prefetch-c2f269075710&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/10kDF/hyM0ByB7ns/y4tWY3HRf0sMI7UpKc6a8k/img.png?width=1200&amp;amp;height=334&amp;amp;face=0_0_1200_334,https://scrap.kakaocdn.net/dn/Cjhox/hyM0Gs9Efe/ASAODuYKkwzQ1uwqbBSAo1/img.png?width=1827&amp;amp;height=510&amp;amp;face=0_0_1827_510,https://scrap.kakaocdn.net/dn/3vwkn/hyM0KCmfE0/jRBwnjk2s3NXApziKS7k0K/img.png?width=60&amp;amp;height=31&amp;amp;face=0_0_60_31&quot; data-og-url=&quot;https://medium.com/google-developers/recyclerview-prefetch-c2f269075710&quot;&gt;&lt;a href=&quot;https://medium.com/google-developers/recyclerview-prefetch-c2f269075710&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/google-developers/recyclerview-prefetch-c2f269075710&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/10kDF/hyM0ByB7ns/y4tWY3HRf0sMI7UpKc6a8k/img.png?width=1200&amp;amp;height=334&amp;amp;face=0_0_1200_334,https://scrap.kakaocdn.net/dn/Cjhox/hyM0Gs9Efe/ASAODuYKkwzQ1uwqbBSAo1/img.png?width=1827&amp;amp;height=510&amp;amp;face=0_0_1827_510,https://scrap.kakaocdn.net/dn/3vwkn/hyM0KCmfE0/jRBwnjk2s3NXApziKS7k0K/img.png?width=60&amp;amp;height=31&amp;amp;face=0_0_60_31');&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;RecyclerView Prefetch&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Smoother Flings and Scrolls by Doing Stuff Sooner&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;(&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;에서 UI 쓰레드와 렌더 스레드의 연관 되는 행위들에 대한 설명이 잘 섞여있습니다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJKJFX/btrp17O10dR/7N43DSfAOwvJLmVz1Gma51/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJKJFX/btrp17O10dR/7N43DSfAOwvJLmVz1Gma51/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJKJFX/btrp17O10dR/7N43DSfAOwvJLmVz1Gma51/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJKJFX%2Fbtrp17O10dR%2F7N43DSfAOwvJLmVz1Gma51%2Fimg.jpg&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;465&quot; height=&quot;310&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1. RecyclerView 및 Adapter에 포함된 함수를 효율적으로 응용하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 주제는 너무 유명하고 쉽게 알 수 있는 권장하는 방식들이라서 긴 설명없이 짧은 설명만 남기고 넘기도록 하겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;(1) setHasStableIds 와 getItemId를 사용하자&lt;/b&gt;.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class RecyclerViewAdapter : RecyclerView.Adapter&amp;lt;...&amp;gt; { init {
    setHasStableIds(true)
} ...
    override fun getItemId(position: Int): Long {
        return position.toLong() // or data id } }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;의 성능향상 중에 가장 기본은 onBindViewHolder 호출을 최소화 하는것입니다. &lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;setHasStableIds(true)&lt;/b&gt;&lt;/span&gt; 를 사용하면 각각 아이템 position에 지정된 Id를 기준으로 상황에 따라 onBindViewHolder() 호출을 제외시킵니다. 값이 변경된 id만 onBindViewHolder를 호출하거나 호출된 아이템의 id가 이전 position 아이템에 이미 존재할 시 onBindViewHolder 함수를 호출 하지 않고 이전에 같은 id를 가진 뷰를 대신 보여줍니다.&lt;br /&gt;&lt;br /&gt;아래 간단하게 설명이 된 블로그 링크가 있습니다.&lt;br /&gt;&lt;a href=&quot;https://blog.kmshack.kr/Stable-Id%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-RecyclerView-%EC%84%B1%EB%8A%A5-%ED%96%A5%EC%83%81%EB%B2%95/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://blog.kmshack.kr/Stable-Id%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-RecyclerView-%EC%84%B1%EB%8A%A5-%ED%96%A5%EC%83%81%EB%B2%95/&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(2) 새로운 데이터 추가/삭제 특정 포지션 변경 함수를 사용하기 or DiffUtil 사용하기 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;//기본적인 셋팅 없이 해당 함수 사용시 목록 전체가 깜빡이는 현상이 일어남
adapter.notifyDataSetChanged()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;notifyDataSetChanged() 는 모든 데이터를 전체적으로 변경할 때 사용합니다. 그래서 전체적으로 onBindViewHolder를 다시 순회를 해야되기 때문에 UI가 깜빡임이 생길 수 있습니다. (&lt;b&gt;setHasStableIds&lt;/b&gt;&lt;b&gt; &lt;/b&gt;를 사용하면 깜빡임이 없지만) 그래서 좋은 UI퍼포먼스를 위해 다른 데이터 변경 함수를 사용하도록 권장합니다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;데이터 변경 -&amp;gt; notifyItemChanged, notifyItemRangeChanged&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;데이터 추가 -&amp;gt; notifyItemInserted, notifyItemRangeInserted&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;데이터 삭제 -&amp;gt; notifyItemRemoved, notifyItemRangeRemoved&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;데이터 이동 -&amp;gt; notifyItemMoved&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;데이터 변경알림 종류 &lt;br /&gt;&lt;a href=&quot;https://todaycode.tistory.com/55&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://todaycode.tistory.com/55&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;불필요한 전체 데이터 셋을 다시 하지 않아도 되고, 심지어 기본적인 애니메이션도 사용되고 있어서 심플한 리스트에서 사용하기에 나쁘지 않습니다. 하지만 위 &lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;데이터 변경알림 함수를 상황에 맞게 골라 사용하다보면 코드가 많아지고 신경 써야 되는 부분이 늘어납니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;그래서 이 대안으로 Diff Util를 사용할 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;a href=&quot;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;DiffUtil은 Eugene W. Myers의 차분 알고리즘을 통해서 두 개의 데이터 목록을 가지고 비교하여서 서로 다른 차이점이 있는 아이템만 업데이트 합니다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;즉, 새로운 데이터 목록을 추가하게 된다면 기존 목록과 새로운 목록이 비교하여 서로 다른 아이템만 따로 업데이트를 합니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;그래서 데이터 추가, 삭제, 변경의 함수를 구분할 필요없이 새로운 데이터 목록만 삽입한다면 알아서 업데이트가 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;DiffUtil 방식으로 많은 함수를 쓰지 않아도 되서 코드상으로도 깔끔하고 실수할 확률도 줄어듭니다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;DiffUtil &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;DiffUtil public class DiffUtil extends Object java.lang.Object &amp;nbsp;&amp;nbsp;&amp;nbsp;↳ androidx.recyclerview.widget.DiffUtil DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Y0K5J/hyM0J4t2YZ/3EmStKZTNSchIsRTzVCOi1/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot; data-og-url=&quot;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil?hl=ko&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Y0K5J/hyM0J4t2YZ/3EmStKZTNSchIsRTzVCOi1/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;DiffUtil &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;DiffUtil public class DiffUtil extends Object java.lang.Object &amp;nbsp;&amp;nbsp;&amp;nbsp;↳ androidx.recyclerview.widget.DiffUtil DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;보통의 경우 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;ListAdapter&lt;/b&gt;&lt;/span&gt;를 어댑터에 상속받아 DiffUtil를 많이 사용합니다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;[Android] ListAdapter로 RecyclerView 효율적으로 사용하기 (DiffUtil, AsyncListDiffer)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;안드로이드에서 동일한 레이아웃을 반복적으로 뿌려주는 리스트형 UI를 만들기 위해 RecyclerView가 사용된다. RecyclerView를 사용하려면 Adapter, LayoutManager, ViewHolder 이렇게 세가지 준비물이 필요하다.&quot; data-og-host=&quot;zion830.tistory.com&quot; data-og-source-url=&quot;https://zion830.tistory.com/86&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dDwaGe/hyM0CElI02/o461klNig2d3ZSuLanzPTK/img.png?width=800&amp;amp;height=286&amp;amp;face=0_0_800_286,https://scrap.kakaocdn.net/dn/Htn9J/hyM0IxKQ6J/Tmhzyj6y0T9KRqzTCPFPWK/img.png?width=800&amp;amp;height=286&amp;amp;face=0_0_800_286,https://scrap.kakaocdn.net/dn/xphUj/hyM0CqNMfl/wHJY26Lozd51KFSBI6vKqk/img.png?width=2396&amp;amp;height=994&amp;amp;face=0_0_2396_994&quot; data-og-url=&quot;https://zion830.tistory.com/86&quot;&gt;&lt;a href=&quot;https://zion830.tistory.com/86&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://zion830.tistory.com/86&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dDwaGe/hyM0CElI02/o461klNig2d3ZSuLanzPTK/img.png?width=800&amp;amp;height=286&amp;amp;face=0_0_800_286,https://scrap.kakaocdn.net/dn/Htn9J/hyM0IxKQ6J/Tmhzyj6y0T9KRqzTCPFPWK/img.png?width=800&amp;amp;height=286&amp;amp;face=0_0_800_286,https://scrap.kakaocdn.net/dn/xphUj/hyM0CqNMfl/wHJY26Lozd51KFSBI6vKqk/img.png?width=2396&amp;amp;height=994&amp;amp;face=0_0_2396_994');&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] ListAdapter로 RecyclerView 효율적으로 사용하기 (DiffUtil, AsyncListDiffer)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드에서 동일한 레이아웃을 반복적으로 뿌려주는 리스트형 UI를 만들기 위해 RecyclerView가 사용된다. RecyclerView를 사용하려면 Adapter, LayoutManager, ViewHolder 이렇게 세가지 준비물이 필요하다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;zion830.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class RecyclerView:ListAdapter&amp;lt;T,VH&amp;gt;(DiffUtil.Callback...){ .... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;ListAdapter 는 기본적으로 AsyncListDiffer를 통해 데이터 계산은 Background Thread 에서 하기 때문에 반복 상용구 코드가 필요없이 ListAdapter 하나로 많은 효율을 얻을 수 있습니다.&lt;br /&gt;&lt;br /&gt;자세한 사용법은 아래링크를 추천합니다.&lt;br /&gt;&lt;a href=&quot;https://zion830.tistory.com/86&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://zion830.tistory.com/86&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;[Android] ListAdapter로 RecyclerView 효율적으로 사용하기 (DiffUtil, AsyncListDiffer)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;안드로이드에서 동일한 레이아웃을 반복적으로 뿌려주는 리스트형 UI를 만들기 위해 RecyclerView가 사용된다. RecyclerView를 사용하려면 Adapter, LayoutManager, ViewHolder 이렇게 세가지 준비물이 필요하다.&quot; data-og-host=&quot;zion830.tistory.com&quot; data-og-source-url=&quot;https://zion830.tistory.com/86&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dDwaGe/hyM0CElI02/o461klNig2d3ZSuLanzPTK/img.png?width=800&amp;amp;height=286&amp;amp;face=0_0_800_286,https://scrap.kakaocdn.net/dn/Htn9J/hyM0IxKQ6J/Tmhzyj6y0T9KRqzTCPFPWK/img.png?width=800&amp;amp;height=286&amp;amp;face=0_0_800_286,https://scrap.kakaocdn.net/dn/xphUj/hyM0CqNMfl/wHJY26Lozd51KFSBI6vKqk/img.png?width=2396&amp;amp;height=994&amp;amp;face=0_0_2396_994&quot; data-og-url=&quot;https://zion830.tistory.com/86&quot;&gt;&lt;a href=&quot;https://zion830.tistory.com/86&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://zion830.tistory.com/86&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dDwaGe/hyM0CElI02/o461klNig2d3ZSuLanzPTK/img.png?width=800&amp;amp;height=286&amp;amp;face=0_0_800_286,https://scrap.kakaocdn.net/dn/Htn9J/hyM0IxKQ6J/Tmhzyj6y0T9KRqzTCPFPWK/img.png?width=800&amp;amp;height=286&amp;amp;face=0_0_800_286,https://scrap.kakaocdn.net/dn/xphUj/hyM0CqNMfl/wHJY26Lozd51KFSBI6vKqk/img.png?width=2396&amp;amp;height=994&amp;amp;face=0_0_2396_994');&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] ListAdapter로 RecyclerView 효율적으로 사용하기 (DiffUtil, AsyncListDiffer)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드에서 동일한 레이아웃을 반복적으로 뿌려주는 리스트형 UI를 만들기 위해 RecyclerView가 사용된다. RecyclerView를 사용하려면 Adapter, LayoutManager, ViewHolder 이렇게 세가지 준비물이 필요하다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;zion830.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 특정 데이터 삽입,변경,삭제를 매우 심플하게만 해도 되는 상황일 시 notIDataSet 함수를 사용하면 좋고,&lt;br /&gt;데이터의 삽입 삭제 변경이 번번하게 일어난다면 DiffUtil로 한번에 관리해도 좋습니다.&lt;br /&gt;&lt;br /&gt;참고로 &lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;DiffUtil를 사용한다면 setHasStableId(true) , getItemId()는 사용하지 않아도 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt; 차이 알고리즘에 의해 필요한 부분만 업데이트가 이미 되기 때문 입니다.&lt;br /&gt;&lt;br /&gt;그래서 DiffUtil를 많이 쓰는 추세이긴 하지만, 상황에 따라&lt;br /&gt;간혹 notiDataSet을 사용하는 것이 더 좋은 경우도 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;notiDataSet 혹은 DiffUtil 중에 무엇을 사용할지는 본인 선택에 달려있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(3) 고정된 크기의 아이템 UI 을 사용하는 것이라면 setHasFixedSize를 true 로 하자 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;void onItemsInsertedOrRemoved() { 
if (hasFixedSize) layoutChildren(); else requestLayout();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;는 데이터가 삽입, 삭제가 될 때 각각 아이템의 레이아웃을 다시 계산해야할지 정합니다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; hasFixedSize가 true 일 경우 고정값으로 인식하고,&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; false일 경우 requestLayout() 호출이 되어 아이템의 레이아웃 계산이 다시 이루어집니다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;View와 관련된 작업은 모두 UI Thread가 위임합니다. 글 초입부에 언급한 60프레임을 달성 하려면 최대한 UI Thread를 효율적으로 활용 해야합니다. &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;그래서 아이템UI의 사이즈가 고정이라면 true로 하는게 좋습니다. &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;만약 CustomView를 View를 상속받아 처음부터 만들어본 사람이라면 저 requestLayout()이 얼마나 많은 작업을 할 것인지 짐작이 될겁니다. &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(4) setItemViewCacheSize 로 캐쉬 크기 조정하기 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;스크롤을 통해 화면에서 UI가 사라졌을 때 사라진 View를 다시 사용 하는 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;recycled view pool에 들어가지 않고,&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Cache&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 저장되어 다시 화면에 나왔을 때 onBindViewHolder 호출 없이 View 보여집니다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 즉, 동일한 뷰를 다시 그리지 않습니다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;recyclerView.setItemViewCacheSize(int n)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 함수를 pool과 함께 차이를 이해하려면 아래 링크를 추천합니다.&lt;br /&gt;&lt;a href=&quot;https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Anatomy of RecyclerView: a Search for a ViewHolder&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Intro&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cI396O/hyM0Fug2kO/D9OUDlbEuG5GzMCpsoqYQ0/img.png?width=901&amp;amp;height=673&amp;amp;face=0_0_901_673,https://scrap.kakaocdn.net/dn/60ox2/hyM0EhP5go/xW5FhlTwc5MBTIR3ge2TDK/img.png?width=901&amp;amp;height=673&amp;amp;face=0_0_901_673,https://scrap.kakaocdn.net/dn/cHzrw3/hyM0CqO8DF/7Fj4xVFvD4LmCVbOQ1JMu0/img.png?width=60&amp;amp;height=53&amp;amp;face=0_0_60_53&quot; data-og-url=&quot;https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714&quot;&gt;&lt;a href=&quot;https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-404ba3453714&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cI396O/hyM0Fug2kO/D9OUDlbEuG5GzMCpsoqYQ0/img.png?width=901&amp;amp;height=673&amp;amp;face=0_0_901_673,https://scrap.kakaocdn.net/dn/60ox2/hyM0EhP5go/xW5FhlTwc5MBTIR3ge2TDK/img.png?width=901&amp;amp;height=673&amp;amp;face=0_0_901_673,https://scrap.kakaocdn.net/dn/cHzrw3/hyM0CqO8DF/7Fj4xVFvD4LmCVbOQ1JMu0/img.png?width=60&amp;amp;height=53&amp;amp;face=0_0_60_53');&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;Anatomy of RecyclerView: a Search for a ViewHolder&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Intro&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(5) 끝없는 스크롤을 해야하는 List 라면 animation 을 제거 하자&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;recyclerView.setItemAnimator(null)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;의 아이템이 한정적이고 추가 , 삭제가 이루어진다면 해당 기능이 필요할 수 있겠지만 깜빡임 현상의 원인이 될 수도 있고 저사양 기기의 앱의 버벅거림을 생기게 하는 기능이 될 수도 있습니다. &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(6) 중첩 RecyclerView는 Pool 를 공유하자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;중첩 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt; 간의 pool를 공유하여 성능을 향상 시킬 수 있습니다.&lt;br /&gt;바깥쪽과 안쪽 pool를 공유하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;override fun onCreateViewHolder(...): RecyclerView.ViewHolder {
    val innerLm = LinearLayoutManager(...) innerRv.apply{
        layoutManager = innerLm recyclerViewPool = sharedPool
    } return OuterAdapter.ViewHolder(innerRv)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNf09s/btrp5ADBnsW/4rjVfG1iCK8eo4nCGKjzk1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNf09s/btrp5ADBnsW/4rjVfG1iCK8eo4nCGKjzk1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNf09s/btrp5ADBnsW/4rjVfG1iCK8eo4nCGKjzk1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNf09s%2Fbtrp5ADBnsW%2F4rjVfG1iCK8eo4nCGKjzk1%2Fimg.jpg&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;456&quot; height=&quot;304&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;2. View 및 onBindViewHolder 최적화&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(1) onBindViewHolder 에서 복잡한 계산, loop 계산, 콜백 및 리스너 set 하지말 것 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;- onBindViewHolder는 가능하면 순수 data set만 하는 것이 가장 이상적 입니다.&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;data를 외부에서 미리 가공해서 onBindViewHolder에서 set 하는 것이 가장 안정적입니다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;- onBindViewHolder에서 가능하면 for, while 과 같은 반복문은 피해야합니다. &lt;br /&gt;- onBindViewHolder에서 특별한 경우를 제외하고 콜백,리스너 정의를 하지 않아야 합니다. (ex: onClickListener)&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;
ovrride fun onBindViewHolder(
    holder: RecyclerView.ViewHolder,
    position: Int
) { 
// 앱 성능 저하의 원인이 됩니다. 
    for (...) {...}
    
// 불 필요한 코드 입니다.
    view.setOnClickListenr { ... }
    
//아래 처럼 set만 하는 코드만 존재하는 것이 가장 이상적 입니다.
    imageView.set...(data[position].url)
    textView.text = data[position].text
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; (2) ViewHolder 및 onCreateViewHolder에서 가능하면 미리 값을 처리 해놓을 것&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;- 예시로 width를 디바이스 기기의 3분의 1 크기로 고정값으로 사용해야하는 경우&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class ViewHolder(v: View) :
    RecyclerView.ViewHolder(v) {
    init {
        // 가능하면 미리 정할 수 있는 값은 ViewHolder나 onCreateView에서 처리하는 것이 좋다.
        v.layoutParms = LayoutParms(....)
        v.itemView.setOnClickListener { ... }
    }

    override fun onBindViewHolder(...) {
        //별로 좋지않는 코드, 성능 저하의 원인 중 일부분이 될 수 있음 
        holder.layoutParms = LayoutParms(....)
        holder.itemView.setOnClickListener { ... }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 처럼 가능하면 ViewHolder에서 미리 처리할 수 있는 것은 set을 해두는 것이 좋고 onBindViewHolder에 최대한 부담을 없애야 한다. &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #232629;&quot;&gt;&lt;b&gt;(3) OnBindViewHolder에서 Html.fromHtml() 사용하지 말것 &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;html 를 사용하기 때문에 마찬가지로 onBindViewHolder에 적합하지 않습니다. 성능저하의 원인 중 하나가 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(4) 아이템 레이아웃 UI 계층을 단순화 하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;안드로이드 xml은 View가 위치한 depth가 한단계 씩 깊어질 수록 UI 연산이 배로 증가합니다.&lt;br /&gt;이는 곧 성능저하로 이어지는 원인이 됩니다.&lt;br /&gt;&lt;br /&gt;특별한 경우를 제외하고 가능하면 xml 에서 &lt;b&gt;ConstraintLayout &lt;/b&gt;를 사용해서 1 depth로 View를 그리는 것이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;lt;android.support.constraint.ConstraintLayout&amp;gt; 
&amp;lt;ImageView /&amp;gt; 
&amp;lt;ImageView /&amp;gt; 
&amp;lt;TextView /&amp;gt; 
&amp;lt;EditText /&amp;gt; 
&amp;lt;TextView /&amp;gt; 
&amp;lt;TextView /&amp;gt; 
&amp;lt;EditText /&amp;gt; 
&amp;lt;Button /&amp;gt; 
&amp;lt;Button /&amp;gt; 
&amp;lt;TextView /&amp;gt; 
&amp;lt;/android.support.constraint.ConstraintLayout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;a href=&quot;https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Understanding the performance benefits of ConstraintLayout&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Posted by Takeshi Hagikura, Developer Programs Engineer Since announcing ConstraintLayout at Google I/O last year, we've continued to im...&quot; data-og-host=&quot;android-developers.googleblog.com&quot; data-og-source-url=&quot;https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BOBoE/hyM0w5eKUe/ZrzGQUcUdyq7UpSI4ZmtmK/img.gif?width=570&amp;amp;height=299&amp;amp;face=0_0_570_299,https://scrap.kakaocdn.net/dn/wBUFR/hyM0EvplVn/RfG2w3tUaZRGchK1IHIVx1/img.gif?width=570&amp;amp;height=470&amp;amp;face=0_0_570_470&quot; data-og-url=&quot;https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html&quot;&gt;&lt;a href=&quot;https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BOBoE/hyM0w5eKUe/ZrzGQUcUdyq7UpSI4ZmtmK/img.gif?width=570&amp;amp;height=299&amp;amp;face=0_0_570_299,https://scrap.kakaocdn.net/dn/wBUFR/hyM0EvplVn/RfG2w3tUaZRGchK1IHIVx1/img.gif?width=570&amp;amp;height=470&amp;amp;face=0_0_570_470');&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;Understanding the performance benefits of ConstraintLayout&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Posted by Takeshi Hagikura, Developer Programs Engineer Since announcing ConstraintLayout at Google I/O last year, we've continued to im...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;android-developers.googleblog.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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(5) 부모 계층 뷰 그룹이 스크롤 뷰 or 리싸이클러 뷰 일 경우 &lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;recyclerView.setNestedScrollingEnabled(false)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 뷰에 따라 스크롤이 부드럽지 않는 경우도 있습니다. 그럴 때 위 코드를 적용하면 한층 더 좋아짐을 알 수가 있으며 xml에서도 셋팅이 가능합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(6) item에 복잡한 drawable를 가능하면 사용 하지 말기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;lt;layout-list&amp;gt; 로 이루어진 복잡한 drawable을 item xml에 가능하면 사용하지 말아야합니다. 저가용 기기에서 GPU 에 부하를 주기 때문에 가능하면 심플하게 사용해야합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(7) ViewStub 을 사용하기 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;ViewStub으로 지연 인플레이트를 사용하는 것이 좋습니다. visible 로 설정된 경우에 inflate 하기 때문에 성능 컨트롤에 효율적 입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(8) 투명색은 가능하면 지양하는 것이 좋다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;투명색 사용은 저가용 기기에서 상당한 부하로 다가옵니다.&lt;br /&gt;투명색 처리는연산처리가 많기 때문에 가급적 피하는 것이 좋습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbokf4/btrqaI0Naka/y3Gl3Q1LrC3qSIxBL7EMyk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbokf4/btrqaI0Naka/y3Gl3Q1LrC3qSIxBL7EMyk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbokf4/btrqaI0Naka/y3Gl3Q1LrC3qSIxBL7EMyk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbokf4%2FbtrqaI0Naka%2Fy3Gl3Q1LrC3qSIxBL7EMyk%2Fimg.jpg&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;527&quot; height=&quot;355&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;862&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3. Image 최적화 &lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;를 사용할 때 일반적으로 Image 처리 때문에 버벅거림이 생기는 경우가 많습니다. 대표적인 예시로 GridLayoutManager로 SpanCount를 3으로 지정했을 때 별도의 이미지 최적화를 하지않으면 국내 점유율이 큰 갤럭시 S10 에서 Debug 모드로 실행 했을 때 스크롤하면 살짝의 버벅거림이 일어나는 것을 확인 할 수 있었습니다.&lt;br /&gt;&lt;br /&gt;일반적으로 Image를 어떻게 효율적으로 컨트롤 하는가에 따라&lt;span style=&quot;color: #ee2323;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt;의 성능이 좌우 되기도 합니다. &lt;br /&gt;이 글에서 안드로이드 개발에서 많이 쓰는 이미지 라이브러리 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Glide&lt;/b&gt;&lt;/span&gt;를 기준으로 설명하도록 하겠습니다. &lt;br /&gt;&lt;br /&gt;다른 이미지 라이브러리도 마찬가지로 흡사한 기능이 있으니 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Glide&lt;/b&gt;&lt;/span&gt;를 사용하지 않더라도 같은 방법으로 사용할 방법이 분명히 있을 것 입니다.&lt;br /&gt;&lt;br /&gt;(라이브러리를 사용하지 않아도 공수가 많이 들지만 Bitmap 으로 직접 컨트롤이 가능하기도 합니다. )&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(1) Cache 를 사용하자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Glide 의 경우 기본적으로 Cache처리가 되어있습니다.&lt;/b&gt;&lt;/span&gt; Cache사용으로 인해 빠른 이미지 호출 및 불필요한 네트워크 호출을 줄일 수 있습니다. skipMemoryCacheOf()를 true, false의 경우 확연하게 성능차이를 보이기도 합니다. 가능하면 특별한 경우에만 true로 해놓는 것이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; Glide. .with(imageView) .load(uri) ... 
 //true일 때 버벅거림이 생성되었고, false일 때 버벅거림이 덜 했습니다.
 //Glide는 기본적으로 false가 디폴트 값이니 true로 변경할일이 없으면 따로 셋팅하지 않아도 됩니다. 
 .skipMemoryCacheOf(false)...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;아래는 Cache 처리를 안했을 때 확연히 메모리 소모가 다른 결과를 보여준 테스트 결과 입니다. (감자튀김님 블로그)&lt;br /&gt;&lt;a href=&quot;https://gamjatwigim.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;b&gt;https://gamjatwigim.tistory.com/91&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Android RecyclerView에서 OOM 방지하기&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;0. 상황과 궁금증 설명 상황 : RecyclerView에서 리스트를 보여줄 때, 많은 양의 이미지를 사용한다. 나는 그러던 중, 스크롤이 버벅이는 현상을 발견하거나 심하면 OOM에러를 마주한 경험이 있다. 그 &quot; data-og-host=&quot;gamjatwigim.tistory.com&quot; data-og-source-url=&quot;https://gamjatwigim.tistory.com/91&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmxdr8/hyM0vrUlRG/fS4CYXG5ZBpdbTb8NIQ4s1/img.png?width=311&amp;amp;height=311&amp;amp;face=0_0_311_311,https://scrap.kakaocdn.net/dn/bJZY8r/hyM0KbyPNd/ciUifw00USSc1KZbkM4nkK/img.png?width=311&amp;amp;height=311&amp;amp;face=0_0_311_311,https://scrap.kakaocdn.net/dn/bwWiOv/hyM1MFzHuT/4PNX5r9w5kUnZgpTMksR1K/img.jpg?width=540&amp;amp;height=540&amp;amp;face=0_0_540_540&quot; data-og-url=&quot;https://gamjatwigim.tistory.com/91&quot;&gt;&lt;a href=&quot;https://gamjatwigim.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gamjatwigim.tistory.com/91&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmxdr8/hyM0vrUlRG/fS4CYXG5ZBpdbTb8NIQ4s1/img.png?width=311&amp;amp;height=311&amp;amp;face=0_0_311_311,https://scrap.kakaocdn.net/dn/bJZY8r/hyM0KbyPNd/ciUifw00USSc1KZbkM4nkK/img.png?width=311&amp;amp;height=311&amp;amp;face=0_0_311_311,https://scrap.kakaocdn.net/dn/bwWiOv/hyM1MFzHuT/4PNX5r9w5kUnZgpTMksR1K/img.jpg?width=540&amp;amp;height=540&amp;amp;face=0_0_540_540');&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 RecyclerView에서 OOM 방지하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;0. 상황과 궁금증 설명 상황 : RecyclerView에서 리스트를 보여줄 때, 많은 양의 이미지를 사용한다. 나는 그러던 중, 스크롤이 버벅이는 현상을 발견하거나 심하면 OOM에러를 마주한 경험이 있다. 그&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gamjatwigim.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(2) RGB 565 사용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이미지를 그리는 픽셀의 포맷을 표시할 때 요즘 이미지 라이브러리는 기본적으로 ARGB_8888가 적용되어 있습니다. &lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;포맷을 RGB_565 로 바꾼다면 메모리 효율을 50% 올릴 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt; &lt;/span&gt;&lt;/span&gt;하지만 ARGB_8888에 비해 이미지 색상 품질 저하가 생깁니다. 여러 이미지를 사용하지 않는 목록에서 사진 크기가 썸네일 수준이라면 ARGB_8888 와 육안으로 큰 차이는 나지 않습니다. &lt;br /&gt;&lt;br /&gt;그래서 고해상도의 이미지를 아이템 당 큰 사이즈로 보여주는 것이라면 ARGB_8888 를, &lt;br /&gt;저해상도의 이미지를 여러장 보여주고 크기가 작은 사이즈로 여러장을 보여주는 것이라면 RGB_565 사용을 권장 합니다.&lt;br /&gt;&lt;br /&gt;RGB 565와 ARGB 8888 차이 &lt;br /&gt;&lt;a href=&quot;https://hanburn.tistory.com/140&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://hanburn.tistory.com/140&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;[graphic] RGB565와 RGBA8888에 대해서&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;android에서 bitmap을 다루다보면 Bitmap의 config에 bitmap의 픽셀포멧을 표시하는 부분 있다. ALPHA_8, RGB_565, ARGB_4444, ARGB_8888 이렇게 4가지가 있는데 주로 사용되는 RGB_565와 ARGB_8888에 대해서 알아..&quot; data-og-host=&quot;hanburn.tistory.com&quot; data-og-source-url=&quot;https://hanburn.tistory.com/140&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxwBuE/hyM0JDJhhM/Ro6ZbEjix8Le4oK3SaOHO0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b4qPBN/hyM1L7KBkp/ka4NNx7P7V67ku1f2lV1mk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot; data-og-url=&quot;https://hanburn.tistory.com/140&quot;&gt;&lt;a href=&quot;https://hanburn.tistory.com/140&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hanburn.tistory.com/140&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxwBuE/hyM0JDJhhM/Ro6ZbEjix8Le4oK3SaOHO0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b4qPBN/hyM1L7KBkp/ka4NNx7P7V67ku1f2lV1mk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_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;[graphic] RGB565와 RGBA8888에 대해서&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;android에서 bitmap을 다루다보면 Bitmap의 config에 bitmap의 픽셀포멧을 표시하는 부분 있다. ALPHA_8, RGB_565, ARGB_4444, ARGB_8888 이렇게 4가지가 있는데 주로 사용되는 RGB_565와 ARGB_8888에 대해서 알아..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hanburn.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Glide 라이브러리는 아래와 같이 적용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;@GlideModule
class MyAppGlideModule : AppGlideModule() {
    override fun applyOptions(context: Context, builder: GlideBuilder) {
        // Glide default Bitmap Format is set to RGB_565 since it
        // consumed just 50% memory footprint compared to ARGB_8888.
        // Increase memory usage for quality with: 
        builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_ARGB_565))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따로 Glide Module 를 만드는 방법 외에&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; Glide. .with(imageView) .load(uri)
 ... 
 .format(DecodeFormat.PREFER_RGB_565)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용시 직접 지정하는 방법도 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(3) ImageView 의 크기를 match_parent 혹은 고정으로 지정하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Glide를 통해 Loading된 Image를 적용하려는 ImageView의 가로 높이가 wrap_content 일 경우 문제가 생깁니다. &lt;br /&gt;별다른 셋팅을 하지 않는다면 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;bitmap의 크기가 wrap_content 일때 사용중인 기기 기준으로 이미지 해상도가 결정 됩니다. &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;예를 들어, 내 장치의 화면 크기가 1080x1794이고 여기에 2000x1000 이미지를로드하려는 경우 로드 된 비트 맵의 크기는 1794 x 1196 가 됩니다. &lt;/span&gt;&lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;이때 ARGB_8888 품질이라면 8.18 MB 용량을 가집니다&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;이는 엄청난 메모리 소비로 이어지고 GC의 호출이 잦아져 결국 성능 저하로 이어집니다.&lt;/span&gt;&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;(4) glide에서 로딩하는 이미지의 해상도 모를 때 centerInside 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를들어 로딩된 이미지의 해상도가 150 x 250 이라고 할 때 ImageView 의 크기가 1000 x 1000 이라면 &lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;ImageView의 사이즈에 맞게 이미지 해상도가 확대 됩니다. &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;이미지 해상도를 억지로 확대 시킨 것이기 때문에 품질이 좋아지도 않을 뿐더러, 불필요하게 이미지 용량이 커집니다.&lt;br /&gt;그래서&lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;3&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;00kb 였던 용량이 2.50mb 까지 확대 되고 저품질 해상도는 유지가 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt; &lt;br /&gt;&lt;br /&gt;이때 centerInside를 사용한다면 이미지 자체의 해상도가 ImageView 크기의 맞게 변경될 일이 없고 &lt;br /&gt;이미지 사이즈만 재조정 됩니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Glide.with(imageView)
     .load(IMAGE_URL) 
     .centerInside() 
     .into(imageView)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;즉, 300kb 를 유지한채 ImageView 사이즈에 맞게 조절이 가능해집니다. &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(5) glide Image Size 직접 조절하기 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Glide.with(imageView) 
     .load(IMAGE_URL) 
     .override(200,200) 
     .into(imageView)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;.override()를 통해서 직접 이미지 사이즈를 조절 할 수 있습니다. 기존 이미지가 고해상도 였다면 &lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;이 기능을 사용하면 RecyclerView 의 Adapter Init이나 Scroll 할때 확연한 차이를 확인 할 수 있습니다. &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(6) RecyclerView의 LifeCycler에 따라 수동으로 Glide 이미지 로딩 취소 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;기본적으로 Glide는 context를 인자로 받아서 해당 LifeCycle에 따라 알아서 이미지 로딩이 취소 되지만 &lt;br /&gt;혹시 예외적인 경우가 있을 것 같아서 이 내용을 적어놓습니다.&lt;br /&gt;&lt;br /&gt;RecyclerView의 뷰 홀더 생성 및 제거 Lifecycle은 다음과 같습니다.&lt;br /&gt;onCreateViewHolder -&amp;gt; onBindViewHolder -&amp;gt;&lt;br /&gt;onViewAttachedToWindow -&amp;gt; onViewDetachedFromWindow -&amp;gt; onViewRecycled -&amp;gt; onCreateViewHolder(반복)..&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
    super.onViewRecycled(holder)
    Glide.with(context)
        .clear(holder.imageView)

}

// 혹은.... 
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
    super.onViewDetachedFromWindow(holder)
    Glide.with(context)
         .clear(holder.imageView)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;자세한 내용은 삐질님의 글을 추천합니다.&lt;br /&gt;&lt;a href=&quot;https://ppizil.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;b&gt;https://ppizil.tistory.com/38&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;[안드로이드] Recyclerview 제대로 알고 쓰자 !&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;안녕하세요&amp;nbsp;개발자 삐질입니다 오늘 제가 소개하고 싶은 내용은 제대로 된 리사이클러뷰의 개념 입니다. 여러분, 리사이클러뷰는 리스트뷰 -&amp;gt; 리사이클러뷰 즉 리스트형식의 UI를 구성하는 데 &quot; data-og-host=&quot;ppizil.tistory.com&quot; data-og-source-url=&quot;https://ppizil.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/obeuW/hyM1zGg9sV/pSqA2LoIAJEEDVOsgVdgB0/img.png?width=770&amp;amp;height=637&amp;amp;face=0_0_770_637,https://scrap.kakaocdn.net/dn/dQUyoe/hyM0znBkE2/fn3GDdISXCaXxw2mC1A6g0/img.png?width=770&amp;amp;height=637&amp;amp;face=0_0_770_637,https://scrap.kakaocdn.net/dn/geMIF/hyM0JKwSZN/xoesXpk6X26hXWS9P1gqhK/img.png?width=769&amp;amp;height=753&amp;amp;face=0_0_769_753&quot; data-og-url=&quot;https://ppizil.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Recyclerview-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90&quot;&gt;&lt;a href=&quot;https://ppizil.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Recyclerview-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ppizil.tistory.com/38&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/obeuW/hyM1zGg9sV/pSqA2LoIAJEEDVOsgVdgB0/img.png?width=770&amp;amp;height=637&amp;amp;face=0_0_770_637,https://scrap.kakaocdn.net/dn/dQUyoe/hyM0znBkE2/fn3GDdISXCaXxw2mC1A6g0/img.png?width=770&amp;amp;height=637&amp;amp;face=0_0_770_637,https://scrap.kakaocdn.net/dn/geMIF/hyM0JKwSZN/xoesXpk6X26hXWS9P1gqhK/img.png?width=769&amp;amp;height=753&amp;amp;face=0_0_769_753');&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;[안드로이드] Recyclerview 제대로 알고 쓰자 !&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요&amp;nbsp;개발자 삐질입니다 오늘 제가 소개하고 싶은 내용은 제대로 된 리사이클러뷰의 개념 입니다. 여러분, 리사이클러뷰는 리스트뷰 -&amp;gt; 리사이클러뷰 즉 리스트형식의 UI를 구성하는 데&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ppizil.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;(7) Glide를 사용함에도 불구하고 OutOfMemory 발생하는 경우&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity 나 기본적으로 Application Class 에 onLowMemory() 와 onTrimMemory() 를 오버라이드 할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;
overrid fun onLowMemory() {
    super.onLowMemory();
    Glide.get(this).clearMemory();
}
overrid fun onTrimMemory(int level) {
    super.onTrimMemory(level);
    Glide.get(this).trimMemory(level);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 상태에 따라 호출되는 함수인데, &lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;Glide를 위와 같이 셋팅하면 OOM를 방지 할 수 있습니다. &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ub9qT/btrp3VgUpxr/lKNzoGmPX3cekFdeXw2LG0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ub9qT/btrp3VgUpxr/lKNzoGmPX3cekFdeXw2LG0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ub9qT/btrp3VgUpxr/lKNzoGmPX3cekFdeXw2LG0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fub9qT%2Fbtrp3VgUpxr%2FlKNzoGmPX3cekFdeXw2LG0%2Fimg.jpg&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;470&quot; height=&quot;314&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;4. 다양한 시도 및 관련 유틸&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(1) &lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;PrecomputedText&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Android는 Text를 표시할 때 필요한 레이아웃 계산을 하고 위치를 배치 시킬때 많은 시스템 자원을 사용하게 됩니다. &lt;br /&gt;특히나 RecyclerView가 아이템을 생성할 때 레이아웃 계산을 하게 되는데 Text의 공간 계산인 경우 많은 자원을 소비하게 됩니다.&lt;br /&gt;&lt;br /&gt;그래서 구글에서 &lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;비동기로 Text의 공간 계산을 background thread로 진행한 뒤 UI 렌더링을 해주는 API를 소개 했었습니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;/* Worker Thread */ // resolve spans on worker thread to reduce load on UI thread
val expensiveSpanned = resolveIntoSpans(networkData.item.textData)

// pre-compute measurement work to reduce load on UI thread
val textParams: PrecomputedTextCompat.Params = ...

// we&amp;rsquo;ll get to this
val precomputedText: PrecomputedTextCompat =
    PrecomputedTextCompat.create(expensiveSpanned, params)

/* UI Thread */
myTextView.setTextMetricParams(precomputedText.getParams())
myTextView.setPrecomputedText(precomputedText)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt; 200자 내의 텍스트에서는 기존 setText 방식과 큰 차이는 없다고 합니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;많은 글을 포함한 아이템 형식의 목록에서라면 좋은 방법 인듯 합니다.&lt;br /&gt;&lt;br /&gt;그외 자세한 설명은 아래 링크에서 확인 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://proandroiddev.com/async-text-loading-in-android-with-precomputedtext-93aa131b0e5b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://proandroiddev.com/async-text-loading-in-android-with-precomputedtext-93aa131b0e5b&lt;/a&gt;&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;(2) 레이아웃매니저 커스텀을 해서 온 크레이트 뷰 호출을 여러번 미리 가져오기 &lt;/b&gt;&lt;b&gt;(PreCache)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;뷰홀더 생성 ~ 제거 Lifecycle에서 onCreateViewHolder 는 최초로 뷰홀더를 생성합니다.&lt;br /&gt;하지만 [생성] 을 미리 해놓으면 다음 LifeCycle에서 &lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;onCreateViewHolder 호출 횟수를 줄이는 방법이 존재합니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;바로 RecyclerView가 내부적으로 인식하는 화면 사이즈를 크게 잡는 것 입니다.&lt;br /&gt;&lt;br /&gt;RecyclerView는 사용자가 눈으로 볼 수 있는 화면 + 보이지 않는 위아래 가상의 영역의 사이즈가 있는데 이 전체 사이즈를 내부적으로 확 늘려버리면 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;defalut로 갖는 아이템의 양의 수보다 더 많은 아이템을 onCreateViewHolder로 생성 해 놓을 수 있습니다. &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;스크롤을 하게 되면 이미 onCreateViewHolder 로 생성한 아이템들로 인해 onCreateViewHolder 함수의 호출이 적어집니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class PreCacheLayoutManager(context: Context, private val extraLayoutSpace: Int) :
    LinearLayoutManager(context) {
    override fun getExtraLayoutSpace(state: RecyclerView.State?) = extraLayoutSpace
} 
//Activity 에서.. 
recyclerView.adapter = PreCacheLayoutManager(context,600)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 단점이 존재합니다. &lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;사이즈를 너무 크게 잡아버리면 init 시 호출되는 onCreateViewHolder가 많아 지고 가상으로 인식하는 사이즈가 매우 넓기 때문에 init 타이밍에 버벅거림이 존재할 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt; &lt;/span&gt;&lt;/span&gt;그래서 이 방식은 적재적소하게 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(3) &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #494e52;&quot;&gt;&lt;b&gt;LayoutManager.setItemPrefetchEnabled(true, false) 사용&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #494e52;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;RecyclerView 25.0.0 부터 기본적으로 setItemPrefetchEnabled() 이 true로 설정되어 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt; &lt;/span&gt;&lt;/span&gt;그래서 true로 하고 싶다면 별도의 셋팅이 필요없습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// false 하고 싶을 때만 직접적으로 명시한다. 
layoutManager.itemPrefetchEnabled = false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능은 onBindViewHolder를 몇 프레임 앞서서 미리 호출하는 방식 입니다. 그래서 유저가 빠른 스크롤을 할때 미리 onBindViewHolder를 미리 호출한 것을 보여주기 때문에 스무스한 스크롤이 가능합니다.&lt;br /&gt;&lt;br /&gt;UI thread에서 View의 inflate 와 bind가 완료되면 순차적으로 GPU Render Thread에서 렌더링 작업을 하게 되는데, 이때 UI Thread는 유후 상태가 됩니다.&lt;br /&gt;&lt;br /&gt;스크롤 할때 이 순서를 반복하게 됩니다.&lt;br /&gt;&lt;br /&gt;하지만 스크롤 할때 새로운 View가 등장하기 위해 UI Thread는 다시 infalte &amp;amp; bind 작업을 하게 되는데,&lt;br /&gt;문제는 위 작업이 Render Thread 에서 렌더링이 끝나고 UI가 사용자 눈에 표시 되려 하는 순간과 겹치게 됩니다. &lt;br /&gt;&lt;br /&gt;그래서 UI Thread 의 View infate &amp;amp; bind 작업은 비용이 매우 크면서 동시에 렌더링 된 UI가 표시가 되기 때문에 순간적으로 버벅거림이 생기게 되기 마련입니다. &lt;br /&gt;&lt;br /&gt;그래서 Prefetch 방식은 스크롤 할때 inflate 가 필요한 경우, UI 스레드가 유휴상태에 들어가는 동안 미리 추가적으로 onBindViewHolder() 를 호출합니다.&lt;br /&gt;&lt;br /&gt;그렇기 때문에 RecyclerView 버전이 업데이트 되면서 setItemPrefetchEnabled() 가 기본적으로 true로 셋팅 되어 있습니다. &lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;하지만 ViewCache 메모리를 추가적으로 사용하게 됩니다. 이 또한 나름의 비용이 들어갑니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;그래서 예를들어 스크롤이 조금만 발생을 하도록 유도한 UX라던가, 전체 아이템 갯수가 매우 적을 경우에 false로 하면 좋고, 반대로 많은 스크롤이 일어나는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;일반적인 경우에는 true로 하면 좋습니다.&lt;/b&gt;&lt;/span&gt;&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;(4) init 시에만 AsycLayoutInflate 사용, UI 스케줄러 제작 (안드로이드 개발자 Phil Olson의 방식 )&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt; 에 adapter를 set하면서 동시에 RecyclerView Adapter 를 사용하는 경우가 아닌, 어댑터에 뒤늦게 아이템이 insert 되는 경우라면&lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt; AsycLayoutInflate 를 사용하여 미리 onCreateViewHolder에서 사용할 View를 만들어 두고 onCreateViewHolder의 부담을 덜해버리는 방법도 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class SmoothListAdapter(val context: Context) :
    ListAdapter&amp;lt;ListItem, SmoothListAdapter.ListItemViewHolder&amp;gt;(ListItemViewHolder.MyDiffCallback()) {
    data class ListItem(val id: String, val text: String)
    class ListItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {

        private val asyncLayoutInflater = AsyncLayoutInflater(context)
        private val cachedViews = Stack&amp;lt;View&amp;gt;()
        init {
            //Create some views asynchronously and add them to our stack 
            for (i in 0..NUM_CACHED_VIEWS) {
                asyncLayoutInflater.inflate(
                    R.layout.list_item,
                    null
                ) { view, layoutRes, viewGroup -&amp;gt; cachedViews.push(view) }
            }
        }
        
        override fun onCreateViewHolder(
            parent: ViewGroup,
            viewType: Int
        ): ListItemViewHolder {
            //Use the cached views if possible, otherwise if we ran out of cached views inflate a new one 
            val view = if (cachedViews.isEmpty()) {
                LayoutInflater.from(context).inflate(R.layout.list_item, parent, false)
            } else {
                cachedViews.pop().also {
                    it.layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
                }
            }
            return ListItemViewHolder(view)
        }

        fun populateFrom(listItem: ListItem) {
            //TODO: populate your view 
        }
        override fun onBindViewHolder(viewHolder: ListItemViewHolder, position: Int) =
            viewHolder.populateFrom(getItem(position))

        class MyDiffCallback : DiffUtil.ItemCallback&amp;lt;ListItem&amp;gt;() {
            override fun areItemsTheSame(firstItem: ListItem, secondItem: ListItem) =
                firstItem.id == secondItem.id override fun areContentsTheSame(
                    firstItem: ListItem,
                    secondItem: ListItem
                ) = firstItem == secondItem
        }

        companion object {
            const val NUM_CACHED_VIEWS = 5
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위 코드 처럼 ListAdapter를 사용한다면 미리 adapter를 초기화 해두고 submitList(colleaction&amp;lt;T&amp;gt;) 를 뒤늦게 사용하게 된다면 미리 만들어둔 view를 stack 에 담아놧던걸 그대로 사용하면 됩니다. &lt;br /&gt;&lt;br /&gt;또한 UI 스케줄러를 따로 만들어 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;
object UIJobScheduler {
    private const val MAX_JOB_TIME_MS: Float = 4f private
            var elapsed = 0L private
                    val jobQueue = ArrayDeque&amp;lt;() -&amp;gt; Unit&amp;gt;() private
                            val isOverMaxTime get () = elapsed &amp;gt; MAX_JOB_TIME_MS * 1_000_000 private
                            val handler = Handler()

    fun submitJob(job: () -&amp;gt; Unit) {
        jobQueue.add(job) if (jobQueue.size == 1) {
            handler.post { processJobs() }
        }
    }

    private fun processJobs() {
        while (!jobQueue.isEmpty() &amp;amp;&amp;amp; !isOverMaxTime) {
            val start =
                System.nanoTime() jobQueue . poll ().invoke() elapsed += System . nanoTime () - start
        } if (jobQueue.isEmpty()) {
            elapsed = 0
        } else if (isOverMaxTime) {
            onNextFrame { elapsed = 0 processJobs () }
        }
    }

    private fun onNextFrame(callback: () -&amp;gt; Unit) =
        Choreographer.getInstance().postFrameCallback { callback() }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 최대 4ms를 사용 합니다. MAX_JOB_TIME_MS 상수를 바꿔서 원하는 최대 ms 를 설정할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; UIJobScheduler.submitJob { setupText2() } 
 UIJobScheduler.submitJob { setupImage2() }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;자세한 내용은 아래에서 확인하시면 됩니다. &lt;br /&gt;&lt;b&gt; &lt;/b&gt;&lt;a href=&quot;https://medium.com/@polson55/smooth-recyclerview-scrolling-in-android-57e7a9b71ca7&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;b&gt;https://medium.com/@polson55/smooth-recyclerview-scrolling-in-android-57e7a9b71ca7&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Smooth RecyclerView scrolling in Android&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Tips for using complex views without skipping frames&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@polson55/smooth-recyclerview-scrolling-in-android-57e7a9b71ca7&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cc0z9D/hyM1EVfq6V/HTKoQ3CrSFRSazW6FoTlmK/img.jpg?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800,https://scrap.kakaocdn.net/dn/b9OIK5/hyM0vFDOmE/ghxOCpEn9js45uHY8lE331/img.jpg?width=60&amp;amp;height=40&amp;amp;face=0_0_60_40&quot; data-og-url=&quot;https://medium.com/@polson55/smooth-recyclerview-scrolling-in-android-57e7a9b71ca7&quot;&gt;&lt;a href=&quot;https://medium.com/@polson55/smooth-recyclerview-scrolling-in-android-57e7a9b71ca7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@polson55/smooth-recyclerview-scrolling-in-android-57e7a9b71ca7&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cc0z9D/hyM1EVfq6V/HTKoQ3CrSFRSazW6FoTlmK/img.jpg?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800,https://scrap.kakaocdn.net/dn/b9OIK5/hyM0vFDOmE/ghxOCpEn9js45uHY8lE331/img.jpg?width=60&amp;amp;height=40&amp;amp;face=0_0_60_40');&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;Smooth RecyclerView scrolling in Android&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Tips for using complex views without skipping frames&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(5) AsyncLayoutInflater 를 사용해서 infalte, bind 하기 (Dmitrii Kachan 의 방식)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;AsyncLayoutInflater 를 사용해서 onCreateViewHolder 에서 뷰홀더를 생성하고 생성 후 onBindViewHolder에서 bind 까지 하는 방식 입니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt; &lt;/span&gt;&lt;/span&gt;기존에 UI Thread 가 하던일을 background Thread 에 맡기는 형식입니다. background Thread 에서 레이아웃 계산부터 모두 비동기 방식으로 사용이 가능 합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;open class AsyncCell(context: Context) : FrameLayout(context, null, 0, 0) {
    init {
        layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)
    }

    open val layoutId = -1

    // override with your layout Id 
    private var isInflated = false
    private val bindingFunctions: MutableList&amp;lt;AsyncCell.() -&amp;gt; Unit&amp;gt; = mutableListOf()

    fun inflate() {
        AsyncLayoutInflater(context).inflate(layoutId, this) { view, _, _ -&amp;gt;
            isInflated = true addView (createDataBindingView(view)) bindView ()
        }
    }

    private fun bindView() {
        with(bindingFunctions) {
            forEach { it() }
            clear()
        }
    }

    fun bindWhenInflated(bindFunc: AsyncCell.() -&amp;gt; Unit) {
        if (isInflated) {
            bindFunc()
        } else {
            bindingFunctions.add(bindFunc)
        }
    }

    // override for usage with dataBinding
    open fun createDataBindingView(view: View): View? = view
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 FrameLayout 를 더미로 만들고 addView를 해서 아래처럼 사용합니다. &lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class RecyclerViewAsyncAdapter(private val items: List&amp;lt;TestItem&amp;gt;) :
    RecyclerView.Adapter&amp;lt;RecyclerView.ViewHolder&amp;gt;() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
        SmallItemViewHolder(SmallItemCell(parent.context).apply { inflate() })

    override fun getItemCount(): Int = items.size
    override fun onBindViewHolder(
        holder: RecyclerView.ViewHolder,
        position: Int
    ) {
        if (holder is SmallItemViewHolder) {
            setUpSmallViewHolder(holder, position)
        }
    }

    private fun setUpLargeViewHolder(holder: LargeItemViewHolder, position: Int) {
        (holder.itemView as LargeItemCell).bindWhenInflated {
            items[position].let { item -&amp;gt;
                holder.itemView.binding?.item = item
            }
        }
    }

    private fun setUpSmallViewHolder(holder: SmallItemViewHolder, position: Int) {
        (holder.itemView as SmallItemCell).bindWhenInflated {
            items[position].let { item -&amp;gt;
                holder.itemView.binding?.item = item
            }
        }
    }

    private inner class SmallItemViewHolder internal constructor(view: ViewGroup) : RecyclerView.ViewHolder(view)
    
    private inner class SmallItemCell(context: Context) : AsyncCell(context) {
        var binding: SmallItemCellBinding? = null override
        val layoutId =
            R.layout.small_item_cell override fun createDataBindingView(view: View): View? {
                binding = SmallItemCellBinding.bind(view) return view.rootView
            }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점으로 복잡한 뷰를 가진 아이템에서는 간혹 UI가 제대로 렌더링 못하는 경우가 있거나, 오히려 더 느려지는 문제점이 생기기도 합니다. 마찬가지로 상황에 맞게 사용하면 좋은 방법이 될 듯 합니다. &lt;br /&gt;&lt;br /&gt;자세한 건 아래 링크에 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://proandroiddev.com/improve-ui-performance-async-recyclerview-layout-loading-7eb525ab19d0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://proandroiddev.com/improve-ui-performance-async-recyclerview-layout-loading-7eb525ab19d0&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Improve UI Performance with Async RecyclerView Layout Loading&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;RecyclerView is one of the most commonly used Android UI components. It can be very powerful but unfortunately it sometimes becomes very&amp;hellip;&quot; data-og-host=&quot;proandroiddev.com&quot; data-og-source-url=&quot;https://proandroiddev.com/improve-ui-performance-async-recyclerview-layout-loading-7eb525ab19d0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/djHNKp/hyM1y8KJLd/EoVMOw8vN5DZt9wYyGbjRk/img.jpg?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/fgb4C/hyM0Dw5gtE/Xef2gOUnpe4rG3iy6iD2kK/img.png?width=1656&amp;amp;height=900&amp;amp;face=0_0_1656_900,https://scrap.kakaocdn.net/dn/LTK3U/hyM0KJLNbo/FuphcXj9XtqjCrs1cM6sl0/img.png?width=1596&amp;amp;height=850&amp;amp;face=0_0_1596_850&quot; data-og-url=&quot;https://proandroiddev.com/improve-ui-performance-async-recyclerview-layout-loading-7eb525ab19d0&quot;&gt;&lt;a href=&quot;https://proandroiddev.com/improve-ui-performance-async-recyclerview-layout-loading-7eb525ab19d0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://proandroiddev.com/improve-ui-performance-async-recyclerview-layout-loading-7eb525ab19d0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/djHNKp/hyM1y8KJLd/EoVMOw8vN5DZt9wYyGbjRk/img.jpg?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/fgb4C/hyM0Dw5gtE/Xef2gOUnpe4rG3iy6iD2kK/img.png?width=1656&amp;amp;height=900&amp;amp;face=0_0_1656_900,https://scrap.kakaocdn.net/dn/LTK3U/hyM0KJLNbo/FuphcXj9XtqjCrs1cM6sl0/img.png?width=1596&amp;amp;height=850&amp;amp;face=0_0_1596_850');&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;Improve UI Performance with Async RecyclerView Layout Loading&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;RecyclerView is one of the most commonly used Android UI components. It can be very powerful but unfortunately it sometimes becomes very&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;proandroiddev.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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;(6) ViewHolder 인자로 넘어가는 View 를 inflate 해서 생성하지 않고 class 로 만들기 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;onCreateViewHolder에서 기본적으로 xml를 LayoutInflate로 View로 inlfate 해서 사용하는게 일반적인 경우이지만&lt;br /&gt;아예 xml 를 사용하지 않고 &lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;아이템 뷰 자체를 class로 직접 만들어서 뷰홀더 인자로 넘기는 방법이 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;inflate 을 하지않고 바로 View를 넘기기 때문에 뷰홀더 생성 비용을 아낄 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;
class ProductCardView : MaterialCardView { 
    ...
}
abstract class MyAdapter : RecyclerView.Adapter&amp;lt;MyAdapter.ViewHolder&amp;gt;() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return RecyclerView.ViewHolder(
            ProductCardView(parent.context)
        )
    }
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 유지보수 측면에서 xml 에 비해 효율은 좋지 않은 듯 합니다. &lt;span style=&quot;background-color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;성능을 중심으로 코딩 하는 상황이 아니라면 가급적 사용하지 않는 방법 입니다. &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chzuPo/btrqaIGuQku/uNK6c3HWfTXjq5qGc8u3z0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chzuPo/btrqaIGuQku/uNK6c3HWfTXjq5qGc8u3z0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chzuPo/btrqaIGuQku/uNK6c3HWfTXjq5qGc8u3z0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchzuPo%2FbtrqaIGuQku%2FuNK6c3HWfTXjq5qGc8u3z0%2Fimg.jpg&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;457&quot; height=&quot;304&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;5. Compose LazyComposable 사용&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;RecyclerView를 최적화 하기 위해 온갖 고생하는 것보다 Compose 를 통해서 목록형 UI를 만드는 방법도 권장합니다. 늦은 초기화를 통해서 View가 사용자 눈에 보일 때 UI 렌더링 합니다. &lt;/span&gt;&lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;복잡한 Adpater 관리도 사라지며 composable function 안에서 쉽게 List를 관리 할 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;
LazyColumn {

// Add a single item
    item { Text(text = &quot;First item&quot;) }
    
// Add 5
    items items (5) { index -&amp;gt; Text(text = &quot;Item: $index&quot;) }

// Add another single 
    item item { Text(text = &quot;Last item&quot;) }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgsTGQ/btrp6QeAWoY/3jeRrKAB4s7vBOmAcvooAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgsTGQ/btrp6QeAWoY/3jeRrKAB4s7vBOmAcvooAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgsTGQ/btrp6QeAWoY/3jeRrKAB4s7vBOmAcvooAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgsTGQ%2Fbtrp6QeAWoY%2F3jeRrKAB4s7vBOmAcvooAK%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;628&quot; height=&quot;265&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;265&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: #000000;&quot;&gt;Compose 는 xml 과 혼용해서 사용할 수 있는데 List 부분만 LazyComposable 를 이용해서 만드는 것도 하나의 방법 입니다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://gift123.tistory.com/m/44&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://gift123.tistory.com/m/44&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;안드로이드 개발 (16) Compose Lists&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;안녕하세요 안드로이드 개발자 Loner입니다. Compose의 정리를 이어서 진행해보도록 하겠습니다. List 기존 Xml방식으로 List는 주로 리싸이클러 뷰 혹은 리스트뷰로 많이 구현을 해왔습니다. Compose에&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/m/44&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cbeg9h/hyM1F7TNU3/aBTAgUVpOmWJQAt4UqJyX0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/2taKE/hyM0ECK2Fw/2Kv77uj6MkbH3rDjng81E0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/cwwGdj/hyM0CLKIi8/r53Rob3xgxIC6zcXyFhy2K/img.jpg?width=1280&amp;amp;height=856&amp;amp;face=0_0_1280_856&quot; data-og-url=&quot;https://gift123.tistory.com/44&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/44&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/m/44&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cbeg9h/hyM1F7TNU3/aBTAgUVpOmWJQAt4UqJyX0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/2taKE/hyM0ECK2Fw/2Kv77uj6MkbH3rDjng81E0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/cwwGdj/hyM0CLKIi8/r53Rob3xgxIC6zcXyFhy2K/img.jpg?width=1280&amp;amp;height=856&amp;amp;face=0_0_1280_856');&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;안드로이드 개발 (16) Compose Lists&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 안드로이드 개발자 Loner입니다. Compose의 정리를 이어서 진행해보도록 하겠습니다. List 기존 Xml방식으로 List는 주로 리싸이클러 뷰 혹은 리스트뷰로 많이 구현을 해왔습니다. Compose에&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이상 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/span&gt; 성능 향상에 대해 알아봤습니다. 오늘도 내가 만든 소프트웨어는 사랑 받기를 원합니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android리싸이클러뷰</category>
      <category>onBindViewHolder</category>
      <category>onCreateViewHolder</category>
      <category>RecyclerView</category>
      <category>RecyclerView 버벅임</category>
      <category>RecyclerView성능</category>
      <category>안드로이드</category>
      <category>안드로이드 리싸이클러뷰</category>
      <category>안드로이드 성능 향상</category>
      <category>안드로이드RecyclerView</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/67</guid>
      <comments>https://gift123.tistory.com/67#entry67comment</comments>
      <pubDate>Sat, 8 Jan 2022 20:04:49 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin (8) kotin in practice</title>
      <link>https://gift123.tistory.com/66</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;Kotlin의 장점을 내가 활용하는 걸까?&quot;&lt;/b&gt;&lt;br /&gt;스스로에게 질문은 던졌고 내 스스로 내린 대답은 No 였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;mobile-2262928_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMdBpQ/btrnDGdIgAO/EOb8p8lyRvLYPMU96EGa81/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMdBpQ/btrnDGdIgAO/EOb8p8lyRvLYPMU96EGa81/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMdBpQ/btrnDGdIgAO/EOb8p8lyRvLYPMU96EGa81/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMdBpQ%2FbtrnDGdIgAO%2FEOb8p8lyRvLYPMU96EGa81%2Fimg.jpg&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;618&quot; height=&quot;348&quot; data-filename=&quot;mobile-2262928_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 변해가는 안드로이드 개발은 언제나 공부 투성이이고 예전 시나리오들을 모르는 개발자들은 어떻게 발전해왔는지도 함께 공부를 해야합니다. 같은 발자취를 걷다가 어느새 Android 의존적인 개발 보다&lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt; Kotlin 이나 Java에 충실하고 변하는 안드로이드 플랫폼에 너무 의존적이지 않고 본질을 파고 들고 싶다는 생각이 듭니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;여러 생각이 들어서 공부하면서 Kotlin 글을 작성해야겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 게시글은 막막한 생각 앞에 특별한 주제 없이 즉흥적으로,, Kotlin을 사용하는 production 개발 업무를 하다가 다시 기본적인 것을 뒤돌아보고 정리한 내용 입니다.&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;br /&gt;&amp;nbsp;1. 변경 가능한 객체는 최대한 불변으로 노출 시키자.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예시) 이미지 갤러리를 직접 구현하는 화면을 제작한다는 가정하에 코드 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;android-photo-gallery.jpg&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pzdSs/btrnGjCfXfo/CmcLiBcpHoYaTiT2Sb9jl1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pzdSs/btrnGjCfXfo/CmcLiBcpHoYaTiT2Sb9jl1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pzdSs/btrnGjCfXfo/CmcLiBcpHoYaTiT2Sb9jl1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpzdSs%2FbtrnGjCfXfo%2FCmcLiBcpHoYaTiT2Sb9jl1%2Fimg.jpg&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;615&quot; height=&quot;367&quot; data-filename=&quot;android-photo-gallery.jpg&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;2000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- RecyclerView.Adapter Class 내의 MutableList 를 사용해서 selectImages.add 혹은 remove를 통해 selectImages에 데이터를 담고&amp;nbsp; 완료 버튼 UI를 누를 시 서버에 선택한 이미지 전송&lt;/p&gt;
&lt;pre id=&quot;code_1639198603094&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Adapter(private val dataList:List&amp;lt;T&amp;gt;):RecyclerView.Adapter ....{
val selectImages = mutableList&amp;lt;String&amp;gt;()
 ....
}


class MainActivity:AppCompatActivity...{

  private val adapter = Adapter()

  override fun onCreate(..){
    super.onCreate(..)
    ...setting UI...
    doneButton.setOnClickListener{
      imageUpload(adapter.selectImages)
    }
  }

  fun imageUpload(images:List&amp;lt;String&amp;gt;){
    //서버에 업로드
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;좋은 코드는 아니지만, 아무튼.. 위 코드는 변경 가능한 mutableList를 외부에 바로 노출해버리는 경우가 있던거 같습니다. Kotlin은 기본적으로 불변 상태를 권장하고 상태를 변경 가능한 객체는 가능하면 immutable 로 노출하는 것이 중요합니다. 그래서 위와 같은 코드는 외부에서 언제든지 해당 객체를 건드려버릴 수 있는 여지가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639199819478&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Adapter(private val dataList:List&amp;lt;T&amp;gt;):RecyclerView.Adapter ....{
private val selectImages = mutableList&amp;lt;String&amp;gt;()
 ....
 fun getSelectImageList():List&amp;lt;String&amp;gt;{
    return selectImages
  }
}
//액티비티에서.. 
doneButton.setOnClickListener{
      imageUpload(adapter.getSelectImageList())
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;Adatepr 내부 selectImages 의 접근제한자를 설정하고 getSelectImagesList() 라는 함수를 만들었습니다.&lt;/b&gt;&lt;/span&gt; mutable collection 의 Return Type을 해당 collection Type 의 read only Type 으로 설정하면 자동적으로 Immutable Collection으로 업캐스팅 되어 반환이 가능합니다.&lt;br /&gt;&lt;br /&gt;이렇게 외부에서 쉽게 변경이 불가능하고 Adapter 내부에서만 변경이 가능한 구조가 됩니다. 위 같이 mutable Colletion은 외부에서 함부로 변경이 불가능하게끔 작성하는 것이 좋습니다.&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;그리고 일반적인 상황에서 mutable List&amp;lt;T&amp;gt; 보단 List&amp;lt;T&amp;gt;를 더 선호하는 것이 좋습니다. plus() 나 minus로 객체를 복제하는 해서 사용하는 것이 기본 적인 불변을 보장하게 만듭니다. 하지만 상황에 따라서 mutableList&amp;lt;T&amp;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;br /&gt;2. 플랫폼 타입은 조심 해야한다.&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ask-4304981_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;779&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWDWjm/btrnBb0vWM2/MnjQBPr6O4Iifp5nBFOUl0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWDWjm/btrnBb0vWM2/MnjQBPr6O4Iifp5nBFOUl0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWDWjm/btrnBb0vWM2/MnjQBPr6O4Iifp5nBFOUl0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWDWjm%2FbtrnBb0vWM2%2FMnjQBPr6O4Iifp5nBFOUl0%2Fimg.jpg&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;521&quot; height=&quot;317&quot; data-filename=&quot;ask-4304981_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;779&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin이 아닌 외부 플랫폼에서 오는 Data의 경우 Kotlin 과 같이 n&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;ull-safety 하지 못하는 언어에서 오는 데이터는 &lt;b&gt;@Nullable 혹은 @NotNull anotation 을 사용해서 null 허용 여부를 결정해야 합니다.&lt;/b&gt; 대표적인 예시로 Java 코드가 있을 겁니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639201045414&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class User {
	public @NotNull User getUser() {
    	
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 null safety 기능으로 미리 컴파일 단계에서 널 허용 여부를 확인을 통해 해당 NPE 에 대한 방지를 미리 확인 할 수 있지만 어노테이션이 작성이 되지 않은 플랫폼 타입은 기본적으로 null 체크 여부를 묻지 않고 non null로 컴파일 단계에서 읽힙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639201434861&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//플랫폼타입
public class JavaType {

    public integer getData() {
    	return null;
    }
}

//자바
fun javaTypeTest(){
    val value = JavaType().data 
    //아래에서 런타임 에러
    println(value) 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그래서 &lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;not null로 믿었던 println(value) 함수에서 에러가 발생합니다.&lt;/b&gt;&lt;/span&gt; 그외에도 플랫폼 타입은 널리 전파가 가능하기도 합니다. 코틀린 interface 에서 아래와 같이 사용할 경우&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639201897740&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface JavaTypeInterface{
    fun getType() = JavaType().data
}&lt;/code&gt;&lt;/pre&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;h4 data-ke-size=&quot;size20&quot;&gt;3. 타입 추론&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;hand-1701969_1280.jpg&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FEvOJ/btrnC31igrP/ynMlmgZO9DstVDdA9eZN60/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FEvOJ/btrnC31igrP/ynMlmgZO9DstVDdA9eZN60/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FEvOJ/btrnC31igrP/ynMlmgZO9DstVDdA9eZN60/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFEvOJ%2FbtrnC31igrP%2FynMlmgZO9DstVDdA9eZN60%2Fimg.jpg&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;408&quot; height=&quot;544&quot; data-filename=&quot;hand-1701969_1280.jpg&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 보통 Java와 많이 비교를 하게 됩니다. Kotlin 중에서 아주 강력한 것은 바로 타입 추론이 가능해서 빠르게 간결한 코드를 작성하게 됩니다. 하지만 이 타입추론에 너무 의지하다 보면 협업할 때 소소한 혼란을 가져오거나 타입을 잘못 유추해서 실수하는 경우가 생깁니다&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 String,Integer 와 같이 기본 데이터는 금방 체크가 가능하지만&lt;/p&gt;
&lt;pre id=&quot;code_1639202572821&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val score = 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참조 타입의 객체의 경우 코드 시나리오가 잘보여지지 않는 상황일 때 타입 추론하기 애매한 경우가 있기도 합니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639202638580&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Url 이야..? String이야..? Image 이라는 참조 타입이 있는거야..?
val getImage = repository.getImage()&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1639202741749&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Image {
    val image:String
}
// 아하 
val getImage:Image = repository.getImage()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유추가 안되는 상황에 있는 변수명이나 코드라인이면 임의적으로 반드시 타입명을 다는 것이 빠른 생산속도나 오해의 여지를 많이 줄여줍니다. 하지만 너무 많이 명시를 한다면 타입추론이라는 강력한 기능을 상실할 수 있어서, &lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;코드 상황상 명시를 해야하는 부분에 판단해서 명시를 하는 것이 좋습니다.&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&gt;4. 표준 에러를 사용하자.&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;architecture-1857175_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HCRE0/btrnC2gZu9K/mGBS6AKTAD4Si5KGz7ewJ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HCRE0/btrnC2gZu9K/mGBS6AKTAD4Si5KGz7ewJ0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HCRE0/btrnC2gZu9K/mGBS6AKTAD4Si5KGz7ewJ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHCRE0%2FbtrnC2gZu9K%2FmGBS6AKTAD4Si5KGz7ewJ0%2Fimg.jpg&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;525&quot; height=&quot;350&quot; data-filename=&quot;architecture-1857175_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특별한 상황을 제외하고 보편적으로 많이쓰는 Stendard Error 를 사용하는 것이 좋습니다. 이러한 &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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;- IlligalArgumentException&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;-&amp;nbsp; IllegalStateException&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;- IndexOutObBoundsException&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;- ConcurrentModificationException&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;- UnsupportedOperaionException&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;- NoSuchElementException&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&gt;5. 함수가 원하는 결과가 아닌,, 다른 결과를 가져올 수 있는 경우 -&amp;gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;Null 혹은 실패를 의미하는 객체를 따로 반환하자-&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;feedback-1213042_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be7CWV/btrnFalkjnb/FBvPlgzcxZvS62qo6KHnO0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be7CWV/btrnFalkjnb/FBvPlgzcxZvS62qo6KHnO0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be7CWV/btrnFalkjnb/FBvPlgzcxZvS62qo6KHnO0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe7CWV%2FbtrnFalkjnb%2FFBvPlgzcxZvS62qo6KHnO0%2Fimg.jpg&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;471&quot; height=&quot;850&quot; data-filename=&quot;feedback-1213042_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;850&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;가장 대표적인 예시로 서버에서 데이터를 가져오는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제든지 Network 혹은 Server 상황에 따라 클라이언트에서 원하는 값을 반환 받지 못하는 경우가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때를 대비해서 &lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;null 반환 혹은 Failure 로 다른 객체를 반환하는 것이 좋습니다.&lt;/b&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;/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) null 반환&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null 반환에 대비해서 엘비스 연산자 &quot;?:&quot; 혹은 null safe call 처리가 너무 수월합니다. 또한 대부분 null 이 값이 없다라는 뜻으로 통용되기도 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1639204232296&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val getResultServer = networkApi.getData()?:&quot;서버에 문제가 생겼습니다.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) Failure&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveData를 사용했을 때의 간단한 예제입니다. ( 아니라 블로그 포스팅중에 즉흥으로 작성한거라 오류가 많은 코드입니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1639204918800&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class Result&amp;lt;out T&amp;gt;
class Success&amp;lt;out T&amp;gt;(val result: T) : Result&amp;lt;T&amp;gt;()
class Failure(val throwable: Throwable) : Result&amp;lt;Nothing&amp;gt;()

class ViewModel:ViewModel(){

private val _liveData:LiveData&amp;lt;Result&amp;lt;Data&amp;gt;&amp;gt;
val liveData:LiveData&amp;lt;Result&amp;lt;Data&amp;gt;&amp;gt; = _liveData


 init{
      runcatching{
        getServerData()
      }.onSuccess{
         _liveData.value = Result.Success(it)
      }.onFailure{
         _liveData.value = Result.Failure(it)
     }
  }
  ..fun getServerData().. 
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1639205060939&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//액티비티
onCreate(){
...
viewModel.liveData.observe(this){ result -&amp;gt;
  val age = when(result){
     is Success -&amp;gt; {...}
     is Failure -&amp;gt; {-1}
   }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Failure 경우 실패와 함께 다른 값도 넘겨 줄 수 있고 when으로 패턴 매칭을 사용하기 때문에 더 상태에 대한 처리가 명확해지는 코드 작성이 됩니다.&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;br /&gt;&lt;br /&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;&amp;nbsp;- 코틀린에서 모든 예외가 고려되지는 않는다. 유저는 이를 핸들링조차 하지 않아도 될 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;- 예외가 예외적인 상황을 위해 디자인되었기 때문에, JVM내에서 명시적인 검사만큼 빠르게 작동하지 않을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- try-catch블록 안에 코드를 구현하는 것은 컴파일러가 최적화를 하는 것을 방해할 수 있다.&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;6. !!을 잘 처리해야 한다.&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;problem-3303403_1280.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6HazF/btrnGjoNbBu/hF2PNDlQsJz3kE1SyJPLf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6HazF/btrnGjoNbBu/hF2PNDlQsJz3kE1SyJPLf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6HazF/btrnGjoNbBu/hF2PNDlQsJz3kE1SyJPLf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6HazF%2FbtrnGjoNbBu%2FhF2PNDlQsJz3kE1SyJPLf0%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;497&quot; height=&quot;207&quot; data-filename=&quot;problem-3303403_1280.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin은 null 을 지배하는 것이 중요한 포인트 입니다. 컴파일에서 부터 널 허용 여부를 묻는데, 해당 객체가 null 이 아닐 것이라 생각하고 쉽게 !! 를 표시하는 경우가 있습니다. 그러다가 나중에 코딩을 하다보면 &lt;b&gt;안전할것이라 생각한 해당 부분에서 NPE이 걸리는 경우가 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639206115747&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//아래 인자중 하나라도 null 일시 NPE 발생
fun largestOf(a: Int, b: Int, c: Int, d: Int): Int = listOf(a, b, c, d).max()!!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이외에도 !!는 경고 같은 표시라서 코드 문법 흐름상보기에도 좋지 않습니다. &lt;br /&gt;이 문제에 대한 대안은 lateinit을 사용하는 경우가 많습니다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;!!의 사용을 피하도록 하는 것이 좋습니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #222222;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 그래서 많은 안드로이드 팀들은 !!를 사용하지 않는 정책을 가지고 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;7. 널러블 허용 컬렉션 타입은 널 대신 빈 콜렉션을 반환하는 것이 좋다.&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;cardboard-5324172_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkfYRG/btrnB7JV06U/gbuMsM1dAbPbMxpeJG5Qyk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkfYRG/btrnB7JV06U/gbuMsM1dAbPbMxpeJG5Qyk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkfYRG/btrnB7JV06U/gbuMsM1dAbPbMxpeJG5Qyk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkfYRG%2FbtrnB7JV06U%2FgbuMsM1dAbPbMxpeJG5Qyk%2Fimg.jpg&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;484&quot; height=&quot;392&quot; data-filename=&quot;cardboard-5324172_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1639207911544&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ImageList(
 val list:List&amp;lt;String&amp;gt; = emptyList()
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;예를 들어 &lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;List&amp;lt;Int&amp;gt;?&lt;span&gt; 라는 데이터를 다룰 때 해당 자료형이 실질 적으로 null 상태인지? 아이템이 빈 상태인지? 구별하기 어렵습니다. List는 값이 없다는 것을 표현하기 위해 emptyList() 나 listOf()를 기본 할당하는 것이 좋습니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span&gt;8. Kotlin 문법을 너무 남용하지 말자&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;confusion-3233392_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;851&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VxFCq/btrnBg1xYB1/kYTWDvzIA8Dky73hrYgz4K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VxFCq/btrnBg1xYB1/kYTWDvzIA8Dky73hrYgz4K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VxFCq/btrnBg1xYB1/kYTWDvzIA8Dky73hrYgz4K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVxFCq%2FbtrnBg1xYB1%2FkYTWDvzIA8Dky73hrYgz4K%2Fimg.jpg&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;530&quot; height=&quot;851&quot; data-filename=&quot;confusion-3233392_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;851&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span&gt;Kotlin 을 하다보면 scope function 을 많이 사용하게 되고, kotlin 다운 문법을 과도하게 활용하려는 경우가 있습니다. 항상 케바케라는 말이 있듯이 &lt;span style=&quot;background-color: #f3c000; color: #ffffff;&quot;&gt;&lt;b&gt;kotlin 특유의 문법을 필요이상으로 사용하면 기본적인 문법만 못하는 경우가 많습니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;대표적인 예시로 take if 와 let으로 null 체크를 과도하게 쓰고 엘비스 연산자로 지저분하게 처리하는 경우가 있습니다.&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 style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span&gt;아래 예제는 teahwan 님 블로그에서 잘못된 예시로 작성된 예제 코드 일부 가져왔습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://thdev.tech/kotlin/2020/09/08/kotlin_effective_01/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://thdev.tech/kotlin/2020/09/08/kotlin_effective_01/&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1639207073568&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;읽기 좋은 Kotlin 코드! Property와 Scope Functions! |&quot; data-og-description=&quot;I&amp;rsquo;m an Android Developer.&quot; data-og-host=&quot;thdev.tech&quot; data-og-source-url=&quot;https://thdev.tech/kotlin/2020/09/08/kotlin_effective_01/&quot; data-og-url=&quot;https://thdev.tech/kotlin/2020/09/08/kotlin_effective_01/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHaNFI/hyMFIRboTg/0EVR9ZBjKm9NtZWzBgtYsK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://thdev.tech/kotlin/2020/09/08/kotlin_effective_01/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://thdev.tech/kotlin/2020/09/08/kotlin_effective_01/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHaNFI/hyMFIRboTg/0EVR9ZBjKm9NtZWzBgtYsK/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;읽기 좋은 Kotlin 코드! Property와 Scope Functions! |&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I&amp;rsquo;m an Android Developer.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;thdev.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;pre id=&quot;code_1639206969851&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; imageResponse.imageList?.let { 
        it.forEach { image -&amp;gt; 
            image.imageUrl?.let { 
                image.imageType?.let {
                    image.imageWidth?.let {
                        image.imageHeight?.let {

                        }
                    }
                }
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과연 좋은 코드라 할 수 있을진 모르겠습니다. 이렇게 scope 가 길어지는 모양새는 오히려 가독성을 하락 시킴으로 이런 경우 &lt;b&gt;if문으로 처리하는게 더 나을거라 생각이 듭니다.&lt;/b&gt; 아래처럼 심플한 경우라면 올바른 scope 함수 사용이라 생각이 듭니다.&lt;/p&gt;
&lt;pre id=&quot;code_1639207262551&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user?.let{
   println(user.name)
}&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;기본적인 코딩 문법과 kotlin scope function 활용을 아름답게 균형을 잡아야 더 아름다운 코딩이&lt;/b&gt; 되는거 같습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>코틀린 Kotlin</category>
      <category>AndroidKotiln</category>
      <category>Android코틀린</category>
      <category>Kotlin</category>
      <category>Kotlin공부</category>
      <category>Kotlin이란</category>
      <category>안드로이드개발</category>
      <category>안드로이드코틀린</category>
      <category>코틀린</category>
      <category>코틀린개발</category>
      <category>코틀린공부</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/66</guid>
      <comments>https://gift123.tistory.com/66#entry66comment</comments>
      <pubDate>Sat, 11 Dec 2021 16:42:07 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 일기 2 - 핵심은 기본기</title>
      <link>https://gift123.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 개발을 시작하고 여러 사람을 봽기도 하고 짦은 시간안에 다양한 경험을 했던 것은 놀랍다. 뒤돌아 생각해보니 사이드 협업프로젝트 4개, 외주 큰 일 2개, 개인앱 3개, 공모전 2번 수상 등등 개발을 좋아했던 마음은 다채로운 경험을 하게 도왔고 정신 차려보니 가슴을 뛰게하는 &lt;b&gt;스타트업에 와서 열정을 불태우고 있었다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;회사의 일을 함부로 언급해서는 안되지만 그나마 언급할 수 있는 것은 &lt;b&gt;기존 리액트 네이티브로 만든 앱에서 네이티브로 전환점&lt;/b&gt;을 가지는 시기에 안드로이드 개발자를 채용하였고, 회사의 재밌는 사업 아이템이 진정 스타트업의 느낌이 물씬 나서 감히 채용에 도전했었다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;고맙게도 이 회사는 나를 반겨주었다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;문제는 기존 리액트 네이티브 앱을 버리고 새롭게 네이티브로 시작하는 건 단순하게 말하면 안드로이드 스튜디오에서 새로 프로젝트를 만들어 시작하는 일인 것이다. 처음부터 만들어야하는 것부터 개발자로써 가슴 뛰게 만들었고 진정한 명작을 만들어보자 라는 열정하에 쓸줄 모르던 Hilt 와 한참 유행이라던 AAC 라이브러리를 활용한 안드로이드 권장 아키텍처에 대해 공부를 했다.&lt;br /&gt;&lt;br /&gt;하지만 회사에서 정해주는 일정이란게 있었고, 서버 개발자, 디자이너, UX 기획자와 꾸준히 소통을 하며 합을 계속 맞춰가야 하기 때문에 수정이 계속 반복될 것이 분명했다. 그래서 내 역량 대비 정해진 기간안에 좋은 소프트웨어 만들 수 있을까 라는 고민이 컸었다.&lt;br /&gt;그렇지만 &quot;남자가 칼을 뽑으면 무라도 썰어야지?&quot; 라는 마음에&lt;b&gt; 퇴근이라는 개념도 없이 모든 시간을 쏟아 부었다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 그 이후의 이야기다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 나의 큰 실수들&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 실수부터 정리하려고 한다. 이 글은 &lt;b&gt;오답노트 용으로 똑같은 실수를 반복하지 않기 위해 작성한 것이기 때문이다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;(1) 잘못 이해한 리팩토링 기법 + 중구난방 옵저버 패턴을 적용한 페이지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 제작 초반에 마틴 파울러의 리팩토링이라는 책을 보기 시작했다. 책의 절반은 마틴 파울러의 생각를 배울 수 있었고 많은 도움이 됬다. 문제는 책의 나머지 절반은 리팩토링 기법들을 소개하는데 &lt;b&gt;이 기법을 잘못 이해하고 적용한 것들이 문제였다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;당시에 MVVM를 잘못 이해하고 있어서, 다시 공부하고 있었다. 그리고 리팩토링 책에 적힌 것은 가능하면 함수를 분리하라 였다. 그래서 최대한 함수를 무.조.건 분리하는 것이 좋구나 라고 &lt;b&gt;일시적으로 잘못 이해한적이 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;나눌 필요도 없는 구문을 함수로 다 분리시켜버렸다. 거기에 잘못 이해한 MVVM방식의 불필요한 옵저버 패턴 기법도 섞여버리니 &lt;b&gt;데이터 흐름이 어디서 시작되고 오가는지 아예 이해하기 힘든 총제적 난국이 되어버렸다.&lt;/b&gt; 다행히도 페이지 하나에서만 위 같이 코드를 짠것이다.&lt;br /&gt;&lt;br /&gt;일주일 뒤에 엄청 잘못된 방식으로 작성된 코드인 것을 깨닫고 땅을 치고 후회했다. 그 이후에 해당 페이지를 간혹 리팩토링을 해서 그나마 불필요한 코드가 많이 줄었으나, 일정상 많은 리팩토링을 하지 못했고 &lt;b&gt;지금도 해당 페이지의 수정요청이 들어올까봐 아찔하다.&lt;br /&gt;&lt;/b&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;br /&gt;&lt;/b&gt;(2) 재사용 UI 컴포넌트를 많이 만들지 않은 것&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인/기획에 대해 공통화가 되지못해서 초반에 적용하지 않아서 지금 상황에 왔다....? 치사하게 남탓(?)을 할 수도 있지만 결론은 내 잘못이 크다. &lt;b&gt;훌륭한 개발자는 변경에 있어서 대비가 되어야 된다고 생각한다. &lt;/b&gt;특히 안드로이드는 코드 전반적인 설계기법도 좋지만, &lt;b&gt;뷰의 재사용 , xml 의 재사용, resource 의 재사용을 염두 하는것이 매우 중요하다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;그래서 style, theme, Custom View로 모듈화해서 디자인에서 반복적으로 사용이 될 수 있는 UI들을 정리해서 공통으로 쓰고 확장까지 가능하게 미리 토대를 잡아놨다면 매우 좋았을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;디자인이 수시로 변하는 것 같아서 만들지 않았다. 그래서 &lt;b&gt;대부분 xml이 쌩 노가다가 이루어졌다.&lt;/b&gt; (물론 Done 버튼이나 Haeder 부분은 최소한 style을 만들어 공통적으로 썻지만)&lt;br /&gt;&lt;br /&gt;그래도 이전 조금의 짠밥(?) 이 있어서&lt;b&gt; 디자인적으로 반복되는 item 이나 adapter 를 미리 캐치해서 공통적으로 만들어 놓고&lt;/b&gt; 쓰긴 하지만 나머지 xml 노가다 성이 생산 속도의 발목을 잡고 있다.&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;(3) 복잡한 여러 페이지를 모두 하나의 AAC ViewModel을 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 당연하게 안드로이드 Jetpack에서 제공하는 ViewModel를 사용했다. 어느날 여러 페이지가 연관되는 화면에서 ViewModel를 하나만 사용했는데,&amp;nbsp;&lt;br /&gt;&lt;br /&gt;그랬더니..? ViewModel이 하나의 복잡한 페이지만 담당해도 많이 뚱뚱한데 여러 페이지에 하나의 ViewModel로 통합해서 쓰려고 하니, ViewModel 내의 변수가 매우 많아지고 ViewModel의 데이터 흐름을 점점 읽기 힘들어 지며 변수도,, 공용 스탯이라서 서로의 변수를 건들게 되면서 &lt;b&gt;미래에 통제할 수 없는 지경까지 이를까봐 무서웠다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;그래서 다른 페이지는 가능하면 ShareViewModel를 사용하는 것은 적은 로직이 들어간 화면이 아니면 사용하지 않거나, Dialog에서만 사용하게 되었다. 분리는 아름다운 것이다.&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;br /&gt;(4) Compose UI 로 변환 염두한 DataBinding&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose 는 정식 출시전 부터 안드로이드 개발자 사이에서 뜨거운 한 소재였었다. 나도 Compose에 관심이 많았고, 개발중인 프로덕션 출시 후 &lt;b&gt;리팩토링 -&amp;gt; 반응형 프로그래밍 심화 도입 -&amp;gt; Compose&lt;/b&gt; 순으로 도입하리라 마음먹었다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래서 개발 초반에 xml 을 활용하지 않는 ViewBinding 으로 UI를 만드려고 했으나, 나의 기량상 작업속도가 나오는 DataBinding을 사용하기로 목표를 바꿧다. 언제나 Compose로 변환을 다짐하고 작업을 했기 때문에 DataBinding을 깊게 사용하지 않으려했고, BindingAdapter 나 xml에 가능한 많은 로직을 넣지 않고 &lt;b&gt;최대한 데이터 set정도로만 사용하려했다.&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;그렇게 한가지 갈피를 못잡고 애매하게 DataBinding을 사용하다보니 &lt;b&gt;DataBinding을 통해 쉽게 해결할 일을 다르게 해결했다.&lt;/b&gt; (DataBinding을 애용하는 사람들이 보면 비효율적인 코드들을 보고 욕할 것 같다.) 그리고 Compose 가 정식 출시 되었지만 알고 보니, &lt;b&gt;Compose 는 제대로 활용해야 좋은 것이었고 레퍼런스가 적기도하며 애매하게 썻다가 안쓴것보다 못한 녀석이었다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Compose 에 대한 사설 레퍼런스가 확실히 채워지고 사용하려고 하는데 실제 프로덕트로 사용하기 위한 레퍼런스 채워지는데 최소 1년~3년이 걸릴탠데, 차라리 그 전에 &lt;b&gt;DataBinding 를 제대로 써서 멋진 코딩하면 좋았을 것&lt;/b&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;(5) 손이 부족했고, 완벽하게 사용못한 Room 을 이용한 데이터 캐싱처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 권장 아키텍처에서 Room 을 이용해서 데이터를 캐싱하는 방식이 나온다. 개발 초반에 해당 방식을 도입을 시도했었다. 캐싱처리를 하니까 &lt;b&gt;정말 네트워크가 일시적으로 죽어도 로컬에 저장된 데이터 리스트들이 화면에 렌더링 하는것을 보며 신기했었다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만, &lt;b&gt;모든 부분을 데이터 캐싱처리 하려했던 것이 문제였다.&amp;nbsp;&lt;/b&gt;프로덕션에 도입하는 데이터는 흔히 블로그 예제로 쓰는 데이터와 달리 많은 Join 이 이루어져 오는 경우가 많다. 만약 A Model 과 B Model 를 Join 해서 C Model 형태로 왔을 때 각각 로컬용 Model의 A , B 에 담아야하는건지 애매했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그외에도 복잡한 데이터를 모두 데이터 캐싱처리를 하려다보니 상당 부분 손이 매우 많이 갔다. 그리고 Room을 다루는데 있어서 기량 부족인지 몰라도 간혹 버그가 생기기도 하는데&amp;nbsp; 또 시간이 걸린다. &lt;b&gt;첫 API 사용시 실수 때에도 내가 파싱을 잘못한건지, 리스폰스를 잘못아는건지, 리퀘스트를 잘못하는건지, 등등 체크해야하는데 로컬 부분 오류까지 생기면 매우 작업 속도를 뎌디게 헀다.&lt;/b&gt; 그래서 결국 우선 포기했다.&lt;br /&gt;&lt;br /&gt;지금 생각해보면 원격 -&amp;gt; 로컬을 캐싱처리하는 몇가지 공통 컴포넌트를 따로 만들어 한번에 처리했어야한다. 출시 후에 반드시 적용하고 싶다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 개발 후기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) 최소한의 아키텍처는 반드시 필요하다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어를 개발하다보면 정말 많은 수정이 이루어진다. 한번은 서버 Api 의 대거 수정이 이루어졌다. 여기서 큰 도움을 받았던 것은 &lt;b&gt;레포지토리 패턴&lt;/b&gt;이었다. ViewModel 은 Data에 관해 레포지토리만 알고 있는 상황이었다.&lt;br /&gt;&lt;br /&gt;Api 수정전에는 Api 하나를 가지고 여러가지 데이터를 불러오는 형식으로 했었다. Request를 넘겨서 판매용인지, 구매용인지 등등을 구별해서 사용하고 있었다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;그래서 Repository에서 해당 Api 의 여러 인자들을 넣었을 때의 데이터 Response를 다 만들어놨었다.&lt;/p&gt;
&lt;pre id=&quot;code_1639153472421&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun getSellList() = dataSource.getDataList(type = TYPE.SELL)

fun getBuyList() = dataSource.getDataList(type = TYPE.BUY)

fun getSoldList() = dataSource.getDataList(type = TYPE.SOLD)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewModel 에서 어떠한 인자를 넘길 필요 없이 Repository에서 만들어둔 상황에 맞는 DataList를 사용하도록 했다.&lt;br /&gt;하지만 갑자기 서버 Api가 바뀌었을때 Request를 넘기지 않고 해당 리스트를 가져올 수 있도록 변경이 되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1639153580079&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun getSellList() = dataSource.getSellList()

fun getBuyList() = dataSource.getBuyList()

fun getSoldList() = dataSource.getSoldList()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataLayer를 수정을 해도 Api가 바뀌어도 ViewModel 의 로직은 변함이 없었다. 그저 Repository에서 정의한 함수를 그대로 가져다 쓸 뿐이었기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;나는 아키텍처에 대해 많이 부족하다. 나중에 깊게 파보고 싶은 분야 이다. 확실한건 잘 모르더라도 최소한의 레이어 구분은 필수다. 그것이 나의 생산속도를 유지해주었다.&lt;br /&gt;&lt;br /&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;h4 data-ke-size=&quot;size20&quot;&gt;(2) onDraw 삽질&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 개발을 하다보면 기본적으로 서버에서 받는 Api 데이터를 UI 로 만드는 일을 많이하게 된다. 간혹 기본 제공 되는 UI로 만드는 것이 불편하거나 불가능할 때 특정 View 혹은 View를 상속받아 onDraw 장인질을 해야한다. 현재 개발중에 Custom View가 한 2~3개 정도 들어간 것 같은데, 열심히 정말 열심히 장인질을 했던 것 같다.&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;br /&gt;(3) 재사용 모듈 제작은 언제나 진리다.&lt;br /&gt;&lt;br /&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 당연한 얘기겠지만 후기임으로... 항상 공통된 로직을 처리할 모듈은 개발 속도를 크게 향상 시킨다. 대표적으로 페이징 처리가 필요한 경우를 대비해 페이징 처리 공동 모듈을 만들어두니 너무 편했다. Kotlin의 경우 익스텐션을 응용하는 것이 너무 편하다.&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;(4) 언어 사용 기본기가 중요하다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 느끼는 것이지만 다시 느꼈다. 내가 사용하는 언어 Kotlin 은 함수형 프로그래밍의 파워도 가지고 있는데, 이를 완벽하게 활용했는가? 라고 묻는다면 No 다. Kotlin을 함수형 답게 완벽하게 썻다는 기분이 전혀 안든다. 최소한 언어의 장점을 잘 응용 했다면, 정말 만족할만한 프로젝트가 됬을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kotlin의 장점을 최대한 이끌어 내는 개발자가 되려고 한다.&lt;/b&gt; 당분간 Kotlin에 매진할 예정이다.&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;(5) 집중력을 유지하자&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩을 하다보면 내 자신의 양심과 싸울 때가 있다. 컨벤션을 어기기도 하고, 별 생각 없이 액티비티에 모든걸 만들어버리고 싶기도 하다. 변수명도 대충 짓고 후딱 넘기고 싶다. 요즘 PR을 필수로 하는 회사가 많이 생겨서 함부로 컨벤션을 무시하기도 힘들기도 하지만 혼자 개발하는 사람이 많은 소기업의 앱 개발자는 유혹에 벗어나기 힘들다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 A~ Z 페이지가 있다고하자 알파벳 순으로 작업을 하다보면, 언젠가 A라는 페이지를 다시 봐야하는 순간이 있다. A 라는 페이지를 만약 개발 출시 후 3달 뒤에 봤을 때, 과거의 내가 작성한 코드를 바로 이해할 수 있을까?&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Z 페이지 마무리 쯤에 A 페이지를 다시 봐야하는 경우가 있었다. 해당 페이지를 보고 오랜만에 보는 페이지라 마치 내가 아닌 다른 사람이 작업한 코드처럼 느꼈다. 다행이도 이 프로젝트에서 정한 컨벤션으로 코딩이 되어있어서 금방 이해할 수 있었다.&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;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 개발 일기</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/65</guid>
      <comments>https://gift123.tistory.com/65#entry65comment</comments>
      <pubDate>Sat, 11 Dec 2021 02:05:13 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (33) Coroutine Flow on Android</title>
      <link>https://gift123.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 스터디에서 발표를 했던 Android 에서의 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Coroutine Flow&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;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 002650.png&quot; data-origin-width=&quot;207&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oGKOD/btrmYE3q8vV/HoRXdfmC2Q77TkxgyKujP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oGKOD/btrmYE3q8vV/HoRXdfmC2Q77TkxgyKujP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oGKOD/btrmYE3q8vV/HoRXdfmC2Q77TkxgyKujP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoGKOD%2FbtrmYE3q8vV%2FHoRXdfmC2Q77TkxgyKujP1%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;207&quot; height=&quot;220&quot; data-filename=&quot;화면 캡처 2021-12-06 002650.png&quot; data-origin-width=&quot;207&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판초라는 청년이 산에 살고 있었습니다.&amp;nbsp;&lt;br /&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 002728.png&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pngEL/btrmZIdbKXR/uGmXkP1hrKgSqAFpNkFjQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pngEL/btrmZIdbKXR/uGmXkP1hrKgSqAFpNkFjQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pngEL/btrmZIdbKXR/uGmXkP1hrKgSqAFpNkFjQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpngEL%2FbtrmZIdbKXR%2FuGmXkP1hrKgSqAFpNkFjQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;477&quot; data-filename=&quot;화면 캡처 2021-12-06 002728.png&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;477&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 호수를 찾아갔더니 호수의 물이 말라있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 002746.png&quot; data-origin-width=&quot;385&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czEgse/btrm0p5vKIu/3dbKOeZkiPlZc8f4tmRsx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czEgse/btrm0p5vKIu/3dbKOeZkiPlZc8f4tmRsx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czEgse/btrm0p5vKIu/3dbKOeZkiPlZc8f4tmRsx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczEgse%2Fbtrm0p5vKIu%2F3dbKOeZkiPlZc8f4tmRsx0%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;385&quot; height=&quot;272&quot; data-filename=&quot;화면 캡처 2021-12-06 002746.png&quot; data-origin-width=&quot;385&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 호수를 찾아가지 않고 물을 길러오는 동시에 물이 말랐는지 확인할 방법을 찾던 판초는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호수와 연결된 기나긴 통로를 만들면 어떨까 생각을 하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 002804.png&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Uxf5R/btrmYbNTvu2/taqo8BmyleyPOmgqGY9zhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Uxf5R/btrmYbNTvu2/taqo8BmyleyPOmgqGY9zhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Uxf5R/btrmYbNTvu2/taqo8BmyleyPOmgqGY9zhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUxf5R%2FbtrmYbNTvu2%2Ftaqo8BmyleyPOmgqGY9zhK%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;441&quot; height=&quot;332&quot; data-filename=&quot;화면 캡처 2021-12-06 002804.png&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;332&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;p data-ke-size=&quot;size16&quot;&gt;그렇게 직접 호수에 찾아갈 일은 사라졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;호수에 물이 말라 있다면 파이프에 물이 흐르지 않을 것이고 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;호수에 물이 남아있다면 파이프에 물이 흐를것 입니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 002825.png&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XQe13/btrmYEvynTL/1ubkOSd0T3DeBk2GyXCwX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XQe13/btrmYEvynTL/1ubkOSd0T3DeBk2GyXCwX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XQe13/btrmYEvynTL/1ubkOSd0T3DeBk2GyXCwX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXQe13%2FbtrmYEvynTL%2F1ubkOSd0T3DeBk2GyXCwX0%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;506&quot; height=&quot;447&quot; data-filename=&quot;화면 캡처 2021-12-06 002825.png&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;447&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;span&gt;하지만 호수에 물이 말랐을 때, 판초는 다른 호수를 찾아가서 직접 길러야했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그래서 판초는 이미 연결된 파이프 중간에 다른 호수와 연결되는 파이프를 만들었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 002914.png&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;607&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzvDWl/btrmZJb3vAD/9NKpblnVjxTncgzchCYVSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzvDWl/btrmZJb3vAD/9NKpblnVjxTncgzchCYVSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzvDWl/btrmZJb3vAD/9NKpblnVjxTncgzchCYVSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzvDWl%2FbtrmZJb3vAD%2F9NKpblnVjxTncgzchCYVSK%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;607&quot; data-filename=&quot;화면 캡처 2021-12-06 002914.png&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;607&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판초는 한 호수가 물이 나오지 않더라도 다른 호수를 통해 최종적으로 물을 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ffffff; background-color: #f89009;&quot;&gt;&lt;b&gt;판초가 굳이 물이 흐르지 않는 호수를 알 필요도 없어졌으며, 직접 호수에 갈 필요도 없습니다.&lt;/b&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: #ee2323;&quot;&gt;&lt;b&gt;Coroutine Flow&lt;/b&gt;&lt;/span&gt;의 활용을 얘기해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 3가지 분류로 나눠서 이야기를 풀어갈 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;&lt;b&gt;반응형&amp;nbsp;프로그래밍&lt;/b&gt;&amp;nbsp;(Reactive&amp;nbsp;Programming) &lt;br /&gt;- &lt;b&gt;Flow 생성, 변환, 그리고 수집&lt;/b&gt; (Creating,transforming, and observing flows) &lt;br /&gt;-&amp;nbsp;&lt;b&gt;안드로이드&amp;nbsp;UI&amp;nbsp;에서&amp;nbsp;Flow&lt;/b&gt;&amp;nbsp;(Flow&amp;nbsp;in&amp;nbsp;the&amp;nbsp;Android&amp;nbsp;UI)&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;1. 반응형 프로그래밍&lt;/b&gt;&amp;nbsp;&amp;nbsp;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) 명령형 프로그래밍&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 002941.png&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;423&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2c8PI/btrmZmgEEDE/Urpa0Jj0okrQhtocHiu4x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2c8PI/btrmZmgEEDE/Urpa0Jj0okrQhtocHiu4x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2c8PI/btrmZmgEEDE/Urpa0Jj0okrQhtocHiu4x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2c8PI%2FbtrmZmgEEDE%2FUrpa0Jj0okrQhtocHiu4x0%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;438&quot; height=&quot;423&quot; data-filename=&quot;화면 캡처 2021-12-06 002941.png&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;423&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판초가 파이프를 설치하기 전의 모습이라고 예를 들 수 있습니다. 판초의 집이 View영역이고 호수가 Data 영역이라고 할때, 판초가 호수를 다녀오는 과정은 아래와 같이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View -&amp;gt; ViewModel -&amp;gt; Repository -&amp;gt; DataSource -&amp;gt; Repository -&amp;gt; ViewModel -&amp;gt; View&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) 반응형 프로그래밍&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 003013.png&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AUTxh/btrmZHyv2OZ/RGrA7qGbii4qa5uI4xKaO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AUTxh/btrmZHyv2OZ/RGrA7qGbii4qa5uI4xKaO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AUTxh/btrmZHyv2OZ/RGrA7qGbii4qa5uI4xKaO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAUTxh%2FbtrmZHyv2OZ%2FRGrA7qGbii4qa5uI4xKaO1%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;292&quot; height=&quot;371&quot; data-filename=&quot;화면 캡처 2021-12-06 003013.png&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;371&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;View &amp;lt;- ViewModel &amp;lt;- Repository &amp;lt;- DataSource &amp;lt;- Remote Or Local Server&lt;br /&gt;&amp;nbsp;&amp;nbsp; &lt;br /&gt;반응형 프로그래밍의 경우 판초의 사례 처럼 다시 호수에 갈일이 사라지고 업스트림을 관찰하고 있을 뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형 프로그래밍의 대부분 옵저버 패턴으로 이루어져있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형 프로그래밍에 대한 정확한 이해는 아주 많은 내용들을 살펴봐야 할 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;안드로이드 개발자 사이에서 RxJava를 사용해서 예전부터 반응형 프로그래밍을 해왔었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Coroutine Flow&lt;/b&gt;&lt;/span&gt; 를 이용해서 안드로이드에서 반응형 프로그래밍을 구현하는 내용을 입문용으로 정리하려고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 003044.png&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PNwoG/btrm1jqeTzd/H9GKNsTE2RKWD0349lafP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PNwoG/btrm1jqeTzd/H9GKNsTE2RKWD0349lafP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PNwoG/btrm1jqeTzd/H9GKNsTE2RKWD0349lafP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPNwoG%2Fbtrm1jqeTzd%2FH9GKNsTE2RKWD0349lafP1%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;1031&quot; height=&quot;515&quot; data-filename=&quot;화면 캡처 2021-12-06 003044.png&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;515&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Coroutine Flow&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아래 내용은 간단한 Flow의 개념부터 생성, 변환, 수집 하는 것을 순차적으로 정리 하겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) Flow 란?&lt;/h3&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: #292929;&quot;&gt;&lt;a href=&quot;https://medium.com/hongbeomi-dev/kotlin-coroutine-flow-ac07cfdca42d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://medium.com/hongbeomi-dev/kotlin-coroutine-flow-ac07cfdca42d&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1638721452918&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;Kotlin coroutine flow&quot; data-og-description=&quot;코틀린 코루틴 플로우 공식 블로그 [번역]&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/hongbeomi-dev/kotlin-coroutine-flow-ac07cfdca42d&quot; data-og-url=&quot;https://medium.com/hongbeomi-dev/kotlin-coroutine-flow-ac07cfdca42d&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vWpUs/hyMAu716bY/Qs6aaSW8mE8d0vkFvFBEZk/img.jpg?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800,https://scrap.kakaocdn.net/dn/blG125/hyMAxcBPON/ks9fdjmaHDlKUW9rsdHyDk/img.jpg?width=4000&amp;amp;height=2666&amp;amp;face=0_0_4000_2666&quot;&gt;&lt;a href=&quot;https://medium.com/hongbeomi-dev/kotlin-coroutine-flow-ac07cfdca42d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/hongbeomi-dev/kotlin-coroutine-flow-ac07cfdca42d&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vWpUs/hyMAu716bY/Qs6aaSW8mE8d0vkFvFBEZk/img.jpg?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800,https://scrap.kakaocdn.net/dn/blG125/hyMAxcBPON/ks9fdjmaHDlKUW9rsdHyDk/img.jpg?width=4000&amp;amp;height=2666&amp;amp;face=0_0_4000_2666');&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;Kotlin coroutine flow&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;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(HongBeomi 님이 flow 원문을 한글로 번역한 글)&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: #ffffff; background-color: #f89009;&quot;&gt;&lt;b&gt;코틀린의 플로우는 순차적으로 값을 내보내고 정상적으로 또는 예외로 완료되는 비동기적인 데이터 스트림입니다.&lt;/b&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;이 글에서 Flow는 크게 2가지 개념을 설명 할겁니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;- Producer&lt;/b&gt; &lt;br /&gt;Producer 는 데이터 생산해내어 방출을 하는 구간입니다.&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;br /&gt;&lt;b&gt;- Collect &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collect 는 Producer 에서 생성한 데이터를 받는 구간입니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;(2) Flow Bulider (Flow 생성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판초의 사례로 봤을 때 &lt;b&gt;Flow 생성은 많은 호수의 물을 적정량으로 파이프에 방출하는 댐으로 비유할 수 있겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dam-6791010_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqI7ae/btrm9ENQ5YG/cHTnxVkwXiPF41cycuZLw1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqI7ae/btrm9ENQ5YG/cHTnxVkwXiPF41cycuZLw1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqI7ae/btrm9ENQ5YG/cHTnxVkwXiPF41cycuZLw1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqI7ae%2Fbtrm9ENQ5YG%2FcHTnxVkwXiPF41cycuZLw1%2Fimg.jpg&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;666&quot; height=&quot;444&quot; data-filename=&quot;dam-6791010_1280.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 판초가 5초마다 호수에서 파이프에 물을 방출한 상황일때 DataSource 클래스에서 아래와 같이 작성이 가능합니다 &lt;/p&gt;
&lt;pre id=&quot;code_1638722258887&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class WaterDataSource(
 private val api:LakeApi
 private val refresh:Long = 5000
){
  val waterList = Flow&amp;lt;List&amp;lt;Water&amp;gt;&amp;gt; = flow{
  whlie(true){
      emit(api)
      delay(refresh)
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flow&amp;lt;T&amp;gt; 로&amp;nbsp; flow를 생성 할 수 있습니다.&lt;br /&gt;emit() 함수가 데이터를 흘러보내는 기능을합니다.&amp;nbsp;&lt;br /&gt;flow{....} 안에서 &lt;span style=&quot;color: #ffffff; background-color: #f89009;&quot;&gt;&lt;b&gt;데이터를 emit 하는 구간을 Producer Block 이라고 부릅니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;판초는&amp;nbsp;위&amp;nbsp;코드를&amp;nbsp;통해&amp;nbsp;5초마다&amp;nbsp;물을&amp;nbsp;받게&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) Flow 변환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flow 를 생성해서 emit 을 했다면 바로 UI 계층에 넘기기 전에 데이터 변환이 가능합니다.&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;판초가 만약 물을 마시기위한 용도로 받는다면 호수의 더러운물을 걸러내는 기능이 필요할 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1638722677866&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class WaterRepository(
 private val dataSource:WaterDataSource
){
 val resultList = dataSource.waterList.map{
 it.마실수있는물로()
 }.filter{
  it.물상태등급 == A급 
 }.catch{ e -&amp;gt;
     if(e is illegalargumentexception) throw e 
     else emit(emptyList)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;위 코드처럼 다른 모델로 변환하거나 데이터를 필터링 할 수 있고 예외 값을 던지거나 예외처리가 가능합니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;위 코드에서 catch 이후의 코드는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&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;h3 data-ke-size=&quot;size23&quot;&gt;(4) Flow 수집&lt;/h3&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;판초는 물을 언제든지 받을 수 있게 되었습니다. &lt;/p&gt;
&lt;pre id=&quot;code_1638723360949&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; viewModel.waterList.collect{
      //판초가 물을 받음
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;collect 함수를 통해서 Flow 에서 흐르는 데이터를 수집하게 됩니다. 기본적인 Flow의 collect 는 생성한 데이터 스트림의 데이터를 수집 하고 다른 데이터 스트림과 공유하지 않습니다.&amp;nbsp; 위 같은 경우 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Cold Flow&lt;/b&gt;&lt;/span&gt; 라고 부릅니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #ffffff; background-color: #f89009;&quot;&gt;&lt;b&gt;이제 판초는 5초마다(생성) 깨끗한 물을(변환) 계속 받을 수 있게 되었습니다.(수집)&lt;/b&gt; &lt;/span&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Android 에서의 Flow 수집&lt;/b&gt;&lt;/h2&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;Android와 상황을 대입해보면 판초가 이를 닦거나 잠을 잘때의 상황은 Background에 있는 상황일 겁니다. Background에서 collect가 유지가 된다면 collect 에서 어떠한 데이터를 받을시 Dialog를 띄운다고 하는 구문이 있다면 앱 크래쉬가 생길겁니다.&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;그래서 LifeCycle에 따라 효율적으로 수집을 유지하거나 닫을지 결정하는 Api를 Android 에서 제공합니다.&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;이 Api는&amp;nbsp;&lt;span style=&quot;background-color: #f8f9fa; color: #37474f;&quot;&gt;androidx.lifecycle:lifecycle-*:2.4.0 버전부터 안정화 되어 지원됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*아래 설명 보기전에 참고로, Flow 는 coroutine Builder 혹은 suspend function 안에서만 사용할 수 있기 있음&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;(1) repeatOnLifeCycle&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;coroutine Scope 내에서 repeatOnLifeCycle 함수를 사용할 수 있습니다. 인자로 LifeCycle 을 받아서 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;repeatOnLifeCycle은 여러 launch를 만들어 셋팅할 수 있도록 하는 용도입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1638724191035&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lifecycleScope.launch{
  repeatOnLifeCycle(LifeCycle.State.STARED){
      viewModel.dataList.collect{
        //판초가 마실 물을 받음 
      }
      
      launch{
       // 판초가 샤워할 물을 받음
      }
      launch{
      // 판초가 흙탕물을 받음
      }      
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;(2) flowWithLifeCycle 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 collect 를 하나만 사용할때 사용 합니다. repeatOnLifeCycle의 경우 다른 launch를 내부에서 만들수 있었지만 flowWithLifeCycle은 하나의 collect를 보일러플레이트 없이 작성할 수 있도록 돕습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1638724589934&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lifecycleScope.launch{
    viewModel.dataList.flowWithLifeCycle(lifecycle,State.STARED).collect{
        //판초가 물을 받음 
    }
}&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;(3) lifecycleScope.launch 혹은 lifecycleScope.when..만 사용하지 않는 이유?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 lifecycleScope 를 안다면 위의 두 api를 사용하지 않고 lifecyclerScope만 써도 해결되지 않을까 라는 의구심이 생길겁니다.&lt;br /&gt;하지만 항상 collect 가 먼저 실행 되고 UI 업데이트가 되기 때문에 안전하지 못한 코드가 된다고 합니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 003142.png&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lwRXC/btrm9D2uY4o/vdFOfTSTKdkgaQtf5jstY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lwRXC/btrm9D2uY4o/vdFOfTSTKdkgaQtf5jstY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lwRXC/btrm9D2uY4o/vdFOfTSTKdkgaQtf5jstY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlwRXC%2Fbtrm9D2uY4o%2FvdFOfTSTKdkgaQtf5jstY0%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;596&quot; height=&quot;298&quot; data-filename=&quot;화면 캡처 2021-12-06 003142.png&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- lifecycleScope.launch 만 사용했을 경우, 실제로 백그라운드에서 수집이 됩니다.&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- lifecycleScope.launchWhenStared 를 사용했을 경우 백그라운드에서 수집은 하지 않지만 Flow 생산자를 계속 활성화시켜서 화면에서 사용한적 없는 데이터로 메모리를 채우게 된다고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 003210.png&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx2Rum/btrm7MrQF8K/5KvHEqFoq1aJTGnJ5aj9oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx2Rum/btrm7MrQF8K/5KvHEqFoq1aJTGnJ5aj9oK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx2Rum/btrm7MrQF8K/5KvHEqFoq1aJTGnJ5aj9oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx2Rum%2Fbtrm7MrQF8K%2F5KvHEqFoq1aJTGnJ5aj9oK%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;542&quot; height=&quot;425&quot; data-filename=&quot;화면 캡처 2021-12-06 003210.png&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;425&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;h3 data-ke-size=&quot;size23&quot;&gt;(4) 구성 변경 시&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 구성변경에 대해서도 대응이 필요합니다. 대표적으로 화면이 세로였다가 가로로 바뀌는 순간이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 003303.png&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doSECv/btrmYFnHDYD/TCiA3SpJzCZKNs7pMNPsy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doSECv/btrmYFnHDYD/TCiA3SpJzCZKNs7pMNPsy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doSECv/btrmYFnHDYD/TCiA3SpJzCZKNs7pMNPsy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoSECv%2FbtrmYFnHDYD%2FTCiA3SpJzCZKNs7pMNPsy0%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;758&quot; height=&quot;392&quot; data-filename=&quot;화면 캡처 2021-12-06 003303.png&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성 변경이 생길 시 액티비티는 다시 시작하지만, 뷰모델은 계속 유지 됩니다. 여기서 문제는 기본적인 Flow의 collect는 cold Flow 입니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;coldFlow의 경우 collect가 호출되면 새로운 데이터 스트림을 연결합니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 화면 변경시 Activity의 onCreate가 다시 호출되면서 collect 를 호출하는 순간 데이터스트림을 새로 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1638725326675&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 액티비티 내의 코드..
lifecycleScope.launch{
// 화면 변경시 아래 collect 가 다시 실행됨 
    viewModel.dataList.flowWithLifeCycle(lifecycle,State.STARED).collect{
       ...
    }
}&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_1638725429577&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  //ViewModel의 코드.. 액티비티의 onCreate 때문에 아래 repository.dataList가 다시 셋팅 됨
  val result = Flow&amp;lt;Result&amp;lt;UiState&amp;gt;&amp;gt; = flow{
     emit(repository.dataList)
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위와 같은 상황을 막아야하는 상황이면 Flow 대신 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;StateFlow&lt;/span&gt; &lt;/b&gt;를 사용합니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;-&amp;nbsp;StateFlow&lt;/b&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;StateFlow 는 Hot Flow 입니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp; HotFlow의 경우 Collect 가 없더라도 데이터를 보관합니다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;판초의&amp;nbsp;사례로&amp;nbsp;예를들면&amp;nbsp;StateFlow&amp;nbsp;는&amp;nbsp;물탱크&amp;nbsp;개념입니다.&amp;nbsp;모았다가&amp;nbsp;꺼내쓸&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;됩니다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1638725615842&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val result:StateFlow&amp;lt;Result&amp;lt;UiState&amp;gt;&amp;gt; = repository.dataList.stateIn(
...
)&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;stateIn()은 StateFlow 로 변환하는 함수 입니다. StateFlow로 구성 변경을 대비할 수 있습니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&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;4. 그외..&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;(1) 기존 LiveData 활용&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow 의 collect로 수집하고 않고 기존 LiveData를 응용하고 싶다면 &lt;b&gt;asLiveData() 함수를 통해 flow를 liveData로 변환해서 사용할 수 있습니다.&lt;/b&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;
&lt;pre id=&quot;code_1638725867363&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//ViewModel 에서..
val result:LiveData&amp;lt;Data&amp;gt; = reposistory.dataList.asLiveData()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;br /&gt;(2) Android 에서의 Flow Bulider 가 지원되는 Api&amp;nbsp; &amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Android&amp;nbsp;에서&amp;nbsp;Flow&amp;nbsp;를&amp;nbsp;지원하는&amp;nbsp;Api&amp;nbsp;가&amp;nbsp;있습니다.&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;대표적으로&amp;nbsp;Room,&amp;nbsp;WorkManager,&amp;nbsp;DataStore&amp;nbsp;가&amp;nbsp;있습니다.&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2021-12-06 003108.png&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAlyQ2/btrmYE3taGF/lTiTzAQ6y5IcAVMDAgxxa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAlyQ2/btrmYE3taGF/lTiTzAQ6y5IcAVMDAgxxa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAlyQ2/btrmYE3taGF/lTiTzAQ6y5IcAVMDAgxxa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAlyQ2%2FbtrmYE3taGF%2FlTiTzAQ6y5IcAVMDAgxxa0%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;792&quot; height=&quot;443&quot; data-filename=&quot;화면 캡처 2021-12-06 003108.png&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Room 에서 기본 지원 되는 Flow 는 해당 테이블의 데이터가 변경이 생기면 바로 emit 을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1638726032059&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Room에서의 Flow
@Dao
interface TestDao{
   @Query(&quot;SELECT...&quot;)
   fun dataList():Flow&amp;lt;List&amp;lt;TestData&amp;gt;&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;이상 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Coroutine Flow&lt;/b&gt;&lt;/span&gt;에 대해 알아봤습니다. Flow를 알면 알수록 더 깊게 공부를 해야할 필요성이 느껴집니다. 앞으로 android Api 보다 Kotlin Api를 더 활용하는 모습으로 발전해가는 듯 합니다. 이상입니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>Android개발</category>
      <category>Coroutine</category>
      <category>coroutineFlow</category>
      <category>flow</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>코루틴</category>
      <category>코루틴플로우</category>
      <category>플로우</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/64</guid>
      <comments>https://gift123.tistory.com/64#entry64comment</comments>
      <pubDate>Mon, 6 Dec 2021 02:43:44 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 시 실수 모음</title>
      <link>https://gift123.tistory.com/63</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;toys-3644073_1280.png&quot; width=&quot;472&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beO7oo/btrftnNEANt/Gaeb0YlXLKbweZK4BU9Jz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beO7oo/btrftnNEANt/Gaeb0YlXLKbweZK4BU9Jz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beO7oo/btrftnNEANt/Gaeb0YlXLKbweZK4BU9Jz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeO7oo%2FbtrftnNEANt%2FGaeb0YlXLKbweZK4BU9Jz0%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;toys-3644073_1280.png&quot; width=&quot;472&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 언제나 열심히 앱 개발 작업중 입니다. 정신 차려보니 벌써, 추석 명절을 맞았고 앱개발 때문에 시간이 항상 바빳지만 오랜만에 여유가 생겨서 블로그를 작성합니다.&lt;span style=&quot;color: #ffffff; background-color: #f89009;&quot;&gt;&lt;b&gt; 명절 이벤트로 안드로이드 개발하면서 한번쯤 경험 할 수 있는 실수들을 모아봤습니다.&amp;nbsp;&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. Manifest.xml&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;923&quot; data-origin-height=&quot;706&quot; data-filename=&quot;화면 캡처 2021-09-20 005214.png&quot; width=&quot;488&quot; height=&quot;373&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLKj1w/btrfzk3GhzI/oGbGYKRfuW66FGKv14ILSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLKj1w/btrfzk3GhzI/oGbGYKRfuW66FGKv14ILSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLKj1w/btrfzk3GhzI/oGbGYKRfuW66FGKv14ILSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLKj1w%2Fbtrfzk3GhzI%2FoGbGYKRfuW66FGKv14ILSk%2Fimg.png&quot; data-origin-width=&quot;923&quot; data-origin-height=&quot;706&quot; data-filename=&quot;화면 캡처 2021-09-20 005214.png&quot; width=&quot;488&quot; height=&quot;373&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 필요한 permission을 추가 안함 &lt;br /&gt;- Activity의 export를 추가안함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;useClearTextTraffic&amp;nbsp;설정을&amp;nbsp;하지&amp;nbsp;않아서&amp;nbsp;삽질&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Service나 receiver 클래스를 만들어두고 manifest 에 설정 안함&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;1. RecyclerView 사용시&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;227&quot; data-filename=&quot;recycling_of_views-300x227.png&quot; width=&quot;353&quot; height=&quot;267&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O1XkS/btrfpgoDfK6/xhAcKInEnRMqyFMi9M7dk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O1XkS/btrfpgoDfK6/xhAcKInEnRMqyFMi9M7dk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O1XkS/btrfpgoDfK6/xhAcKInEnRMqyFMi9M7dk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO1XkS%2FbtrfpgoDfK6%2FxhAcKInEnRMqyFMi9M7dk0%2Fimg.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;227&quot; data-filename=&quot;recycling_of_views-300x227.png&quot; width=&quot;353&quot; height=&quot;267&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어댑터, xml , 로직 다 멋지게 만들었으나 layoutManager를 쓰지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 아이템 height를 match_parent로 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- nested&amp;nbsp;scrollview 안에서 recyclerView의 height 를 wrap_parent로 지정하면 재활용을 하지 않고 항상 만들어냄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- onBindViewHolder() 에서 데이터 기준으로 UI 갱신을 하는것이 아니라 아이템 뷰 자체의 속성을 바꿧을 때 스크롤 시 recyclerView 재생성으로 인해 아이템 포지션에 맞지 않는 뷰 속성을 그대로 가져옴 (재생성 이슈)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;b&gt;2. 데이터바인딩 &amp;amp; 뷰바인딩 사용시&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;456&quot; data-filename=&quot;K5Spa.jpg&quot; width=&quot;521&quot; height=&quot;270&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9mTA3/btrfsdLf4Mf/0aKqjuDVWGn5rjC16Tdf2K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9mTA3/btrfsdLf4Mf/0aKqjuDVWGn5rjC16Tdf2K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9mTA3/btrfsdLf4Mf/0aKqjuDVWGn5rjC16Tdf2K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9mTA3%2FbtrfsdLf4Mf%2F0aKqjuDVWGn5rjC16Tdf2K%2Fimg.jpg&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;456&quot; data-filename=&quot;K5Spa.jpg&quot; width=&quot;521&quot; height=&quot;270&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;데바&amp;nbsp;뷰바&amp;nbsp;사용시&amp;nbsp;화면과&amp;nbsp;맞지않는&amp;nbsp;바인딩된&amp;nbsp;클래스를&amp;nbsp;사용할수&amp;nbsp;있음&lt;br /&gt;- 데바 사용시 xml 타입 불일치&amp;nbsp;&lt;br /&gt;- 데바 사용시 xml 변수의 pojo클래스의 위치가 바뀌어도 자동으로 바뀌지 않음&lt;br /&gt;-&amp;nbsp;데바&amp;nbsp;사용시&amp;nbsp;3항&amp;nbsp;연산자로&amp;nbsp;비지빌리티&amp;nbsp;사용시&amp;nbsp;의도하지&amp;nbsp;않는&amp;nbsp;뷰에&amp;nbsp;사용했거나&amp;nbsp;결과가&amp;nbsp;반대로&amp;nbsp;나옴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데바 사용시 excutePandingBindings() 함수를 사용 안해서 UI갱신이 느림&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;3. Room 사용시&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;893&quot; data-filename=&quot;mYsKP.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXKc2k/btrfuQhIs1O/2tqYNR07PAxzitifkjqEX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXKc2k/btrfuQhIs1O/2tqYNR07PAxzitifkjqEX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXKc2k/btrfuQhIs1O/2tqYNR07PAxzitifkjqEX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXKc2k%2FbtrfuQhIs1O%2F2tqYNR07PAxzitifkjqEX0%2Fimg.png&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;893&quot; data-filename=&quot;mYsKP.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DAO 쿼리 작성시 :변수명 에서 :를 빼먹음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Room의 디비구조 변경시 데이터베이스 버전업을 안함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;로컬&amp;nbsp;데이터베이스&amp;nbsp;(sqlite,room..)&amp;nbsp;정보를&amp;nbsp;수정했을때&amp;nbsp;기존앱&amp;nbsp;데이터&amp;nbsp;유지&amp;nbsp;되는거&amp;nbsp;몰라서&amp;nbsp;삽질&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 코루틴으로 사용시 룸의 DAO 함수 관련 처리시 백그라운드 쓰레드를 안써서 에러&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;4. Hilt 사용시&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;600&quot; data-filename=&quot;다운로드.png&quot; width=&quot;519&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOS2t2/btrfw4tfwIM/sJwZfo9KFTkSii4U8yDXBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOS2t2/btrfw4tfwIM/sJwZfo9KFTkSii4U8yDXBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOS2t2/btrfw4tfwIM/sJwZfo9KFTkSii4U8yDXBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOS2t2%2Fbtrfw4tfwIM%2FsJwZfo9KFTkSii4U8yDXBK%2Fimg.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;600&quot; data-filename=&quot;다운로드.png&quot; width=&quot;519&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- AndroidEntryPoint, HiltViewModel 주석을 항상 까먹음&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;5&lt;/b&gt;. &lt;b&gt;Dialog 사용 시&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;794&quot; data-filename=&quot;LINIr.gif&quot; width=&quot;334&quot; height=&quot;561&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqRyGs/btrfxNLB13b/FteKwYxO3Xg18Uczf17o11/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqRyGs/btrfxNLB13b/FteKwYxO3Xg18Uczf17o11/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqRyGs/btrfxNLB13b/FteKwYxO3Xg18Uczf17o11/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cqRyGs/btrfxNLB13b/FteKwYxO3Xg18Uczf17o11/img.gif&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;794&quot; data-filename=&quot;LINIr.gif&quot; width=&quot;334&quot; height=&quot;561&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- show() dismiss() 셋팅을 자주 까먹음&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;6. Gradle&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;306&quot; data-filename=&quot;Gradle_logo.png&quot; width=&quot;611&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbMwYy/btrfw5Z0IoC/CNblNBO7IV4KLI8HjB9zaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbMwYy/btrfw5Z0IoC/CNblNBO7IV4KLI8HjB9zaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbMwYy/btrfw5Z0IoC/CNblNBO7IV4KLI8HjB9zaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbMwYy%2Fbtrfw5Z0IoC%2FCNblNBO7IV4KLI8HjB9zaK%2Fimg.png&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;306&quot; data-filename=&quot;Gradle_logo.png&quot; width=&quot;611&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- dependency 적고 싱크 안하기&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;7. 데이터 공유&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1520&quot; data-origin-height=&quot;760&quot; data-filename=&quot;1_zkDZ_-Ekh63Nncq7AEeC6Q.png&quot; width=&quot;422&quot; height=&quot;211&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nbMiK/btrfpfDjc0R/VKqKkBJ4m0YoNVcJQAYbD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nbMiK/btrfpfDjc0R/VKqKkBJ4m0YoNVcJQAYbD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nbMiK/btrfpfDjc0R/VKqKkBJ4m0YoNVcJQAYbD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnbMiK%2FbtrfpfDjc0R%2FVKqKkBJ4m0YoNVcJQAYbD1%2Fimg.png&quot; data-origin-width=&quot;1520&quot; data-origin-height=&quot;760&quot; data-filename=&quot;1_zkDZ_-Ekh63Nncq7AEeC6Q.png&quot; width=&quot;422&quot; height=&quot;211&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&lt;/b&gt; 인텐트로 데이터 공유시 pojo data인 경우 해당 모델에 직렬화 관련 implements 를 안함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터 공유시 key값을 상수 식별자로 해놓지 않아서 key 오타로 인한 데이터 공유 실패&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;8. 배포 관련&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;373&quot; data-filename=&quot;aab-file.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwB9aZ/btrfpga90M9/dWOeroZg7JWD0yf3ulzyf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwB9aZ/btrfpga90M9/dWOeroZg7JWD0yf3ulzyf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwB9aZ/btrfpga90M9/dWOeroZg7JWD0yf3ulzyf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwB9aZ%2Fbtrfpga90M9%2FdWOeroZg7JWD0yf3ulzyf0%2Fimg.png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;373&quot; data-filename=&quot;aab-file.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내부 DB 에 R.drawable.xxx 저장 0 버전 배포 이후에 업데이트 된 1버전에서 저장된 R.drawable.xxx 꺼내쓰면 다른 리소스가 보임&lt;b&gt;&lt;br /&gt;&lt;/b&gt;(리소스는 내부적으로 R파일로 관리되는데, 배포될 때마다 변경 됨)&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;9. gson&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;154&quot; data-filename=&quot;다운로드 (1).png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lk2H8/btrfpfQSAvI/GbIulRnQdLLaFKvZVeP440/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lk2H8/btrfpfQSAvI/GbIulRnQdLLaFKvZVeP440/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lk2H8/btrfpfQSAvI/GbIulRnQdLLaFKvZVeP440/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flk2H8%2FbtrfpfQSAvI%2FGbIulRnQdLLaFKvZVeP440%2Fimg.png&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;154&quot; data-filename=&quot;다운로드 (1).png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- gson 사용시 serializationName 을 적지 않아서 프로가드 적용시 name 이 지워져 에러&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;10. Fragment&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;523&quot; data-filename=&quot;4crU7.png&quot; width=&quot;515&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYwpTb/btrfpBTEuxP/zxByLrPKYAvkORqeiRQ7L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYwpTb/btrfpBTEuxP/zxByLrPKYAvkORqeiRQ7L1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYwpTb/btrfpBTEuxP/zxByLrPKYAvkORqeiRQ7L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYwpTb%2FbtrfpBTEuxP%2FzxByLrPKYAvkORqeiRQ7L1%2Fimg.png&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;523&quot; data-filename=&quot;4crU7.png&quot; width=&quot;515&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Fragment&amp;nbsp;를&amp;nbsp;fragmentManager&amp;nbsp;로&amp;nbsp;처리하지&amp;nbsp;않고,&amp;nbsp;프로퍼티&amp;nbsp;혹은&amp;nbsp;멤버변수로&amp;nbsp;두고&amp;nbsp;사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Fragment를&amp;nbsp;tag로&amp;nbsp;강제호출해서&amp;nbsp;사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프래그먼트에서 뷰바인딩할때 return binding.root 까먹음&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;11. ViewModel&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;236&quot; data-filename=&quot;7f26738df2266dd6.png&quot; width=&quot;491&quot; height=&quot;117&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eNP3bK/btrftmHX9Hu/zKbzvNwYklSyY6n8Iy2qZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eNP3bK/btrftmHX9Hu/zKbzvNwYklSyY6n8Iy2qZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eNP3bK/btrftmHX9Hu/zKbzvNwYklSyY6n8Iy2qZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeNP3bK%2FbtrftmHX9Hu%2FzKbzvNwYklSyY6n8Iy2qZ1%2Fimg.png&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;236&quot; data-filename=&quot;7f26738df2266dd6.png&quot; width=&quot;491&quot; height=&quot;117&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- viewModel&amp;nbsp;에서&amp;nbsp;context&amp;nbsp;사용&amp;nbsp;혹은&amp;nbsp;리소스&amp;nbsp;아이디를&amp;nbsp;직접적으로&amp;nbsp;사용&amp;nbsp;R.string.xxx&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;12. Glide&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;240&quot; data-filename=&quot;1_f82fDTCyMUyRTePMC3xuCQ.png&quot; width=&quot;450&quot; height=&quot;135&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S9Plc/btrfuPQGpPk/ajKGeyc8CuZ9xLrKzxFjp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S9Plc/btrfuPQGpPk/ajKGeyc8CuZ9xLrKzxFjp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S9Plc/btrfuPQGpPk/ajKGeyc8CuZ9xLrKzxFjp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS9Plc%2FbtrfuPQGpPk%2FajKGeyc8CuZ9xLrKzxFjp0%2Fimg.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;240&quot; data-filename=&quot;1_f82fDTCyMUyRTePMC3xuCQ.png&quot; width=&quot;450&quot; height=&quot;135&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- glide 캐싱 되는줄 모르고 이미지 왜 안 바뀌는지 답답해함&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;13. 기타&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot; data-filename=&quot;Arctic_Fox_Splash_2x (1).png&quot; width=&quot;622&quot; height=&quot;389&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4kWN9/btrfp2wH3FH/KVYTXPANT0XNNuppi5965k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4kWN9/btrfp2wH3FH/KVYTXPANT0XNNuppi5965k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4kWN9/btrfp2wH3FH/KVYTXPANT0XNNuppi5965k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4kWN9%2Fbtrfp2wH3FH%2FKVYTXPANT0XNNuppi5965k%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot; data-filename=&quot;Arctic_Fox_Splash_2x (1).png&quot; width=&quot;622&quot; height=&quot;389&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 수많은 listener 들 register 하고 unRegister 안하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카카오 로그인 할 때 applicationContext 사용&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;- grid형식의 리싸이클러뷰 span count를 xml 와 kotlin code 양쪽에 설정을 해놓고 kotlin code 설정으로 만들고 싶었으나 xml span count으로만 설정이 되서 원인 파악하는데 삽질&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;- shared viewmodel을 fragment에서 각각 선언해놨었는데 shared가 되지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Koin 을 쓰고있어서 by sharedViewModel&amp;lt;SharedViewModel&amp;gt;()로 해야되는데 by viewModel&amp;lt;SharedViewModel&amp;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;- 안드로이드 자동생성 파일 git에 업로드&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 뒤로가기 버튼 클릭리스너 구문을 자주 빼먹음&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- res 에서 대문자 및 허용되지 않는 특수문자 사용&amp;nbsp;&lt;br /&gt;- 한글 경로로 인한 이슈&lt;br /&gt;- Amd cpu 이슈로 인한 hiper-v 설치 실패&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; xml 에서 잘못된 문법 사용 예시:&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;TextView&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;EditText&amp;gt; &lt;br /&gt;&amp;lt;/TextVIew&amp;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;- startActivity 할 때 applicationContext 를 사용하는 경우 (백스택 문제 생길 여지있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-분명&amp;nbsp;MVVM으로&amp;nbsp;클래스는&amp;nbsp;나누긴&amp;nbsp;했으나&amp;nbsp;로직&amp;nbsp;분포된&amp;nbsp;건&amp;nbsp;MVVM이&amp;nbsp;아닌&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 alignLeft&quot; width=&quot;563&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;906&quot; data-filename=&quot;thanksgiving-4430570_1280.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4vPmz/btrfw32b2O2/CA3VZMnS7DNR4xNgUaf29K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4vPmz/btrfw32b2O2/CA3VZMnS7DNR4xNgUaf29K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4vPmz/btrfw32b2O2/CA3VZMnS7DNR4xNgUaf29K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4vPmz%2Fbtrfw32b2O2%2FCA3VZMnS7DNR4xNgUaf29K%2Fimg.png&quot; width=&quot;563&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;906&quot; data-filename=&quot;thanksgiving-4430570_1280.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의견 주신 홍드로이드, 현썽, 이현우님, 레몬, 휴, 코린이, 신입입니다, 예슬님, 도마, Moon SungJun, betty, 케니스 여러분들 모두 감사합니다. &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;즐거운 한가위 잘 보내시길 바랍니다!&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>AndroidDevelop</category>
      <category>AndroidDeveloper</category>
      <category>Android개발</category>
      <category>Android개발자</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>안드로이드개발공감</category>
      <category>안드로이드개발자</category>
      <category>안드로이드실수</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/63</guid>
      <comments>https://gift123.tistory.com/63#entry63comment</comments>
      <pubDate>Mon, 20 Sep 2021 01:12:10 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (32) LiveData와 MutableLiveData</title>
      <link>https://gift123.tistory.com/62</link>
      <description>&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; width=&quot;603&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/WNpmX/btrcvxSsD5B/gDPJyNP7fLktsKesqg3IL1/img.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WNpmX/btrcvxSsD5B/gDPJyNP7fLktsKesqg3IL1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WNpmX/btrcvxSsD5B/gDPJyNP7fLktsKesqg3IL1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WNpmX/btrcvxSsD5B/gDPJyNP7fLktsKesqg3IL1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWNpmX%2FbtrcvxSsD5B%2FgDPJyNP7fLktsKesqg3IL1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; width=&quot;603&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/WNpmX/btrcvxSsD5B/gDPJyNP7fLktsKesqg3IL1/img.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt; LiveData는 이미 내부적으로 setValue와 postValue가 존재하지만 이를 외부로 공개하지 않고 상속을 받은 서브클래스를 통해 외부로 공개합니다.&lt;br&gt;그렇기 때문에 이미 안드로이드에서 지원하는 LiveData는 MutableLive라는 서브클래스를 만들어두고 MutableLive에서 set을 허용하도록 개발자에게 환경을 제공합니다.&lt;br&gt; &lt;br&gt;그렇게&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; View는 LiveData 에서 읽기만 할 수있고, ViewModel 에서 MutableLiveData를 통해 변경할 수 있는 권한이 생깁니다.&lt;/b&gt;&lt;/span&gt;&lt;br&gt; &lt;br&gt;불변과 가변의 차이를 가진 LiveData와 MutableLiveData에 대해 각각 정리해봤습니다.&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1.MutableLiveData&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;package androidx.lifecycle; /** * {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method. * * @param &amp;lt;T&amp;gt; The type of data hold by this instance */ @SuppressWarnings(&quot;WeakerAccess&quot;) public class MutableLiveData&amp;lt;T&amp;gt; extends LiveData&amp;lt;T&amp;gt; { /** * Creates a MutableLiveData initialized with the given {@code value}. * * @param value initial value */ public MutableLiveData(T value) { super(value); } /** * Creates a MutableLiveData with no value assigned to it. */ public MutableLiveData() { super(); } @Override public void postValue(T value) { super.postValue(value); } @Override public void setValue(T value) { super.setValue(value); } }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;위는 MutableLiveData의 내부 코드 모습입니다. 주석을 제외하면 라인수가 17~20줄 밖에 안됩니다. 정말 17~20줄 빼고는 아무것도 존재하지않고 그저 LiveData를 상속받아서 LiveData 내부에서만 사용할 수 있는 postValue와 setValue를 &lt;br&gt;override 함으로써 외부에서 사용할 수 있게끔만 하고 그외 LiveData의 생성자를 사용한다는것 빼고 아무것도 없습니다.&lt;br&gt; &lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;MutableLiveData는 그저 불변인 LiveData를 상속받아서 postValue와 setValue를 public함수에 호출함으로써 가변성을 부여하기 위함&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인 것을 추측할 수 있습니다.&lt;/span&gt;&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. LiveData &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;/** * LiveData is a data holder class that can be observed within a given lifecycle. * ... */ public abstract class LiveData&amp;lt;T&amp;gt; { ... // 대략 400~500줄 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;LiveData의 내부를 살피면 위와 같은 주석을 통해서 자세한 내용을 살펴볼 수 있기도 합니다. &lt;br&gt;LiveData는 일반 observe과 달리 Android 개발 전용으로 효율이 좋습니다. 왜냐하면 LiveData를 사용할 때 액티비티, 프레그먼트, 서비스 등등의 Lifecycle을 체크해서 해당 Lifecycle에 따라 livedata 객체가 유지가 되거나 해제가 됩니다. &lt;br&gt; &lt;br&gt;그래서 개발자가 Lifecycle에 따라 직접 메모리를 해제하지 않아도 알아서 liveData에서 Lifecycle 상황에 맞게 해제하기 때문에 메모리누수를 사전에 방지해줍니다. &lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; @MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull Observer&amp;lt;? super T&amp;gt; observer) { assertMainThread(&quot;observe&quot;); if (owner.getLifecycle().getCurrentState() == DESTROYED) { // ignore return; } LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing != null &amp;amp;&amp;amp; !existing.isAttachedTo(owner)) { throw new IllegalArgumentException(&quot;Cannot add the same observer&quot; + &quot; with different lifecycles&quot;); } if (existing != null) { return; } owner.getLifecycle().addObserver(wrapper); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;LiveData에서 값이 변경하면 호출되는 observe 메서드 입니다. 시작부터 if문에서 매개로 받은 owner의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Lifecycle의 State이 DESTORYED가 되는 상황이면 바로 return 을 하는것을 확인할 수 있습니다.&lt;/b&gt;&lt;/span&gt; 위 코드에서 lifecycle에 따라 조건문이 걸려있다는 것을 확인이 가능합니다.&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. LiveData 는 최신데이터를 유지한다. &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 LiveData는 과연 어떻게 동작할까? 한번 살펴보도록 했습니다.&lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;mutableLiveData.value = value //or mutableLiveData.postValue(value)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 LiveData는 value를 set을 하면 수신을 대기하는 observe 가 호출됩니다. 왜냐하면 LiveData는 언제나 최신 데이터만 유지하는것을 목적으로 합니다. 그래서 set 이 되는 순간 가장 최신 데이터 임으로 별도의 호출 구문 없이 setValue를 하면 수신 대기하는 observe가 호출 됩니다. &lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; @MainThread protected void setValue(T value) { assertMainThread(&quot;setValue&quot;); mVersion++; mData = value; dispatchingValue(null); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의외로 단순한 LiveData의 setValue 메서드 입니다.&lt;br&gt;mData가 LiveData가 가진 data입니다. 컬렉션 종류가 아니라 순수 객체 하나임으로 그저 최신 데이터만 항상 유지 합니다. &lt;br&gt;&lt;br&gt;예를 들어서 setValue를 연속적으로 하는 상황에서,&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;_livedata.value = &quot;1&quot; _livedata.value = &quot;2&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번의 set이 일어났으니,&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 두번의 호출이 있을탠데 두번의 콜백 호출 모두 &quot;2&quot; 라는 데이터로 받게 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;_livedata.value = &quot;1&quot; _livedata.postValue(&quot;2&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(setValue와 postValue 같이 연속적으로 사용 경우 최신 데이터가 간혹 보장이 안되는 경우가 있다고 합니다.&lt;br&gt; setValue는 mData 객체에 값을 치환해서 사용하고, postValue는 mPendingData에 임시로 값을 할당해서 사용하기 때문에 과정속에서 충돌이 일어나지 않았나 싶습니다. )&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. LiveData 는 최종적으로 UI 스레드에서 동작한다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;LiveData의 value를 set하는 함수가 2가지가 존재합니다.&lt;br&gt;setValue와 postValue가 존재합니다. &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt; &lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) setValue()&lt;/b&gt;&lt;/h4&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; @MainThread protected void setValue(T value) { assertMainThread(&quot;setValue&quot;); mVersion++; mData = value; dispatchingValue(null); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;setValue에 assertMainThread(&quot;setValue&quot;) 라는 함수가 존재합니다. 이 함수 내용은 아래와 같습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; static void assertMainThread(String methodName) { if (!ArchTaskExecutor.getInstance().isMainThread()) { throw new IllegalStateException(&quot;Cannot invoke &quot; + methodName + &quot; on a background&quot; + &quot; thread&quot;); } }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;그 내부에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메인 쓰레드가 아니라면 throw를 통해 예외 발생시키는 구문이 숨겨져있었습니다.&lt;/b&gt;&lt;/span&gt;&lt;br&gt;그렇기 때문에 setValue는 반드시 MainThread 환경에서 호출해야합니다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) postValue()&lt;/b&gt;&lt;/h4&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;protected void postValue(T value) { boolean postTask; synchronized (mDataLock) { postTask = mPendingData == NOT_SET; mPendingData = value; } if (!postTask) { return; } ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;반면 postValue는 mPendingData 객체에 임시로 value를 할당합니다. 이 함수는 setValue와 달리 MainThread 환경에서 호출되지 않아도 예외처리 되는 부분은 존재하지 않습니다. 다만,&lt;br&gt; &lt;br&gt;ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable) 를 통해서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;내부적으로 MainThread 로 바꿔서 실행합니다.&lt;/b&gt;&lt;/span&gt;&lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; private final Runnable mPostValueRunnable = new Runnable() { @SuppressWarnings(&quot;unchecked&quot;) @Override public void run() { Object newValue; synchronized (mDataLock) { newValue = mPendingData; mPendingData = NOT_SET; } setValue((T) newValue); } };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;매개변수로 들어갔던 mPostValueRunnable 객체 입니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;mPendingData의 값은 newValue라는 지역변수에 할당 되고 mPendingData는 다시 기초 값으로 초기화 됩니다. &lt;/b&gt;&lt;/span&gt;&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) setValue , postValue 차이&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setValue -&amp;gt; MainThread에서 실행해야함 mData라는 멤버변수의 값을 할당함 &lt;br&gt;postValue -&amp;gt; MainThread에서 실행하지 않아도 됨, 하지만 다시 MainThread환경으로 바꿔줌, mPendingData를 통해 값을 전달하고 mPendingData객체는 디폴트값으로 초기화 됨 &lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. LiveData의 getValue()&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveData는 getValue를 통해서 LiveData 내부에 있는 mData를 참조해서 사용할 수 있습니다. 하지만 실제로 mData를 직접적으로 참조하는 것이 아닙니다. &lt;br&gt; &lt;br&gt;내부를 보기전에는 아래와 같이 되있을것이라 코드를 상상했습니다.&lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;//실제 코드가 아닙니다. void getValue(){ retrun this.mData }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;하지만 실제로 getValue()를 살펴보니 아래와 같은 코드로 되어있었습니다. &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt; @Nullable public T getValue() { Object data = mData; if (data != NOT_SET) { return (T) data; } return null; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;data지역 변수를 만들어서 return 합니다&lt;/b&gt;&lt;/span&gt;. &lt;br&gt; &lt;br&gt;하지만 MutableLiveData &amp;lt;MutableList&amp;lt;T&amp;gt;&amp;gt;와 같이 List Type을 가진 LiveData에서 List에 .add()를 하고 싶다면 아래와 같은 코드로 작성할 수가 있습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; MutableLiveData&amp;lt;T&amp;gt;.notifyObserver() { this.value = this.value }&lt;/code&gt;&lt;/pre&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;fun addIssuePost(issuePost: IssuePost) { mIssuePostLiveData.value?.add(issuePost) mIssuePostLiveData.notifyObserver() }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt; &lt;br&gt;setValue = getValue를 하는 형식입니다. setValue() 통해 observe 호출을 하기 위해 사용되는 구문 같습니다.&lt;br&gt; &lt;br&gt;List 같은 타입은 call by reference로 값을 참조하기 때문에 getValue에서 만든 data와 mData가 같은 주소값을 참조하고 있어서, 임시로 만든 data의 값이 변하면 똑같이 mData의 값을 변경할 수 있습니다.&lt;br&gt; &lt;br&gt;그래서 this.value = this.value를 하면 이전에 .add()를 통해 변경된 값이 mData에 적용이 되어있기 때문에 최신 값 그대로 setValue()를 통해서 observe를 호출 할수 있습니다.&lt;br&gt; &lt;br&gt;이상 &lt;b&gt;LiveData와 MutableLiveData에 대해 정리해봤습니다.&lt;/b&gt;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>AndroidJetpack</category>
      <category>AndroidLivedata</category>
      <category>AndroidStudio</category>
      <category>Android개발</category>
      <category>Android라이브데이타</category>
      <category>Jetpack</category>
      <category>livedata</category>
      <category>MutableLiveData</category>
      <category>라이브데이타</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/62</guid>
      <comments>https://gift123.tistory.com/62#entry62comment</comments>
      <pubDate>Tue, 17 Aug 2021 23:14:42 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (31)  Activity Life Cycle</title>
      <link>https://gift123.tistory.com/61</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍에서 주로 사용되는 생명주기(Life Cycle)은 프로그래밍 공학 쪽으로나 다양한 곳곳에서 사용되는 용어입니다.&lt;br&gt; &lt;br&gt;Android 에서 말하는 LifeCycle은 주로 상황에 따라 생성 ~ 제거가 호출되는 전체 단계를 뜻합니다. Google Android는 LifeCycle에 맞게 콜백함수를 제공함으로써, Android 개발자가 특정 생명주기나 전체 생명주기를 이용해서 원하는 상황에 원하는 동작을 실행하도록 코드를 구현할 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1008&quot; width=&quot;707&quot; height=&quot;371&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/qBWIb/btrbCuhY7Cv/RPOSiNWRej3jTKnKfoB7t1/img.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qBWIb/btrbCuhY7Cv/RPOSiNWRej3jTKnKfoB7t1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qBWIb/btrbCuhY7Cv/RPOSiNWRej3jTKnKfoB7t1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qBWIb/btrbCuhY7Cv/RPOSiNWRej3jTKnKfoB7t1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqBWIb%2FbtrbCuhY7Cv%2FRPOSiNWRej3jTKnKfoB7t1%2Fimg.jpg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1008&quot; width=&quot;707&quot; height=&quot;371&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/qBWIb/btrbCuhY7Cv/RPOSiNWRej3jTKnKfoB7t1/img.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;Android 에서 Life Cycle은 여러곳에 존재합니다. View, Service, BroadCastReceiver 등등 생성 ~ 제거 되는 순간에 따라 콜백함수를 커스텀 할 수 있습니다.&lt;br&gt; &lt;br&gt;여러 Life Cycle 중에 실제 개발하면서 자주 신경쓰게 될 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Activity LifeCycle&lt;/b&gt;&lt;/span&gt;에 대해 정리를 해보려 합니다.&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Activity &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액티비티는 앱에 반드시 최소 1개이상이 존재해야합니다. Manifest.xml에 Activity가 없는 앱은 실행할 수 없습니다.&lt;br&gt;그래서 기본적으로 App을 키자마자 Activity의 라이프 사이클부터 직면하게 됩니다. &lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Activity는 LifeCycle에 따른 크게 6가지의 콜백함수를 제공합니다. &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;663&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/zPULY/btrbrUbAPAe/yzO4LTgWT9TNAAckTjtVxk/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zPULY/btrbrUbAPAe/yzO4LTgWT9TNAAckTjtVxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zPULY/btrbrUbAPAe/yzO4LTgWT9TNAAckTjtVxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zPULY/btrbrUbAPAe/yzO4LTgWT9TNAAckTjtVxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzPULY%2FbtrbrUbAPAe%2FyzO4LTgWT9TNAAckTjtVxk%2Fimg.png&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;663&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/zPULY/btrbrUbAPAe/yzO4LTgWT9TNAAckTjtVxk/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;onCreate()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;시스템이 액티비티가 생성될 때 실행됩니다&lt;/b&gt;&lt;/span&gt;. Activity의&lt;span style=&quot;color: #202124;&quot;&gt; LifeCycle 에서 한 번만 발생해야 하는 시작 로직을 실행합니다. 콜백함수의 경우 onCreate(&lt;/span&gt;&lt;span style=&quot;color: #202124;&quot;&gt;savedInstanceState: Bundle?) 매개 변수를 가집니다. 이번에 최초로 Activity가 생성이 됬다면 savedInstanceState는 null 입니다. &lt;/span&gt;&lt;br&gt; &lt;br&gt;LifeCycle 동안 한번만 호출이 되는 함수이고 머무르지 않습니다.&lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class LifecycleComponent : LifecycleObserver { ... @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun setData() { .... } ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(해당 Lifecycle를 인식하는 Componet를 사용한다면 이 component는 ON_CREATE 이벤트를 수신하고 @OnLifecycleEvent라는 주석이 있는 메서드가 호출이 됩니다.)&lt;br&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;onStart()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- onCreate() 이후에 이어서 onStart() 가 호출이 됩니다.&lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;onStart() 부터 사용자에게 Activity가 표시됩니다. &lt;/b&gt;&lt;/span&gt;&lt;br&gt;Activity를 Forground로 보내 사용자와 상호작용할 준비를 합니다.&lt;br&gt;onCreate()와 마찬가지로 머무르지 않고 다음 단계로 이동합니다.&lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class LifecycleComponent : LifecycleObserver { ... @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun setData() { .... } ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(해당 Lifecycle를 인식하는 Componet를 사용한다면 이 component는 ON_START 이벤트를 수신하고 @OnLifecycleEvent라는 주석이 있는 메서드가 호출이 됩니다.)&lt;br&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;onResume()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- onStart() 이후에 호출됩니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;사용자가 본격적으로 앱과 상호작용할 수 있는 콜백함수 입니다. &lt;/b&gt;&lt;/span&gt;&lt;br&gt;onCreate()와 onStart()와 달리 이벤트가 발생 하지 않는다면 계속 onResume() 단계에서 머물게 됩니다. &lt;br&gt; &lt;br&gt;onResume()를 방해하는 이벤트가 발생하면 onPause()가 발생하는데, onPause()에서&lt;br&gt;다시 onResume()으로 돌아갈 수 있습니다.&lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class LifecycleComponent : LifecycleObserver { ... @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun setData() { .... } ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(해당 Lifecycle를 인식하는 Componet를 사용한다면 이 component는&lt;br&gt;ON_RESUME 이벤트를 수신하고 @OnLifecycleEvent라는 주석이 있는 메서드가 호출이 됩니다.)&lt;br&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;onPause()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;해당 Activity가 forground Process 를 떠나있을때 첫번째로 호출이 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;br&gt;(하지만 상황에 따라 Activity가 여전히 사용자에게 보이는 경우가 있습니다.)&lt;br&gt; &lt;br&gt;Activity가 화면의 일부분으로 표시되면서 포커스를 잃는 경우에는&lt;br&gt;다음 단계로 가지 않고 onPause() 상태에서 머뭅니다. (대표적인 예: 멀티윈도우에서 포커스를 잃었을 때)&lt;br&gt; &lt;br&gt;&lt;span style=&quot;color: #202124;&quot;&gt;상황에 따라 Activity가 일부분으로 표시되는 경우가 있기 때문에, &lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #202124;&quot;&gt;UI 관련 리소스와 작업을 완전히 해제하거나 조정할 때는 onPause()를 사용하지 않는것이 좋습니다. &lt;/span&gt;&lt;br&gt; &lt;br&gt;반대로 Activity가 아예 화면이 표시가 되지않을때 머무르지 않고 다음 단계로 넘어갑니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class LifecycleComponent : LifecycleObserver { ... @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun setData() { .... } ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(해당 Lifecycle를 인식하는 Componet를 사용한다면 이 component는&lt;br&gt;ON_PAUSE 이벤트를 수신하고 @OnLifecycleEvent라는 주석이 있는 메서드가 호출이 됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br&gt;&lt;b&gt;onStop()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;기존 Activity가 아예 화면에서 완전히 벗어난다면 onStop() 이 호출이 됩니다. &lt;/b&gt;&lt;/span&gt;&lt;br&gt;(Process Lifecycle 기준으로 background process 일때)&lt;br&gt; &lt;br&gt;&lt;span style=&quot;color: #202124;&quot;&gt;메서드에서 앱이 사용자에게 보이지 않는 동안 앱은 필요하지 않은 리소스를 해제하거나 조정하거나 &lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #202124;&quot;&gt;CPU를 비교적 많이 소모하는 종료 작업을 실행해야 합니다.&lt;/span&gt;&lt;br&gt; &lt;br&gt;그리고 Activity 객체가 메모리안에 머물게 됩니다. &lt;br&gt;또한 시스템은 레이아웃에 있는 각 View 객체의 현재 상태도 기록합니다.&lt;br&gt;그래서 사용자가 홈버튼으로 화면을 벗어놨다가 TASK를 통해 화면으로 돌아와도 UI 데이터들이 그대로 남아있습니다.&lt;br&gt; &lt;br&gt;*하지만 OS 가 메모리가 부족하다면 해당 프로세스 자체를 해제시킬 수 있습니다.&lt;br&gt;bundle로 저장한게 있다면 onCreate()에서 bundle를 이용해서 기존 값들로 다시 불러올수 있습니다.&lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class LifecycleComponent : LifecycleObserver { ... @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun setData() { .... } ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(해당 Lifecycle를 인식하는 Componet를 사용한다면 이 component는&lt;br&gt;ON_STOP 이벤트를 수신하고 @OnLifecycleEvent라는 주석이 있는 메서드가 호출이 됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br&gt;&lt;b&gt;onDestory()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;onDestory()는 Activity가 소멸되기 직전에 호출됩니다.&lt;/b&gt;&lt;/span&gt;&lt;br&gt;아래 2가지 경우로 onDestory()가 호출됩니다.&lt;br&gt;Activity가 종료되는 경우,&lt;br&gt;구성 변경으로 인해 시스템이 일시적으로 Activity를 소멸 시키는 경우 (ex: 화면 회전)&lt;br&gt; &lt;br&gt;Activity의 마지막 Lifecycle 입니다.&lt;br&gt;이전 콜백에서 처리하지 않은 리소스 해제가 있다면 onDestory()에서 마저 처리를 하기도 합니다. &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;class LifecycleComponent : LifecycleObserver { ... @OnLifecycleEvent(Lifecycle.Event.ON_DESTORY) fun setData() { .... } ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(해당 Lifecycle를 인식하는 Componet를 사용한다면 이 component는&lt;br&gt;ON_DESTORY 이벤트를 수신하고 @OnLifecycleEvent라는 주석이 있는 메서드가 호출이 됩니다.)&lt;br&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 다시 호출 될 수 있는 &lt;/b&gt;&lt;b&gt;Activity&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;LifeCycle&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;콜백함수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CREATE ~ DESTORY 까지 상황에 따라 단계별로 움직입니다. &lt;br&gt;이미 단계를 지났어도 상황에 따라 다시 호출 되는 콜백 함수가 존재합니다.&lt;br&gt; &lt;br&gt;&lt;b&gt; 1) onPause -&amp;gt;onResume &lt;/b&gt;&lt;br&gt;- onPause 상황에서 일부 액티비티가 보이는 상황에서 일부였던 액티비티가 포커스를 찾았을 때 다시&lt;br&gt;onResume을 호출합니다.&lt;br&gt; &lt;br&gt;&lt;b&gt; 2) onStop -&amp;gt; onRestart -&amp;gt; onStart&lt;/b&gt;&lt;br&gt;- 유저의 액션에따라 해당 액티비티가 화면에서 벗어놨다가 다시 해당 화면을 불러왔을 때 onRestart라는 함수가 호출 됩니다. 다시 onStart가 호출됩니다.&lt;br&gt; &lt;br&gt;&lt;b&gt; 3) onStop -&amp;gt; onCreate&lt;/b&gt;&lt;br&gt;- onStop 일때, Android 의 메모리 부족으로 앱의 프로세스를 소멸한 상황에서&lt;br&gt;유저에 의해 해당 액티비티를 불러온다면 onCreate 부터 호출 됩니다. (프로세스를 소멸 시켯을뿐 액티비티는 그대로 남아있습니다.) onStop 에서 onSaveInstanceState()를 통해 bundle의 data를 저장했다면 onCreate에서 다시 소멸 되기전 data를 불러올 수 있습니다.&lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Activity 이동간의 LifeCycle 확인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 액티비티 첫 실행 main&lt;/b&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;318&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/bsMhCy/btrbuJ1oeWy/o69CiK5p8QxaNn9se5KXb0/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsMhCy/btrbuJ1oeWy/o69CiK5p8QxaNn9se5KXb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsMhCy/btrbuJ1oeWy/o69CiK5p8QxaNn9se5KXb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsMhCy/btrbuJ1oeWy/o69CiK5p8QxaNn9se5KXb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsMhCy%2FbtrbuJ1oeWy%2Fo69CiK5p8QxaNn9se5KXb0%2Fimg.png&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;318&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/bsMhCy/btrbuJ1oeWy/o69CiK5p8QxaNn9se5KXb0/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;첫 실행시 onCreate, onStart , onResume 순서대로 호출이 되고 onResume()에서 머문다는 것을 알 수가 있습니다.&lt;br&gt; &lt;br&gt;&lt;b&gt;2) 액티비티 이동 main -&amp;gt; second &lt;/b&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;424&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/yarYu/btrbvco84fg/ziCHYPXl3dRcfkSKhpuqL0/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yarYu/btrbvco84fg/ziCHYPXl3dRcfkSKhpuqL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yarYu/btrbvco84fg/ziCHYPXl3dRcfkSKhpuqL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yarYu/btrbvco84fg/ziCHYPXl3dRcfkSKhpuqL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyarYu%2Fbtrbvco84fg%2FziCHYPXl3dRcfkSKhpuqL0%2Fimg.png&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;424&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/yarYu/btrbvco84fg/ziCHYPXl3dRcfkSKhpuqL0/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;-MainActivity가 포커스를 잃는 순간 onPause() 를 호출 합니다.&lt;br&gt;-&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;SeocondActivity 가 onResume()호출 되고 나서야 MainActivity의 onStop()이 호출 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;br&gt;-onStop 이후에 onSaveInstanceState() 호출 됩니다.&lt;br&gt; &lt;br&gt;위의 경우 main -&amp;gt; secondActivity를 startActivity()로 이동했기 때문에&lt;br&gt;main이 backStack에 쌓여서 onDestory는 호출되지 않습니다.&lt;br&gt; &lt;br&gt;&lt;b&gt;3) 액티비티 뒤로가기 main &amp;lt;- second &lt;/b&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1043&quot; data-origin-height=&quot;449&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/XfTjR/btrbpPvaBMP/wBSbdxwYAzgqkgn8UXKmgK/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XfTjR/btrbpPvaBMP/wBSbdxwYAzgqkgn8UXKmgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XfTjR/btrbpPvaBMP/wBSbdxwYAzgqkgn8UXKmgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XfTjR/btrbpPvaBMP/wBSbdxwYAzgqkgn8UXKmgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXfTjR%2FbtrbpPvaBMP%2FwBSbdxwYAzgqkgn8UXKmgK%2Fimg.png&quot; data-origin-width=&quot;1043&quot; data-origin-height=&quot;449&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/XfTjR/btrbpPvaBMP/wBSbdxwYAzgqkgn8UXKmgK/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;뒤로가기 버튼을 눌러서 main으로 돌아갔을 때의 LifeCycle 입니다.&lt;br&gt;- Second의 onPause()&lt;br&gt;- main의 onRestart() -&amp;gt; onStart() -&amp;gt; onResume()&lt;br&gt;- Second의 onStop() -&amp;gt; onDestory() &lt;br&gt; &lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;onStop에 있는 main을 다시 불러와서 onRestart가 호출이 되는것을 볼 수 있습니다. &lt;/b&gt;&lt;/span&gt;&lt;br&gt;그리고 뒤로가기 버튼을 눌러서 Second의 메모리를 저장하지 않음으로 onSaveInstanceState()를 호출하지 않습니다.&lt;br&gt;그리고 onDestory()를 호출합니다. &lt;br&gt; &lt;br&gt;&lt;b&gt;4) second에서 task 창에서 activity 강제 종료 &lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;401&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/kwKFg/btrbkVPQzbi/KVPSBLkYUrcH7zDbL03KS1/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwKFg/btrbkVPQzbi/KVPSBLkYUrcH7zDbL03KS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwKFg/btrbkVPQzbi/KVPSBLkYUrcH7zDbL03KS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwKFg/btrbkVPQzbi/KVPSBLkYUrcH7zDbL03KS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwKFg%2FbtrbkVPQzbi%2FKVPSBLkYUrcH7zDbL03KS1%2Fimg.png&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;401&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/kwKFg/btrbkVPQzbi/KVPSBLkYUrcH7zDbL03KS1/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;secondActivity을 보고 있는 상황에서 안드로이드 App의 Task 창에서 어플을 강제 종료했을 때 백스택에 담겻던 MainActivity까지 onDestory()가 호출이 되는 것을 알 수 있습니다. &lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Activity LifeCycle 과 프로세스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android System 은 절대 Activity를 직접적으로 종료하지 않습니다. 다만, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메모리가 부족해지는 경우에 Activity 를 실행하는 Process를 종료함으로써 메모리를 확보하는 경우가 있습니다&lt;/b&gt;&lt;/span&gt;. 아래는 시스템이 Process를 종료할 가능성을 나타내는 표 입니다. &lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;987&quot; data-origin-height=&quot;380&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/OLk3Q/btrbvw1OiqU/chjd5BKRO7LOoKcWRtAyK0/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OLk3Q/btrbvw1OiqU/chjd5BKRO7LOoKcWRtAyK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OLk3Q/btrbvw1OiqU/chjd5BKRO7LOoKcWRtAyK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OLk3Q/btrbvw1OiqU/chjd5BKRO7LOoKcWRtAyK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOLk3Q%2Fbtrbvw1OiqU%2Fchjd5BKRO7LOoKcWRtAyK0%2Fimg.png&quot; data-origin-width=&quot;987&quot; data-origin-height=&quot;380&quot; data-image-src=&quot;https://k.kakaocdn.net/dn/OLk3Q/btrbvw1OiqU/chjd5BKRO7LOoKcWRtAyK0/img.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;STOP ~ DESTORY 가 가장 프로세스가 종료될 가능성이 커집니다. 이에 대비해서 &lt;span style=&quot;color: #202124;&quot;&gt;onSaveInstanceState(), ViewModel, 로컬 DB 등등을 통해서 프로세스가 종료됬을 때의 보존할 데이터들을 다시 불러올 수 있도록 준비해야합니다. &lt;/span&gt;&lt;br&gt; &lt;br&gt;&lt;span style=&quot;color: #202124;&quot;&gt;이상 Activity의 LifeCycle에 대해 알아봤습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Activity</category>
      <category>ActivityLifecycle</category>
      <category>androidActivity</category>
      <category>androidActivityLifecycle</category>
      <category>안드로이드</category>
      <category>안드로이드Activity</category>
      <category>안드로이드Activity생명주기</category>
      <category>안드로이드개발</category>
      <category>액티비티LifeCycle</category>
      <category>액티비티라이프사이클</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/61</guid>
      <comments>https://gift123.tistory.com/61#entry61comment</comments>
      <pubDate>Sun, 8 Aug 2021 15:12:21 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (30) viewModelScope</title>
      <link>https://gift123.tistory.com/60</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Android 는 현재 집중적으로 Coroutine 을 밀고 있습니다. Android에서 Coroutine을 응용한 api와 Coroutine 관련된 코드 스니펫 등등이 등장하면서 앞으로 Android에서 Coroutine을 활용할일이 많아지고 있습니다. 그래서 과거 비동기 관련 코드들을 Coroutine 으로 마이그레이션을 진행중이거나 완료한 소식들을 간혹 접하기도 했습니다.&amp;nbsp;그만큼 Coroutine에 대한 열풍이 뜨겁다고 느껴집니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;scooter-2792992_1280.jpg&quot; width=&quot;597&quot; height=&quot;398&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eoQHi2/btrbrValKNl/w7YMrstzmKmakuXKvmeBFk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eoQHi2/btrbrValKNl/w7YMrstzmKmakuXKvmeBFk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eoQHi2/btrbrValKNl/w7YMrstzmKmakuXKvmeBFk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeoQHi2%2FbtrbrValKNl%2Fw7YMrstzmKmakuXKvmeBFk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;scooter-2792992_1280.jpg&quot; width=&quot;597&quot; height=&quot;398&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. ViewModelScope 란?&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Jetpack Library 를 많이 사용하는데 Jetpack에 viewModelScope 라는 Coroutine Scope를 지원해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewModelScope는&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ViewModel에서 onCleared() 호출 할때 직접 coroutine context를 명시적으로 취소를 하지않아도 자동적으로 onCleared() 호출 될때 coroutine 작업을 취소합니다.&lt;/b&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;pre id=&quot;code_1628345644991&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ViewModelScope 비사용시..
class SampleViewModel : ViewModel() {
    private val job = SupervisorJob()
    private val uiScope = CoroutineScope(Dispatchers.Main + job)
        ...
    fun loadData() = uiScope.launch {
        ...
    }

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewModelScope를 사용하지 않고 ViewModel에서 Coroutine을 사용한다면&amp;nbsp; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;onCleared()에서 직접 job.cancel()를 통해서 Coroutine 작업을 취소를 해야했었습니다.&lt;/b&gt; &lt;/span&gt;그래야 ViewModel에서 CLEARED가 호출이 될때 Coroutine 작업을 취소해서 메모리 누수를 방지할 수 있습니다.&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;다음은 viewModelScope를 사용했을 때의 경우입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628345844670&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//ViewModel를 사용했을 때
class SampleViewModel : ViewModel() {
    fun loadData() = viewModelScope.launch {
        ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상당히 코드가 깔끔해졌습니다. viewModelScope는 구글 Android팀에서 ViewModel의 onCleard가 호출된다면 자동적으로 viewModelScope의 작업을 취소하도록 만들어놨습니다.&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: #ee2323;&quot;&gt;&lt;b&gt; onCleared()에서 job.cancle()를 명시적으로 지정하지 않아도 되서 개발자가 실수할 확률이 줄어듭니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. ViewModelScope 내부 원리&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1628345087279&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private const val JOB_KEY = &quot;androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY&quot;

//뷰모델 스코프 구현 코드
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        coroutineContext.cancel()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewModelScope는 kotlin 언어로 작성되었습니다. ViewModel의 확장 프로퍼티로 사용이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewModelScope 변수는 getter로 CoroutineScope를 return 해줍니다.&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;getter 에서 CloseableCoroutineScope() 클래스를 생성할 때 매개변수로 SupervisorJob() + Dispatchers.Main.immediate를 넘겨줍니다.&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;viewModelScope는 기본적으로 Main thread로 작업을 하는것임을 알수가 있습니다.&lt;/b&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;return 하는 setTagIfAbsent() 함수는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628347394741&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;....
@Nullable
private final Map&amp;lt;String, Object&amp;gt; mBagOfTags = new HashMap&amp;lt;&amp;gt;();

&amp;lt;T&amp;gt; T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {
            previous = (T) mBagOfTags.get(key);
            if (previous == null) {
                mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {
            closeWithRuntimeException(result);
        }
        return result;
    }&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;setTagIfAbsent는 ViewModel의 mBagOfTags 라는 HashMap 에 중복되는 key가없다면 인자로 받아온&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key,newValue를 mBagOfTags에 put 하고 난 후 매개변수로 받은 newValue를 return 해줍니다.&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;onCleared() 함수는 실은 ViewModel의 clear() 라는 함수로 인해 호출이 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628347922853&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @MainThread
    final void clear() {
        mCleared = true;
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1628353142307&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//위 예제 CloseableCoroutineScope 클래스에서 구현한 인터페이스 

public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #384452;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;clear() 함수가 호출되면 mBagOfTags에 있는 모든 value를 close() 합니다.&lt;/b&gt;&lt;/span&gt; 그 다음&lt;/span&gt;&lt;span style=&quot;color: #384452;&quot;&gt;&amp;nbsp;onCleared()를 호출합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628353228784&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context

    override fun close() {
        coroutineContext.cancel()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #384452;&quot;&gt;다시 viewModelScope에서 사용되었던 CloseableCoroutineScope코드를 다시보자면, Closeable 인터페이스를 상속 받아 close 함수에 coroutinecontext 객체가 cancel() 되는것을 알 수 있습니다.&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: #384452;&quot;&gt;clear 메서드는 오직 AndroidX 내부에서 호출되기 위해서 만들어졌으며 프레임워크에서 사용되는 함수입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #384452;&quot;&gt;Activity 혹은 Fragment의 LifceCycle이 DESTORY 가 호출될 때 사용됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1628349543603&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ComponentActivity ... {
    ...
    getLifecycle().addObserver(new LifecycleEventObserver() {
      @Override
      public void onStateChanged(@NonNull LifecycleOwner source, 
        @NonNull Lifecycle.Event event) {
       ...
        if (event == Lifecycle.Event.ON_DESTROY) {                   
                   ...
          if (!isChangingConfigurations()) {
          // ViewModel.clear()를 ViewModelStore의 clear() 함수 안에서 사용함       
            getViewModelStore().clear(); 
          }
       }&lt;/code&gt;&lt;/pre&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;1) viewModelScope 안에서 mBagOfTags에 &lt;span style=&quot;color: #384452;&quot;&gt;CloseableCoroutineScope를 통해서&lt;/span&gt; 해당 CoroutineContext를 put&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2) Lifecycle == DESTORY 일때 clear 함수가 호출됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3) clear 함수에서 mBagOfTags의 값들을 모두 close() 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4) &lt;span style=&quot;color: #384452;&quot;&gt;CloseableCoroutineScope class 에서 정의한 close() 의 내용이 &lt;span style=&quot;color: #384452;&quot;&gt;&amp;nbsp;coroutineContext.cancel() 임으로, 해당 작업을 취소함&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;&lt;span style=&quot;color: #384452;&quot;&gt;&lt;span style=&quot;color: #384452;&quot;&gt;위와 같은 과정으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;LifeCycle DESTORY 일때 알아서 CoroutineContext가 취소&lt;/b&gt; &lt;/span&gt;되기 때문에 viewModelScope를 사용하면 안드로이드 개발자가 뷰모델에서 편하게 Coroutine을 사용할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. ViewModelScope와 Retrofit2 사용시 &lt;/b&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ViewModelScope의 Dispatchers를 바꾸지 않아도 되는 이유&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628355763611&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  fun getProduct() {
        viewModelScope.launch {
            _productList.value = productRepo.getProduct()
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설명대로 ViewModelScope는 withContext를 통해 Dispatchers 전환이 없다면 기본적으로 Main Thread 로 작업을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 신기한 점은 ViewModelScope 에서 Retrofit2를 사용할 때 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;IO Thread가 아니더라도 문제없이 Retrofit2를 사용할 수 있습니다.&lt;/b&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;우선 Retrofit2를 사용할 때 Coroutine을 응용해서 사용한다면 보통 아래와 같이 사용이 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628354627519&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; @GET(&quot;product&quot;)
 suspend fun getProduct(): Product&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Call&amp;lt;Product&amp;gt;를 사용했던 것과 달리 CoroutineScope 에서 실행되는 suspend 함수로 지정하면 Retrofit Package를 이용하는 개발자는 return type에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Retrofit2에서 제공하는 Call&amp;lt;T&amp;gt;를 사용하지 않고 사용합니다.&lt;/b&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;&lt;a href=&quot;https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/KotlinExtensions.kt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/KotlinExtensions.kt&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1628355106218&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - square/retrofit: A type-safe HTTP client for Android and the JVM&quot; data-og-description=&quot;A type-safe HTTP client for Android and the JVM. Contribute to square/retrofit development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/KotlinExtensions.kt&quot; data-og-url=&quot;https://github.com/square/retrofit&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oRESq/hyK9BfNnIz/yhreCCaPwWyHzpieckVEIk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/KotlinExtensions.kt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/KotlinExtensions.kt&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oRESq/hyK9BfNnIz/yhreCCaPwWyHzpieckVEIk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - square/retrofit: A type-safe HTTP client for Android and the JVM&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A type-safe HTTP client for Android and the JVM. Contribute to square/retrofit development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1628355207127&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;suspend fun &amp;lt;T : Any&amp;gt; Call&amp;lt;T&amp;gt;.await(): T {
  return suspendCancellableCoroutine { continuation -&amp;gt;
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback&amp;lt;T&amp;gt; {
      override fun onResponse(call: Call&amp;lt;T&amp;gt;, response: Response&amp;lt;T&amp;gt;) {
        if (response.isSuccessful) {
          val body = response.body()
          if (body == null) {
            val invocation = call.request().tag(Invocation::class.java)!!
            val method = invocation.method()
            val e = KotlinNullPointerException(&quot;Response from &quot; +
                method.declaringClass.name +
                '.' +
                method.name +
                &quot; was null but response body type was declared as non-null&quot;)
            continuation.resumeWithException(e)
          } else {
            continuation.resume(body)
          }
        } else {
          continuation.resumeWithException(HttpException(response))
        }
      }

      override fun onFailure(call: Call&amp;lt;T&amp;gt;, t: Throwable) {
        continuation.resumeWithException(t)
      }
    })
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retrofit를 만든 square에서 Coroutine 사용을 돕기 위한 kotlinExtensions를 지원해주기 때문입니다. 그래서 Coroutine과 Retorift2를 같이 사용하게 되면 Call를 내부적으로 호출하게 됩니다. 위 코드를 보면 애초에 enqueue를 통해서 내부적으로 통신을 통한 결과값을 받아서 통신이 성공/실패 여부를 알려주고 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;내부적으로 이미 통신을 해서 responseBody를 알려주고 enqueue로 결과 값을 활용하고 있었습니다.&lt;/b&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;그래서 아래 코드는 내부적으로 통신을 통해 받은 값을 활용중이라서 IO Thread가 반드시 필요한 상황은 이미 지난 상황이 되는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1628355999799&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  

  fun getProduct() {
        viewModelScope.launch {
            _productList.value = productRepo.getProduct()
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수 return 받는 값을 사용하기 때문에 withContext()를 사용하지 않아도 됩니다.&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;이상 viewModelScope에 대해 알아봤습니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Coroutine</category>
      <category>Jetpack</category>
      <category>jetpackCoroutine</category>
      <category>retrofit2</category>
      <category>viewmodelscope</category>
      <category>viewModelScope.launch</category>
      <category>viewModelScope란</category>
      <category>뷰모델스코프</category>
      <category>뷰모델스코프란</category>
      <category>코루틴</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/60</guid>
      <comments>https://gift123.tistory.com/60#entry60comment</comments>
      <pubDate>Sun, 8 Aug 2021 02:22:08 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin (7) getter 와 Assignment (=)의 차이</title>
      <link>https://gift123.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin 예제들을 보다보면 get() 통해서 해당 프로퍼티에 직접적으로 값을 대입하지 않고(=) get()을 통해 다른 객체의 값을 사용하는 경우가 있습니다.&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;예를들어, 안드로이드 스튜디오에서 제공하는 Jetpack 의 ViewModel에서 LiveData를 사용할 때 MutableLiveData와 함께 사용해서 LiveData에 MutableLive의 value를 get() 하는 상황들을 많이 보셧을겁니다.&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 alignLeft&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;242&quot; data-filename=&quot;get()대표.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbj1OG/btra34rLONt/WyLZgBgktNPODuC2wHCj9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbj1OG/btra34rLONt/WyLZgBgktNPODuC2wHCj9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbj1OG/btra34rLONt/WyLZgBgktNPODuC2wHCj9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcbj1OG%2Fbtra34rLONt%2FWyLZgBgktNPODuC2wHCj9K%2Fimg.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;242&quot; data-filename=&quot;get()대표.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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: #333236;&quot;&gt; Assignment (&lt;/span&gt;=) 을 사용하지 않고 get()를 사용하는지 살펴보도록 하겠습니다.&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;1.val, var&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;코틀린의 프로퍼티는 기본적으로 field와 getter / setter를 자동생성 해줍니다. &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 변수를 하나만들어서 이 변수를 자바 코드로 변환을 한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 자동 생성이 된다는걸 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- Val&lt;/p&gt;
&lt;pre id=&quot;code_1627959622510&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; // kotlin
class GetterTest {
    val nullableVer: Int? = null
    val nonNullVer = 0
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1627959647748&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; // java
public final class GetterTest {
   @Nullable
   private final Integer nullableVer;
   private final int nonNullVer;

   @Nullable
   public final Integer getNullableVer() {
      return this.nullableVer;
   }

   public final int getNonNullVer() {
      return this.nonNullVer;
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;변수를 val로 사용할 경우 setter는 만들어지지 않고 getter만 만들어집니다.&lt;/b&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;size18&quot;&gt;-Var&lt;/p&gt;
&lt;pre id=&quot;code_1627959792773&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// kotlin
class GetterTest {
    var nullableVer: Int? = null
    var nonNullVer = 0
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1627959801639&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// java
public final class GetterTest {
   @Nullable
   private Integer nullableVer;
   private int nonNullVer;

   @Nullable
   public final Integer getNullableVer() {
      return this.nullableVer;
   }

   public final void setNullableVer(@Nullable Integer var1) {
      this.nullableVer = var1;
   }

   public final int getNonNullVer() {
      return this.nonNullVer;
   }

   public final void setNonNullVer(int var1) {
      this.nonNullVer = var1;
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;var 로 변수를 생성할 경우 getter setter 두개다 동시에 생성이 되는것을 볼 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. get(), set()&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 만약 getter / setter를 명시적으로 커스텀을 해야할때 get(), set()를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1627959947332&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//kotlin 
class GetterTest {
    var getterVar: Int = 0
        get() = field
        set(value) {
            field = value * 2 
        }
    
    init {
        getterVar = 2
        println(getterVar) //output:4
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1627959966748&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//java
public final class GetterTest {
   private int getterVar;

   public final int getGetterVar() {
      return this.getterVar;
   }

   public final void setGetterVar(int value) {
      this.getterVar = value * 2;
   }

   public GetterTest() {
      this.setGetterVar(2);
      int var1 = this.getGetterVar();
      boolean var2 = false;
      System.out.println(var1); //output:4
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제는 set 할때 대입되는 값이 *2로 field에 할당하도록 커스텀 한것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 자바로 변환한 코드를 보면 setGetterVar 함수에서 매개변수로 받는 value 를 *2를 하여 field에 값을 대입 하는것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;output : 4&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;358&quot; data-filename=&quot;custom get 객체 출력.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkyw1O/btraZMLUq7K/bifSDqsM0KaTkpFNZwehKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkyw1O/btraZMLUq7K/bifSDqsM0KaTkpFNZwehKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkyw1O/btraZMLUq7K/bifSDqsM0KaTkpFNZwehKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkyw1O%2FbtraZMLUq7K%2FbifSDqsM0KaTkpFNZwehKK%2Fimg.png&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;358&quot; data-filename=&quot;custom get 객체 출력.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. get() 과 Assignment (=)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 비슷해보이는 get()과 =를 한번 보도록 하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627960613884&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//kotlin
class GetterTest {
    val normalVar = 5

    val getterVar: Int
        get() = 5
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;= 으로 값을 대입하는 normalVer 변수와&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;get() 을 사용해서 값을 return 받는 getterVar가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드 또한 자바 코드로 변환해보면 아래와 같이 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627960772100&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//java 
public final class GetterTest {
   private final int normalVar = 5;

   public final int getGetterVar() {
      return 5;
   }

   public final int getNormalVar() {
      return this.normalVer;
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;normalVar 의 경우 field 값이 5로 할당이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getterVar의 경우&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; field가 따로 생성되지 않고 getter 함수만 생성이되었습니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;get() 과&lt;span&gt;&amp;nbsp;&lt;/span&gt;Assignment (=)의 차이&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getter로 return만 받는 객체의 경우 사실 필드는 생략되었으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getter 함수만 생성되서 값을 return 하고 있다는 사실을 볼 수 있었습니다.&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;pre id=&quot;code_1627961156308&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//kotlin 
class GetterTest {
    var number = 5
    private val getterVar
        get() = number

    private val normalVar = number
    init {
        number+= 10
        println(getterVar) // output: 15
        println(normalVer) // output: 5
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getterVar는 getter를 통해 number 객체를 return하고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;normalVar는 직접 number의 값을 복사해서 대입 합니다.&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;그래서 number 의 값이 바뀐다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getterVar는 number의 객체 자체를 return 하기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;number의 값을 return합니다.&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;반대로 normalVar는 number의 값이 바뀌어도 영향을 받지 않습니다.&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;즉, 변수에 getter만 사용하는것은&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; field를 생성하지 않고 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;getter() 함수를 통해 특정 객체만 return 하겠다는 의미&lt;/b&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;변수에 = 로 값을 대입하는 것은 field 를 생성하는 동시에 field의 값을 할당하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;=로 값을 대입한 변수의 getter는 field의 값을 return 해줍니다.&lt;/b&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;이상 getter 와 Assignment의 차이를 알아봤습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>코틀린 Kotlin</category>
      <category>customgetterinKoltin</category>
      <category>getter</category>
      <category>kotlingetter</category>
      <category>propertygetter</category>
      <category>setterandgetter</category>
      <category>안드로이드kotlin</category>
      <category>안드로이드kotlinget()</category>
      <category>코틀린getter</category>
      <category>코틀린getter와=의차이</category>
      <category>코틀린게터</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/59</guid>
      <comments>https://gift123.tistory.com/59#entry59comment</comments>
      <pubDate>Tue, 3 Aug 2021 12:46:01 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (29) Fragment에서 ViewBinding 사용 시 주의할 점</title>
      <link>https://gift123.tistory.com/58</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. ViewBinding&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;383&quot; data-filename=&quot;뷰바인딩.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGYBHw/btrbafZYflk/qiHFYlkDKOscdQVIaX9DX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGYBHw/btrbafZYflk/qiHFYlkDKOscdQVIaX9DX0/img.png&quot; data-alt=&quot;빌드 속도가 빠르고 안전하고 코드가 이뻐지는 ViewBinding&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGYBHw/btrbafZYflk/qiHFYlkDKOscdQVIaX9DX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGYBHw%2FbtrbafZYflk%2FqiHFYlkDKOscdQVIaX9DX0%2Fimg.png&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;383&quot; data-filename=&quot;뷰바인딩.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;빌드 속도가 빠르고 안전하고 코드가 이뻐지는 ViewBinding&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ViewBinding 은 xml를 자동으로 바인딩 클래스로 생성해서 xml의 View를 안전하게 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kotlin extension deprecated 이 되고나서 요즘은 ViewBinding, DataBinding을 위주로 사용하는 추세이기도 합니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Fragment에서 ViewBinding 사용시 주의할 점이 한가지가 있는데 이에 대해 정리를 해보도록 하겠습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Fragment 의 LifeCycle&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fragment 는 onCreateView에서 View를 생성하기 때문에 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;2개의 LifeCycle이 존재합니다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Fragment LifeCycle, Fragment View LifeCycle 이 두 가지가 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&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 alignLeft&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;491&quot; data-filename=&quot;Fragment-Life-Cycle-In-Android.jpg&quot; width=&quot;542&quot; height=&quot;330&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccMiof/btraVEG588H/a0WTJ2OXdmAa6Y25b0juSk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccMiof/btraVEG588H/a0WTJ2OXdmAa6Y25b0juSk/img.jpg&quot; data-alt=&quot;onDestortView()가 발생했을 때 onCreateView()가 다시 호출될 수 있음.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccMiof/btraVEG588H/a0WTJ2OXdmAa6Y25b0juSk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccMiof%2FbtraVEG588H%2Fa0WTJ2OXdmAa6Y25b0juSk%2Fimg.jpg&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;491&quot; data-filename=&quot;Fragment-Life-Cycle-In-Android.jpg&quot; width=&quot;542&quot; height=&quot;330&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;onDestortView()가 발생했을 때 onCreateView()가 다시 호출될 수 있음.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fragment LifeCycle: onAttach ~ onDetach&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fragment View LifeCycle: onCreateView ~ onDestoryView&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;기본적으로 Fragment 는 Fragment View 보다 LifeCycle이 깁니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Fragment 의 재사용 대비&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fragment는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Fragment의 재사용을 대비해서 Fragment는 View들을 메모리에 보관하도록 되어 있습니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Fragment의 onDestory가 호출 되고 나면 Fragment에 대한 레퍼런스는 존재하지 않지만 내부적으로 View들은 재사용을 위해 보관하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 메모리 누수 위험 막기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/view-binding#fragments&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/topic/libraries/view-binding#fragments&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1627921968846&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;뷰 결합 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;뷰 결합 기능을 사용하면 뷰와 상호작용하는 코드를 쉽게 작성할 수 있습니다. 모듈에서 사용 설정된 뷰 결합은 모듈에 있는 각 XML 레이아웃 파일의 결합 클래스를 생성합니다. 바인딩 클래스의&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/topic/libraries/view-binding#fragments&quot; data-og-url=&quot;https://developer.android.com/topic/libraries/view-binding?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cM2yO5/hyK4O1IgIF/t1v8WakKk5D8fkxQEN5gwk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/view-binding#fragments&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/topic/libraries/view-binding#fragments&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cM2yO5/hyK4O1IgIF/t1v8WakKk5D8fkxQEN5gwk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;뷰 결합 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;뷰 결합 기능을 사용하면 뷰와 상호작용하는 코드를 쉽게 작성할 수 있습니다. 모듈에서 사용 설정된 뷰 결합은 모듈에 있는 각 XML 레이아웃 파일의 결합 클래스를 생성합니다. 바인딩 클래스의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1627788735299&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private var _binding: ResultProfileBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = ResultProfileBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }&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;위는 구글 공식 샘플 입니다. onDestoryView 함수에서 _binding 객체를 null 만드는것을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fragment의 생명주기는 view보다 더 길기 때문에 Fragment 의 LifeCycle에 따라, view보다 Fragment가 더 오래 유지됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://wooooooak.github.io/android/2019/08/05/fragment%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EC%99%80%EA%B0%9D%EC%B2%B4%ED%8C%8C%EA%B4%B4%EC%8B%9C%EC%A0%90/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://wooooooak.github.io/android/2019/08/05/fragment%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EC%99%80%EA%B0%9D%EC%B2%B4%ED%8C%8C%EA%B4%B4%EC%8B%9C%EC%A0%90/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1627921172013&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;Fragment 메모리 누수에 유의하자. 특히 liveData! &amp;middot; 쾌락코딩&quot; data-og-description=&quot;Fragment 메모리 누수에 유의하자. 특히 liveData! 05 Aug 2019 | fragment memory_leak 프래그먼트의 복잡한 lifecycle 프래그먼트는 복잡한 lifecycle을 가지고 있다. 따라서 여러 프래그먼트를 사용하는 앱에서는 &quot; data-og-host=&quot;wooooooak.github.io&quot; data-og-source-url=&quot;https://wooooooak.github.io/android/2019/08/05/fragment%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EC%99%80%EA%B0%9D%EC%B2%B4%ED%8C%8C%EA%B4%B4%EC%8B%9C%EC%A0%90/&quot; data-og-url=&quot;https://wooooooak.github.io/android/2019/08/05/fragment%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EC%99%80%EA%B0%9D%EC%B2%B4%ED%8C%8C%EA%B4%B4%EC%8B%9C%EC%A0%90/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cLLZyK/hyK6goQhUS/P75VLYQuCfGsgPEuDXLuEk/img.png?width=363&amp;amp;height=858&amp;amp;face=0_0_363_858,https://scrap.kakaocdn.net/dn/d9PVL1/hyK6oAqyw3/9iEZ5tbMAkIDRUBLG8xKM1/img.png?width=279&amp;amp;height=412&amp;amp;face=0_0_279_412&quot;&gt;&lt;a href=&quot;https://wooooooak.github.io/android/2019/08/05/fragment%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EC%99%80%EA%B0%9D%EC%B2%B4%ED%8C%8C%EA%B4%B4%EC%8B%9C%EC%A0%90/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wooooooak.github.io/android/2019/08/05/fragment%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0%EC%99%80%EA%B0%9D%EC%B2%B4%ED%8C%8C%EA%B4%B4%EC%8B%9C%EC%A0%90/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cLLZyK/hyK6goQhUS/P75VLYQuCfGsgPEuDXLuEk/img.png?width=363&amp;amp;height=858&amp;amp;face=0_0_363_858,https://scrap.kakaocdn.net/dn/d9PVL1/hyK6oAqyw3/9iEZ5tbMAkIDRUBLG8xKM1/img.png?width=279&amp;amp;height=412&amp;amp;face=0_0_279_412');&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;Fragment 메모리 누수에 유의하자. 특히 liveData! &amp;middot; 쾌락코딩&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Fragment 메모리 누수에 유의하자. 특히 liveData! 05 Aug 2019 | fragment memory_leak 프래그먼트의 복잡한 lifecycle 프래그먼트는 복잡한 lifecycle을 가지고 있다. 따라서 여러 프래그먼트를 사용하는 앱에서는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wooooooak.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;위 링크를 보면 Fragment 객체가 얼마나 오래 유지되는지 알 수 있습니다. &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;letter-spacing: 0px;&quot;&gt;그래서 onDestoryView() 함수에서 직접 binding 객체를 null 로 만들어서 gc에서 수집해가도록 해야 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메모리 누수를 방지할 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;172&quot; data-filename=&quot;123.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl6fg1/btra8mZkaFR/FkOdXilre3zUPs2huL4Gc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl6fg1/btra8mZkaFR/FkOdXilre3zUPs2huL4Gc1/img.png&quot; data-alt=&quot;공식문서의 Note&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl6fg1/btra8mZkaFR/FkOdXilre3zUPs2huL4Gc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl6fg1%2Fbtra8mZkaFR%2FFkOdXilre3zUPs2huL4Gc1%2Fimg.png&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;172&quot; data-filename=&quot;123.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공식문서의 Note&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 반복 코드 제거&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 프레그먼트마다 항상 onDestoryView 함수에 binding = null 을 일일히 반복적으로 코드를 작성해야하는 것은 다들 원치 않는 일 일겁니다. 이를 피하기 위해 다른 대안들이 존재합니다.&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;대안 1) BaseFragment 를 만들어서 상속해서 사용하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1627917972758&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BaseFragment.kt

typealias Inflate&amp;lt;T&amp;gt; = (LayoutInflater, ViewGroup?, Boolean) -&amp;gt; T

abstract class BaseFragment&amp;lt;VB: ViewBinding&amp;gt;(
        private val inflate: Inflate&amp;lt;VB&amp;gt;
) : Fragment() {
    
    private var _binding: VB? = null
    val binding get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        _binding = inflate.invoke(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }&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_1627917988637&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeFragment() : BaseFragment&amp;lt;FragmentHomeBinding&amp;gt;(FragmentHomeBinding::inflate) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.homeText.text = &quot;Hello view binding&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://stackoverflow.com/questions/64819181/how-to-make-basefragment-with-view-binding&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/64819181/how-to-make-basefragment-with-view-binding&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1627918011367&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;How to make BaseFragment with View Binding&quot; data-og-description=&quot;My BaseFragment: abstract class BaseFragment&amp;lt;ViewModel : BaseViewModel, Binding : ViewBinding&amp;gt; : Fragment() { protected abstract val viewModel: ViewModel private var _binding: Bindin...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/64819181/how-to-make-basefragment-with-view-binding&quot; data-og-url=&quot;https://stackoverflow.com/questions/64819181/how-to-make-basefragment-with-view-binding&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxqsiG/hyK6uHnnDk/zhjUpQsgWiVnwe6J3H2LF0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/64819181/how-to-make-basefragment-with-view-binding&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/64819181/how-to-make-basefragment-with-view-binding&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxqsiG/hyK6uHnnDk/zhjUpQsgWiVnwe6J3H2LF0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;How to make BaseFragment with View Binding&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;My BaseFragment: abstract class BaseFragment : Fragment() { protected abstract val viewModel: ViewModel private var _binding: Bindin...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BaseFragment 를 따로 만들어서 반복되는 코드를 미리 제거할 수 있는 방법 입니다. 가장 많이 사용하는 방법이지 않을까 싶습니다.&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;대안 2) kotlin Delegated properties 사용하기&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;- 구글 샘플앱에 포함된 AutoCleardValue 사용하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/util/AutoClearedValue.kt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/util/AutoClearedValue.kt&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1627918498942&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - android/architecture-components-samples: Samples for Android Architecture Components.&quot; data-og-description=&quot;Samples for Android Architecture Components. . Contribute to android/architecture-components-samples development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/util/AutoClearedValue.kt&quot; data-og-url=&quot;https://github.com/android/architecture-components-samples&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2cQlj/hyK6oUGJZl/rzy4YEWW0UnILQLiX6x0jK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/util/AutoClearedValue.kt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/util/AutoClearedValue.kt&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2cQlj/hyK6oUGJZl/rzy4YEWW0UnILQLiX6x0jK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - android/architecture-components-samples: Samples for Android Architecture Components.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Samples for Android Architecture Components. . Contribute to android/architecture-components-samples development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 공식문서와 달리 구글 샘플 앱의 소스 코드에서는 AutoCleardValue를 만들어서 위임 패턴으로 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;binding 객체에 Delegated properties 를 사용해서 아래와 같이 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1627919619126&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  
private var binding by autoCleared&amp;lt;FragmentUserProfileBinding&amp;gt;() 

override fun onCreateView( 
inflater: LayoutInflater, 
container: ViewGroup?, 
savedInstanceState: Bundle? 
): View? {
binding = FragmentUserProfileBinding.inflate(inflater, container, false) 
return binding.root 
} 

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

binding.txtMain.text = &quot;Hello World!&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;size18&quot;&gt;대안 3)&amp;nbsp; 라이브러리 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kirich1409/ViewBindingPropertyDelegate&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kirich1409/ViewBindingPropertyDelegate&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1627919781406&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - kirich1409/ViewBindingPropertyDelegate: Make work with Android View Binding simpler&quot; data-og-description=&quot;Make work with Android View Binding simpler. Contribute to kirich1409/ViewBindingPropertyDelegate development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kirich1409/ViewBindingPropertyDelegate&quot; data-og-url=&quot;https://github.com/kirich1409/ViewBindingPropertyDelegate&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Yx4GM/hyK6rql9xs/R8z3cM71BKYXnbmgHEeDs0/img.png?width=1200&amp;amp;height=600&amp;amp;face=963_127_1056_228&quot;&gt;&lt;a href=&quot;https://github.com/kirich1409/ViewBindingPropertyDelegate&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kirich1409/ViewBindingPropertyDelegate&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Yx4GM/hyK6rql9xs/R8z3cM71BKYXnbmgHEeDs0/img.png?width=1200&amp;amp;height=600&amp;amp;face=963_127_1056_228');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - kirich1409/ViewBindingPropertyDelegate: Make work with Android View Binding simpler&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Make work with Android View Binding simpler. Contribute to kirich1409/ViewBindingPropertyDelegate development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리를 사용하는 방법도 존재합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방법들 말고도 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;검색을 하면 할수록 반복 코드를 줄이기 위한 다양한 방법들이 많았습니다.&lt;/b&gt;&lt;/span&gt; 어떤 방식을 선택할지는 개발자의 판단인 것 같다는 생각이 듭니다. 이상 Fragment에서 ViewBinding을 사용할 때 주의할점에 대해 적었습니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>fragmentLifeCycle</category>
      <category>fragmentView</category>
      <category>onDestoryView</category>
      <category>ViewBinding</category>
      <category>viewBindingExample</category>
      <category>ViewBindinginAndroid</category>
      <category>viewLifeCycle</category>
      <category>뷰바인딩</category>
      <category>안드로이드뷰바인딩</category>
      <category>프레그먼트에서뷰바인딩</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/58</guid>
      <comments>https://gift123.tistory.com/58#entry58comment</comments>
      <pubDate>Tue, 3 Aug 2021 01:36:51 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (28) ViewLifeCycleOwner</title>
      <link>https://gift123.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 재작성 시작입니다. jetpack 라이브러리를 사용하다보면 뷰모델 프로바이더를 생성할 때나 라이브 데이타 observe 함수에 매개 변수로 lifeCycleOwner 를 전달해줘야하는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색을 하다보면&amp;nbsp;주로 viewLifeCycleOwner 를 매개변수로 전달하는 예제가 많은데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewLifeCycleOwner에 대해 알아보도록 하겠습니다.&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;0. viewLifeCycleOwner 란?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;background-681969_1280.jpg&quot; width=&quot;387&quot; height=&quot;387&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bchL5A/btraLbeBczt/ZWUwd6KTIrl22Iz1igZjs1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bchL5A/btraLbeBczt/ZWUwd6KTIrl22Iz1igZjs1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bchL5A/btraLbeBczt/ZWUwd6KTIrl22Iz1igZjs1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbchL5A%2FbtraLbeBczt%2FZWUwd6KTIrl22Iz1igZjs1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;background-681969_1280.jpg&quot; width=&quot;387&quot; height=&quot;387&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fragment 를 사용하다보면 onCreateView() 함수이 호출 될 때, view 를 생성하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onViewCreated() 함수에서 이미 생성한 view를 사용하는 경우가 많습니다.&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;viewLifeCycleOwner 는 onCreateView() 에서 생성되는 view의 LifeCycle 를 뜻 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;onCreateView() ~ onDestoryView()&amp;nbsp; 라이프 사이클을 가집니다.&lt;/b&gt;&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_1627784032407&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  * &amp;lt;ol&amp;gt;
     * &amp;lt;li&amp;gt;{@link Lifecycle.Event#ON_CREATE created} after {@link #onViewStateRestored(Bundle)}&amp;lt;/li&amp;gt;
     * &amp;lt;li&amp;gt;{@link Lifecycle.Event#ON_START started} after {@link #onStart()}&amp;lt;/li&amp;gt;
     * &amp;lt;li&amp;gt;{@link Lifecycle.Event#ON_RESUME resumed} after {@link #onResume()}&amp;lt;/li&amp;gt;
     * &amp;lt;li&amp;gt;{@link Lifecycle.Event#ON_PAUSE paused} before {@link #onPause()}&amp;lt;/li&amp;gt;
     * &amp;lt;li&amp;gt;{@link Lifecycle.Event#ON_STOP stopped} before {@link #onStop()}&amp;lt;/li&amp;gt;
     * &amp;lt;li&amp;gt;{@link Lifecycle.Event#ON_DESTROY destroyed} before {@link #onDestroyView()}&amp;lt;/li&amp;gt;
     * &amp;lt;/ol&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Fragment LifeCycle과 viewLifeCycle의 차이&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fragment LifeCycle은 onAttch&lt;span style=&quot;color: #384452;&quot;&gt; ~ onDestroy 의 생명주기를 가지며,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #384452;&quot;&gt;Fragment ViewLifeCycle 은 createView ~ destoryView 를 가집니다.&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;Fragment LifeCycle은 프레그먼트가 생성되는 create 과정부터, 프레그먼트가 제거되는 destory 까지 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Fragment ViewLifeCycle은 View 가 생성되는 onCreateView 부터 뷰가 제거되는 onDestoryView 까지만 진행이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ViewLifeCycle은 Fragment LifeCycle 보다 라이프사이클이 짦습니다.&lt;/b&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;그래서 onCreateView ~ onDestoryView를 제외한 Fragment LifeCycle에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;viewLifeCycleOwner를 호출 할 때, 아래와 같은 IllegalStateException 문구가 뜰것 입니다.&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_1627783855496&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   @MainThread
    @NonNull
    public LifecycleOwner getViewLifecycleOwner() {
        if (mViewLifecycleOwner == null) {
            throw new IllegalStateException(&quot;Can't access the Fragment View's LifecycleOwner when &quot;
                    + &quot;getView() is null i.e., before onCreateView() or after onDestroyView()&quot;);
        }
        return mViewLifecycleOwner;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. viewLifeCycleOwner 를 사용 해야할 때&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;1000&quot; data-filename=&quot;fragmentCycle.png&quot; width=&quot;522&quot; height=&quot;752&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC6F7D/btraPuZwjxw/tbNIOutykvGIPzulhBgnZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC6F7D/btraPuZwjxw/tbNIOutykvGIPzulhBgnZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC6F7D/btraPuZwjxw/tbNIOutykvGIPzulhBgnZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC6F7D%2FbtraPuZwjxw%2FtbNIOutykvGIPzulhBgnZK%2Fimg.png&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;1000&quot; data-filename=&quot;fragmentCycle.png&quot; width=&quot;522&quot; height=&quot;752&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;1120&quot; data-filename=&quot;fragmentCycle2.png&quot; width=&quot;522&quot; height=&quot;733&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV4uVF/btraWSYUACR/H2vG8Fs8YkItnLiGqOnKs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV4uVF/btraWSYUACR/H2vG8Fs8YkItnLiGqOnKs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV4uVF/btraWSYUACR/H2vG8Fs8YkItnLiGqOnKs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV4uVF%2FbtraWSYUACR%2FH2vG8Fs8YkItnLiGqOnKs1%2Fimg.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;1120&quot; data-filename=&quot;fragmentCycle2.png&quot; width=&quot;522&quot; height=&quot;733&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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: #384452;&quot;&gt;Activity는 onDestory가 호출되지 않은 상황에서 onCreate()가 호출 될일이 없지만&lt;/span&gt;&lt;span style=&quot;color: #384452;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #384452;&quot;&gt;Fragment는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;onDestroy&lt;span style=&quot;color: #384452;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 호출되지 않은 상태에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;onCreateView()&lt;span style=&quot;color: #384452;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 여러 번 호출될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #384452;&quot;&gt;만약 Lifecycle 에 따라 호출하는 CallBack 함수가 있다면 해당 LifeCycle이 Fragment 일때, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #384452;&quot;&gt;경우에 따라 fragment Destory 가 되지않았는데 CallBack 함수가 여러번 호출되는 상황을 경험 할 수도 있습니다.&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_1627785280718&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//onAttch ~ onDestory
viewModel.liveData.observe(this, { itemId -&amp;gt;
           //LifeCycle이 Destory 되지 않았는데 옵저버 다수 생성 가능성 up
          ...
        })&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1627785232183&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//onCreateView ~ onDestoryView
viewModel.liveData.observe(viewLifecycleOwner, { itemId -&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;대표적으로 LiveData의 매개변수로 this(현재 Fragment)를 전달했다면 경우에 따라 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Fragment 가 Destory 되지 않았는데 새로운 observer가 등록되어 복수의 Observer가 호출되는 현상이 발생할 수 있습니다.&amp;nbsp;&lt;/b&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;그래서 과거에 google Android 개발자 len lake님이 일반적으로 UI를 업데이트 용으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FragmentView LifeCycle를 사용하기를 권장한다는&lt;span style=&quot;letter-spacing: 0px;&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;a href=&quot;https://android.googlesource.com/platform/frameworks/support/+/eb89fcf1decf9044f53330ea4bb689d25d2328b1%5E!/fragment/src/main/java/androidx/fragment/app/Fragment.java&quot;&gt;https://android.googlesource.com/platform/frameworks/support/+/eb89fcf1decf9044f53330ea4bb689d25d2328b1%5E!/fragment/src/main/java/androidx/fragment/app/Fragment.java&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1627785464433&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;Diff - eb89fcf1decf9044f53330ea4bb689d25d2328b1^! - platform/frameworks/support - Git at Google&quot; data-og-description=&quot;authorIan Lake Mon Mar 12 12:57:20 2018 -0700&quot; data-og-host=&quot;android.googlesource.com&quot; data-og-source-url=&quot;https://android.googlesource.com/platform/frameworks/support/+/eb89fcf1decf9044f53330ea4bb689d25d2328b1%5E!/fragment/src/main/java/androidx/fragment/app/Fragment.java&quot; data-og-url=&quot;https://android.googlesource.com/platform/frameworks/support/+/eb89fcf1decf9044f53330ea4bb689d25d2328b1%5E!/fragment/src/main/java/androidx/fragment/app/Fragment.java&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://android.googlesource.com/platform/frameworks/support/+/eb89fcf1decf9044f53330ea4bb689d25d2328b1%5E!/fragment/src/main/java/androidx/fragment/app/Fragment.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://android.googlesource.com/platform/frameworks/support/+/eb89fcf1decf9044f53330ea4bb689d25d2328b1%5E!/fragment/src/main/java/androidx/fragment/app/Fragment.java&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;Diff - eb89fcf1decf9044f53330ea4bb689d25d2328b1^! - platform/frameworks/support - Git at Google&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;authorIan Lake Mon Mar 12 12:57:20 2018 -0700&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;android.googlesource.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;Android 를 잠시 놔두고 컴퓨터 기본 지식, 면접 준비, 개인 사업, 댄스 디렉팅 등등 많은 일을 하고 나니 Android에 대한 지식이 많이 잊혀져 가는 것 같았습니다. 최근 다행히도 좋은 지인을 멘토를 둬서 부족한 기본기가 무엇인지 다시 깨닫게 되었고 차츰 고쳐가는 중입니다.&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;이상 viewLifeCycleOwner에 대해 알아 봤습니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>fragment</category>
      <category>fragmentLifeCycle</category>
      <category>lifecycle</category>
      <category>lifecycleinfragment</category>
      <category>livedata</category>
      <category>viewLifeCycleOwner</category>
      <category>viewLifeCycleOwner란</category>
      <category>라이브데이터</category>
      <category>라이프사이클</category>
      <category>프레그먼트</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/57</guid>
      <comments>https://gift123.tistory.com/57#entry57comment</comments>
      <pubDate>Sun, 1 Aug 2021 11:50:08 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (27) paging 처리에 안드로이드 권장 architecture 적용하기</title>
      <link>https://gift123.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 페이징 처리에 관한 이전 포스팅과 이어지는 내용입니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. 정리&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;news-1172463_1280.jpg&quot; width=&quot;548&quot; height=&quot;411&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AzEG4/btq80n0pvoK/R5JCGgEG7ZVJSTHIJst451/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AzEG4/btq80n0pvoK/R5JCGgEG7ZVJSTHIJst451/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AzEG4/btq80n0pvoK/R5JCGgEG7ZVJSTHIJst451/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAzEG4%2Fbtq80n0pvoK%2FR5JCGgEG7ZVJSTHIJst451%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;news-1172463_1280.jpg&quot; width=&quot;548&quot; height=&quot;411&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;주제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 검색창에 검색어 입력시 검색 Api를 이용해서 네트워크에서 데이터를 20개씩 받아오면 화면에 렌더링하는 페이징 처리&lt;/b&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;- 검색어를 변경했을 때 기존 데이터는 리셋하고 1페이지의 데이터 갯수 20개를 가져옴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 스크롤을 데이터 끝까지 터치 했을 때 다음 페이지의 데이터 갯수 20개를 추가로 불러와서 현재 데이터와 합침&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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. paging 처리에 필요한 핵심 로직 구상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/52&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1625567549241&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;안드로이드 개발 (23) - 페이징 처리 로직&quot; data-og-description=&quot;안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최근에 겪은 문제를 참고해서 정리하는 글입니다. 페이징 처리를 &quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/52&quot; data-og-url=&quot;https://gift123.tistory.com/52&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bcuHCs/hyKN1Mlrsa/WoUtEBBNOIE6QidFMkmKr1/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/cx4Zuf/hyKN7TkUTF/DfPNqDerZrCd0A7yBmvt6K/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/KEeHE/hyKN2R1CDw/ZKinSrKyEy99OA2IkPev30/img.png?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/52&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bcuHCs/hyKN1Mlrsa/WoUtEBBNOIE6QidFMkmKr1/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/cx4Zuf/hyKN7TkUTF/DfPNqDerZrCd0A7yBmvt6K/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/KEeHE/hyKN2R1CDw/ZKinSrKyEy99OA2IkPev30/img.png?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280');&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;안드로이드 개발 (23) - 페이징 처리 로직&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최근에 겪은 문제를 참고해서 정리하는 글입니다. 페이징 처리를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Android에서 실제 paging 처리 구현&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/53&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/53&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1625567584026&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;안드로이드 개발 (24) - 페이징 처리 실제 구현&quot; data-og-description=&quot;https://gift123.tistory.com/52 안드로이드 개발 (23) - 페이징 처리 1편 안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/53&quot; data-og-url=&quot;https://gift123.tistory.com/53&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fjhpc/hyKN5uqiia/5bWl11k23Ra2dY2kbhCrD0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/dznJ0j/hyKOaP2A0F/Os1Cp9ba6J46axhhl8uCXk/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/ylxa6/hyKN5HX0Ym/ox4TNq9g8bazlk8b3kXVqk/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/53&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/53&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fjhpc/hyKN5uqiia/5bWl11k23Ra2dY2kbhCrD0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/dznJ0j/hyKOaP2A0F/Os1Cp9ba6J46axhhl8uCXk/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/ylxa6/hyKN5HX0Ym/ox4TNq9g8bazlk8b3kXVqk/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (24) - 페이징 처리 실제 구현&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://gift123.tistory.com/52 안드로이드 개발 (23) - 페이징 처리 1편 안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Corutine Job사용으로 인한 불필요한 호출 제거, AutoCompleteTextView를 이용한 기존 검색 기록&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/54&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/54&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1625567591204&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;안드로이드 개발 (25) - Coroutine Job 사용과 AutoCompleteTextView를 사용한 검색 기록 보기&quot; data-og-description=&quot;https://gift123.tistory.com/52?category=967702 안드로이드 개발 (23) - 페이징 처리 로직 안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 &quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/54&quot; data-og-url=&quot;https://gift123.tistory.com/54&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dJzegz/hyKNYa1e2L/knahhlT2KKZAKpS2E7MhO1/img.jpg?width=800&amp;amp;height=385&amp;amp;face=0_0_800_385,https://scrap.kakaocdn.net/dn/dYzZ10/hyKN7eIOW1/hfAabroszW0AhzUDkqxQk0/img.jpg?width=800&amp;amp;height=385&amp;amp;face=0_0_800_385,https://scrap.kakaocdn.net/dn/bGZfJ9/hyKOaWPKbg/rlPyyiZmjarGPdkBECqYZ1/img.png?width=1061&amp;amp;height=592&amp;amp;face=0_0_1061_592&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/54&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/54&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dJzegz/hyKNYa1e2L/knahhlT2KKZAKpS2E7MhO1/img.jpg?width=800&amp;amp;height=385&amp;amp;face=0_0_800_385,https://scrap.kakaocdn.net/dn/dYzZ10/hyKN7eIOW1/hfAabroszW0AhzUDkqxQk0/img.jpg?width=800&amp;amp;height=385&amp;amp;face=0_0_800_385,https://scrap.kakaocdn.net/dn/bGZfJ9/hyKOaWPKbg/rlPyyiZmjarGPdkBECqYZ1/img.png?width=1061&amp;amp;height=592&amp;amp;face=0_0_1061_592');&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;안드로이드 개발 (25) - Coroutine Job 사용과 AutoCompleteTextView를 사용한 검색 기록 보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://gift123.tistory.com/52?category=967702 안드로이드 개발 (23) - 페이징 처리 로직 안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Android 권장 아키텍쳐 심플하게 정리&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.android.com/jetpack/guide?hl=en&quot;&gt;https://developer.android.com/jetpack/guide?hl=en&lt;/a&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1625567357892&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;앱 아키텍처 가이드 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;이 가이드에는 고품질의 강력한 앱을 빌드하기 위한 권장사항 및 권장 아키텍처가 포함되어 있습니다. 이 페이지는 Android 프레임워크 기본을 잘 아는 사용자를 대상으로 합니다. Android 앱을 처&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/jetpack/guide?hl=en&quot; data-og-url=&quot;https://developer.android.com/jetpack/guide&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c4GpcB/hyKObnUkP0/M7MS71FICx6TNI42HrNvI0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676,https://scrap.kakaocdn.net/dn/e5kDh/hyKNZOwNlY/0jtQVkGROZCPtdGrBw69Pk/img.png?width=960&amp;amp;height=720&amp;amp;face=0_0_960_720,https://scrap.kakaocdn.net/dn/Tfxd8/hyKNXDaqHc/ZwfiRcvrtKT5l4UisbmqvK/img.png?width=534&amp;amp;height=465&amp;amp;face=0_0_534_465&quot;&gt;&lt;a href=&quot;https://developer.android.com/jetpack/guide?hl=en&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/jetpack/guide?hl=en&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c4GpcB/hyKObnUkP0/M7MS71FICx6TNI42HrNvI0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676,https://scrap.kakaocdn.net/dn/e5kDh/hyKNZOwNlY/0jtQVkGROZCPtdGrBw69Pk/img.png?width=960&amp;amp;height=720&amp;amp;face=0_0_960_720,https://scrap.kakaocdn.net/dn/Tfxd8/hyKNXDaqHc/ZwfiRcvrtKT5l4UisbmqvK/img.png?width=534&amp;amp;height=465&amp;amp;face=0_0_534_465');&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;앱 아키텍처 가이드 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 가이드에는 고품질의 강력한 앱을 빌드하기 위한 권장사항 및 권장 아키텍처가 포함되어 있습니다. 이 페이지는 Android 프레임워크 기본을 잘 아는 사용자를 대상으로 합니다. Android 앱을 처&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 액비티티, 프레그먼트에 대한 의존성을 최대한 없애버릴 것&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반드시 모델 기준으로 UI를 그릴 것 (스마트폰이 어떤 상황에 있어도 유저가 보던 UI를 유지하게끔 데이터 기반으로 보여줘야함)&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;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;720&quot; data-filename=&quot;final-architecture.png&quot; width=&quot;548&quot; height=&quot;411&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kzjlf/btq8TQJWshP/pVKvgzYHEE5GhI4vix76M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kzjlf/btq8TQJWshP/pVKvgzYHEE5GhI4vix76M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kzjlf/btq8TQJWshP/pVKvgzYHEE5GhI4vix76M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKzjlf%2Fbtq8TQJWshP%2FpVKvgzYHEE5GhI4vix76M0%2Fimg.png&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;720&quot; data-filename=&quot;final-architecture.png&quot; width=&quot;548&quot; height=&quot;411&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위 사진 처럼 책임 분리를 나뉘어서 앱 전체의 흐름을 컨트롤 할 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Act,Frag -&amp;gt; ViewModel -&amp;gt; Repository -&amp;gt;&amp;nbsp; Local / Remote)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위 사진 처럼 저수준에서 고수준으로 한 방향으로만 의존 할것 (단방향 흐름)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Activity와 Fragment 는 최대한 멍청해야하고 ViewModel의 상태 변화 체크만 함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ViewModel은 모델과 커뮤니케이션하기 위한 데이터 처리에 관한 비즈니스 로직이나 UI 컴포넌트에 관한 데이터를 제공함 (하지만 뷰모델이 직접적으로 UI 컴포넌트를 알지 못함으로 UI컴포넌트가 변경되어도 뷰모델에게 아무런 영향이 없음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Repository는 ViewModel이 직접적으로 데이터 비즈니스로직 처리를 다해버린다면 관심사 분리에 어긋나기 때문에 Repository를 통해서 한번더 책임 분리를 함, Local,Remote 데이터 소스간의 중재자 역활을 해줌&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;- 가능하면 Remote Data Source를 Local Data Source로 캐싱처리해서 사용하는것을 권장 (네트워크 반복 호출 방지 및 네트워크가 끊겨도 UI 변경이 심하지 않게끔)&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;- 그외 DI 라이브러리 사용 권장, 테스트환경 구축... 등등 권장&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;유저가 어떤 상황에서든 간에 보고 있던 UI 데이터의 흐름이 끊김이 없도록 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;UI는 모델 데이터로부터 렌더링을 해서 보여줘야 합니다.&lt;/b&gt; &lt;span style=&quot;color: #000000;&quot;&gt;각 모듈을 나뉘어서 책임을 분리&lt;/span&gt;&lt;/span&gt;하고 액티비티나 프레그먼트는 뷰모델의 상태변화만 체크해서 변화 감지시 ui의 상태를 변화시키도록 하도록 하는 것이 앱 아키텍처 가이드에서 말하는 권장 설계 패턴입니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 앱 권장 아키텍쳐 적용&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;blueprints-894779_1280.jpg&quot; width=&quot;515&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l8ipq/btq8ZZFuAgW/OwsWELOWj3brG1rL9MKEbK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l8ipq/btq8ZZFuAgW/OwsWELOWj3brG1rL9MKEbK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l8ipq/btq8ZZFuAgW/OwsWELOWj3brG1rL9MKEbK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl8ipq%2Fbtq8ZZFuAgW%2FOwsWELOWj3brG1rL9MKEbK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;blueprints-894779_1280.jpg&quot; width=&quot;515&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Room , jetpack ViewModel,LiveData, Retrofit2,Coroutine&lt;/b&gt;&lt;/span&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 alignLeft&quot; data-origin-width=&quot;485&quot; data-origin-height=&quot;137&quot; data-filename=&quot;화면 캡처 2021-07-06 222707.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CoqzC/btq8UPqQdOY/SAox3IJ41ENChuj3ciMvyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CoqzC/btq8UPqQdOY/SAox3IJ41ENChuj3ciMvyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CoqzC/btq8UPqQdOY/SAox3IJ41ENChuj3ciMvyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCoqzC%2Fbtq8UPqQdOY%2FSAox3IJ41ENChuj3ciMvyk%2Fimg.png&quot; data-origin-width=&quot;485&quot; data-origin-height=&quot;137&quot; data-filename=&quot;화면 캡처 2021-07-06 222707.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) Remote Model&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅에서 페이징처리는 Daum 검색 Api를 활용해서 다음 서버에서 데이터를 가져와 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;네트워크에서 데이터를 받을 준비를 해야합니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 안드로이드에서 통신을 도와주는 라이브러리는 OkHttp 나 Retrofit2이 있습니다.&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;첫번째로 서버에서 보내주는 DB구조에 맞게 클라에서 필요한 부분을 잘 맞춰줍시다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625578104416&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 네트워크에서 받아오는 데이터 형식

@Parcelize
data class DocumentResult(
    val collection: String,
    val datetime: String,
    val display_sitename: String,
    val doc_url: String,
    val height: Int,
    val image_url: String,
    val thumbnail_url: String,
    val width: Int
):Parcelable&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_1625578166113&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 레트로핏 사용
// 레트로핏 셋팅은 이미 해놨다는 가정

interface Api {
    @GET(&quot;v2/search/image&quot;)
    suspend fun getApiSearchList(
        @Query(&quot;query&quot;) query: String,
        @Query(&quot;page&quot;) page: Int = 1,
        @Query(&quot;size&quot;) size: Int = 20
    ): ImgSearchResult
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서는&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 네트워크에서 데이터만 가져와 로컬 데이터로 캐싱 처리할 예정입니다.&lt;/b&gt;&lt;/span&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;(2) Local Model&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 권장 아키텍쳐에 따르면 바로 네트워크에서 받아온 데이터로 UI렌더링해서 사용하지 말고 domain Model로 캐싱처리를 해서 사용하라고 얘기를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 하기 위해서&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 로컬 전용 DB를 만들어 줍시다.&lt;/b&gt; &lt;/span&gt;이 예제에서는 Room을 사용했습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625578218880&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Parcelize
data class Document(

    @PrimaryKey(autoGenerate = true)
    val idx:Long = 0L,
    val thumbnail_url:String,
    val collection: String,
    val datetime: String?,
    val display_sitename: String?,
    val doc_url: String,
    val image_url: String,
):Parcelable&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1625578232783&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Database(entities = [Document::class],version = 2)
abstract class DocumentDB : RoomDatabase() {
    abstract val documentDao: DocumentDao
    companion object {
        @Volatile
        private var INSTANCE: DocumentDB? = null
        fun getInstance(context: Context): DocumentDB = synchronized(this) {
            INSTANCE ?: Room.databaseBuilder(
                context,
                DocumentDB::class.java,
                &quot;Document_Database&quot;
            ).fallbackToDestructiveMigration()
                .build().also {
                    INSTANCE = it
                }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Room 셋팅을 한 후 데이터에 대한 CRUD 함수들도 만들어줍시다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625578264336&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Dao
interface DocumentDao {
    @Query(&quot;SELECT * FROM Document&quot;)
    fun getDocumentList(): LiveData&amp;lt;List&amp;lt;Document&amp;gt;&amp;gt;


    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertDocumentList(documents: List&amp;lt;Document&amp;gt;)

    @Query(&quot;DELETE FROM Document&quot;)
    fun deleteDocumentListAll()
...
}&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;(3) Repository&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Local 저장소와 Remote 저장소를 다 가져옵시다.&lt;/p&gt;
&lt;pre id=&quot;code_1625578337529&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface DocumentLocalDataSource {
    fun getDocumentList(): LiveData&amp;lt;List&amp;lt;Document&amp;gt;&amp;gt;
    suspend fun insertDocumentList(documents: List&amp;lt;Document&amp;gt;)
    suspend fun deleteDocumentListAll()
}

interface DocumentRemoteDataSource {
    suspend fun getSearchResult(query: String, page: Int = 1): ImgSearchResult
}

class DocumentRepository(private val documentDao: DocumentDao) :
    DocumentLocalDataSource, DocumentRemoteDataSource {


    override fun getDocumentList(): LiveData&amp;lt;List&amp;lt;Document&amp;gt;&amp;gt; =
        documentDao.getDocumentList()

...

}&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;아래처럼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크에서 데이터를 받아와서 Local 모델로 변환해주고 로컬 저장소에 저장해주는 함수를 만듭니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625578365559&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //캐싱 처리
    suspend fun convertToLocalDb(documentResults: List&amp;lt;DocumentResult&amp;gt;) {
        insertDocumentList(documentResults.asDomainModel())
    }

    //DocumentResult List를 Document List로 변환
    private fun List&amp;lt;DocumentResult&amp;gt;.asDomainModel() = this.map {
        Document(
            thumbnail_url = it.thumbnail_url,
            collection = it.collection,
            datetime = it.datetime,
            display_sitename = it.display_sitename,
            doc_url = it.doc_url,
            image_url = it.image_url,
        )
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 네트워크가 잠깐 끊겻을 때 UI 깨짐이 덜 하게끔 할수 있기도하고,&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 style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;개발자 마음대로 캐싱의 장점을 상황에 맞게 이용할 수 있습니다.&lt;/b&gt;&lt;/span&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;(4) ViewModel&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;repository를 매개변수로 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 ViewModel의 상태 변화를 View에게 알리기 위해 LiveData를 사용해야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625578425632&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SearchViewModel(
    private val documentRepository: DocumentRepository
) : ViewModel {

    val documentList: LiveData&amp;lt;List&amp;lt;Document&amp;gt;&amp;gt;
        get() = documentRepository.getDocumentList()
...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 LiveData에 Room(로컬디비)에서 DB의 모든 데이터를 리턴해주는 repository의 함수를 초기화 해줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Room DAO의 모든 데이터 리스트를 리턴하는 함수가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveData&amp;lt;T&amp;gt;로 리턴을 하기 때문에 로컬 DB 변화가 있을시 바로 알림을 줄수가 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625578487208&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   @Query(&quot;SELECT * FROM Document&quot;)
    fun getDocumentList(): LiveData&amp;lt;List&amp;lt;Document&amp;gt;&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;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;로컬 데이터의 변화 시 바로 LiveData의 값이 변경 될것이고&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;LiveData&amp;lt;T&amp;gt;.observe로 변화 감지시 UI를 변경할 수 있게끔 합시다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625578607994&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//액티비티 OR 프레그먼트 

viewModel.documentList.observe(requireActivity(), { documents -&amp;gt;
                updateAdapter.submitList(documents.toList())
        })&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;이렇게 ViewModel은 상태만 변화할뿐 View의 존재 사실을 모른채 열심히 자기일만 하게 됩니다.&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;(5) 네트워크로 부터 데이터 호출&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 search함수로 Repository의 Api호출 함수를 사용한다음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크에서 데이터를 받아서 로컬 DB 에 저장하는 로직을 만들면&lt;/p&gt;
&lt;pre id=&quot;code_1625578775897&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    fun search(query: String, page: Int = 1, isNotNetWork: (() -&amp;gt; Unit) = {}) {
        viewModelScope.launch(Dispatchers.Default) {

            val repo = documentRepository
            val apiResult = runCatching { repo.getSearchResult(query, page) }

//apiResult는 네트워크에서 받아온 데이터를 가짐
            apiResult.onSuccess {
            //로컬 db에 저장 
                     repo.convertToLocalDb(it.documents)

            }

        ...
        }
    }&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;그러면 아까 ViewModel의 LiveData와 로컬DB를 연결했기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;DB변화시 ViewModel의 LiveData에 알림이가고 LiveData의 알림을 받아서 UI를 변경 하게 됩니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(모델로 부터 UI가 변경되는 작업)&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;검색어를 새로 입력하거나 스크롤 맨 아래 터치를 하는 부분에 위 함수를 사용하면 언제든지 LiveData의 상태변화 시킬 수 있습니다.&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;(6) Activity or Fragment&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액티비티나 프레그먼트는&amp;nbsp; ViewModel의 LiveData를 관찰하다가 변화 감지시 obseve() 함수를 실행하면서 매개변수로 Local DB도 같이 넘어오게 됩니다. 받은 데이터로 UI로 렌더링해주면 됩니다. 이렇게 ViewModel의 상태만 관찰했다가 변화가 생긴다면 그 즉시 UI가 변경 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ViewModel의 상태를 관찰하거나 사용자로부터 받은 이벤트를 뷰모델에 알리는 준비만 하면 됩니다.&lt;/b&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: #000000;&quot;&gt;&lt;b&gt;관찰 대기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625578984867&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun setUpObserver() {
        
        viewModel.documentList.observe(requireActivity(), { documents -&amp;gt;
            rvSearchList.adapter ?: setAdapter()
         

        })
   ...
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이벤트를 ViewModel에 전달&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625579045448&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; private fun itemFetch( query: String) {
            viewModel.apiSearch(query)
            ...
            }&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity나 Fragment에 LiveData&amp;lt;T&amp;gt;.obseve를 셋팅하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveData 변화 감지시 BindingAdapter에서 바로 값을 넘겨줄수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(xml을 위주로 View로 취급해서 사용하는 구조라면 많이 쓰는 방법입니다.)&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;/p&gt;
&lt;pre id=&quot;code_1625579133153&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@BindingAdapter(&quot;app:setAutoTextList&quot;)
fun AutoCompleteTextView.setAutoTextList(textList: List&amp;lt;String&amp;gt;?) {
    this.setAdapter(
        ArrayAdapter(
            this.context,
            android.R.layout.simple_dropdown_item_1line,
            textList!!
        )
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;뷰모델&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625579167921&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  private var _autoTextList = MutableLiveData&amp;lt;MutableList&amp;lt;String&amp;gt;&amp;gt;()
    val autoTextList: LiveData&amp;lt;MutableList&amp;lt;String&amp;gt;&amp;gt;
        get() = _autoTextList&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;XML&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625579203368&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;AutoCompleteTextView
   android:id=&quot;@+id/et_search&quot;
    app:setAutoTextList=&quot;@{vm.autoTextList}&quot;
     ...
     /&amp;gt;&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;(7) 총 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-사용자가 무언가 클릭해서 데이터를 화면에 비춰줘야할 때-&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;View -&amp;gt;&amp;nbsp; ViewModel -&amp;gt;&amp;nbsp; Model -&amp;gt; ViewModel(상태 변경!)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;viewModel의 상태가 변화하게 된다면 View는 UI를 변경합니다.&amp;nbsp;&lt;/b&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Activity가 신경 쓸 일은 Activity에게만 맡기고 ViewModel이 신경 쓸 일은 뷰모델 에게 맡겼습니다.&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;즉, 유지보수를 할때 데이터 처리 관련은 Model 레이어를 살펴보면 되고 UI관련 레이너는 View단을 보면 되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;각각 책임에 맞는 레이어를 찾아서 필요한 부분만 살펴볼 수 있게 되었습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 주의할 점&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;806&quot; data-filename=&quot;question-2004314_1280.jpg&quot; width=&quot;484&quot; height=&quot;305&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mh63H/btq8ZrvICrn/jokq0Gb6ZKsc3pHCliMOd0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mh63H/btq8ZrvICrn/jokq0Gb6ZKsc3pHCliMOd0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mh63H/btq8ZrvICrn/jokq0Gb6ZKsc3pHCliMOd0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmh63H%2Fbtq8ZrvICrn%2Fjokq0Gb6ZKsc3pHCliMOd0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;806&quot; data-filename=&quot;question-2004314_1280.jpg&quot; width=&quot;484&quot; height=&quot;305&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍 역사가 늘 그래왔듯이 완벽한 개발론, 완벽한 설계는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계는 이론적인 이야기이고 실전 프로그래밍은 다양한 변수가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;프레임워크 환경, 프로젝트의 주제, 프로젝트의 규모, 프로젝트 일정, 사용하는 API 등등을 고려해서 최적의 설계를 만드는 것이 가장 중요하다고 생각합니다.&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: #ee2323;&quot;&gt;&lt;b&gt;기술 부채를 허용하고 항상 깔끔한 컨벤션으로 만들고&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 이론적으로 탄탄한 소프트웨어를 만들 수 있는지는 개발자들의 영원한 숙제&lt;/b&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;이상 페이징 처리를 이용한 아키텍쳐까지 알아봤습니다.&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>Androidarchitecture</category>
      <category>AndroidPaging</category>
      <category>Android개발</category>
      <category>안드로이드</category>
      <category>안드로이드Architecture</category>
      <category>안드로이드Paging</category>
      <category>안드로이드개발</category>
      <category>안드로이드아키텍쳐</category>
      <category>안드로이드페이징처리</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/56</guid>
      <comments>https://gift123.tistory.com/56#entry56comment</comments>
      <pubDate>Tue, 6 Jul 2021 22:50:15 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (26) ISO 8601 변환하기</title>
      <link>https://gift123.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner입니다. 오늘은 짦게 포스팅하려고 합니다.&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;1. ISO 8601&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;516&quot; data-filename=&quot;ISO_8601_explanation.svg.png&quot; width=&quot;445&quot; height=&quot;613&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wsXBB/btq8KYalV6Q/D9YkoCDWhp4k3XBhFF5U5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wsXBB/btq8KYalV6Q/D9YkoCDWhp4k3XBhFF5U5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wsXBB/btq8KYalV6Q/D9YkoCDWhp4k3XBhFF5U5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwsXBB%2Fbtq8KYalV6Q%2FD9YkoCDWhp4k3XBhFF5U5K%2Fimg.png&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;516&quot; data-filename=&quot;ISO_8601_explanation.svg.png&quot; width=&quot;445&quot; height=&quot;613&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근의 어떠한 Api를 사용하다가 서버에서 저장된 time이 ISO 8601형식의 String으로 DB에 있다는걸 확인을 하게 되었습니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ISO 8601의 경우 UTC 시간대인지 확인할 수 있고 비교도 할수 있으며 밀리세컨드의 시간대까지 확인할 수 있는 특징이 있습니다.&lt;/b&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://java119.tistory.com/24&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://java119.tistory.com/24&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1625493032378&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;[ISO] ISO 8601 개념&quot; data-og-description=&quot;ISO&amp;nbsp;날짜&amp;nbsp;형식이란? 정식&amp;nbsp;명칭 Date&amp;nbsp;elements&amp;nbsp;and&amp;nbsp;interchange&amp;nbsp;formats&amp;nbsp;-&amp;nbsp;Information&amp;nbsp;interchange&amp;nbsp;-&amp;nbsp;Representation&amp;nbsp;of&amp;nbsp;dates&amp;nbsp;and&amp;nbsp;times 미쳤다.. 현재&amp;nbsp;제일&amp;nbsp;최신&amp;nbsp;버전&amp;nbsp;ISO&amp;nbsp;8601 구버전&amp;nbsp;IS..&quot; data-og-host=&quot;java119.tistory.com&quot; data-og-source-url=&quot;https://java119.tistory.com/24&quot; data-og-url=&quot;https://java119.tistory.com/24&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/9jbo9/hyKObgwwMv/IPSCnwxzyKdZidAstotAHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/DF88m/hyKOdrScIe/oJ9O5bxgI8qdt9ZjURUWO1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://java119.tistory.com/24&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://java119.tistory.com/24&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/9jbo9/hyKObgwwMv/IPSCnwxzyKdZidAstotAHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/DF88m/hyKOdrScIe/oJ9O5bxgI8qdt9ZjURUWO1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_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;[ISO] ISO 8601 개념&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ISO&amp;nbsp;날짜&amp;nbsp;형식이란? 정식&amp;nbsp;명칭 Date&amp;nbsp;elements&amp;nbsp;and&amp;nbsp;interchange&amp;nbsp;formats&amp;nbsp;-&amp;nbsp;Information&amp;nbsp;interchange&amp;nbsp;-&amp;nbsp;Representation&amp;nbsp;of&amp;nbsp;dates&amp;nbsp;and&amp;nbsp;times 미쳤다.. 현재&amp;nbsp;제일&amp;nbsp;최신&amp;nbsp;버전&amp;nbsp;ISO&amp;nbsp;8601 구버전&amp;nbsp;IS..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;java119.tistory.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;ISO 8601로 서버에 시간을 저장해야하는 경우도 있거나 ISO 8601로 받아서 원하는 형태로 UI렌더링을 해줘야하는 등등 다양한 경우가 있을 것 같아서 이 포스팅에 일어날 수 있는 4가지 경우를 정리를 해놨습니다.&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;2. 변환&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/java/text/SimpleDateFormat&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/reference/java/text/SimpleDateFormat&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1625493329663&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;SimpleDateFormat &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/java/text/SimpleDateFormat&quot; data-og-url=&quot;https://developer.android.com/reference/java/text/SimpleDateFormat?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Asye7/hyKMHOSImY/ol6bA2WxrIv75btAp6j2o0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/java/text/SimpleDateFormat&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/java/text/SimpleDateFormat&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Asye7/hyKMHOSImY/ol6bA2WxrIv75btAp6j2o0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SimpleDateFormat &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;463&quot; data-filename=&quot;화면 캡처 2021-07-05 225655.png&quot; width=&quot;492&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r6Hya/btq8TRnoaTO/I0KOtSlbkmmGq6VYkwDDxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r6Hya/btq8TRnoaTO/I0KOtSlbkmmGq6VYkwDDxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r6Hya/btq8TRnoaTO/I0KOtSlbkmmGq6VYkwDDxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr6Hya%2Fbtq8TRnoaTO%2FI0KOtSlbkmmGq6VYkwDDxk%2Fimg.png&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;463&quot; data-filename=&quot;화면 캡처 2021-07-05 225655.png&quot; width=&quot;492&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;SimpleDateFormat&lt;/b&gt;&lt;/span&gt;를 위주로 사용했습니다. 더 원하는 형태로 다루고 싶다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서 내용의 Date and Time Patterns 표를 참고하면 좋을 것 같습니다.&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;1) 현재 시간을&amp;nbsp; ISO 8601 형식으로 String 변환 저장&lt;/h4&gt;
&lt;pre id=&quot;code_1625492382105&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val sdf = SimpleDateFormat(&quot;yyyy-MM-dd'T'HH:mm:ssZ&quot;, Locale.KOREAN)
val formattedDate = sdf.format(Date())&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;2) ISO 8601 형식의 String을 Date로 변환 저장&lt;/h4&gt;
&lt;pre id=&quot;code_1625492526891&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//time:Stirng
val format = SimpleDateFormat(&quot;YYYY-MM-DD'T'hh:mm:ss.SSSZ&quot;, Locale.KOREAN)
val date = format.parse(time)&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;3) ISO 8601 형식의 Date를 원하는 형식으로 String으로 변환&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1625492645674&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//date:Date
val formatter = SimpleDateFormat(&quot;yyyy-MM-dd&quot;,Locale.KOREAN)
val dateString: String = formatter.format(date!!)&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;4) ISO 8601 String -&amp;gt; Date -&amp;gt; String&lt;/h4&gt;
&lt;pre id=&quot;code_1625492704891&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//time:String
val format = SimpleDateFormat(&quot;YYYY-MM-DD'T'hh:mm:ss.SSSZ&quot;, Locale.KOREAN)
val date = format.parse(time)
val formatter = SimpleDateFormat(&quot;yyyy-MM-dd&quot;,Locale.KOREAN)
val dateString: String = formatter.format(date!!)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 보니 정말 SimpleDateFormat을 자주쓰게 되네요 만약 서버에 ISO 8601를 발견했다! 그러면 util 구조의 파일안에 위의 경우들을 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;모두 함수로 만들어두고 필요할 때 함수를 통해 빠르게 원하는 결과값을 얻어내면 좀더 편한 코딩라이프&lt;/span&gt;&lt;/b&gt;가 될 것 이라 기대합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>androidIso8601</category>
      <category>AndroidTimeConverter</category>
      <category>dateToString</category>
      <category>iso8601</category>
      <category>Iso8601ConverterInAndroid</category>
      <category>iso8601toString</category>
      <category>안드로이드ISO8601</category>
      <category>안드로이드timeConverter</category>
      <category>안드로이드Time변환</category>
      <category>안드로이드시간변환</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/55</guid>
      <comments>https://gift123.tistory.com/55#entry55comment</comments>
      <pubDate>Mon, 5 Jul 2021 23:02:44 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (25) - Coroutine Job 사용과 AutoCompleteTextView를 사용한 검색 기록 보기</title>
      <link>https://gift123.tistory.com/54</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/52?category=967702&quot;&gt;https://gift123.tistory.com/52?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1625486522101&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;안드로이드 개발 (23) - 페이징 처리 로직&quot; data-og-description=&quot;안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최근에 겪은 문제를 참고해서 정리하는 글입니다. 페이징 처리를 &quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/52?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/52&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b56Wyk/hyKN9QtD4m/JjlAq03KyWLB9eGsZyTrWk/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/doHP0E/hyKN76bSf3/nzuxYkngZuutCyE2XOB0ak/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/bnPVOA/hyKN8YkHY4/VX8UkoCTJfzThhxKHpyfn0/img.png?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/52?category=967702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/52?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b56Wyk/hyKN9QtD4m/JjlAq03KyWLB9eGsZyTrWk/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/doHP0E/hyKN76bSf3/nzuxYkngZuutCyE2XOB0ak/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/bnPVOA/hyKN8YkHY4/VX8UkoCTJfzThhxKHpyfn0/img.png?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280');&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;안드로이드 개발 (23) - 페이징 처리 로직&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최근에 겪은 문제를 참고해서 정리하는 글입니다. 페이징 처리를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/53?category=967702&quot;&gt;https://gift123.tistory.com/53?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1625486531926&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;안드로이드 개발 (24) - 페이징 처리 실제 구현&quot; data-og-description=&quot;https://gift123.tistory.com/52 안드로이드 개발 (23) - 페이징 처리 1편 안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/53?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/53&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cbIEbY/hyKMTuVBm0/sU1KddqTUlADYnjvvsp0b0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/b2YALS/hyKN8KNzaf/FeV0joQtku75IsaezWIIx1/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/cxjddK/hyKMQ53W2a/758YIDNkktKWncWZikEQt1/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/53?category=967702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/53?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cbIEbY/hyKMTuVBm0/sU1KddqTUlADYnjvvsp0b0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/b2YALS/hyKN8KNzaf/FeV0joQtku75IsaezWIIx1/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/cxjddK/hyKMQ53W2a/758YIDNkktKWncWZikEQt1/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (24) - 페이징 처리 실제 구현&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://gift123.tistory.com/52 안드로이드 개발 (23) - 페이징 처리 1편 안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner입니다.&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;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;1. Coroutine의 Job를 사용한 addTextChangedListener 내부 로직 컨트롤&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;616&quot; data-filename=&quot;turn-on-2933016_1280.jpg&quot; width=&quot;538&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kEabD/btq8KWQ74OC/AKWY5QXSkjd4G1dEkonypK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kEabD/btq8KWQ74OC/AKWY5QXSkjd4G1dEkonypK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kEabD/btq8KWQ74OC/AKWY5QXSkjd4G1dEkonypK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkEabD%2Fbtq8KWQ74OC%2FAKWY5QXSkjd4G1dEkonypK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;616&quot; data-filename=&quot;turn-on-2933016_1280.jpg&quot; width=&quot;538&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 아래 처럼 텍스트를 적으면 알아서 검색이 되는 구조입니다.&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 편에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;addTextChangedListener안에 api 호출하는 함수를 넣어서 EditText의 text가 하나라도 바뀔때마다 실행이 되게끔 했습니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이와 같이 구현을 해놓으면 문제점이&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 너무 많은 api호출이 일어난다는 점입니다.&amp;nbsp;&lt;/b&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;ㅇ ..아 .. 안.. 안ㄴ.. 안녀 .. 안녕....안녕ㅎ.. 한글자라도 추가되면 계속 api 호출이 일어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- ㅇ 검색 -&amp;gt; 아 검색 -&amp;gt; 안 검색 -&amp;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;페이지네이션의 가장 근본은 많은 데이터를 한꺼번에 불러오지 않게해서 db과부하를 줄이기 위함입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 위처럼 아무런 조치를 취하지 않는다면 클라이언트의 db를 여러번 호출하기 때문에 db 과부하가 똑같이 일어나는 현상이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;817&quot; data-filename=&quot;stress-2379631_1280.jpg&quot; width=&quot;531&quot; height=&quot;339&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnUytn/btq8RTMYvs7/8cHCbkyEavYAPDwgIHkrFk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnUytn/btq8RTMYvs7/8cHCbkyEavYAPDwgIHkrFk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnUytn/btq8RTMYvs7/8cHCbkyEavYAPDwgIHkrFk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnUytn%2Fbtq8RTMYvs7%2F8cHCbkyEavYAPDwgIHkrFk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;817&quot; data-filename=&quot;stress-2379631_1280.jpg&quot; width=&quot;531&quot; height=&quot;339&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;h4 data-ke-size=&quot;size20&quot;&gt;그렇다면 어떻게 해야하는가?&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;woman-687560_1280.jpg&quot; width=&quot;400&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q7kFh/btq8RtVgMrx/40DikCN1Z2GFkAUHXBk4uK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q7kFh/btq8RtVgMrx/40DikCN1Z2GFkAUHXBk4uK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q7kFh/btq8RtVgMrx/40DikCN1Z2GFkAUHXBk4uK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq7kFh%2Fbtq8RtVgMrx%2F40DikCN1Z2GFkAUHXBk4uK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;woman-687560_1280.jpg&quot; width=&quot;400&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 목표는 사용자가 원하는 단어를 적었을 때만 그때 Api함수를 호출하게끔 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;글을 적고 있을땐 Api호출을 막아야하고 글을 다 적고난 뒤,&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 잠시 글쓰기가 멈췃다면 마지막에서야 Api가 호출이 되야합니다.&amp;nbsp;&lt;/b&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;addTextChangedListener는 text가 바뀔때마다 호출이되는 함수인데 내부적으로 어떻게 건드려야할까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 경우 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;코루틴을 사용&lt;/b&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;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;제어에 사용되는 Job 클래스 안에 코투틴 블록을 취소하기 위한 cancel() 함수가 존재합니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&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-origin-width=&quot;1193&quot; data-origin-height=&quot;618&quot; data-filename=&quot;화면 캡처 2021-07-05 212947.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/orw1H/btq8TwqciYx/xJndhgq8XKkjuNTJWN5KL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/orw1H/btq8TwqciYx/xJndhgq8XKkjuNTJWN5KL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/orw1H/btq8TwqciYx/xJndhgq8XKkjuNTJWN5KL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Forw1H%2Fbtq8TwqciYx%2FxJndhgq8XKkjuNTJWN5KL0%2Fimg.png&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;618&quot; data-filename=&quot;화면 캡처 2021-07-05 212947.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null을 허용하는 job 객체를 만들어서 addTextChangedListener 실행시 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;job?.cancel()를 무조건 한번 실행했습니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음&lt;span style=&quot;color: #000000;&quot;&gt; job = lanch{ delay(1000) ... 로직}&amp;nbsp; job 객체에 작업할 내용을 초기화 시켜줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 실행지연을 1초정도 걸어서 만약 1초내에 다시 addTextChagnedListener가 호출되지 않는다면 이전에 예약된 작업을 실행하도록 했습니다.&amp;nbsp;&lt;/b&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;그래서 위 같은 로직으로 만든다면 &quot;ㅇ&quot; , &quot;아&quot; &quot;안&quot;...으로 모두 api 호출이 되지않고 &quot;안녕&quot; 을 적고 1초뒤에 반복 호출 없이 한번의 호출로 &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;코드로는 아래와 같이 작성이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625488443432&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private fun EditText.afterTextChangedCustom(delay: Long, search: (String) -&amp;gt; Unit) {
        var job: Job? = null
        this.addTextChangedListener {
            it ?: return@addTextChangedListener
            job?.cancel()
            job = lifecycleScope.launch {
                delay(delay)
                search(it.toString())
            }
        }
    }&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_1625488473976&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    etSearch.apply {
            afterTextChangedCustom(1000) { itemFetch(ItemFetchMode.SEARCH, it) }
            ...
            }&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;b&gt;변경 후&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;b&gt;2. AutoCompleteTextView를 활용한 검색 기록 보기&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1061&quot; data-origin-height=&quot;592&quot; data-filename=&quot;화면 캡처 2021-07-05 221916.png&quot; width=&quot;512&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kVyRG/btq8TvLHlGg/D2jIc15UpzH8vq7u0upcVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kVyRG/btq8TvLHlGg/D2jIc15UpzH8vq7u0upcVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kVyRG/btq8TvLHlGg/D2jIc15UpzH8vq7u0upcVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkVyRG%2Fbtq8TvLHlGg%2FD2jIc15UpzH8vq7u0upcVk%2Fimg.png&quot; data-origin-width=&quot;1061&quot; data-origin-height=&quot;592&quot; data-filename=&quot;화면 캡처 2021-07-05 221916.png&quot; width=&quot;512&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지 기본적으로 EditText를 사용했지만&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; AutoCompleteTextView를 대신 사용해봅시다.&lt;/b&gt;&lt;/span&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AutoCompleteTextView는 EditText를 상속받아 만들어진 자식클래스 입니다. 그래서 Solid의 원칙 리스코프의 치환 법칙을 떠올리며.. 위대한 갓 구글 안드로이드팀은 분명 부모클래스를 하위클래스가 대체할 수 있게끔 잘 만들었을겁니다. 그래서&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;EditText를 지우고 AutoComplteTextView를 대신 사용해도 문제없이 텍스트 입력이 가능합니다.&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&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 alignLeft&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;571&quot; data-filename=&quot;화면 캡처 2021-07-05 221954.png&quot; width=&quot;425&quot; height=&quot;309&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTIdXg/btq8Pv0mQDw/MU7s1DNfdijtmF7Z1ZFaFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTIdXg/btq8Pv0mQDw/MU7s1DNfdijtmF7Z1ZFaFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTIdXg/btq8Pv0mQDw/MU7s1DNfdijtmF7Z1ZFaFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTIdXg%2Fbtq8Pv0mQDw%2FMU7s1DNfdijtmF7Z1ZFaFK%2Fimg.png&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;571&quot; data-filename=&quot;화면 캡처 2021-07-05 221954.png&quot; width=&quot;425&quot; height=&quot;309&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그렇다면 다른점은? &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;바로 텍스트를 세로형식으로 스피너처럼 나열하는 표시를 만들 수 있습니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AutoComplteTextView는 어댑터 패턴을 사용해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스피너처럼 나열되는 UI 리스트의 아이템을 설정할 수 있도록&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;아래는 안드로이드 공식 샘플(JAVA)입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625489670961&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;         ArrayAdapter&amp;lt;String&amp;gt; adapter = new ArrayAdapter&amp;lt;String&amp;gt;(this,
                 android.R.layout.simple_dropdown_item_1line, COUNTRIES);
         AutoCompleteTextView textView = (AutoCompleteTextView)
                 findViewById(R.id.countries_list);
         textView.setAdapter(adapter);
     }

     private static final String[] COUNTRIES = new String[] {
         &quot;Belgium&quot;, &quot;France&quot;, &quot;Italy&quot;, &quot;Germany&quot;, &quot;Spain&quot;
     };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅의 경우 addTextChangedListener에서 검색에 성공했을때 어댑터에 검색어를 추가를 해서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;autoComplteTextView.setAdatper(adapter) 를&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;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625490200192&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;textList.add(검색한단어)
autoTextView.
setAdapter(ArrayAdapter(this.context,android.R.layout.simple_dropdown_item_1line,textList!!))&lt;/code&gt;&lt;/pre&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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot; data-filename=&quot;crowd-1699137_1280.jpg&quot; width=&quot;505&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJ0WG/btq8UafY648/rJjWvFkNtiVmWCPZvU1bWK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJ0WG/btq8UafY648/rJjWvFkNtiVmWCPZvU1bWK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJ0WG/btq8UafY648/rJjWvFkNtiVmWCPZvU1bWK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJ0WG%2Fbtq8UafY648%2FrJjWvFkNtiVmWCPZvU1bWK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot; data-filename=&quot;crowd-1699137_1280.jpg&quot; width=&quot;505&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 기존 페이징 처리에서 텍스트 자동완성 기능과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불필요한 검색어 호출을 줄일 수 있음으로 사용자를 위한 서비스로 한발자국 가까워 졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 페이징처리를 안드로이드 권장 아키텍쳐를 응용해서 어떻게 코드를 작성하는것이 좋은 방법인가에 대해 고찰을 해보도록 하겠습니다. 이상입니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>Android개발</category>
      <category>AutoCompleteTextView</category>
      <category>Coroutine</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>안드로이드페이징처리</category>
      <category>코루틴</category>
      <category>코루틴job</category>
      <category>페이징처리</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/54</guid>
      <comments>https://gift123.tistory.com/54#entry54comment</comments>
      <pubDate>Mon, 5 Jul 2021 22:22:07 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (24) - 페이징 처리 실제 구현</title>
      <link>https://gift123.tistory.com/53</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;a href=&quot;https://gift123.tistory.com/52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/52&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1625471811514&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;안드로이드 개발 (23) - 페이징 처리 1편&quot; data-og-description=&quot;안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최근에 겪은 문제를 참고해서 정리하는 글입니다. 페이징 처리를 &quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/52&quot; data-og-url=&quot;https://gift123.tistory.com/52&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/brAtgo/hyKN4nWLuz/9pWGpuCdtTM79DAnHJtRy1/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/bndJy6/hyKMSWY5MQ/7pMKSnrtVDk5LMrLrONoF0/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/8Zcgd/hyKMIfLt5H/Sj5pctVkiRdk0x2dJPPMGk/img.png?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/52&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/brAtgo/hyKN4nWLuz/9pWGpuCdtTM79DAnHJtRy1/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/bndJy6/hyKMSWY5MQ/7pMKSnrtVDk5LMrLrONoF0/img.jpg?width=800&amp;amp;height=531&amp;amp;face=0_0_800_531,https://scrap.kakaocdn.net/dn/8Zcgd/hyKMIfLt5H/Sj5pctVkiRdk0x2dJPPMGk/img.png?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280');&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;안드로이드 개발 (23) - 페이징 처리 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다. 최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최근에 겪은 문제를 참고해서 정리하는 글입니다. 페이징 처리를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;b&gt;연습용 api:&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1625477259146&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;Kakao Developers&quot; data-og-description=&quot;카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.&quot; data-og-host=&quot;developers.kakao.com&quot; data-og-source-url=&quot;https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide&quot; data-og-url=&quot;https://developers.kakao.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/p3Bq4/hyKMGhYxoM/vOMyKCVIpmzoPypIgMi201/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/6ChMz/hyKMGWBeT5/ikTwYJCODo40OUlfh5Pkh0/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000,https://scrap.kakaocdn.net/dn/OSpPf/hyKMJsgMJx/k6KvrIjkhiOIkTwZP6fF90/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000&quot;&gt;&lt;a href=&quot;https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.kakao.com/docs/latest/ko/daum-search/dev-guide&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/p3Bq4/hyKMGhYxoM/vOMyKCVIpmzoPypIgMi201/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/6ChMz/hyKMGWBeT5/ikTwYJCODo40OUlfh5Pkh0/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000,https://scrap.kakaocdn.net/dn/OSpPf/hyKMJsgMJx/k6KvrIjkhiOIkTwZP6fF90/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000');&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;Kakao Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(위에서 daum Api 이미지검색을 사용)&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;안녕하세요 Loner 입니다. 1편에 이어서 작성해가보도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1편은 페이징 처리에 관한 간단한 점화식을 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 (현재데이터갯수/20) +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;그래서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;api(page = (현재데이터갯수/20) +1, count = 20)&lt;/b&gt;&lt;/span&gt; 와 같은 수식으로 서버로부터 필요한 데이터만 추가로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 가져올 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;434&quot; height=&quot;277&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;816&quot; data-filename=&quot;phone-1869510_1280 (1).jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sjj4o/btq8T8Wwpqi/UkG32okXzfhgeQj1H9TNvk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sjj4o/btq8T8Wwpqi/UkG32okXzfhgeQj1H9TNvk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sjj4o/btq8T8Wwpqi/UkG32okXzfhgeQj1H9TNvk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsjj4o%2Fbtq8T8Wwpqi%2FUkG32okXzfhgeQj1H9TNvk%2Fimg.jpg&quot; width=&quot;434&quot; height=&quot;277&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;816&quot; data-filename=&quot;phone-1869510_1280 (1).jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 준비&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;woman-498257_1280.jpg&quot; width=&quot;477&quot; height=&quot;318&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LGBho/btq8SBkN5Lr/4lknz0653GwZSP9Dz2qOV1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LGBho/btq8SBkN5Lr/4lknz0653GwZSP9Dz2qOV1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LGBho/btq8SBkN5Lr/4lknz0653GwZSP9Dz2qOV1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLGBho%2Fbtq8SBkN5Lr%2F4lknz0653GwZSP9Dz2qOV1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;woman-498257_1280.jpg&quot; width=&quot;477&quot; height=&quot;318&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1편에서 설명햇듯이 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;검색창이 검색어를 입력하면 그에 맞는 아이템들이 검색이 되고 20개씩 데이터를 가져옵니다. &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;용자가 맨 아래로 스크롤 하면 20개를 추가로 더 가져옵니다.&lt;/b&gt; &lt;/span&gt;이에 맞는 필요한 것들을 우선 셋팅해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) RecyclerView 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;528&quot; data-filename=&quot;화면 캡처 2021-07-05 182503.png&quot; width=&quot;430&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qBDrm/btq8UafIBer/N3OmE2L0n3cOwe0aOMm5fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qBDrm/btq8UafIBer/N3OmE2L0n3cOwe0aOMm5fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qBDrm/btq8UafIBer/N3OmE2L0n3cOwe0aOMm5fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqBDrm%2Fbtq8UafIBer%2FN3OmE2L0n3cOwe0aOMm5fk%2Fimg.png&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;528&quot; data-filename=&quot;화면 캡처 2021-07-05 182503.png&quot; width=&quot;430&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 갯수만큼 Item을 생성해줄 RecyclerView를 사용합시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) Bouncy 사용 혹은 NestedScrollView 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NestedScrollView를 사용하거나&amp;nbsp;아래 Bouncy 라이브러리&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;Bouncy 라이브러리를 사용하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 스크롤 맨 위나 맨 아래에서 바운스 효과를 줄 수 있습니다.&lt;/b&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;UI로 유저와 상호작용하길 좋아하시는 분에게 추천합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Valkriaine/Bouncy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/Valkriaine/Bouncy&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1625473455985&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Valkriaine/Bouncy&quot; data-og-description=&quot;NestedScrollView and RecyclerView with physics-based bouncy overscroll effect - Valkriaine/Bouncy&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Valkriaine/Bouncy&quot; data-og-url=&quot;https://github.com/Valkriaine/Bouncy&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CEOmZ/hyKN2wU79E/AjLLFC1cL0QoVKkXqhje1K/img.png?width=1200&amp;amp;height=600&amp;amp;face=984_150_1062_234&quot;&gt;&lt;a href=&quot;https://github.com/Valkriaine/Bouncy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Valkriaine/Bouncy&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CEOmZ/hyKN2wU79E/AjLLFC1cL0QoVKkXqhje1K/img.png?width=1200&amp;amp;height=600&amp;amp;face=984_150_1062_234');&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;Valkriaine/Bouncy&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;NestedScrollView and RecyclerView with physics-based bouncy overscroll effect - Valkriaine/Bouncy&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(다음기회에 안드로이드에서 기본 지원해주는 NestedScrollView에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bounce 효과를 만드는 법을 포스팅 할 예정입니다.)&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: #ee2323;&quot;&gt;&lt;b&gt; RecyclerView의 부모 노드로 설정하면 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xml에서 아래와 같은 구조를 가지면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625475431513&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;NestedScrollView&amp;gt;

   &amp;lt;EditText/&amp;gt;

  &amp;lt;RecyclerView/&amp;gt;

&amp;lt;/NestedScrollView&amp;gt;&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;(3) EditText&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;587&quot; data-filename=&quot;화면 캡처 2021-07-05 182523.png&quot; width=&quot;313&quot; height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8haKb/btq8UOcpDDt/xEbsVViIHBKSsv8v0Y7N4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8haKb/btq8UOcpDDt/xEbsVViIHBKSsv8v0Y7N4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8haKb/btq8UOcpDDt/xEbsVViIHBKSsv8v0Y7N4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8haKb%2Fbtq8UOcpDDt%2FxEbsVViIHBKSsv8v0Y7N4K%2Fimg.png&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;587&quot; data-filename=&quot;화면 캡처 2021-07-05 182523.png&quot; width=&quot;313&quot; height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색어를 검색할 EditText 입니다. 상단에 가볍게 하나 배치합시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색어 입력시 api를 호출해서 데이터를 받아옵시다.&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;2. 구현&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;colored-pencils-374771_1280.jpg&quot; width=&quot;417&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kS3cV/btq8RtnhTL5/QXZrpk0wsZ5IG3ljd36lN1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kS3cV/btq8RtnhTL5/QXZrpk0wsZ5IG3ljd36lN1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kS3cV/btq8RtnhTL5/QXZrpk0wsZ5IG3ljd36lN1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkS3cV%2Fbtq8RtnhTL5%2FQXZrpk0wsZ5IG3ljd36lN1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;colored-pencils-374771_1280.jpg&quot; width=&quot;417&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;1) xml에서 3개의 UI를 선언&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 에딧텍스트의 검색어 입력시 콜백 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;gt; 기존 RecyclcerView Adapter의 Data를 모두 초기화&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;-&amp;gt; Api(page= 1 , count = 20) 호출로 RecyclerView의 Adapter에 dataList를 update&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;-&amp;gt; Update된 데이터를 RecyclerView Adpater에 다시 설정&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;3) NestedScrollView (혹은 Bouncy)에서 사용자가 맨아래로 움직였을 때 콜백 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;-&amp;gt; Api(page= (현재데이터갯수/20)+1 , count = 20) 호출로 RecyclerView의 Adapter에 dataList를 update&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;-&amp;gt; Update된 데이터를 RecyclerView Adpater에 다시 설정&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3가지만 설정한다면 페이징 처리가 완료 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EditText의 Callback 은&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; addTextedListener&lt;/b&gt;&lt;/span&gt;를 사용해서 설정하시면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1625475911018&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; editText.addTextChangedListener {
 // fun getData(page:Int, count:Int):List&amp;lt;Data&amp;gt;
 getData(1,20)
   ...
  
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지는 위 예제 생략...에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 db를 다 지우고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 recyclerView Adpater에 값을 갱신해주는 로직을 설정하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 위 사안들을 getData함수 내부적으로 셋팅해주셔도 됩니다.&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;ScrollView의 Callback은&lt;b&gt;&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;setOnScrollChangeListener&lt;/span&gt;&lt;/b&gt;를 사용하시면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625476067057&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  scrollView.setOnScrollChangeListener { v, _, scrollY, _, _ -&amp;gt;
            if (scrollY == scrollViewRoot.getChildAt(0).measuredHeight - v.measuredHeight){
                getData((dataList.size/20)+1,20)
                ...
                }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 if문으로 if (scrollY == scrollViewRoot.getChildAt(0).measuredHeight - v.measuredHeight) 조건을 만듭시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt;&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;scrollView의 mesauseredHeight와 &lt;/span&gt;scrollView의 첫번째 자식 뷰의 mesuredHeighht를 뺀 값에서&amp;nbsp; &amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;현재의 scroll 좌표를 표시하는 scrollY와 비교하는 것.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;-&amp;gt;if문의 조건이 true면 사용자가 스크롤을 맨 아래로 이동했다는 의미가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt;맨 아래로 이동했을때 api를 호출해줍시다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;현재데이터 총 갯수를 이용해서 계산하여 다음 페이지 값 구한다음 구한 페이지 값을 api 매개변수로 넣어서 데이터를 호출해줍니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1625476519018&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;getData((dataList.size/20)+1,20)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;editText 콜백과 마찬가지로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지는 위 예제 생략...에 recyclerView Adpater에 값을 갱신해주는 로직을 설정하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 getData함수 내부적으로 셋팅해주시면 됩니다.&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;3. 마무리&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;puzzle-81457_1280.jpg&quot; width=&quot;408&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfOtuU/btq8Qz2kiHg/XebeIQHF77rCXJWGvWJsb0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfOtuU/btq8Qz2kiHg/XebeIQHF77rCXJWGvWJsb0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfOtuU/btq8Qz2kiHg/XebeIQHF77rCXJWGvWJsb0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfOtuU%2Fbtq8Qz2kiHg%2FXebeIQHF77rCXJWGvWJsb0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;puzzle-81457_1280.jpg&quot; width=&quot;408&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 사용하면 아주 간단한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;페이징 처리 구현&lt;/b&gt;&lt;/span&gt;은 끝이 납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 EditText의 addTextedListener는 글자가 하나라도 바뀌면 바로 콜백을 타서 api가 호출된다는 것을 알 수 있습니다.&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;다음 포스팅에서 코루틴의 job 을 이용해서 필요한때만 addTextedListener에서 셋팅한 로직이 사용되도록 하는 방법과 더 나아가 사용자가 검색했던 검색어를 미리보기 추천으로 표시하는 법을 알아보도록 하겠습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>android개발페이징처리</category>
      <category>Android페이징</category>
      <category>Android페이징처리</category>
      <category>Paging</category>
      <category>개발페이지처리</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>안드로이드페이징처리</category>
      <category>페이징처리</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/53</guid>
      <comments>https://gift123.tistory.com/53#entry53comment</comments>
      <pubDate>Mon, 5 Jul 2021 18:39:15 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (23) - 페이징 처리 로직</title>
      <link>https://gift123.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner입니다. 오랜만에 글을 작성하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 많은 사건이 있었고, 수습하느라 정신이 없었네요 이포스팅은 최근에 겪은 문제를 참고해서 정리하는 글입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이징 처리를 해야되는 상황이 있었는데 페이징 처리를 어떻게 해야할지 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. 페이징 처리를 왜 하는가?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;851&quot; data-filename=&quot;book-1283865_1280.jpg&quot; width=&quot;537&quot; height=&quot;357&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PsS6q/btq8QyhJYig/uk7XjhJ38McxLuFX3AGxGK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PsS6q/btq8QyhJYig/uk7XjhJ38McxLuFX3AGxGK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PsS6q/btq8QyhJYig/uk7XjhJ38McxLuFX3AGxGK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPsS6q%2Fbtq8QyhJYig%2Fuk7XjhJ38McxLuFX3AGxGK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;851&quot; data-filename=&quot;book-1283865_1280.jpg&quot; width=&quot;537&quot; height=&quot;357&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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: #ee2323;&quot;&gt;&lt;b&gt;한꺼번에 많은 네트워크 호출을 피하기 위해서 사용&lt;/b&gt;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 리스트를 보여준다고 할때 사용자가 스크롤 하기전 최대로 볼 수 있는 아이템 갯수가 15개이지만 서버에 있는 전체 데이터량이 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;그래서 사용자가 원하는 만큼만 상황에 맞게 보여준다면&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; DB의 불필요한 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;과부하를 줄일 수 있고&lt;/b&gt;&lt;/span&gt; 사용자가 보지 않았는데 미리 데이터를 가져올 필요도 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Android에서 페이징처리를 하려면?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;android-2995824_1280.png&quot; width=&quot;305&quot; height=&quot;566&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MISh7/btq8Rtm4dLl/1zvHwWJcKRd8w3fKmiVsn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MISh7/btq8Rtm4dLl/1zvHwWJcKRd8w3fKmiVsn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MISh7/btq8Rtm4dLl/1zvHwWJcKRd8w3fKmiVsn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMISh7%2Fbtq8Rtm4dLl%2F1zvHwWJcKRd8w3fKmiVsn0%2Fimg.png&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;android-2995824_1280.png&quot; width=&quot;305&quot; height=&quot;566&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 두가지 방법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 기본적으로 jetPack에서 지원해주는 Paging 라이브러리를 사용한다.&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;&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 로직을 만드는 2번을 선택했습니다.&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 페이징 처리를 이용한 구현 조건&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;checklist-1622517_1280.png&quot; width=&quot;531&quot; height=&quot;531&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Etm1u/btq8TP3GiW6/u5sq3ohgHKbuLbuDTClKjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Etm1u/btq8TP3GiW6/u5sq3ohgHKbuLbuDTClKjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Etm1u/btq8TP3GiW6/u5sq3ohgHKbuLbuDTClKjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEtm1u%2Fbtq8TP3GiW6%2Fu5sq3ohgHKbuLbuDTClKjk%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;checklist-1622517_1280.png&quot; width=&quot;531&quot; height=&quot;531&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 검색창으로 단어를 검색하면 네트워크 db로부터 데이터를 가져와서 ui로 보여줄 것&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) 데이터를 가져올때 20 개씩 추가로 가져올 것&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) 네트워크 서버로부터 DB를 가져올때 page , count 매개변수를 통해서 필요한 데이터와 갯수를 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX: &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;getData(page=1 , count= 20 ) ,getData( page = 2 , count =20 ) ....&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;4) 검색어가 바뀌면 기존 db는 삭제가 되고 다시 page = 1 , count = 20 부터 가져와야합니다.&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) 검색 결과 데이터들을 보여주는 리스트가 맨 하단 아래 스크롤에 도착하면 다음 페이지의 데이터 20개를 가져와서&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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물로 위와같이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;스크롤이 맨 아래에 내려오면 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;다음페이지의 데이터 20개를 추가로 불러옵니다.&lt;/span&gt;&lt;/b&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5가지 조건에 맞는 로직을 만들어야했습니다.&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;3. 페이징 처리 로직&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;mathematics-1509559_1280.jpg&quot; width=&quot;582&quot; height=&quot;388&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjJukw/btq8T98Lk5K/xwCha52fqsuzjvT8jzmhK1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjJukw/btq8T98Lk5K/xwCha52fqsuzjvT8jzmhK1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjJukw/btq8T98Lk5K/xwCha52fqsuzjvT8jzmhK1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjJukw%2Fbtq8T98Lk5K%2FxwCha52fqsuzjvT8jzmhK1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;mathematics-1509559_1280.jpg&quot; width=&quot;582&quot; height=&quot;388&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 첫번째로 생각난 것은 page 객체를 만들어서 상황에 맞게 1씩 추가하거나 다시 1로 할당하거나를 생각했지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 나중에 유지보수 할때 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;실수로 page 객체를 건드려서 부수적인 문제들이 계속나타날까봐 생각을 바꿧습니다.&lt;/b&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;- 두번째로 생각난 것은 빈틈없는 점화식으로 page객체를 사용하지 않고 페이징 처리를 하는것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 전역 객체를 하나라도 없애고 싶은 마음에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;두번째 방식을 선택했습니다.&lt;/b&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;size18&quot;&gt;만든 로직은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 ui로 렌더링해서 사용되고 있는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 데이타 총 갯수를 나누기 20를 해줍니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(데이타 갯수 / 20)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 20개면 1, 40개면 2, 60개면 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;즉 현재 데이터갯수만으로 현재 페이지를 알 수가 있습니다.&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;그렇다면 api의 매개변수로&amp;nbsp; page , count를 설정해서 데이터 리스트를 가져온다 했을 때&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;Api (page = (currentSize/20) + 1 , count = 20&lt;/b&gt;) 로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;page객체를 따로 써서 컨트롤하지 않아도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이미 불러온 데이터 갯수에 따라 다음페이지를 꾸준히 가져올 수 있습니다.&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;페이징 처리 테스트 코드&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1625469253414&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Test
    fun `API검색시_페이지_변경_로직_체크`(){

        val list = mutableListOf&amp;lt;Int&amp;gt;()
        assertEquals((list.size/20),0)
        repeat(20){
            list.add(it)
        }
        assertEquals((list.size/20),1)
        repeat(20){
            list.add(it)
        }
        assertEquals((list.size/20),2)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 UnitTest에 대해 모르시는 분들을 위해 말씀드리자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;assertEquals()의 첫번째 인자는 결과값, 두번째 인자는 예상값 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 값이 다르면 false 고 같다면 true로 확인됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;위 테스트 코드는 실패 없이 모두 true입니다.&amp;nbsp;&lt;/b&gt;&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_1625469328901&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  val list = mutableListOf&amp;lt;Int&amp;gt;()
        assertEquals((list.size/20),0)
          repeat(20){
            list.add(it)
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨처음 검색하기전 데이터 갯수가 0일 때 20을 나누면 당연히 0으로 떨어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;assertEquels는 true 였습니다.&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;여기서 page = (list.size/20)+1 , count =20 를 한다면 결국,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;page= 1 , count =20 이기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;첫 검색시 기본적으로 1번 페이지의 데이터를 가져올 수 있습니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(위 코드는 로컬 테스트 코드 이기 때문에 실제 api page = 1 , count = 20 를 넣지 않고 반복문으로 데이터를 추가했습니다.)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1625469379884&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;     assertEquals((list.size/20),1)
        repeat(20){
            list.add(it)
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이미 한번 데이터를 가져왔다면 현재 데이터 갯수는 총 20일 겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 20 나누기 20 을 하면 1이 나옵니다. assertEquls는 true가 됩니다.&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;마찬가지로 page= (list.size/20) +2 를 한다면 사실상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;getData(page = 2 ,count = 20)로 검색하게 됩니다.&amp;nbsp;&lt;/b&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;/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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 페이징 처리 조건 충족&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;cup-1009225_1280.jpg&quot; width=&quot;508&quot; height=&quot;508&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rMu8r/btq8Twwx9pX/z3KcL5t0NyYWjZwdHHRNKk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rMu8r/btq8Twwx9pX/z3KcL5t0NyYWjZwdHHRNKk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rMu8r/btq8Twwx9pX/z3KcL5t0NyYWjZwdHHRNKk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrMu8r%2Fbtq8Twwx9pX%2Fz3KcL5t0NyYWjZwdHHRNKk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;cup-1009225_1280.jpg&quot; width=&quot;508&quot; height=&quot;508&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 터치로 스크롤해서 데이터 끝에 도달하는 순간 현재 size를 이용해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(현재사이즈/20)+1를 Api의 page 매개변수에 넣어서 다음 데이터를 가져오면 됩니다.&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번째 조건 이었던 검색어 변경시 데이터를 모두지우고 Api (page= 1 , count =20)로 불러와야 하는 경우는&amp;nbsp;&lt;br /&gt;검색 기능을 통해서 데이터를 가져올때 기존 데이터를 삭제해주고 Api(page =1 , count = 20)을 적용하면 됩니다.&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;이제 (현재데이터 사이즈/20)+1 를 Api 매개변수 page에 넣어주고 나머지는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;프레임 워크 특성을 이용해서&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;콜백 리스너만 잘 설정해주면 1~5번의 문제는 모두 충족하며 구현 할 수 있습니다.&lt;/b&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(EditText, AutoCompleteTextView, RecyclerView,외 라이브러리 하나 사용예정 )&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>Android개발</category>
      <category>Paging</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>안드로이드개발페이징처리</category>
      <category>안드로이드페이지네이션</category>
      <category>안드로이드페이징처리</category>
      <category>페이지네이션</category>
      <category>페이징처리</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/52</guid>
      <comments>https://gift123.tistory.com/52#entry52comment</comments>
      <pubDate>Mon, 5 Jul 2021 16:46:47 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드(Android)가 태어난 과정</title>
      <link>https://gift123.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; Loner&lt;/b&gt;&lt;/span&gt;입니다. 본래 하는일도 안드로이드 개발자이고 취미도 안드로이드 개발 사이드 협업 입니다. 이 포스팅하는 당일날 회의 2시간을 앞두고 간단히 블로그 포스팅 하나 남겨보려고 합니다. 내가 왜 안드로이드를 상당히 마음에 들어할까 라는 &lt;s&gt;쓸데없는 철학적인 &lt;/s&gt;생각이 들어서 &lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;안드로이드OS가 어디서 온것인지 정리를 해봤습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;694&quot; data-filename=&quot;1_18N1NWBg1sFyOa3bqrIztA.jpeg&quot; width=&quot;546&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H7H7u/btq7L0rtGsJ/yp8UrFVTqfqyBAlSqk9020/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H7H7u/btq7L0rtGsJ/yp8UrFVTqfqyBAlSqk9020/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H7H7u/btq7L0rtGsJ/yp8UrFVTqfqyBAlSqk9020/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH7H7u%2Fbtq7L0rtGsJ%2Fyp8UrFVTqfqyBAlSqk9020%2Fimg.jpg&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;694&quot; data-filename=&quot;1_18N1NWBg1sFyOa3bqrIztA.jpeg&quot; width=&quot;546&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. Android의 정체성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;b&gt;&amp;nbsp;The platform changing what mobile can do.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;모바일의 가능성을 바꾸는 플랫폼&quot;&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.android.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.android.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1624185790306&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Android | The platform pushing what&amp;rsquo;s possible&quot; data-og-description=&quot;Find the latest and greatest on the world&amp;rsquo;s most powerful mobile platform. Browse devices, explore resources and learn about the latest updates.&quot; data-og-host=&quot;www.android.com&quot; data-og-source-url=&quot;https://www.android.com/&quot; data-og-url=&quot;https://www.android.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bnFXJf/hyKCHnHbiR/VlVc5cQ4IRF5lzKII0E3Z1/img.jpg?width=512&amp;amp;height=269&amp;amp;face=0_0_512_269&quot;&gt;&lt;a href=&quot;https://www.android.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.android.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bnFXJf/hyKCHnHbiR/VlVc5cQ4IRF5lzKII0E3Z1/img.jpg?width=512&amp;amp;height=269&amp;amp;face=0_0_512_269');&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 | The platform pushing what&amp;rsquo;s possible&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Find the latest and greatest on the world&amp;rsquo;s most powerful mobile platform. Browse devices, explore resources and learn about the latest updates.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일에는 많은 뜻이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@readhead/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://brunch.co.kr/@readhead/5&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1624185528127&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;모바일은 어떻게 전화기가 되었나&quot; data-og-description=&quot;뜻밖의 인문학 | 언어는 쓰이는 장소나 시대에 따라 의미가 변하다 보면 같은 단어라도 이전에 사용했던 의미가 달라져 조금 다른 의미를 갖거나, 유사하지만 표현할 수 있는 범위가 줄어들어 &quot; data-og-host=&quot;brunch.co.kr&quot; data-og-source-url=&quot;https://brunch.co.kr/@readhead/5&quot; data-og-url=&quot;https://brunch.co.kr/@readhead/5&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eJdJj/hyKCLRaEJA/OxiE1ktmxJufRLN7iDTNeK/img.jpg?width=1280&amp;amp;height=1919&amp;amp;face=0_0_1280_1919,https://scrap.kakaocdn.net/dn/MwpJr/hyKCOG9ezh/V5LY0jJhMCVm9zQT8HVBZK/img.jpg?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/hXmfa/hyKCHH1rIG/p1MW9GIwnISf5Iez4hEXO0/img.jpg?width=1280&amp;amp;height=706&amp;amp;face=606_158_1086_236&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@readhead/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://brunch.co.kr/@readhead/5&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eJdJj/hyKCLRaEJA/OxiE1ktmxJufRLN7iDTNeK/img.jpg?width=1280&amp;amp;height=1919&amp;amp;face=0_0_1280_1919,https://scrap.kakaocdn.net/dn/MwpJr/hyKCOG9ezh/V5LY0jJhMCVm9zQT8HVBZK/img.jpg?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/hXmfa/hyKCHH1rIG/p1MW9GIwnISf5Iez4hEXO0/img.jpg?width=1280&amp;amp;height=706&amp;amp;face=606_158_1086_236');&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;brunch.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;모바일은 '움직이는' 이라는 뜻보다&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; '이동이 가능한'&lt;/b&gt;&lt;/span&gt; 혹은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;'이동이 자유로운'&lt;/b&gt;&lt;/span&gt;의 뜻으로 더 많이 사용되면서, 휴대할 수 있는 물건이나 이동하기 용이한 물건들 혹은 상태를 나타내는 말로 더 많이 쓰임을 받게 되었다.&quot; - by.김구암씨&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;많은 사람들이 안드로이드하면 당연히 스마트폰 os로 먼저 생각을 합니다. 하지만 안드로이드는 태블릿, 시계 , 차량 등등 다양한 기기들을 지원합니다. 그리고 안드로이드의 공식 정체성은 위에서 설명한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;모바일의 가능성을 바꾸는 플랫폼&lt;/span&gt;&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;으로 모바일 기기의 가능성을 최대한 넓혀주는 모바일 전용 운영체제가 바로 &lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;b&gt;Android&lt;/b&gt;&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;wall-2831911_1280.png&quot; width=&quot;542&quot; height=&quot;361&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0ZBIf/btq7KmBx3sX/Nyi2EoLAQWQiRlgQKoLBPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0ZBIf/btq7KmBx3sX/Nyi2EoLAQWQiRlgQKoLBPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0ZBIf/btq7KmBx3sX/Nyi2EoLAQWQiRlgQKoLBPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0ZBIf%2Fbtq7KmBx3sX%2FNyi2EoLAQWQiRlgQKoLBPk%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;wall-2831911_1280.png&quot; width=&quot;542&quot; height=&quot;361&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 안드로이드에서 빼놓을 수 없는 매력은 개방적이라는 것 입니다. 제조사, 개발자들에게 전체적으로 Open이 된 시스템이라서 관련 전문가라면 누구든지 어렵지 않게 접할 수 있는것이 안드로이드고 개방형이라는 특징으로 시장을 잡았고&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; 현재&lt;/span&gt;&lt;/b&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;모바일 OS 점유율71.82%를 차지하고 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;176&quot; data-filename=&quot;다운로드.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt7Nxn/btq7CPMpxsR/pKwjop7gRbpAg8FIm2wOl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt7Nxn/btq7CPMpxsR/pKwjop7gRbpAg8FIm2wOl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt7Nxn/btq7CPMpxsR/pKwjop7gRbpAg8FIm2wOl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt7Nxn%2Fbtq7CPMpxsR%2FpKwjop7gRbpAg8FIm2wOl0%2Fimg.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;176&quot; data-filename=&quot;다운로드.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드는 어째서 오픈소스로 제작되었는지, 어떻게 전세계에서 가장 많이 사용하는 os가 되었는지 한번 살펴보도록 하겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. 한 아이가 태어남&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;719&quot; data-filename=&quot;statue-of-liberty-267948_1280.jpg&quot; width=&quot;572&quot; height=&quot;321&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Iwj3i/btq7DrSlRFS/i0SaJKZFU5I3vYGWNeUtSk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Iwj3i/btq7DrSlRFS/i0SaJKZFU5I3vYGWNeUtSk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Iwj3i/btq7DrSlRFS/i0SaJKZFU5I3vYGWNeUtSk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIwj3i%2Fbtq7DrSlRFS%2Fi0SaJKZFU5I3vYGWNeUtSk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;719&quot; data-filename=&quot;statue-of-liberty-267948_1280.jpg&quot; width=&quot;572&quot; height=&quot;321&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1964년 뉴욕 차파콰에서 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;루빈이라는 한 아이가 태어납니다.&lt;/span&gt; &lt;/b&gt;그리고 그 집안에는 마케팅 회사를 운영하는 아버지가 전자기기의 사진을 찍어 마케팅을 활용하는 방식으로 회사를 운영하고 있었기 때문에 이 집안에는 늘 최신 전자 기기로 가득했습니다. 이 집에서 태어난 아이는 언제나 전자 기계에만 관심이 가득 차있었습니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;847&quot; data-filename=&quot;apple-1853259_1280.jpg&quot; width=&quot;521&quot; height=&quot;345&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lzFQN/btq7JQ3Fkqn/JQ5wNkRCUkd1DKcfgtbdH0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lzFQN/btq7JQ3Fkqn/JQ5wNkRCUkd1DKcfgtbdH0/img.jpg&quot; data-alt=&quot;사과로 유명한 그 회사&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lzFQN/btq7JQ3Fkqn/JQ5wNkRCUkd1DKcfgtbdH0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlzFQN%2Fbtq7JQ3Fkqn%2FJQ5wNkRCUkd1DKcfgtbdH0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;847&quot; data-filename=&quot;apple-1853259_1280.jpg&quot; width=&quot;521&quot; height=&quot;345&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사과로 유명한 그 회사&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;span style=&quot;color: #000000;&quot;&gt;루빈은 자신의 운명인 마냥 컴퓨터와 항상 함께하는 삶을 하게되고 &lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;b&gt;1989년 루빈은 Apple에 취직하게 됩니다.&lt;/b&gt; &lt;/span&gt;그러던 어느날 &quot;모바일 플랫폼&quot; 분야라는 것을 알게 되고 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;루빈은 모바일 분야에 관심이 생깁니다.&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 새로운 도전&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;429&quot; data-filename=&quot;pda.jpg&quot; width=&quot;401&quot; height=&quot;344&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgs7xE/btq7L1DUYXQ/E9H4AZETbjzq20a8bJMbG1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgs7xE/btq7L1DUYXQ/E9H4AZETbjzq20a8bJMbG1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgs7xE/btq7L1DUYXQ/E9H4AZETbjzq20a8bJMbG1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgs7xE%2Fbtq7L1DUYXQ%2FE9H4AZETbjzq20a8bJMbG1%2Fimg.jpg&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;429&quot; data-filename=&quot;pda.jpg&quot; width=&quot;401&quot; height=&quot;344&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span style=&quot;color: #000000;&quot;&gt;그 당시 Apple 내의 플랫폼의 주요 프로젝트는 PDA용 운영체제 였습니다. 이 프로젝트에 참여한 개발자들은 곧 PDA와 같은 소형기기가 세상을 혁신할 것이다 라는 확신이 있었지만 아쉽게도 현실은 그렇지 않았습니다. Apple 운영진은 점점 프로젝트를 축소 시키기 시작했습니다.&lt;/span&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;460&quot; data-filename=&quot;landscape-4455748_1280.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAWzCU/btq7I0lcJVn/cXGdQqwgCry021uN0r7K00/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAWzCU/btq7I0lcJVn/cXGdQqwgCry021uN0r7K00/img.jpg&quot; data-alt=&quot;새로운 곳으로 출발하는 세명의 개발자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAWzCU/btq7I0lcJVn/cXGdQqwgCry021uN0r7K00/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAWzCU%2Fbtq7I0lcJVn%2FcXGdQqwgCry021uN0r7K00%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;460&quot; data-filename=&quot;landscape-4455748_1280.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;새로운 곳으로 출발하는 세명의 개발자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;span style=&quot;color: #000000;&quot;&gt;&quot;소형 기기의 세상이 올 것이다&quot; 라는 확신을 가졌던 프로젝트에 참여한 3명의 개발자는 1990년 회사를 새로 설립함으로써 그들의 도전을 위한 새출발을 하게됩니다. &lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;모바일에 관심을 보였던 루빈은 Apple를 떠나서 이들이 새로 설립한 회사의 엔지니어로 활약하게 됩니다.&amp;nbsp;&lt;/b&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 style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 운영체제와 인터페이스를 포함한 통합 프로그램을 만드는 프로젝트인 매직캡을 개발하기 시작했습니다.&lt;/b&gt;&lt;/span&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 alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;721&quot; data-filename=&quot;woman-1006100_1280.jpg&quot; width=&quot;424&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MXPZG/btq7HEpapob/UgfnkZNcZvyuFjnkt0YGTk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MXPZG/btq7HEpapob/UgfnkZNcZvyuFjnkt0YGTk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MXPZG/btq7HEpapob/UgfnkZNcZvyuFjnkt0YGTk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMXPZG%2Fbtq7HEpapob%2FUgfnkZNcZvyuFjnkt0YGTk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;721&quot; data-filename=&quot;woman-1006100_1280.jpg&quot; width=&quot;424&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만.. 시간이흘러.. 이 회사는 Apple 분사 시켜주기로 해서 설립한 회사이지만 Apple의 지원이 미미해지기 시작했고 프로젝트 진행은 더뎌지면서 결국 &lt;span style=&quot;color: #ffffff; background-color: #ee2323;&quot;&gt;&lt;b&gt;1995년 그들의 도전을 실패로 끝나게 됩니다.&amp;nbsp;&lt;/b&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: #ee2323;&quot;&gt;&lt;b&gt;하지만 루빈은 이 경험을 통해 OS 개발 기초를 다질 수 있습니다.&amp;nbsp;&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 사이드 킥&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;315&quot; data-filename=&quot;Sidekick3.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blafFI/btq7Mb7xRqS/H0JcjYA9rWFiDkgnTDWRY1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blafFI/btq7Mb7xRqS/H0JcjYA9rWFiDkgnTDWRY1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blafFI/btq7Mb7xRqS/H0JcjYA9rWFiDkgnTDWRY1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/blafFI/btq7Mb7xRqS/H0JcjYA9rWFiDkgnTDWRY1/img.gif&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;315&quot; data-filename=&quot;Sidekick3.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 어느날.. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;루빈은 데인져 라는 회사를 공동 설립해 다시 새로운 도전을 하게 됩니다.&lt;/b&gt;&lt;/span&gt; 모바일 기기 &lt;b&gt;&amp;lsquo;사이드킥(SideKick)&amp;rsquo;&lt;/b&gt;을 만들게 됩니다. 사이드킥은 슬라이드방식의 쿼티 휴대폰으로서 당시에는 상당히 획기적인 제품으로 인정 받았습니다. 그리고 마이크로소프트 사가 눈독들여서 해당 회사를 인수합병하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;583&quot; data-filename=&quot;it_photo_58788.jpg&quot; width=&quot;420&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mNKVy/btq7LZ0pcUe/DxNKFRQqv6VSXw4BveB3F1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mNKVy/btq7LZ0pcUe/DxNKFRQqv6VSXw4BveB3F1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mNKVy/btq7LZ0pcUe/DxNKFRQqv6VSXw4BveB3F1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmNKVy%2Fbtq7LZ0pcUe%2FDxNKFRQqv6VSXw4BveB3F1%2Fimg.jpg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;583&quot; data-filename=&quot;it_photo_58788.jpg&quot; width=&quot;420&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만.. 아쉽게도 그 당시 &lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;루빈이 HipTop이라는 OS를 만들었으나 큰 관심을 받지 못했습니다.&lt;/b&gt;&lt;/span&gt; 그 당시 OS의 점유율은 윈도우가 모두 압승이 었고 제조사들은 눈물 머금으며&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; MS에 고가의 로열티를 지불하는것이 당연한 현실이었습니다.&lt;/b&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;이 광경을 보던 루빈은 OS시장을 독점한 윈도우와 다른 매력으로 OS 개방형 시스템으로 만들어 누구든지 무료로 모바일 OS를 사용할 수 있도록 만들고, 소프트웨어 파워가 무엇인지 보여주기 위해&lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;b&gt; Android 회사를 설립하게 됩니다.&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 안드로이드 사 설립&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;512&quot; data-filename=&quot;large_thumbnail.webp&quot; width=&quot;597&quot; height=&quot;336&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFLZd/btq7DnWbUxV/iQZfbl0cFgUJZ6kkbYzUaK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFLZd/btq7DnWbUxV/iQZfbl0cFgUJZ6kkbYzUaK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFLZd/btq7DnWbUxV/iQZfbl0cFgUJZ6kkbYzUaK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFLZd%2Fbtq7DnWbUxV%2FiQZfbl0cFgUJZ6kkbYzUaK%2Fimg.webp&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;512&quot; data-filename=&quot;large_thumbnail.webp&quot; width=&quot;597&quot; height=&quot;336&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈형 모바일 OS를 만들겟다고 마음 먹은 루빈은 엄청난 열정으로 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;하지만.. &lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;1년이 지나 그가 모아둔 돈은 모두 고갈이 되었고 월세 낼돈도 모잘라서 경제난에 허덕이기 시작합니다.&amp;nbsp;&lt;/b&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;848&quot; data-filename=&quot;man-2635036_1280.jpg&quot; width=&quot;582&quot; height=&quot;386&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvOiBJ/btq7EFIBGXL/YZmXduMkrD5niAruZo7mk1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvOiBJ/btq7EFIBGXL/YZmXduMkrD5niAruZo7mk1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvOiBJ/btq7EFIBGXL/YZmXduMkrD5niAruZo7mk1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvOiBJ%2Fbtq7EFIBGXL%2FYZmXduMkrD5niAruZo7mk1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;848&quot; data-filename=&quot;man-2635036_1280.jpg&quot; width=&quot;582&quot; height=&quot;386&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;b&gt;루빈은 이 경영진에게 현실에 존재하지 않았던 모바일 전용 OS 안드로이드에 관해 설명하기 시작합니다.&lt;/b&gt;&lt;/span&gt;&amp;nbsp;그외 많은 제조사들이 비싼돈을 주고 사용하면서 스트레스를 받으니 누구든지 사용할 수 있는 개방형 모바일 OS 안드로이드를 만들어서 OS 시장에서 점유율을 차지할 것임을 이야기 합니다.&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;5. 운명의 시작&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 지나 2005년..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루빈의 이야기를 들어 주었던&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 경영진은 회사의 CEO가 되고 Android 사를 공식적으로 인수하면서 Android 회사를 만든 앤디 루빈의 안드로이드 OS 프로젝트에 본격적인 지원을 해주기 시작&lt;/b&gt;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;386&quot; data-filename=&quot;larry-page.jpg&quot; width=&quot;537&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uO5tA/btq7HEQhqXT/wKPqBBO1GSkaKFANT3hqNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uO5tA/btq7HEQhqXT/wKPqBBO1GSkaKFANT3hqNk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uO5tA/btq7HEQhqXT/wKPqBBO1GSkaKFANT3hqNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuO5tA%2Fbtq7HEQhqXT%2FwKPqBBO1GSkaKFANT3hqNk%2Fimg.jpg&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;386&quot; data-filename=&quot;larry-page.jpg&quot; width=&quot;537&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 회사 이름은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Google&lt;/b&gt;&lt;/span&gt;입니다. 루빈의 이야기를 들었던 &lt;span style=&quot;color: #ffffff; background-color: #ee2323;&quot;&gt;&lt;b&gt;경영진은 구글의 공동 창업자 래리페이지&lt;/b&gt;&lt;/span&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 alignLeft&quot; width=&quot;591&quot; height=&quot;417&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;904&quot; data-filename=&quot;google-485611_1280.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpUEOa/btq7Kmn2cSR/6IDUt098I72AyjlZiZs6G1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpUEOa/btq7Kmn2cSR/6IDUt098I72AyjlZiZs6G1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpUEOa/btq7Kmn2cSR/6IDUt098I72AyjlZiZs6G1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpUEOa%2Fbtq7Kmn2cSR%2F6IDUt098I72AyjlZiZs6G1%2Fimg.png&quot; width=&quot;591&quot; height=&quot;417&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;904&quot; data-filename=&quot;google-485611_1280.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에 Android 사가 인수 되면서 앤디 루빈은 자신의 철학과 시장을 공략할 OS 제작에 아이디어를 마음껏 뽐내기 시작합니다. 그리고 시간이 지남에 따라 모바일의 흐름 변화가 스마트폰으로 초점이 맞춰지면서 &lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;b&gt;안드로이드는 자연스레 스마트폰 기준으로 발전이 됩니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;477&quot; height=&quot;339&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;355&quot; data-filename=&quot;20200213112829801086.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NMGzT/btq7DUmuTnv/NQcTyv8cxsf9k5A2Y1XU31/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NMGzT/btq7DUmuTnv/NQcTyv8cxsf9k5A2Y1XU31/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NMGzT/btq7DUmuTnv/NQcTyv8cxsf9k5A2Y1XU31/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNMGzT%2Fbtq7DUmuTnv%2FNQcTyv8cxsf9k5A2Y1XU31%2Fimg.jpg&quot; width=&quot;477&quot; height=&quot;339&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;355&quot; data-filename=&quot;20200213112829801086.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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: #ee2323;&quot;&gt;&lt;b&gt; 2007년 그토록 원했던 앤디 루빈의 안드로이드OS 프로토타입이 완성됩니다.&lt;/b&gt;&lt;/span&gt; 그리고 시간이 흐르면서.. 앤디 루빈의 생각대로 전세계 제조사들이 저가 OS에 대 환영을 하면서 &lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;상당히 많은 제조사가 안드로이드 OS를 사용하기 시작&lt;/b&gt;&lt;/span&gt;했고, &lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;b&gt;오픈 소스라는 특징 때문에 수 많은 안드로이드 앱 개발자가 등장하게 됩니다.&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;나의 생각&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot; data-filename=&quot;light-bulb-3104355_1280.jpg&quot; width=&quot;595&quot; height=&quot;335&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcqfDy/btq7FiUtihi/5nKZ6OZXPfuhVXFy0ZVbq0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcqfDy/btq7FiUtihi/5nKZ6OZXPfuhVXFy0ZVbq0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcqfDy/btq7FiUtihi/5nKZ6OZXPfuhVXFy0ZVbq0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcqfDy%2Fbtq7FiUtihi%2F5nKZ6OZXPfuhVXFy0ZVbq0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot; data-filename=&quot;light-bulb-3104355_1280.jpg&quot; width=&quot;595&quot; height=&quot;335&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재의 안드로이드가 탄생하기 전까지 수 많은 일이 있었습니다. 마이크로소프트, 애플, 구글 모두 경험한 앤디 루빈은 굳이 안드로이드 OS를 개발하지 않았어도 사이드킥을 만들기도 하고 세계적인 기업에 다니는 정도이면 엄청난 개발자임은 분명한데, 새로운 도전으로 안드로이드 OS를 만든 것을 보면 과거 그의 열정이 엄청났다는걸 지레짐작하게 됩니다.&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;안드로이드는 앤디 루빈의 소프트 웨어 개발인생의 결과물&lt;/b&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 data-ke-size=&quot;size16&quot;&gt;1. 90년대 초에 이미 모바일 운영체제 만들기에 도전하면서 당시 안드로이드 os를 만들 기초를 다지게 되었음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 데인져라는 회사를 설립하면서 HipTop 이라는 실제 os를 만들어 서비스를 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. MS의 독점으로 제조사가 눈물의 로열티를 내는것을 봄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Apple, 마이크로소프트, 구글을 다니면서 큰 시스템을 보며 배웟을 점들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 1~4번이 모두 혼합되어 탄탄하고 큰 시장 규모를 수렴할 오픈형 os를 만들었을거라 생각&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 alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;816&quot; data-filename=&quot;phone-1869510_1280.jpg&quot; width=&quot;552&quot; height=&quot;352&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tGh8a/btq7DsKs7Gq/qujpVo8KhKwqZZLt8syB21/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tGh8a/btq7DsKs7Gq/qujpVo8KhKwqZZLt8syB21/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tGh8a/btq7DsKs7Gq/qujpVo8KhKwqZZLt8syB21/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtGh8a%2Fbtq7DsKs7Gq%2FqujpVo8KhKwqZZLt8syB21%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;816&quot; data-filename=&quot;phone-1869510_1280.jpg&quot; width=&quot;552&quot; height=&quot;352&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앤디 루빈이 의도 했던 개방형 OS인 안드로이드는 이제 전세계 사람들이 사랑하는 OS가 되었습니다. 어쩌면 앤디 루빈이 없었다면 아직도 많은 제조사들이 MS에 비싼 로열티를 주고 OS를 사용했을 것 같다는 생각이 드네요 그리고 오픈형 OS를 원했던 앤디 루빈의 철학은 현재 안드로이드에서도 아직 그 철학을 느낄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(물론 점점 ios 닮아가는 듯한 느낌이 있긴 합니다.)&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;이제 오픈형 OS라는 특징을 떠나서 모바일의 가능성을 바꾸는 플랫폼이 될 수 있을지 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;앞으로의 행보도 쭉 기대가 됩니다.&lt;/b&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;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>구글안드로이드</category>
      <category>안드로이드</category>
      <category>안드로이드Android</category>
      <category>안드로이드개발</category>
      <category>안드로이드구글</category>
      <category>안드로이드아버지</category>
      <category>안드로이드역사</category>
      <category>안드로이드인수</category>
      <category>앤디루빈</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/51</guid>
      <comments>https://gift123.tistory.com/51#entry51comment</comments>
      <pubDate>Sun, 20 Jun 2021 21:55:32 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (22) ScrollView 문제, RecyclerView의 item의 일부 뷰만 업데이트</title>
      <link>https://gift123.tistory.com/50</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 안드로이드 개발자&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; Loner&lt;/b&gt;&lt;/span&gt;입니다. 주말을 맞아 사이드 프로젝트를 진행하고 있어서 오늘도 건드려본 관련 내용을 정리할겸 포스팅을 합니다. 따로 공부해서 올리는 대단한 내용은 없지만 단편적인 내용 정리가 우연히 블로그를 보게되는 사람들에게 이슈해결의 힌트로 도움이 됬으면 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. ScrollView 안에서 중첩 RecyclerView를 사용하면 끝까지 스크롤 할 수 없다.&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;452&quot; data-filename=&quot;화면 캡처 2021-06-20 164354.png&quot; width=&quot;567&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKuDpF/btq7I1RKZuN/dDh4HLiWRiCQvdGFBiX731/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKuDpF/btq7I1RKZuN/dDh4HLiWRiCQvdGFBiX731/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKuDpF/btq7I1RKZuN/dDh4HLiWRiCQvdGFBiX731/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKuDpF%2Fbtq7I1RKZuN%2FdDh4HLiWRiCQvdGFBiX731%2Fimg.png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;452&quot; data-filename=&quot;화면 캡처 2021-06-20 164354.png&quot; width=&quot;567&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 사이드 프로젝트 협업을 하던 도중에 같은 팀원에게 댓글 구현방법에 대해 도움을 주기 위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 프로젝트를 켰다가 댓글을 여러개 생성해보니 &lt;span style=&quot;color: #000000;&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;처음에는 댓글 db 비즈니스 로직 관련해서 문제가 생긴줄 알았으나 알고보니&lt;span style=&quot;color: #000000;&quot;&gt; 안드로이드 UI 프레임워크 일부 문제&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: #ee2323;&quot;&gt;&lt;b&gt;스크롤 뷰의 자식으로&lt;/b&gt; &lt;b&gt;RecyclerView를 사용한다면 퍼포먼스적으로 문제가 생깁니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;b&gt;하지만 RecyclerView안의 Item이 하나 더 RecyclerView를 가지고 있다면?&amp;nbsp;&lt;/b&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;382&quot; data-filename=&quot;화면 캡처 2021-06-20 164746.png&quot; width=&quot;419&quot; height=&quot;210&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjOMTP/btq7GAtBHp4/OXqBWGh8l6zNlRHhBqi0dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjOMTP/btq7GAtBHp4/OXqBWGh8l6zNlRHhBqi0dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjOMTP/btq7GAtBHp4/OXqBWGh8l6zNlRHhBqi0dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjOMTP%2Fbtq7GAtBHp4%2FOXqBWGh8l6zNlRHhBqi0dK%2Fimg.png&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;382&quot; data-filename=&quot;화면 캡처 2021-06-20 164746.png&quot; width=&quot;419&quot; height=&quot;210&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위 예제는 RecyclerView로 구현한 댓글 아이템 안에&amp;nbsp; RecyclerView를 하나 더 써서 댓글의 답글을 표시하는 경우 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모든 아이템이 스크롤 되지 않습니다. 스크롤 맨 아래에&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 아이템이&amp;nbsp;4~5개가 더 있어야 정상 입니다.&lt;/b&gt;&lt;/span&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-origin-width=&quot;1035&quot; data-origin-height=&quot;458&quot; data-filename=&quot;화면 캡처 2021-06-20 164427.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xxIB9/btq7Kmg3o0Y/dkGlTPeuqGznUdhbQT25i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xxIB9/btq7Kmg3o0Y/dkGlTPeuqGznUdhbQT25i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xxIB9/btq7Kmg3o0Y/dkGlTPeuqGznUdhbQT25i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxxIB9%2Fbtq7Kmg3o0Y%2FdkGlTPeuqGznUdhbQT25i0%2Fimg.png&quot; data-origin-width=&quot;1035&quot; data-origin-height=&quot;458&quot; data-filename=&quot;화면 캡처 2021-06-20 164427.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와 같은 경우 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;NestedScrollView&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 사용해야합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇게 된다면 깔끔하게 모든 아이템을 스크롤 해서 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;325&quot; data-filename=&quot;화면 캡처 2021-06-20 164735.png&quot; width=&quot;455&quot; height=&quot;204&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbCyPa/btq7EedMPUO/bBTq9mdktaTpH7DhJKRM9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbCyPa/btq7EedMPUO/bBTq9mdktaTpH7DhJKRM9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbCyPa/btq7EedMPUO/bBTq9mdktaTpH7DhJKRM9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbCyPa%2Fbtq7EedMPUO%2FbBTq9mdktaTpH7DhJKRM9k%2Fimg.png&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;325&quot; data-filename=&quot;화면 캡처 2021-06-20 164735.png&quot; width=&quot;455&quot; height=&quot;204&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- UI 문제로 보이지 않았던 아이템이 다 보입니다.&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: #000000;&quot;&gt;뿐만 아니라 스크롤뷰 안에 중첩이 아닌 기본 RecyclerView를 쓸때도&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;NestedScrollView&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 사용해야 &lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;b&gt;터치 스크롤 퍼포먼스 시 문제가 없습니다.&lt;/b&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. RecyclerView의 item중의 일부 View만 UI를 자연스럽게 변경하려면 itemAnimation을 제거하자&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Diff Util를 사용했거나 기타 함수들로 리싸이클러뷰의 아이템 변경시 깜빡임을 기본적으로 제거한 상태에서 설명합니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;412&quot; data-filename=&quot;화면 캡처 2021-06-20 165115.png&quot; width=&quot;196&quot; height=&quot;308&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qNorT/btq7I0yy5uE/3iAkKJMcGXFW7qUYVZLK9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qNorT/btq7I0yy5uE/3iAkKJMcGXFW7qUYVZLK9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qNorT/btq7I0yy5uE/3iAkKJMcGXFW7qUYVZLK9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqNorT%2Fbtq7I0yy5uE%2F3iAkKJMcGXFW7qUYVZLK9K%2Fimg.png&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;412&quot; data-filename=&quot;화면 캡처 2021-06-20 165115.png&quot; width=&quot;196&quot; height=&quot;308&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;아래 예시 영상은 안드로이드 코드는 건들지 않고 서버에서 직접 DB를 건드려서 콜백으로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RecyclerView의 아이템이 업데이트 되는 모습입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;리싸이클러뷰에서 애니메이션이 디폴트로 적용이 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨간점만 바뀌는게 아니라 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;아이템의 전체가 바뀌는 모션이 보입니다.&amp;nbsp;&lt;/b&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;span style=&quot;color: #ffffff; background-color: #ee2323;&quot;&gt;&lt;b&gt;RecyclerView 내의 아이템에서 일부의 뷰만 바뀌는 듯한 모션을 주고 싶은 순간이 분명 있을것 입니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴땐 RecyclerView의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;itemAnimation을 null로 할당해 줍시다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1624175399337&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rvChatList.itemAnimator = null&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;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&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;- 위 처럼 item의 일부 뷰만 업데이트 된것 처럼 보여집니다. ( onBindViewHolder 호출 되서 다시 갱신이 됫을탠데 말이죠)&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;876&quot; data-filename=&quot;relaxing-1979674_1280.jpg&quot; width=&quot;510&quot; height=&quot;349&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MXbkR/btq7Efp96PA/eKCkkhpYK5XPlkPsmRlEs0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MXbkR/btq7Efp96PA/eKCkkhpYK5XPlkPsmRlEs0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MXbkR/btq7Efp96PA/eKCkkhpYK5XPlkPsmRlEs0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMXbkR%2Fbtq7Efp96PA%2FeKCkkhpYK5XPlkPsmRlEs0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;876&quot; data-filename=&quot;relaxing-1979674_1280.jpg&quot; width=&quot;510&quot; height=&quot;349&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상 2가지 이슈를 심플하게 정리를 해봤습니다.&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;오늘은 가볍게 1~2시간만 코딩을 한것 같은데 이 짦은 시간에도 간단히 이슈 해결하면서 스스로도 정리를 해가는 재미가 있네요&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일요일에 바쁘더라고 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;안드로이드 개발은 소소한 재미들이 가득하기 때문에 오늘도 보람찬 일요일을 보내고있습니다.&lt;/b&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;</description>
      <category>안드로이드 Android</category>
      <category>Android개발</category>
      <category>nestedscrollview</category>
      <category>RecyclerView아이템</category>
      <category>ScrollView</category>
      <category>스크롤뷰</category>
      <category>안드로이드loner</category>
      <category>안드로이드개발</category>
      <category>안드로이드개발자</category>
      <category>안드로이드앱</category>
      <category>중첩스크롤뷰</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/50</guid>
      <comments>https://gift123.tistory.com/50#entry50comment</comments>
      <pubDate>Sun, 20 Jun 2021 17:22:13 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (21) 홀수 짝수, Release Key, Diff Utill 등등</title>
      <link>https://gift123.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 &lt;b&gt;안드로이드 개발자 &lt;span style=&quot;color: #ee2323;&quot;&gt;Loner&lt;/span&gt;&lt;/b&gt;입니다. 주말이 다가와서 잠시 사이드 프로젝트를 즐기고 있습니다. 사이드 프로젝트를 함께하는 삶은 개발자만이 가질 수 있는 큰 특권이 아닐까 싶네요 오늘도 경험했던 일을 소소하게 정리하는 글을 남기도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 프로그래밍에서 홀수 짝수 찾는법&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;393&quot; data-filename=&quot;화면 캡처 2021-06-18 223333.png&quot; width=&quot;582&quot; height=&quot;267&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CUhob/btq7CHtAktJ/fWDCBXvM1lnwiO0m3k2pfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CUhob/btq7CHtAktJ/fWDCBXvM1lnwiO0m3k2pfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CUhob/btq7CHtAktJ/fWDCBXvM1lnwiO0m3k2pfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCUhob%2Fbtq7CHtAktJ%2FfWDCBXvM1lnwiO0m3k2pfk%2Fimg.png&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;393&quot; data-filename=&quot;화면 캡처 2021-06-18 223333.png&quot; width=&quot;582&quot; height=&quot;267&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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 style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 간단하게 조건문으로 2의 나머지를 구하면 됩니다. 0이면 짝수이고 , 1이면 홀수&lt;/b&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;b&gt;예제&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1624022955205&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;       if (userMissions.size() % 2 == 1) {
                    userMissions.remove(userMissions.size() - 1);
                }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; %2 == 1은 홀수인지 체크합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반대로 %2 == 0 은 짝수인지 체크합니다.&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;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. ReleaseKey 언제든지 빠르게 구하는 법&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1104&quot; data-filename=&quot;key-311986_1280.png&quot; width=&quot;370&quot; height=&quot;319&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxb5Al/btq7BeyJFt5/Y0g3ZbrPZWM2Sfutyykn21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxb5Al/btq7BeyJFt5/Y0g3ZbrPZWM2Sfutyykn21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxb5Al/btq7BeyJFt5/Y0g3ZbrPZWM2Sfutyykn21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxb5Al%2Fbtq7BeyJFt5%2FY0g3ZbrPZWM2Sfutyykn21%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1104&quot; data-filename=&quot;key-311986_1280.png&quot; width=&quot;370&quot; height=&quot;319&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;소셜 로그인 관련해서 Release Key가 필요한 경우가 많습니다.&lt;/b&gt; 디버깅 Key 같은 경우 간단하게 안드로이드 스튜디오 우측 에 &lt;b&gt;signingReport&lt;/b&gt;를 더블 클릭해서 빠르게 sha 1 키를 얻는 경우가 많습니다. 하지만 기본적으로 Relese Key의 경우 직접 명령어을 쳐야 확인 할 수 있습니다.&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;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Release Key를 signingReport로 빨리 찾습니다.&amp;nbsp;&lt;/b&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: #000000;&quot;&gt;&lt;b&gt;주의 할점은 아래와 같은 셋팅은 언제나 제 스타일임으로..&amp;nbsp; &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;차후에 문제가 있을 수 있습니다. 나중가서 반드시 원래 셋팅으로 돌려놓기를 권장합니다. &lt;/b&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;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- SigningReport 로 릴리즈 키 확인하는법 -&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;727&quot; data-filename=&quot;화면 캡처 2021-06-18 224349.png&quot; width=&quot;618&quot; height=&quot;530&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2B9Nk/btq7CjfqyNY/KjLnZFm3VuIkjK8k4X01U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2B9Nk/btq7CjfqyNY/KjLnZFm3VuIkjK8k4X01U0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2B9Nk/btq7CjfqyNY/KjLnZFm3VuIkjK8k4X01U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2B9Nk%2Fbtq7CjfqyNY%2FKjLnZFm3VuIkjK8k4X01U0%2Fimg.png&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;727&quot; data-filename=&quot;화면 캡처 2021-06-18 224349.png&quot; width=&quot;618&quot; height=&quot;530&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 좌측 하단에 Bulid Variants 라는 탭을 누르면 위같은 창이 뜹니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;debug와 release를 선택할 수 있는데 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;release로 설정합니다.&lt;/b&gt;&lt;/span&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 alignLeft&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;823&quot; data-filename=&quot;화면 캡처 2021-06-18 223517.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zoZN0/btq7DTGy6EV/hANNWkY6oUsrIfXJ2B4RwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zoZN0/btq7DTGy6EV/hANNWkY6oUsrIfXJ2B4RwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zoZN0/btq7DTGy6EV/hANNWkY6oUsrIfXJ2B4RwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzoZN0%2Fbtq7DTGy6EV%2FhANNWkY6oUsrIfXJ2B4RwK%2Fimg.png&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;823&quot; data-filename=&quot;화면 캡처 2021-06-18 223517.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 app 모듈을 오른쪽 클릭하면 open Module Settings가 나옵니다. 눌러줍시다.&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-origin-width=&quot;893&quot; data-origin-height=&quot;467&quot; data-filename=&quot;화면 캡처 2021-06-18 224440.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k8zDV/btq7Dr4ESiu/rHiYfbIJF8ZjNt4AGi56IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k8zDV/btq7Dr4ESiu/rHiYfbIJF8ZjNt4AGi56IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k8zDV/btq7Dr4ESiu/rHiYfbIJF8ZjNt4AGi56IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk8zDV%2Fbtq7Dr4ESiu%2FrHiYfbIJF8ZjNt4AGi56IK%2Fimg.png&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;467&quot; data-filename=&quot;화면 캡처 2021-06-18 224440.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위 같은 화면이 나오는데 Signing Configs 탭을 눌러서 + 버튼을 눌러줍시다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누르면 name를 적으라고 뜨고 적고 싶은 Name 를 입력해주고 생성합시다.&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-origin-width=&quot;897&quot; data-origin-height=&quot;573&quot; data-filename=&quot;화면 캡처 2021-06-18 224458.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/css8cs/btq7C3waDKz/sUknqsAn6aoTpQHSKSa2n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/css8cs/btq7C3waDKz/sUknqsAn6aoTpQHSKSa2n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/css8cs/btq7C3waDKz/sUknqsAn6aoTpQHSKSa2n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcss8cs%2Fbtq7C3waDKz%2FsUknqsAn6aoTpQHSKSa2n0%2Fimg.png&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;573&quot; data-filename=&quot;화면 캡처 2021-06-18 224458.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+를 눌러서 생성하면 위 같은 선택지들이 뜹니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;502&quot; data-filename=&quot;화면 캡처 2021-06-18 224528.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs3AFx/btq7CSIrc5f/vsxsDcB7QglHM2YwMCbvnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs3AFx/btq7CSIrc5f/vsxsDcB7QglHM2YwMCbvnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs3AFx/btq7CSIrc5f/vsxsDcB7QglHM2YwMCbvnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs3AFx%2Fbtq7CSIrc5f%2FvsxsDcB7QglHM2YwMCbvnK%2Fimg.png&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;502&quot; data-filename=&quot;화면 캡처 2021-06-18 224528.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Store File은 내가 해당 앱의 Apk를 만들때 서명 했던 .jks 파일을 선택하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 선택했던 .jks 파일의 Password와 KeyAlias를 입력해줍시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;632&quot; data-filename=&quot;화면 캡처 2021-06-18 224607.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mqZkJ/btq7CigvHwl/LlDWW4J5TV0srkdG39dXz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mqZkJ/btq7CigvHwl/LlDWW4J5TV0srkdG39dXz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mqZkJ/btq7CigvHwl/LlDWW4J5TV0srkdG39dXz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmqZkJ%2Fbtq7CigvHwl%2FLlDWW4J5TV0srkdG39dXz0%2Fimg.png&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;632&quot; data-filename=&quot;화면 캡처 2021-06-18 224607.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다 적었으면 좌측 탭에 Bulid Variants 탭을 누르면 위같은 창이 뜹니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;513&quot; data-filename=&quot;화면 캡처 2021-06-18 224718.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btYzsY/btq7EewZbgQ/XxBtkp83onQnZnv5FjAi71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btYzsY/btq7EewZbgQ/XxBtkp83onQnZnv5FjAi71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btYzsY/btq7EewZbgQ/XxBtkp83onQnZnv5FjAi71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtYzsY%2Fbtq7EewZbgQ%2FXxBtkp83onQnZnv5FjAi71%2Fimg.png&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;513&quot; data-filename=&quot;화면 캡처 2021-06-18 224718.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Debuggable를 true로 바꿔주고 Signing Config를 설정해 줍니다.&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: #ee2323;&quot;&gt;&lt;b&gt;자, 이제 셋팅 완료했으니 사용하면 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;376&quot; data-filename=&quot;화면 캡처 2021-06-18 224741.png&quot; width=&quot;703&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcjS2U/btq7CigvHyc/snnKJm3g0AqeGT2SiNZ030/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcjS2U/btq7CigvHyc/snnKJm3g0AqeGT2SiNZ030/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcjS2U/btq7CigvHyc/snnKJm3g0AqeGT2SiNZ030/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcjS2U%2Fbtq7CigvHyc%2FsnnKJm3g0AqeGT2SiNZ030%2Fimg.png&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;376&quot; data-filename=&quot;화면 캡처 2021-06-18 224741.png&quot; width=&quot;703&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 우측에 Gradle 탭을 누릅시다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;607&quot; data-filename=&quot;화면 캡처 2021-06-18 224800.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNVORV/btq7CTm1Yrc/DmU4kAg4dk73Mb448LAT1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNVORV/btq7CTm1Yrc/DmU4kAg4dk73Mb448LAT1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNVORV/btq7CTm1Yrc/DmU4kAg4dk73Mb448LAT1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNVORV%2Fbtq7CTm1Yrc%2FDmU4kAg4dk73Mb448LAT1k%2Fimg.png&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;607&quot; data-filename=&quot;화면 캡처 2021-06-18 224800.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 signingReport를 찾아서 더블클릭하면&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;282&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJIpj6/btq7CjmbDsW/hFK6rQoFKGay8Rkrl4NfDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJIpj6/btq7CjmbDsW/hFK6rQoFKGay8Rkrl4NfDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJIpj6/btq7CjmbDsW/hFK6rQoFKGay8Rkrl4NfDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJIpj6%2Fbtq7CjmbDsW%2FhFK6rQoFKGay8Rkrl4NfDk%2Fimg.png&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;282&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span style=&quot;color: #000000;&quot;&gt;이제 디버그 키가 아니라 릴리즈 키를 언제든지 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;signing Report를 눌러서 확인 할 수 있습니다.&amp;nbsp;&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Diff Util 활용&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;884&quot; data-filename=&quot;checklist-1266989_1280.png&quot; width=&quot;528&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HDlvr/btq7CCTk1Jg/GOwDghFDKlwfikOISHFgKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HDlvr/btq7CCTk1Jg/GOwDghFDKlwfikOISHFgKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HDlvr/btq7CCTk1Jg/GOwDghFDKlwfikOISHFgKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHDlvr%2Fbtq7CCTk1Jg%2FGOwDghFDKlwfikOISHFgKk%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;884&quot; data-filename=&quot;checklist-1266989_1280.png&quot; width=&quot;528&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 개발자는 거의 필수로 사용하는것이 바로 RecyclerView 입니다. 하지만 RecyclerView를 사용할 때 한번쯤 겪어봤을 &lt;b&gt;고충은 바로 아이템 새로 추가했을 시 깜빡임 현상을 어떻게 제거 시키냐는 고민은 한번쯤 했을 것 입니다.&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;대표적으로 notifyDataSetChanged&lt;span style=&quot;color: #9876aa;&quot;&gt;()&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;대신에 notifyItemInserted() 를 사용하는 방법이 있습니다. 하지만 이런 방법들은 리스트가 매우 자주 변하는 상황일시 다른 상황에 따른 조건 처리가 들어가는 경우가 많기 때문에 차라리&lt;b&gt; Diff Util 로 심플하게 처리하는것도 나쁘지 않는 방법입니다.&lt;/b&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 style=&quot;color: #9876aa;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Diff Util에 대한 자세한 설명은 &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9876aa;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1624025431874&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;DiffUtil &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;DiffUtil public class DiffUtil extends Object java.lang.Object &amp;nbsp;&amp;nbsp;&amp;nbsp;↳ androidx.recyclerview.widget.DiffUtil DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil&quot; data-og-url=&quot;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HKwOL/hyKCHzIQLD/EulrbkD2JPvVAxm0s7fhJ0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HKwOL/hyKCHzIQLD/EulrbkD2JPvVAxm0s7fhJ0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;DiffUtil &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;DiffUtil public class DiffUtil extends Object java.lang.Object &amp;nbsp;&amp;nbsp;&amp;nbsp;↳ androidx.recyclerview.widget.DiffUtil DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;Diff Util의 기본 구현 방법은&amp;nbsp; 아래 링크에서 참고하시길 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://onemask514.tistory.com/48&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://onemask514.tistory.com/48&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1624025649320&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] RecyclerView DiffUtil&quot; data-og-description=&quot;Intro 안드로이드에서 Recycler View와 Adpater는 거의 뭐 매번 사용됩니다. ReyclerView가 갖고 있는 item이 변하게 되면 notifyItemChanged로 알려줘야 하는데요 그런데 위 메소드가 불러지고 나면 adapter는 새..&quot; data-og-host=&quot;onemask514.tistory.com&quot; data-og-source-url=&quot;https://onemask514.tistory.com/48&quot; data-og-url=&quot;https://onemask514.tistory.com/48&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/M8FJL/hyKBNn2BAw/JkOmk9ponuNTVnZcKIFP31/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/UB2td/hyKBCUlk9K/Ip6T1P71Bp2XNfwaiSw8Bk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://onemask514.tistory.com/48&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://onemask514.tistory.com/48&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/M8FJL/hyKBNn2BAw/JkOmk9ponuNTVnZcKIFP31/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/UB2td/hyKBCUlk9K/Ip6T1P71Bp2XNfwaiSw8Bk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_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;[Android] RecyclerView DiffUtil&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Intro 안드로이드에서 Recycler View와 Adpater는 거의 뭐 매번 사용됩니다. ReyclerView가 갖고 있는 item이 변하게 되면 notifyItemChanged로 알려줘야 하는데요 그런데 위 메소드가 불러지고 나면 adapter는 새..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;onemask514.tistory.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;b&gt;전체 깜빡임 현상이 있어서 DiffUtil로 간단하게 해결했습니다.&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;협업하는 동료가 채팅방을 나갈때 onStop()에서 해당 채팅방에 대한 db를 업데이트를 하는 구문&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;그렇다고 그 구문을 빼버리자니 또다른 난처한 상황이 올까봐 Diff Util의 특성으로 해결하기로 했습니다.&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;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1624025992944&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   class DiffCallback : DiffUtil.ItemCallback&amp;lt;ChatList&amp;gt;() {
        override fun areItemsTheSame(oldItem: ChatList, newItem: ChatList): Boolean {
            return oldItem == newItem
        }
        
        ....&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;areItemsTheSame은 바꾸기전 아이템과 새로운 아이템을 비교합니다. &lt;span style=&quot;color: #000000;&quot;&gt;areContentsTheSame와 달리 전체를 비교하기 때문에 db의 일부분이라도 바뀌어서 받게 된다면 바로 true를 반환해서 새로운 아이템으로 교체합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동료가 채팅방을 나갈때 onStop에서 db의 일부분을 바꾸는 업데이트를 했기 때문에 서버에서 db변경을 감지하고&lt;b&gt; 클라이언트로 다시 데이터를 보낼때 전체 모델로 받기 때문에 사소한 변경이라도 있을 시 위 구문을 true&lt;/b&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;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;변경 후 예제&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1624026179168&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   class DiffCallback : DiffUtil.ItemCallback&amp;lt;ChatList&amp;gt;() {
        override fun areItemsTheSame(oldItem: ChatList, newItem: ChatList): Boolean {
            return oldItem.chatRoom.lastChatTimeStamp == newItem.chatRoom.lastChatTimeStamp
        }
        ...&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;필드가 변경시에만 아이템이 수정되도록 설정을 해놨습니다.&amp;nbsp;&lt;/b&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;b&gt;DiffUtil의 편리함 덕분에 위 코드를 적용하고 아주 부드러운 UI처리가 가능하게 되었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅에서는 Diff Util에 따른 사용을 어떻게 했었는지 제가 기록만 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 사항은 위에 있는 예제 링크를 확인하세요&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. FireBase RealTimeBase는 기본적으로 Array가 존재하지 않는다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1128&quot; data-filename=&quot;sign-304093_1280.png&quot; width=&quot;392&quot; height=&quot;345&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QfLg1/btq7FieCK6x/V869XhVagbetg28Xxp9cG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QfLg1/btq7FieCK6x/V869XhVagbetg28Xxp9cG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QfLg1/btq7FieCK6x/V869XhVagbetg28Xxp9cG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQfLg1%2Fbtq7FieCK6x%2FV869XhVagbetg28Xxp9cG1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1128&quot; data-filename=&quot;sign-304093_1280.png&quot; width=&quot;392&quot; height=&quot;345&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RealTimeBase로 작게 프로젝트를 하나 진행하고 있습니다. 어떤 테이블의 필드가 ArrayList&amp;lt;String&amp;gt; 였는데 서버 데이터에 테이블 필드에 ArrayList&amp;lt;String&amp;gt;에 element를 하나씩 추가를 해야하는 상황이었습니다. 하지만 RealTimeBase는 알고보니 Array를 정식적으로 지원하지 않아서 &lt;b&gt;해당 필드를 Map으로 사용해야만 update기능을 공식적으로 사용할 수 있었습니다.&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;Array로 db에 insert는 할 수 있습니다. 그렇다면 서버에서 해당 Array를 가져와서 거기에 아이템을 하나 추가한뒤 다시 서버에 덮어씌우는 형식으로 사용해야할탠데 이러면&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 여러 유저가 동시에 insert를 하게 될때 문제가 되기 때문에&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인앱을 만드는데 RealTimeBase 를 사용한다면 가능하면 Array대신 Map를 사용하기를 권장합니다.&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;혹은 RealTimeBase 말고, FireStore를 사용해도 됩니다. FireStore는 배열도 덮어씌우지 않고 1개씩 update를 할 수 있게끔 지원해줍니다.&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;5. 앱을 아름답게 하려면 앱 테마색깔을 잘 사용하자&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot; data-filename=&quot;rainbow-26389_1280.png&quot; width=&quot;542&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byccln/btq7CTHpJtr/h3EIhxAVQ9iy0F30xIHTeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byccln/btq7CTHpJtr/h3EIhxAVQ9iy0F30xIHTeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byccln/btq7CTHpJtr/h3EIhxAVQ9iy0F30xIHTeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyccln%2Fbtq7CTHpJtr%2Fh3EIhxAVQ9iy0F30xIHTeK%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot; data-filename=&quot;rainbow-26389_1280.png&quot; width=&quot;542&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 진행한 사이드 프로젝트에서 &lt;b&gt;ProgressBar&lt;/b&gt;와 &lt;b&gt;Floating Action Button&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;머트리얼 디자인 가이드에 따라 이러한 아이콘들은 단색 Color를 중점으로&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;그래서 특별한 경우를 제외하고 자동적으로 테마색이 사용되는 View들은 모두 임의로 색깔을 설정하지말고 테마 색을 미리 설정해놓은 뒤 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;해당&lt;b&gt; View가 자연스럽게 앱 스타일에 녹아들도록 하는것이 가장 편리하고 효과적인 작업 방식&lt;/b&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;&lt;b&gt;1. 색감 적용전&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;480&quot; data-filename=&quot;화면 캡처 2021-06-18 233712.png&quot; width=&quot;639&quot; height=&quot;263&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3yxTL/btq7CT8tpG7/x1gKUJhKCgKWDmkHzJKBTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3yxTL/btq7CT8tpG7/x1gKUJhKCgKWDmkHzJKBTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3yxTL/btq7CT8tpG7/x1gKUJhKCgKWDmkHzJKBTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3yxTL%2Fbtq7CT8tpG7%2Fx1gKUJhKCgKWDmkHzJKBTK%2Fimg.png&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;480&quot; data-filename=&quot;화면 캡처 2021-06-18 233712.png&quot; width=&quot;639&quot; height=&quot;263&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;80&quot; data-origin-height=&quot;73&quot; data-filename=&quot;화면 캡처 2021-06-18 233842.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNwkgS/btq7EdrlC3Y/qCngpGrmkddjYap3RCU6F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNwkgS/btq7EdrlC3Y/qCngpGrmkddjYap3RCU6F0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNwkgS/btq7EdrlC3Y/qCngpGrmkddjYap3RCU6F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNwkgS%2Fbtq7EdrlC3Y%2FqCngpGrmkddjYap3RCU6F0%2Fimg.png&quot; data-origin-width=&quot;80&quot; data-origin-height=&quot;73&quot; data-filename=&quot;화면 캡처 2021-06-18 233842.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&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;b&gt;2) 색감 적용후&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;506&quot; data-filename=&quot;화면 캡처 2021-06-18 233724.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgxlm4/btq7Bfxy6gK/Gma6ky6K8lJhOueX1Kxkt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgxlm4/btq7Bfxy6gK/Gma6ky6K8lJhOueX1Kxkt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgxlm4/btq7Bfxy6gK/Gma6ky6K8lJhOueX1Kxkt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgxlm4%2Fbtq7Bfxy6gK%2FGma6ky6K8lJhOueX1Kxkt1%2Fimg.png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;506&quot; data-filename=&quot;화면 캡처 2021-06-18 233724.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;87&quot; data-origin-height=&quot;67&quot; data-filename=&quot;화면 캡처 2021-06-18 233817.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be5kAw/btq7Bd7BC3O/t5wvFZwCMbUssz1yR1kdGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be5kAw/btq7Bd7BC3O/t5wvFZwCMbUssz1yR1kdGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be5kAw/btq7Bd7BC3O/t5wvFZwCMbUssz1yR1kdGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe5kAw%2Fbtq7Bd7BC3O%2Ft5wvFZwCMbUssz1yR1kdGK%2Fimg.png&quot; data-origin-width=&quot;87&quot; data-origin-height=&quot;67&quot; data-filename=&quot;화면 캡처 2021-06-18 233817.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발은 역시 사랑이네요.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>Android개발</category>
      <category>DiffUtil</category>
      <category>firebase</category>
      <category>ReleaseKey</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>안드로이드개발Loner</category>
      <category>안드로이드개발자</category>
      <category>안드로이드개발자loner</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/49</guid>
      <comments>https://gift123.tistory.com/49#entry49comment</comments>
      <pubDate>Sat, 19 Jun 2021 00:03:35 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (20)  Camera Capture 및 Image button,ripple</title>
      <link>https://gift123.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요&lt;b&gt; 안드로이드 개발자 &lt;span style=&quot;color: #ee2323;&quot;&gt;Loner&lt;/span&gt;입니다.&lt;/b&gt; 오늘 겪었던 사소한 이슈나 구현해본 것들을 정리하려고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;(1) imageButton&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;turn-on-2923046_1280.jpg&quot; width=&quot;501&quot; height=&quot;334&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tF1kn/btq7yrwHmD7/kUgz7lweaJPBJa2fKiOjZk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tF1kn/btq7yrwHmD7/kUgz7lweaJPBJa2fKiOjZk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tF1kn/btq7yrwHmD7/kUgz7lweaJPBJa2fKiOjZk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtF1kn%2Fbtq7yrwHmD7%2FkUgz7lweaJPBJa2fKiOjZk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;turn-on-2923046_1280.jpg&quot; width=&quot;501&quot; height=&quot;334&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 ImageButton을 좋아합니다. 왜냐하면&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; android:src = &quot;이미지 경로&quot; 를 사용할 수 있기 때문 입니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, android:background 설정을 바꾸지 않았기 때문에 ripple 효과가 사라지지 않고 그대로 남아있기 때문입니다. 이미지를 클릭해서 이벤트를 만들어내야하는 경우 가능하면 imageButton을 사용하는것을 선호합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623938952353&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    &amp;lt;ImageButton
        android:id=&quot;@+id/ib_noticeBoardUploadImage&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginBottom=&quot;40dp&quot;
        android:src=&quot;@drawable/ic_iconfinder_plus_empty_24&quot;
        android:background=&quot;@drawable/bg_noticeboard_upload_ripple&quot;
        app:layout_constraintBottom_toTopOf=&quot;@+id/btn_boardUpload&quot;
        app:layout_constraintStart_toStartOf=&quot;@+id/et_boardUploadDescribe&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; android:src 과 android:background를 같이 써서 둘을 혼합해서 사용 할 수 있습니다.&lt;/b&gt;&lt;/span&gt; 일반 Button의 경우 android:src를 사용할 수 없기 때문에 완전히 background로 만들어야하는것과 달리 ImageButton은 이점에 있어서 상당히 유용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;112&quot; data-origin-height=&quot;123&quot; data-filename=&quot;화면 캡처 2021-06-17 230928.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi0fiI/btq7ucOl1kw/KjxBXqGDkbKHlqLvEeON01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi0fiI/btq7ucOl1kw/KjxBXqGDkbKHlqLvEeON01/img.png&quot; data-alt=&quot;src와 백그라운드를 같이 사용한 이미지버튼&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi0fiI/btq7ucOl1kw/KjxBXqGDkbKHlqLvEeON01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi0fiI%2Fbtq7ucOl1kw%2FKjxBXqGDkbKHlqLvEeON01%2Fimg.png&quot; data-origin-width=&quot;112&quot; data-origin-height=&quot;123&quot; data-filename=&quot;화면 캡처 2021-06-17 230928.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;src와 백그라운드를 같이 사용한 이미지버튼&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ImageView만 사용해서 아이콘을 설정해 클릭이벤트를 넣어봤거나 그림과 백그라운드를 혼합해야하는 경우 ImageButton을 사용해보는것이 어떨까요? 오늘 imageButton을 사용하는 김에 블로그에 적어봤습니다.&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;(2) ripple&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;duck-110878_1280.jpg&quot; width=&quot;556&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0hpWe/btq7zrQwzvs/AO7nuQ7Pf4eLAb9foUpp51/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0hpWe/btq7zrQwzvs/AO7nuQ7Pf4eLAb9foUpp51/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0hpWe/btq7zrQwzvs/AO7nuQ7Pf4eLAb9foUpp51/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0hpWe%2Fbtq7zrQwzvs%2FAO7nuQ7Pf4eLAb9foUpp51%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;duck-110878_1280.jpg&quot; width=&quot;556&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예제를 먼저 보시겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623936036532&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;shape
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:shape=&quot;oval&quot;&amp;gt;

    &amp;lt;solid android:color=&quot;#ffffff&quot; /&amp;gt;

    &amp;lt;size
        android:width=&quot;68dp&quot;
        android:height=&quot;68dp&quot; /&amp;gt;
    &amp;lt;stroke
        android:width=&quot;1dp&quot;
        android:color=&quot;#000&quot; /&amp;gt;
&amp;lt;/shape&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;&lt;b&gt; 클릭 반응 모습&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;112&quot; data-origin-height=&quot;123&quot; data-filename=&quot;화면 캡처 2021-06-17 230928.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi0fiI/btq7ucOl1kw/KjxBXqGDkbKHlqLvEeON01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi0fiI/btq7ucOl1kw/KjxBXqGDkbKHlqLvEeON01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi0fiI/btq7ucOl1kw/KjxBXqGDkbKHlqLvEeON01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi0fiI%2Fbtq7ucOl1kw%2FKjxBXqGDkbKHlqLvEeON01%2Fimg.png&quot; data-origin-width=&quot;112&quot; data-origin-height=&quot;123&quot; data-filename=&quot;화면 캡처 2021-06-17 230928.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌측 하단 동그란 + 버튼을 클릭하면 사진 촬영화면으로 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;사용자가 버튼을 클릭했을때 자신이 정확히 어디를 눌렀는지 ui로 피드백을 하지 않습니다.&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;개인적으로 사용자의 상호작용 하는 부분들을 중요시 여깁니다. 사용자가 어떠한 ui를 클릭을 했을 때 사소한 반응 하나하나가 앱의 디테일을 살려준다 생각하고 좋은 평가를 받을 수 있을거라 생각합니다. 그래서 대표적으로 &lt;b&gt;ripple&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;ripple 효과로 앱의 반응을 표시해주는것이 &lt;b&gt;사용자와 소통하는 방법중 하나라고 생각합니다.&lt;/b&gt; ripple를 통해 사용자는 내가 무엇을 클릭했는지 시각적으로 부담스럽지 않는 선에서 확인 할 수 있고 디자인에 따라 ripple를 더하면 더 품격있는 앱처럼 보일거라 생각이 듭니다.&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;기본적으로 Button과 ImageButton에 ripple이 적용되어있습니다. 하지만 보통 별다른 셋팅없이 Button이나 ImageButton을 만들어서 ripple를 사용하게 됩니다. 하지만 해당 뷰의&lt;b&gt; background를 변경했을시 background가 다른 값으로 할당이 되기 때문에 ripple 효과가 사라집니다.&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;그렇다면 직접 ripple를 만들어줘야합니다.&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;ripple을 xml로 만든 예제는 다음과 같습니다.&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;/p&gt;
&lt;pre id=&quot;code_1623935850509&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;ripple xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:color=&quot;#4E4E4E&quot;&amp;gt;
    &amp;lt;item&amp;gt;
        &amp;lt;shape android:shape=&quot;oval&quot;&amp;gt;
            &amp;lt;solid android:color=&quot;#ffffff&quot; /&amp;gt;
            &amp;lt;size
                android:width=&quot;68dp&quot;
                android:height=&quot;68dp&quot; /&amp;gt;
            &amp;lt;stroke
                android:width=&quot;1dp&quot;
                android:color=&quot;#000&quot; /&amp;gt;
        &amp;lt;/shape&amp;gt;
    &amp;lt;/item&amp;gt;

&amp;lt;/ripple&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;ripple를 상위태그로 감싸서 기존 xml background를 만들면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- android:color = 는 리플을 눌렀을때 색감을 지정해줍니다.&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;b&gt;결과&lt;/b&gt;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&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;b&gt;자신이 어디를 클릭했는지 파악할 수 있고 앱은 사용자가 클릭을 했음을 알립니다.&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;그외 통해 보조색상이나 잔 물결의 반경을 설정할 수 있습니다.&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://developer.android.com/reference/android/graphics/drawable/RippleDrawable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/reference/android/graphics/drawable/RippleDrawable&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623935394341&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;RippleDrawable &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/android/graphics/drawable/RippleDrawable&quot; data-og-url=&quot;https://developer.android.com/reference/android/graphics/drawable/RippleDrawable?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pGsyK/hyKBGOQSk2/odoLMbVlkvho9Baz7AO87K/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/android/graphics/drawable/RippleDrawable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/android/graphics/drawable/RippleDrawable&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pGsyK/hyKBGOQSk2/odoLMbVlkvho9Baz7AO87K/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RippleDrawable &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;(3) Camera Capture&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;smartphone-407108_1280.jpg&quot; width=&quot;609&quot; height=&quot;406&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZlTq5/btq7wyDAKCO/7p2fLx9PyIGzFUx88FzABK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZlTq5/btq7wyDAKCO/7p2fLx9PyIGzFUx88FzABK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZlTq5/btq7wyDAKCO/7p2fLx9PyIGzFUx88FzABK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZlTq5%2Fbtq7wyDAKCO%2F7p2fLx9PyIGzFUx88FzABK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;smartphone-407108_1280.jpg&quot; width=&quot;609&quot; height=&quot;406&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Camera Capture 후 이미지 업로드는 스마트폰 앱에서 거의 필수적인 기능이 되버렸습니다. 오래전부터 사용해왔던 기능이기 때문에 많은 사용자에게 이미 익숙한 기능입니다. 하지만 흔히 하는 기능이라 단순하게 접근했다가 간혹 작은 실수로&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;안드로이드에서 카메라 촬영 후 이미지 저장은 Intent 액션으로 &lt;b&gt;ACTION_IMAGE_CAPTURE&lt;/b&gt;를 사용해서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt; startActivityForResult()&lt;/span&gt;&lt;/b&gt;&lt;span&gt;로 인텐트를 넘기거나 &lt;b&gt;registerForActivityResult&lt;/b&gt;&lt;span style=&quot;color: #4d5156;&quot;&gt;&lt;b&gt;()&lt;/b&gt;를 사용해서 .&lt;b&gt;launch&lt;/b&gt;(intent)를 사용해 넘깁니다.&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;&lt;span style=&quot;color: #4d5156;&quot;&gt;아래 예제는 &lt;b&gt;startActivityForResult&lt;/b&gt;를 사용한 예제입니다. &lt;/span&gt;&lt;span style=&quot;color: #4d5156;&quot;&gt;(개인적으로 registerForActivityResult를 더 선호합니다. 한번 써보시길 추천)&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_1623936969475&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    val REQUEST_IMAGE_CAPTURE = 1

    private fun dispatchTakePictureIntent() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -&amp;gt;
            takePictureIntent.resolveActivity(packageManager)?.also {
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }&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;span&gt;&lt;span style=&quot;color: #4d5156;&quot;&gt;카메라 촬영 후 체크시 콜백으로 data를 받게 되는데 여기서 &lt;b&gt;문제는 원본 사진의 uri를 받지 못합니다&lt;/b&gt;. 카메라 사진을 캡쳐만 하고 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;썸네일을 사용할 정도의 적은 화질의 bitmap만 제공될뿐&lt;/b&gt;&lt;/span&gt; 입니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623936976323&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        if (requestCode == REQUEST_IMAGE_CAPTURE &amp;amp;&amp;amp; resultCode == RESULT_OK) {
            val imageBitmap = data.extras.get(&quot;data&quot;) as Bitmap
            imageView.setImageBitmap(imageBitmap)
        }
    }
    &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #4d5156;&quot;&gt;위처럼 bitmap만 받을 수 있고 상당히 저퀄리티 사진에 실망하실수 있습니다.&amp;nbsp;&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: #4d5156;&quot;&gt;( 적은 썸네일 사진을 원한게 아니라면 위 예제 두개 다 지워버립시다. )&amp;nbsp;&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: #4d5156;&quot;&gt;그렇기 때문에 원본 사이즈를 카메라 촬영후 갤러리에 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #4d5156;&quot;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;- 원본 사이즈 사진 Uri 얻는법 -&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1623937253580&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  &amp;lt;manifest ...&amp;gt;
        &amp;lt;uses-permission android:name=&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;
                         android:maxSdkVersion=&quot;18&quot; /&amp;gt;
        ...
    &amp;lt;/manifest&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;pre id=&quot;code_1623937277516&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  lateinit var currentPhotoPath: String

    @Throws(IOException::class)
    private fun createImageFile(): File {
        //파일 이름의 일부분 만들기 (예제는 날짜기준)
        val timeStamp: String = SimpleDateFormat(&quot;yyyyMMdd_HHmmss&quot;).format(Date())
        val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        return File.createTempFile(
                &quot;JPEG_${timeStamp}_&quot;, /* prefix */
                &quot;.jpg&quot;, /* suffix */
                storageDir /* directory */
        ).apply {
            // 아래 Path를 적어놓으면 사진 클릭후 콜백시 바로 이미지뷰에 사진을 표시한다했을때
            //아래 변수를 사용하면 됩니다!!
            currentPhotoPath = absolutePath
        }
    }
    &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;span style=&quot;color: #202124;&quot;&gt;- 중복되지 않는 파일이름을 가진 파일을 만들어야합니다. 위 예제는 날짜로 중복을 피헀습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- File.crateTempFile() 로 임시 파일을 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그리고 &lt;b&gt;currentPhotoPath에 파일 path를 할당해서 사진 클릭후 바로 이미지 뷰에 선택한 사진을 보여줄 수 있습니다!&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;pre id=&quot;code_1623937474859&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   val REQUEST_IMAGE_CAPTURE = 1

    private fun dispatchTakePictureIntent() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent -&amp;gt;
            // Ensure that there's a camera activity to handle the intent
            takePictureIntent.resolveActivity(packageManager)?.also {
                // Create the File where the photo should go
                val photoFile: File? = try {
                    createImageFile()
                } catch (ex: IOException) {
                    // Error occurred while creating the File
                    ...
                    null
                }
                // Continue only if the File was successfully created
                photoFile?.also {
                    val photoURI: Uri = FileProvider.getUriForFile(
                            this,
                            &quot;com.example.android.fileprovider&quot;,
                            it
                    )
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
                }
            }
        }
    }
    &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-인텐트를 생성하면서 Temp파일도 같이 생성해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-생성한 Temp파일인 photoFile 를 통해서 FileProvider로 URI를 다시 만들어야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;gt; API 24 이상 기기에서 패키지 경계를 넘어 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;file:// URI를 전달하면 FileUriExposedException이 발생&lt;/b&gt;&lt;/span&gt;하기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; FileProvider를 이용해서 content::// URI를 반환합니다.&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;&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;FileProvider는 ContentProvider로 파일을 만들어 앱과 관련된 파일의 안전한 공유하는 클래스입니다&lt;span&gt;&amp;nbsp;&lt;/span&gt;.&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;FileProvider의 설명은 아래링크를 참고하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/androidx/core/content/FileProvider&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/reference/androidx/core/content/FileProvider&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623938152204&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;FileProvider &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;From class android.content.ContentProvider ContentProviderResult[] applyBatch(String arg0, ArrayList arg1) ContentProviderResult[] applyBatch(ArrayList arg0) void attachInfo(Context arg0, ProviderInfo arg1) int bulkInsert(Uri arg0, ContentValues[] arg1) Bu&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/androidx/core/content/FileProvider&quot; data-og-url=&quot;https://developer.android.com/reference/androidx/core/content/FileProvider?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uLV70/hyKBGnNTjM/Xwk3b04D9wc7PEc24m8DMK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/androidx/core/content/FileProvider&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/androidx/core/content/FileProvider&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uLV70/hyKBGnNTjM/Xwk3b04D9wc7PEc24m8DMK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;FileProvider &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;From class android.content.ContentProvider ContentProviderResult[] applyBatch(String arg0, ArrayList arg1) ContentProviderResult[] applyBatch(ArrayList arg0) void attachInfo(Context arg0, ProviderInfo arg1) int bulkInsert(Uri arg0, ContentValues[] arg1) Bu&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FileProvider를 사용한다면 ContentProvider를 사용하는 것이기 때문에 매니페스트에 선언을 해줘야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4대 컴포넌트는 반드시 매니페스트 선언)&lt;/p&gt;
&lt;pre id=&quot;code_1623938078500&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;application&amp;gt;
   ...
   &amp;lt;provider
        android:name=&quot;androidx.core.content.FileProvider&quot;
        android:authorities=&quot;com.example.android.fileprovider&quot;
        android:exported=&quot;false&quot;
        android:grantUriPermissions=&quot;true&quot;&amp;gt;
        &amp;lt;meta-data
            android:name=&quot;android.support.FILE_PROVIDER_PATHS&quot;
            android:resource=&quot;@xml/file_paths&quot;&amp;gt;&amp;lt;/meta-data&amp;gt;
    &amp;lt;/provider&amp;gt;
    ...
&amp;lt;/application&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;그다음 XML를 하나 만들어야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위 디렉터리에 대한 콘텐츠 URI를 요청하겠다고 FileProvider에 알립니다 .&lt;/p&gt;
&lt;pre id=&quot;code_1623938527467&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;paths xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&amp;gt;
    &amp;lt;external-files-path name=&quot;my_images&quot; path=&quot;Pictures&quot; /&amp;gt;
&amp;lt;/paths&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위 예에서는 &quot;Pictures&quot; 하위 디렉토리에 대한 콘텐츠 URI를 FileProvider에게 요청함&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;위 같이 다 셋팅을 했다면 이제 원본 uri를 얻을 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1623938669346&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        if (requestCode == REQUEST_IMAGE_CAPTURE &amp;amp;&amp;amp; resultCode == RESULT_OK) {
             //아까 설정해놨던 uri!
             imageView.setImageURI(currentPhotoPath)
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 이제 원본 화질의 uri를 얻을 수 있습니다.&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 alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;champagne-3515140_1280.jpg&quot; width=&quot;498&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMcXiY/btq7yB7dMXc/CSNKjoA0Ee3sOUiOQt13ek/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMcXiY/btq7yB7dMXc/CSNKjoA0Ee3sOUiOQt13ek/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMcXiY/btq7yB7dMXc/CSNKjoA0Ee3sOUiOQt13ek/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMcXiY%2Fbtq7yB7dMXc%2FCSNKjoA0Ee3sOUiOQt13ek%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;champagne-3515140_1280.jpg&quot; width=&quot;498&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상 카메라 캡쳐 및 리플 및 이미지 버튼에 대해 적어봤습니다.&lt;b&gt; Camera Capture&lt;/b&gt; 같은 경우는 생각보다 셋팅해야 할게 많아서 꽤 귀찮게 느껴지는 작업이긴 하네요 이런 부분들은 구글 안드로이드 개발팀이 좀 신경써줫으면 좋겟다는 개인적인 바람입니다. 이상입니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>androidCamera</category>
      <category>androidCameraCapture</category>
      <category>AndroidImageButton</category>
      <category>AndroidRippeEffect</category>
      <category>안드로이드</category>
      <category>안드로이드CameraCapture</category>
      <category>안드로이드ImageButton</category>
      <category>안드로이드ripple</category>
      <category>안드로이드개발</category>
      <category>안드로이드카메라캡쳐</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/48</guid>
      <comments>https://gift123.tistory.com/48#entry48comment</comments>
      <pubDate>Thu, 17 Jun 2021 23:22:58 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (19) Compose의 맞춤 그래픽</title>
      <link>https://gift123.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Loner&lt;/b&gt;&lt;/span&gt;입니다.&lt;b&gt; Compose에 대한 뜨거운 열망은 계속 이어집니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번편은 Compose의 그래픽이라는 문서를 정리한 내용입니다.&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;Compose의 그래픽&lt;/b&gt;&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;905&quot; data-filename=&quot;pink-1311251_1280.jpg&quot; width=&quot;528&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eLSKnR/btq7wxDZ6Q1/OnRuKjaqPLjAX53xrY2zyK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eLSKnR/btq7wxDZ6Q1/OnRuKjaqPLjAX53xrY2zyK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eLSKnR/btq7wxDZ6Q1/OnRuKjaqPLjAX53xrY2zyK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeLSKnR%2Fbtq7wxDZ6Q1%2FOnRuKjaqPLjAX53xrY2zyK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;905&quot; data-filename=&quot;pink-1311251_1280.jpg&quot; width=&quot;528&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose를 사용하면 커스텀 그래픽을 더 쉽게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose의 선언적 방식을 사용하면 모든 그래픽 구성이 메서드 호출과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Paint&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;Compose가 필요한 객체를 효율적인 방법으로 만들고 업데이트합니다.&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 id=&quot;declarative-graphics&quot; data-text=&quot;Compose를 사용한 선언적 그래픽&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;1.Compose를 사용한 그래픽&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;816&quot; data-filename=&quot;fairy-tale-1077863_1280.jpg&quot; width=&quot;486&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blU0ka/btq7xCkLyWS/VkbuQYtBQmqKcUgSby47W1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blU0ka/btq7xCkLyWS/VkbuQYtBQmqKcUgSby47W1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blU0ka/btq7xCkLyWS/VkbuQYtBQmqKcUgSby47W1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblU0ka%2Fbtq7xCkLyWS%2FVkbuQYtBQmqKcUgSby47W1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;816&quot; data-filename=&quot;fairy-tale-1077863_1280.jpg&quot; width=&quot;486&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose는 그래픽도 선언적 접근 방식으로 처리합니다. Compose의 접근방식은 여러 이점을 제공합니다.&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)Compose는 그래픽 요소의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;상태&lt;/b&gt;를 최소화하므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;상태의 프로그래밍 실수를 피할 수 있음&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2)항목을 그릴 때 모든 옵션이 Composable 함수의 예상되는 위치에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3)Compose의 그래픽 API가 효율적인 방법으로 객체를 만들고 해제합니다.&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는 View 기반 UI의&amp;nbsp;캔버스 및 기타 관련 객체를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Compose를 사용하면&amp;nbsp;내부에서 세부내용을 처리하기 때문에&amp;nbsp;Canvas의 많은 부분을 대부분 단순화됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;canvas&quot; data-text=&quot;캔버스&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;2. Canvas&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;851&quot; data-filename=&quot;paint-1220087_1280.jpg&quot; width=&quot;450&quot; height=&quot;299&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blKZuc/btq7tYWCsWE/syMEo2bws18CQsPykpNA61/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blKZuc/btq7tYWCsWE/syMEo2bws18CQsPykpNA61/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blKZuc/btq7tYWCsWE/syMEo2bws18CQsPykpNA61/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblKZuc%2Fbtq7tYWCsWE%2FsyMEo2bws18CQsPykpNA61%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;851&quot; data-filename=&quot;paint-1220087_1280.jpg&quot; width=&quot;450&quot; height=&quot;299&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 맞춤 그래픽의 핵심 Composable은&lt;span&gt;&amp;nbsp;&lt;/span&gt;Canvas&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 레이아웃에&lt;span&gt;&amp;nbsp;&lt;/span&gt;Canvas를 배치해서 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;Canvas&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;b&gt;참고:&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Canvas&lt;span&gt; Composable&lt;/span&gt;은&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 특수한 Compose&amp;nbsp;Canvas&amp;nbsp;객체를 사용&lt;/b&gt;&lt;/span&gt;하며 이 객체가 뷰 기반&lt;span&gt;&amp;nbsp;&lt;/span&gt;Canvas를 만들고 관리합니다. 하지만 Compose는 상태를 유지하고 필요한 도우미 객체를 만들고 해제하는 등 많은 작업을 처리합니다.&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;pre id=&quot;code_1623909501072&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Canvas(modifier = Modifier.fillMaxSize()){

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;116&quot; data-filename=&quot;화면 캡처 2021-06-17 151427.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oelMV/btq7yr3PkIB/p1Tn7TSgDZoblqiZoyEXek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oelMV/btq7yr3PkIB/p1Tn7TSgDZoblqiZoyEXek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oelMV/btq7yr3PkIB/p1Tn7TSgDZoblqiZoyEXek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoelMV%2Fbtq7yr3PkIB%2Fp1Tn7TSgDZoblqiZoyEXek%2Fimg.png&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;116&quot; data-filename=&quot;화면 캡처 2021-06-17 151427.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DrawScope -&amp;gt; Canvas는 자체 상태를 유지하는 범위가 지정된 그리기 환경&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DrawScope는&lt;span&gt;&amp;nbsp;&lt;/span&gt;DrawScope의 현재 및 최대 크기를 지정하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Size&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체인&lt;span&gt;&amp;nbsp;&lt;/span&gt;size와 같은 여러 유용한 필드를 제공합니다.&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;예제1.drawLine&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623910593870&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Canvas(modifier = Modifier.fillMaxSize()){
    val canvasWidth = size.width
    val canvasHeight = size.height

    drawLine(
        start = Offset(x=canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- drawLine은 선을 그어줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Canvas의 DrawScope에서 현재 size를 구할 수 있음&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1ZJao/btq7sYCvWZn/obx1pZXxnslxGfsczLPhv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1ZJao/btq7sYCvWZn/obx1pZXxnslxGfsczLPhv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1ZJao/btq7sYCvWZn/obx1pZXxnslxGfsczLPhv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1ZJao%2Fbtq7sYCvWZn%2Fobx1pZXxnslxGfsczLPhv0%2Fimg.png&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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. drawLine에 굵기 설정 추가&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623910954942&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Canvas(modifier = Modifier.fillMaxSize()){
    val canvasWidth = size.width
    val canvasHeight = size.height

    drawLine(
        start = Offset(x=canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue,
        strokeWidth = 5F
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- strokeWidth 매개변수를 추가해주었습니다.&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;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HX9b7/btq7tFvD3Nr/K8LUV2FgkmJiMcz5I2Y6b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HX9b7/btq7tFvD3Nr/K8LUV2FgkmJiMcz5I2Y6b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HX9b7/btq7tFvD3Nr/K8LUV2FgkmJiMcz5I2Y6b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHX9b7%2Fbtq7tFvD3Nr%2FK8LUV2FgkmJiMcz5I2Y6b0%2Fimg.png&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;drawLine 말고도 drawRect,&lt;span&gt;&amp;nbsp;&lt;/span&gt;drawCircle&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;b&gt;예제3. drawCircle&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623911636366&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  Canvas(modifier = Modifier.fillMaxSize()) {
            val canvasWidth = size.width
            val canvasHeight = size.height

            drawLine(
                start = Offset(x = canvasWidth, y = 0f),
                end = Offset(x = 0f, y = canvasHeight),
                color = Color.Blue,
                strokeWidth = 5f
            )
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 기본적으로 drawCircle은 채워진 원을 그림&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2evr4/btq7uUMDXCk/PVFWTcmGjFEkd3dIiFK1hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2evr4/btq7uUMDXCk/PVFWTcmGjFEkd3dIiFK1hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2evr4/btq7uUMDXCk/PVFWTcmGjFEkd3dIiFK1hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2evr4%2Fbtq7uUMDXCk%2FPVFWTcmGjFEkd3dIiFK1hK%2Fimg.png&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;/b&gt;으로 하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;DrawScope&lt;span&gt;&amp;nbsp;&lt;/span&gt;그리기 메서드에 명시적 매개변수를 제공하여 이 기능을 활용할 수 있습니다.&lt;/p&gt;
&lt;h2 data-text=&quot;DrawScope&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;drawscope&quot; data-text=&quot;DrawScope&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;3. DrawScope&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;abstract-1225376_1280.jpg&quot; width=&quot;380&quot; height=&quot;380&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dx4SYN/btq7xT7IyKv/7K0TkwRcQQ675tu5kwKPU1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dx4SYN/btq7xT7IyKv/7K0TkwRcQQ675tu5kwKPU1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dx4SYN/btq7xT7IyKv/7K0TkwRcQQ675tu5kwKPU1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdx4SYN%2Fbtq7xT7IyKv%2F7K0TkwRcQQ675tu5kwKPU1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;abstract-1225376_1280.jpg&quot; width=&quot;380&quot; height=&quot;380&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compose&lt;span&gt;&amp;nbsp;&lt;/span&gt;Canvas는 범위가 지정된 그리기 환경인&lt;span&gt;&amp;nbsp;&lt;/span&gt;DrawScope를 노출합니다.&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;위에서 언급한 DrawScope와 같습니다.&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;/p&gt;
&lt;pre id=&quot;code_1623911855877&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Canvas(modifier = Modifier.fillMaxSize()){
    val canvasQuadrantSize = size / 2F
    drawRect(
        color = Color.Green,
        size = canvasQuadrantSize
    )
}&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;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;602&quot; data-filename=&quot;화면 캡처 2021-06-17 153841.png&quot; width=&quot;314&quot; height=&quot;419&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uVBrP/btq7xCLNZdp/KKFJjvLoUtVUGn1E0yVej0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uVBrP/btq7xCLNZdp/KKFJjvLoUtVUGn1E0yVej0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uVBrP/btq7xCLNZdp/KKFJjvLoUtVUGn1E0yVej0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuVBrP%2Fbtq7xCLNZdp%2FKKFJjvLoUtVUGn1E0yVej0%2Fimg.png&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;602&quot; data-filename=&quot;화면 캡처 2021-06-17 153841.png&quot; width=&quot;314&quot; height=&quot;419&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;b&gt;예제2. DrawScope.inset()&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623911999350&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val canvasQuadrantSize = size / 2F
inset(50F,30F){
    drawRect(
        color = Color.Green,
        size = canvasQuadrantSize
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- inset() 함수를 사용하면 현재 범위의 기본 매개변수를 조정하고 그리기 경계를 변경하고 그림을 적절히 변환할 수 있습니다.&amp;nbsp;inset() 은 상응하는 람다 내 모든 그리기 작업에 적용됩니다.&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;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;507&quot; data-filename=&quot;화면 캡처 2021-06-17 154701.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ay0Y8/btq7us4hpZ7/kUttvNOvjwuRkDGkAJ7jm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ay0Y8/btq7us4hpZ7/kUttvNOvjwuRkDGkAJ7jm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ay0Y8/btq7us4hpZ7/kUttvNOvjwuRkDGkAJ7jm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAy0Y8%2Fbtq7us4hpZ7%2FkUttvNOvjwuRkDGkAJ7jm0%2Fimg.png&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;507&quot; data-filename=&quot;화면 캡처 2021-06-17 154701.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;예제3. rotate()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623912014557&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val canvasSize = size
val canvasWidth = size.width
val canvasHeight = size.height
drawRect(
    color = Color.Gray,
    topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
    size = canvasSize / 3F
)&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;span&gt;&lt;/span&gt;- 위 예제는 rotate()를 사용하지 않은 코드입니다.&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 alignLeft&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r0a8z/btq7tYWrQy7/UmeIxEE6sNhiwukuvsLBOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r0a8z/btq7tYWrQy7/UmeIxEE6sNhiwukuvsLBOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r0a8z/btq7tYWrQy7/UmeIxEE6sNhiwukuvsLBOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr0a8z%2Fbtq7tYWrQy7%2FUmeIxEE6sNhiwukuvsLBOK%2Fimg.png&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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_1623912189997&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rotate(degrees = 45F){
    drawRect(
        color = Color.Gray,
        topLeft = Offset(x = canvasWidth / 3F , y = canvasHeight / 3F),
        size = canvasSize / 3F
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-이제 DrawScope에 회전을 적용하여 직사각형을 회전할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- rotate() 를 사용하면 회전 할수 있습니다.&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 alignLeft&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XDPPQ/btq7usQAdOM/F9G8gwhRJKIRUD2qiEgg11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XDPPQ/btq7usQAdOM/F9G8gwhRJKIRUD2qiEgg11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XDPPQ/btq7usQAdOM/F9G8gwhRJKIRUD2qiEgg11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXDPPQ%2Fbtq7usQAdOM%2FF9G8gwhRJKIRUD2qiEgg11%2Fimg.png&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 변환을 적용하려는 경우 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;중첩된&amp;nbsp;DrawScope&amp;nbsp;환경을 만드는건 좋지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그래서 &lt;/span&gt;&lt;b&gt;withTransform()&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수를 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- withTransform()는 원하는 모든 변경사항을 결합한 &lt;b&gt;단일 변환을 만들고 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- withTransform()은 Compose가 중첩된 각 변환을 계산하고 저장할 필요 없이 단일 작업으로 실행되므로 중첩 호출하는 것보다 더 효율적입니다.&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;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623912618867&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;withTransform({
    translate(left = canvasWidth/5F)
    rotate(degrees=45F)
}) {
    drawRect(
        color = Color.Gray,
        topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
        size = canvasSize / 3F
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;-translate와 rotate를 같이 사용&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;&lt;span&gt;결과&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blqql2/btq7usXihLR/9CJ4mfBnMoC6hWJRzyViEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blqql2/btq7usXihLR/9CJ4mfBnMoC6hWJRzyViEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blqql2/btq7usXihLR/9CJ4mfBnMoC6hWJRzyViEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblqql2%2Fbtq7usXihLR%2F9CJ4mfBnMoC6hWJRzyViEK%2Fimg.png&quot; width=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;2367&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 정리&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;768&quot; data-filename=&quot;idea-1876659_1280.jpg&quot; width=&quot;447&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AH1al/btq7vljvL5R/4oT1CxwvqE4W8BX7wEWYSk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AH1al/btq7vljvL5R/4oT1CxwvqE4W8BX7wEWYSk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AH1al/btq7vljvL5R/4oT1CxwvqE4W8BX7wEWYSk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAH1al%2Fbtq7vljvL5R%2F4oT1CxwvqE4W8BX7wEWYSk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;768&quot; data-filename=&quot;idea-1876659_1280.jpg&quot; width=&quot;447&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compose의 커스텀 그래픽은 선언형 방식으로 진행되기 때문에 프로그래밍적으로 실수할 확률이 적음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compose의 그래픽 API는 효율적으로 객체를 만들거나 해제함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compose만의 특수한 Canvas Composable은 내부적으로 알아서 세부 작업을 해주기 때문에 간결하게 사용할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Canvas Composable의 DrawScope 영역안에서 주로 커스텀 UI를 제작할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Canvas Composable의 DrawScope 영역은 현재 Composable의 size 나 그리기를 돕는 여러 함수들을 지원해줌&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DrawScope 영역은 기본적으로 상위 범위의 설정을 기준으로 잡음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DrawScope에서 기본적으로 drawLine, drawCircle, drawRect 등등으로 그릴 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DrawScope의 inset(),rotate() 와 같이 변환을 지원해주는 함수들이 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DrawScope에서 여러변환을 적용할 때 withTransform을 사용할 것을 권장함 모든 변경 사항을 단일 변환으로 적용하기 때문에 중첩으로 여러 변환을 적용하는것보다 훨씬 효율적임&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;선언형 프로그래밍의 위대함을 다시 살펴볼 수 있는 명목이네요 Canvas 작업까지 프레임워크에서 세부내용을 다 처리해준다니 안&lt;b&gt;드로이드 개발을 너무 편리하게 할 수 있는 세상이 다가온거 같습니다.&lt;/b&gt; 여전히 스테이블을 기다리는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Loner&lt;/b&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;&lt;b&gt;이전 Compose 내용 정리&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;a href=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;https://gift123.tistory.com/33?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623913624768&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;안드로이드 개발 (8) Compose 이해 정리&quot; data-og-description=&quot;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;안드로이드 개발 (8) Compose 이해 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;https://gift123.tistory.com/34?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623913624769&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;안드로이드 개발 (9) Compose 상태 관리&quot; data-og-description=&quot;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078');&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;안드로이드 개발 (9) Compose 상태 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;https://gift123.tistory.com/38?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623913624769&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;안드로이드 개발 (10) Compose Composable Lifecycle&quot; data-og-description=&quot;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/38?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420');&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;안드로이드 개발 (10) Compose Composable Lifecycle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;https://gift123.tistory.com/39?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623913624769&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;안드로이드 개발 (11) Compose Side-effects&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824');&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;안드로이드 개발 (11) Compose Side-effects&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;https://gift123.tistory.com/41&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623913624769&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;안드로이드 개발 (13) Layout in Compose 1편&quot; data-og-description=&quot;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/41&quot; data-og-url=&quot;https://gift123.tistory.com/41&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dml8f7/hyKAqec0Cx/614plyeHa0PYhdk7ndaT3K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bvI9GW/hyKy62Jkgo/AsBT5LySWlNHgIBlmhtttK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/OnT3t/hyKy3EUT8z/GKUhvtCkSqAmEDxllBKHs0/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot; data-source-url=&quot;https://gift123.tistory.com/41&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dml8f7/hyKAqec0Cx/614plyeHa0PYhdk7ndaT3K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bvI9GW/hyKy62Jkgo/AsBT5LySWlNHgIBlmhtttK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/OnT3t/hyKy3EUT8z/GKUhvtCkSqAmEDxllBKHs0/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (13) Layout in Compose 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;https://gift123.tistory.com/42?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623913624770&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;안드로이드 개발 (14) Layout in Compose 2편&quot; data-og-description=&quot;1편 정리 https://gift123.tistory.com/41 안드로이드 개발 (13) Layout in Compose 1편 지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/42?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/42&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHnHfG/hyKAljFdZK/2ECx5Xv8Liz2TCo6a4jK7k/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/bV4tPS/hyKyZ3AMoL/yAKHv6G6HcfNnkHOBoxnt0/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/kK9DB/hyKy9LSbr9/ZH55ZpQzTT0KYubAsIaKb0/img.jpg?width=1280&amp;amp;height=846&amp;amp;face=0_372_254_650&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHnHfG/hyKAljFdZK/2ECx5Xv8Liz2TCo6a4jK7k/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/bV4tPS/hyKyZ3AMoL/yAKHv6G6HcfNnkHOBoxnt0/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/kK9DB/hyKy9LSbr9/ZH55ZpQzTT0KYubAsIaKb0/img.jpg?width=1280&amp;amp;height=846&amp;amp;face=0_372_254_650');&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;안드로이드 개발 (14) Layout in Compose 2편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1편 정리 https://gift123.tistory.com/41 안드로이드 개발 (13) Layout in Compose 1편 지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/43&quot;&gt;https://gift123.tistory.com/43&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623913624770&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;안드로이드 개발 (15) Theming in Compose&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/themes?hl=en Compose의 레이아웃 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers Jetpack Compose를 사용하면 앱의 UI를 훨씬 쉽게 디자인하고 빌드할 수 있습니다...&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/43&quot; data-og-url=&quot;https://gift123.tistory.com/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/buq1xz/hyKAnhegq4/BrqKiDoAsN5xqkd1jbmIBk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bI5bXl/hyKy2ltoWr/OBOtuvr49CwUpHx9nQXkwk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/quoEb/hyKyWeuYAt/lIvRSrXX6xsWEavKCtd9Gk/img.jpg?width=1280&amp;amp;height=960&amp;amp;face=0_0_1280_960&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/43&quot; data-source-url=&quot;https://gift123.tistory.com/43&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/buq1xz/hyKAnhegq4/BrqKiDoAsN5xqkd1jbmIBk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bI5bXl/hyKy2ltoWr/OBOtuvr49CwUpHx9nQXkwk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/quoEb/hyKyWeuYAt/lIvRSrXX6xsWEavKCtd9Gk/img.jpg?width=1280&amp;amp;height=960&amp;amp;face=0_0_1280_960');&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;안드로이드 개발 (15) Theming in Compose&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/themes?hl=en Compose의 레이아웃 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers Jetpack Compose를 사용하면 앱의 UI를 훨씬 쉽게 디자인하고 빌드할 수 있습니다...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/44&quot;&gt;https://gift123.tistory.com/44&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/45?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623913624770&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;안드로이드 개발 (16) Compose Lists&quot; data-og-description=&quot;안녕하세요 안드로이드 개발자 Loner입니다. Compose의 정리를 이어서 진행해보도록 하겠습니다. List 기존 Xml방식으로 List는 주로 리싸이클러 뷰 혹은 리스트뷰로 많이 구현을 해왔습니다. Compose에&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/44&quot; data-og-url=&quot;https://gift123.tistory.com/44&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhkKqR/hyKAi9DgBB/PXSz4eksa6tcuvIh1rvW7k/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/hD3NX/hyKAmxq6BU/Ii8YCrc4ar1teiWs11CFx0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bm8Fim/hyKAti1dXK/WBB533TIiaNDa8o1teielk/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/44&quot; data-source-url=&quot;https://gift123.tistory.com/44&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhkKqR/hyKAi9DgBB/PXSz4eksa6tcuvIh1rvW7k/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/hD3NX/hyKAmxq6BU/Ii8YCrc4ar1teiWs11CFx0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bm8Fim/hyKAti1dXK/WBB533TIiaNDa8o1teielk/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (16) Compose Lists&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 안드로이드 개발자 Loner입니다. Compose의 정리를 이어서 진행해보도록 하겠습니다. List 기존 Xml방식으로 List는 주로 리싸이클러 뷰 혹은 리스트뷰로 많이 구현을 해왔습니다. Compose에&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/45?category=967702&quot;&gt;https://gift123.tistory.com/45?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623913624770&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;안드로이드 개발 (17) Compose Text  1편&quot; data-og-description=&quot;안녕하세요 안드로이드 개발자 Loner입니다. Compose 연구에 푹 빠져사는 요즘 새로운 세계를 맛보고 있어서 즐거운것 같습니다. 이번에는 Compose의 텍스트에 관해 정리를 해봤습니다. Compose 의 텍스&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/45?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/45&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ct6XNJ/hyKAkTW8k3/1ncRkkbZI4KbineJNh6i7K/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/cT1k8l/hyKAlZADX5/FdfH9PpkxhgZYRHLpMaBQ1/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/bi8e8o/hyKBA1TMmQ/mBEZ7c6KFKvoa8reQ2zSzK/img.jpg?width=1280&amp;amp;height=856&amp;amp;face=0_0_1280_856&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/45?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/45?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ct6XNJ/hyKAkTW8k3/1ncRkkbZI4KbineJNh6i7K/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/cT1k8l/hyKAlZADX5/FdfH9PpkxhgZYRHLpMaBQ1/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/bi8e8o/hyKBA1TMmQ/mBEZ7c6KFKvoa8reQ2zSzK/img.jpg?width=1280&amp;amp;height=856&amp;amp;face=0_0_1280_856');&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;안드로이드 개발 (17) Compose Text 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 안드로이드 개발자 Loner입니다. Compose 연구에 푹 빠져사는 요즘 새로운 세계를 맛보고 있어서 즐거운것 같습니다. 이번에는 Compose의 텍스트에 관해 정리를 해봤습니다. Compose 의 텍스&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/46&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/46&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/45?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623913666437&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;안드로이드 개발 (18) Compose Text 2편&quot; data-og-description=&quot;이전 내용 Compose Text 1편 https://gift123.tistory.com/45?category=967702 안드로이드 개발 (17) Compose Text 1편 안녕하세요 안드로이드 개발자 Loner입니다. Compose 연구에 푹 빠져사는 요즘 새로운 세계..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/46&quot; data-og-url=&quot;https://gift123.tistory.com/46&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bDBTAv/hyKBJR5q71/1C6mnArIlDMN0oz6VZ4a8k/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/cdIYj6/hyKBNNIAho/1NGQhnn0G9nK40HOFdLps0/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/cMkgmg/hyKAq7ENlA/2m9IHzkNiJJpxXUmsiEH8k/img.png?width=1041&amp;amp;height=262&amp;amp;face=0_0_1041_262&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/46&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/46&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bDBTAv/hyKBJR5q71/1C6mnArIlDMN0oz6VZ4a8k/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/cdIYj6/hyKBNNIAho/1NGQhnn0G9nK40HOFdLps0/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/cMkgmg/hyKAq7ENlA/2m9IHzkNiJJpxXUmsiEH8k/img.png?width=1041&amp;amp;height=262&amp;amp;face=0_0_1041_262');&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;안드로이드 개발 (18) Compose Text 2편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 내용 Compose Text 1편 https://gift123.tistory.com/45?category=967702 안드로이드 개발 (17) Compose Text 1편 안녕하세요 안드로이드 개발자 Loner입니다. Compose 연구에 푹 빠져사는 요즘 새로운 세계..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>AndroidCompose</category>
      <category>AndroidComposeCanvas</category>
      <category>AndroidComposeCustomUI</category>
      <category>compose</category>
      <category>안드로이드Canvas</category>
      <category>안드로이드ComposeCanvas</category>
      <category>안드로이드CustomUI</category>
      <category>안드로이드개발</category>
      <category>안드로이드커스텀ui</category>
      <category>안드로이드컴포즈</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/47</guid>
      <comments>https://gift123.tistory.com/47#entry47comment</comments>
      <pubDate>Thu, 17 Jun 2021 16:16:01 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (18) Compose Text 2편</title>
      <link>https://gift123.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 내용 Compose Text 1편&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/45?category=967702&quot;&gt;https://gift123.tistory.com/45?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623906157286&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;안드로이드 개발 (17) Compose Text  1편&quot; data-og-description=&quot;안녕하세요 안드로이드 개발자 Loner입니다. Compose 연구에 푹 빠져사는 요즘 새로운 세계를 맛보고 있어서 즐거운것 같습니다. 이번에는 Compose의 텍스트에 관해 정리를 해봤습니다. Compose 의 텍스&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/45?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/45&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ct6XNJ/hyKAkTW8k3/1ncRkkbZI4KbineJNh6i7K/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/cT1k8l/hyKAlZADX5/FdfH9PpkxhgZYRHLpMaBQ1/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/bi8e8o/hyKBA1TMmQ/mBEZ7c6KFKvoa8reQ2zSzK/img.jpg?width=1280&amp;amp;height=856&amp;amp;face=0_0_1280_856&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/45?category=967702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/45?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ct6XNJ/hyKAkTW8k3/1ncRkkbZI4KbineJNh6i7K/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/cT1k8l/hyKAlZADX5/FdfH9PpkxhgZYRHLpMaBQ1/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/bi8e8o/hyKBA1TMmQ/mBEZ7c6KFKvoa8reQ2zSzK/img.jpg?width=1280&amp;amp;height=856&amp;amp;face=0_0_1280_856');&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;안드로이드 개발 (17) Compose Text 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 안드로이드 개발자 Loner입니다. Compose 연구에 푹 빠져사는 요즘 새로운 세계를 맛보고 있어서 즐거운것 같습니다. 이번에는 Compose의 텍스트에 관해 정리를 해봤습니다. Compose 의 텍스&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;b&gt;안드로이드 개발자 Loner&lt;/b&gt;입니다. 저번 Text 설명 1편에 이어서 2편을 작성해보도록 하겠습니다. Text는 크게 색다로운 내용이 있다긴보다 되짚어보는 정리정도 되는거 같습니다. 2편 같은 경우 사용자 상호작용 개념이 있어서 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;
&lt;h2 id=&quot;user-interaction&quot; data-text=&quot;사용자 상호작용&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;1. 사용자 상호작용&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;910&quot; data-filename=&quot;phone-2655101_1280.png&quot; width=&quot;549&quot; height=&quot;390&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pFfQN/btq7sFbX92i/OBLaYwWtfzOF41KBkoYDl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pFfQN/btq7sFbX92i/OBLaYwWtfzOF41KBkoYDl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pFfQN/btq7sFbX92i/OBLaYwWtfzOF41KBkoYDl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpFfQN%2Fbtq7sFbX92i%2FOBLaYwWtfzOF41KBkoYDl1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;910&quot; data-filename=&quot;phone-2655101_1280.png&quot; width=&quot;549&quot; height=&quot;390&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;사용자 상호작용을 사용 설정할 수 있는 다양한 API를 설명합니다.&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;- 여러 Composable 레이아웃에서 더 유연하게 텍스트를 선택할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Text&lt;span&gt;&amp;nbsp;&lt;/span&gt;Composable 의 부분에 제어자를 추가할 수 없어서 &lt;b&gt;Text의 사용자 상호작용은 다른 Composable 레이아웃과 다름&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;select-text&quot; data-text=&quot;텍스트 선택하기&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;1) 텍스트 선택하기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본적으로 Composable은 선택할 수 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 선택 기능을 사용 설정하려면 Text를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SelectionContainer&lt;span&gt;&amp;nbsp;&lt;/span&gt;Composable&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;pre id=&quot;code_1623865606348&quot; class=&quot;html xml&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun SelectableText() {
    SelectionContainer {
        Text(&quot;This text is selectable&quot;)
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;400&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;222&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdTAvt/btq7o4bEOTt/5e9p26LFalsJXKW8PIkkLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdTAvt/btq7o4bEOTt/5e9p26LFalsJXKW8PIkkLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdTAvt/btq7o4bEOTt/5e9p26LFalsJXKW8PIkkLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdTAvt%2Fbtq7o4bEOTt%2F5e9p26LFalsJXKW8PIkkLK%2Fimg.png&quot; width=&quot;400&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;222&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;DisableSelection&lt;span&gt; Composable&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;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623865683883&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun PartiallySelectableText() {
    SelectionContainer {
        Column {
            Text(&quot;This text is selectable&quot;)
            Text(&quot;This one too&quot;)
            Text(&quot;This one as well&quot;)
            DisableSelection {
                Text(&quot;But not this one&quot;)
                Text(&quot;Neither this one&quot;)
            }
            Text(&quot;But again, you can select this one&quot;)
            Text(&quot;And this one too&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;결과&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;400&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;767&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf1uwt/btq7onCtVzH/v1lqg9ImBudfNyvmk3gaJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf1uwt/btq7onCtVzH/v1lqg9ImBudfNyvmk3gaJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf1uwt/btq7onCtVzH/v1lqg9ImBudfNyvmk3gaJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf1uwt%2Fbtq7onCtVzH%2Fv1lqg9ImBudfNyvmk3gaJ0%2Fimg.png&quot; width=&quot;400&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;767&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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 id=&quot;get-click-position&quot; data-text=&quot;텍스트에서 클릭 위치 가져오기&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;2) 텍스트에서 클릭 위치 가져오기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Text의 클릭을 수신 대기하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;clickable&lt;span&gt; &lt;/span&gt;&amp;nbsp;추가하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Text&lt;span&gt; Composable&lt;/span&gt;&amp;nbsp;내에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;클릭 위치를 가져오려면&lt;/b&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;ClickableText&lt;/b&gt;를 Text Compsable 대신 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623865853869&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun SimpleClickableText() {
    ClickableText(
        text = AnnotatedString(&quot;Click Me&quot;),
        onClick = { offset -&amp;gt;
            Log.d(&quot;ClickableText&quot;, &quot;$offset -th character is clicked.&quot;)
        }
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;3) 주석이 추가된 클릭&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가&lt;span&gt;&amp;nbsp;&lt;/span&gt;Text&lt;span&gt; Composable&lt;/span&gt;을 클릭할 때 특정 단어에 연결된 하이퍼링크 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;Text&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;- tag(String), annotation(String), 텍스트 범위를 매개변수로 사용하는 주석을 추가해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;AnnotatedString&lt;/b&gt;에서 이러한 주석을 태그 또는 텍스트 범위로 필터링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623866035157&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun AnnotatedClickableText() {
    val annotatedText = buildAnnotatedString {
        append(&quot;Click &quot;)

        // We attach this *URL* annotation to the following content
        // until `pop()` is called
        pushStringAnnotation(tag = &quot;URL&quot;,
                             annotation = &quot;https://developer.android.com&quot;)
        withStyle(style = SpanStyle(color = Color.Blue,
                                    fontWeight = FontWeight.Bold)) {
            append(&quot;here&quot;)
        }

        pop()
    }

    ClickableText(
        text = annotatedText,
        onClick = { offset -&amp;gt;
            // We check if there is an *URL* annotation attached to the text
            // at the clicked position
            annotatedText.getStringAnnotations(tag = &quot;URL&quot;, start = offset,
                                                    end = offset)
                .firstOrNull()?.let { annotation -&amp;gt;
                    // If yes, we log its value
                    Log.d(&quot;Clicked URL&quot;, annotation.item)
                }
        }
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;enter-modify-text&quot; data-text=&quot;텍스트 입력 및 수정&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;2. 텍스트 입력 및 수정&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot; data-filename=&quot;keyboard-311803_1280.png&quot; width=&quot;596&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPW0HL/btq7wyP9apT/X508IKf96QYgHBUsMffLgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPW0HL/btq7wyP9apT/X508IKf96QYgHBUsMffLgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPW0HL/btq7wyP9apT/X508IKf96QYgHBUsMffLgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPW0HL%2Fbtq7wyP9apT%2FX508IKf96QYgHBUsMffLgK%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot; data-filename=&quot;keyboard-311803_1280.png&quot; width=&quot;596&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextField를 사용하면 유저가 텍스트를 입력하거나 수정할 수 있습니다.&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) TextField는 머티리얼 디자인 가이드라인을 따라 구현됨&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 기본 스타일이&lt;span&gt;&amp;nbsp;채워짐&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- OutlinedTextField는&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;(2) BasicTextField는 유저가 키보드를 통해 텍스트를 수정할 수 있지만 &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;예제1. TextField&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623866172817&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun SimpleFilledTextFieldSample() {
    var text by remember { mutableStateOf(&quot;Hello&quot;) }

    TextField(
        value = text,
        onValueChange = { text = it },
        label = { Text(&quot;Label&quot;) }
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제1. 결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;400&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;154&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EVHUN/btq7sWxk0D5/olCIFwdqvVr5Ic6PfPyKsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EVHUN/btq7sWxk0D5/olCIFwdqvVr5Ic6PfPyKsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EVHUN/btq7sWxk0D5/olCIFwdqvVr5Ic6PfPyKsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEVHUN%2Fbtq7sWxk0D5%2FolCIFwdqvVr5Ic6PfPyKsK%2Fimg.png&quot; width=&quot;400&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;154&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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. outlinedTextField&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623866247285&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun SimpleOutlinedTextFieldSample() {
    var text by remember { mutableStateOf(&quot;&quot;) }

    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        label = { Text(&quot;Label&quot;) }
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;예제2. 결과&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;400&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;168&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1zEat/btq7p8kwAVi/JlqTowTvG5nglmZQrzdxHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1zEat/btq7p8kwAVi/JlqTowTvG5nglmZQrzdxHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1zEat/btq7p8kwAVi/JlqTowTvG5nglmZQrzdxHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1zEat%2Fbtq7p8kwAVi%2FJlqTowTvG5nglmZQrzdxHK%2Fimg.png&quot; width=&quot;400&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;168&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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 id=&quot;styling-textfield&quot; data-text=&quot;TextField 스타일 지정&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;1) TextField 스타일 지정&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextField&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;BasicTextField는 커스텀 할 수 있는 많은 공통 매개변수를 공유합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextField의 전체 목록은&lt;span&gt; 아래링크&lt;/span&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt;l=149?hl=ko&quot;&gt;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt;l=149?hl=ko&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623866344631&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;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt;l=149?hl=ko&quot; data-og-description=&quot;&quot; data-og-host=&quot;cs.android.com&quot; data-og-source-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt;l=149?hl=ko&quot; data-og-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt;l=149?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt;l=149?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt;l=149?hl=ko&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;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/TextField.kt;l=149?hl=ko&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;cs.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;singleLine&lt;/li&gt;
&lt;li&gt;maxLines&lt;/li&gt;
&lt;li&gt;textStyle&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1623866377901&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun StyledTextField() {
    var value by remember { mutableStateOf(&quot;Hello\nWorld\nInvisible&quot;) }

    TextField(
        value = value,
        onValueChange = { value = it },
        label = { Text(&quot;Enter text&quot;) },
        maxLines = 2,
        textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
        modifier = Modifier.padding(20.dp)
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;400&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;301&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKJ9xw/btq7s6GuDk4/6GzKoi70xU1UsPHjpJGXE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKJ9xw/btq7s6GuDk4/6GzKoi70xU1UsPHjpJGXE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKJ9xw/btq7s6GuDk4/6GzKoi70xU1UsPHjpJGXE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKJ9xw%2Fbtq7s6GuDk4%2F6GzKoi70xU1UsPHjpJGXE1%2Fimg.png&quot; width=&quot;400&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;301&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 디자인이 Material TextField 또는 OutlineTextField를 호출하는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;TextField를 사용하는 할것을 권장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;머티리얼 사양의 장식이 필요하지 않은 디자인을 빌드하는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;BasicTextField를 사용해야함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;keyboard-options&quot; data-text=&quot;키보드 옵션&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;2) 키보드 옵션&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextField를 사용하면 키보드 레이아웃과 같은 키보드 구성 옵션을 설정하거나 키보드에서 지원하는 경우&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;capitalization&lt;/li&gt;
&lt;li&gt;autoCorrect&lt;/li&gt;
&lt;li&gt;keyboardType&lt;/li&gt;
&lt;li&gt;imeAction&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;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/KeyboardOptions?hl=ko#KeyboardOptions(androidx.compose.ui.text.input.KeyboardCapitalization,kotlin.Boolean,androidx.compose.ui.text.input.KeyboardType,androidx.compose.ui.text.input.ImeAction)&quot;&gt;https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/KeyboardOptions?hl=ko#KeyboardOptions(androidx.compose.ui.text.input.KeyboardCapitalization,kotlin.Boolean,androidx.compose.ui.text.input.KeyboardType,androidx.compose.ui.text.input.ImeAction)&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623866579440&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;KeyboardOptions &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/KeyboardOptions?hl=ko#KeyboardOptions(androidx.compose.ui.text.input.KeyboardCapitalization,kotlin.Boolean,androidx.compose.ui.text.input.KeyboardType,androidx.compose.ui.text.input.ImeAction)&quot; data-og-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/KeyboardOptions?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dsTXIo/hyKAjtGOuX/4NXF3KCssrMLmK5WjkKhtk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/KeyboardOptions?hl=ko#KeyboardOptions(androidx.compose.ui.text.input.KeyboardCapitalization,kotlin.Boolean,androidx.compose.ui.text.input.KeyboardType,androidx.compose.ui.text.input.ImeAction)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/KeyboardOptions?hl=ko#KeyboardOptions(androidx.compose.ui.text.input.KeyboardCapitalization,kotlin.Boolean,androidx.compose.ui.text.input.KeyboardType,androidx.compose.ui.text.input.ImeAction)&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dsTXIo/hyKAjtGOuX/4NXF3KCssrMLmK5WjkKhtk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;KeyboardOptions &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-text=&quot;형식 지정&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 id=&quot;formatting&quot; data-text=&quot;형식 지정&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;3) 형식 지정&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextField를 사용하면 입력 값에 형식을 설정할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623866624900&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun PasswordTextField() {
    var password by rememberSaveable { mutableStateOf(&quot;&quot;) }

    TextField(
        value = password,
        onValueChange = { password = it },
        label = { Text(&quot;Enter password&quot;) },
        visualTransformation = PasswordVisualTransformation(),
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
    )
}&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;span&gt;- &lt;b&gt;비밀번호를 나타내는 문자를&amp;nbsp;*로 바꾸거나 신용카드 번호의 4자리마다 하이픈을 삽입할 수 있습니다.&lt;/b&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;b&gt;&lt;span&gt;결과&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;400&quot; data-origin-width=&quot;1041&quot; data-origin-height=&quot;262&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cV12mV/btq7uTsFDfo/WkIaHqBjQjqV99I2NDoC30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cV12mV/btq7uTsFDfo/WkIaHqBjQjqV99I2NDoC30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cV12mV/btq7uTsFDfo/WkIaHqBjQjqV99I2NDoC30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcV12mV%2Fbtq7uTsFDfo%2FWkIaHqBjQjqV99I2NDoC30%2Fimg.png&quot; width=&quot;400&quot; data-origin-width=&quot;1041&quot; data-origin-height=&quot;262&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크에서 더 다양한 예제들을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/VisualTransformationSamples.kt?hl=ko&quot;&gt;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/VisualTransformationSamples.kt?hl=ko&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623866665839&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;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/VisualTransformationSamples.kt?hl=ko&quot; data-og-description=&quot;&quot; data-og-host=&quot;cs.android.com&quot; data-og-source-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/VisualTransformationSamples.kt?hl=ko&quot; data-og-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/VisualTransformationSamples.kt?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/VisualTransformationSamples.kt?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/VisualTransformationSamples.kt?hl=ko&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;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/VisualTransformationSamples.kt?hl=ko&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;cs.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 정리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;Compose 의 클릭영역은 SelectionContainer Composable로 TextComposable를 감싸서 만들면 사용할 수 있게 됨&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SelectionContainer 안에서 클릭영역 허용하지 않을 공간을 DisableSelection&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Composable로 설정할 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Text대신 CilckableText Composable 를 사용하면 유저가 클릭한 좌표를 가져올 수 있음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 추가된 클릭 표시를 하려면 &lt;b&gt;AnnotatedString&lt;/b&gt;를 사용해서 tag , anotation 매개변수를 설정하면 됨&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TextField 사용하면 유저가 글을 수정할 수 있게끔 됨 (xml의 editText 대신)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TextField는 기본 제공되는 스타일이 존재 , BaseTextField는 힌트나 자리표시자 등등을 제공하지 않는 기본 형식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BaseTextField는 머트리얼 디자인 가이드를 따르지 않고 쓸때 사용하기를 권장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TextField에서 사용자가 글을 입력시 표시되면 글 형식을 지정할 수 있음 (ex: 패스워드면 **)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- TextField에서 키보드 옵션 설정 가능&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편은 사용자 반응 컨트롤과 Compose 버전의 EditText 설정을 보는것 같네요 항상 개발 관련 일을 할때 사용자 상호작용에 대한 컨트롤을 정의할때가 가장 개발자 스러운 작업이 아닌가 싶기도 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상 Compose Text에 관한 정리였습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;https://gift123.tistory.com/33?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623907043278&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;안드로이드 개발 (8) Compose 이해 정리&quot; data-og-description=&quot;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;안드로이드 개발 (8) Compose 이해 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;https://gift123.tistory.com/34?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623907043278&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;안드로이드 개발 (9) Compose 상태 관리&quot; data-og-description=&quot;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078');&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;안드로이드 개발 (9) Compose 상태 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;https://gift123.tistory.com/38?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623907043279&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;안드로이드 개발 (10) Compose Composable Lifecycle&quot; data-og-description=&quot;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/38?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420');&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;안드로이드 개발 (10) Compose Composable Lifecycle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;https://gift123.tistory.com/39?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623907043279&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;안드로이드 개발 (11) Compose Side-effects&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824');&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;안드로이드 개발 (11) Compose Side-effects&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;https://gift123.tistory.com/41&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623907043279&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;안드로이드 개발 (13) Layout in Compose 1편&quot; data-og-description=&quot;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/41&quot; data-og-url=&quot;https://gift123.tistory.com/41&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dml8f7/hyKAqec0Cx/614plyeHa0PYhdk7ndaT3K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bvI9GW/hyKy62Jkgo/AsBT5LySWlNHgIBlmhtttK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/OnT3t/hyKy3EUT8z/GKUhvtCkSqAmEDxllBKHs0/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot; data-source-url=&quot;https://gift123.tistory.com/41&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dml8f7/hyKAqec0Cx/614plyeHa0PYhdk7ndaT3K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bvI9GW/hyKy62Jkgo/AsBT5LySWlNHgIBlmhtttK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/OnT3t/hyKy3EUT8z/GKUhvtCkSqAmEDxllBKHs0/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (13) Layout in Compose 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;https://gift123.tistory.com/42?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623907043279&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;안드로이드 개발 (14) Layout in Compose 2편&quot; data-og-description=&quot;1편 정리 https://gift123.tistory.com/41 안드로이드 개발 (13) Layout in Compose 1편 지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/42?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/42&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHnHfG/hyKAljFdZK/2ECx5Xv8Liz2TCo6a4jK7k/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/bV4tPS/hyKyZ3AMoL/yAKHv6G6HcfNnkHOBoxnt0/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/kK9DB/hyKy9LSbr9/ZH55ZpQzTT0KYubAsIaKb0/img.jpg?width=1280&amp;amp;height=846&amp;amp;face=0_372_254_650&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHnHfG/hyKAljFdZK/2ECx5Xv8Liz2TCo6a4jK7k/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/bV4tPS/hyKyZ3AMoL/yAKHv6G6HcfNnkHOBoxnt0/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/kK9DB/hyKy9LSbr9/ZH55ZpQzTT0KYubAsIaKb0/img.jpg?width=1280&amp;amp;height=846&amp;amp;face=0_372_254_650');&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;안드로이드 개발 (14) Layout in Compose 2편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1편 정리 https://gift123.tistory.com/41 안드로이드 개발 (13) Layout in Compose 1편 지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/43&quot;&gt;https://gift123.tistory.com/43&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623907043280&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;안드로이드 개발 (15) Theming in Compose&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/themes?hl=en Compose의 레이아웃 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers Jetpack Compose를 사용하면 앱의 UI를 훨씬 쉽게 디자인하고 빌드할 수 있습니다...&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/43&quot; data-og-url=&quot;https://gift123.tistory.com/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/buq1xz/hyKAnhegq4/BrqKiDoAsN5xqkd1jbmIBk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bI5bXl/hyKy2ltoWr/OBOtuvr49CwUpHx9nQXkwk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/quoEb/hyKyWeuYAt/lIvRSrXX6xsWEavKCtd9Gk/img.jpg?width=1280&amp;amp;height=960&amp;amp;face=0_0_1280_960&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/43&quot; data-source-url=&quot;https://gift123.tistory.com/43&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/buq1xz/hyKAnhegq4/BrqKiDoAsN5xqkd1jbmIBk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bI5bXl/hyKy2ltoWr/OBOtuvr49CwUpHx9nQXkwk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/quoEb/hyKyWeuYAt/lIvRSrXX6xsWEavKCtd9Gk/img.jpg?width=1280&amp;amp;height=960&amp;amp;face=0_0_1280_960');&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;안드로이드 개발 (15) Theming in Compose&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/themes?hl=en Compose의 레이아웃 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers Jetpack Compose를 사용하면 앱의 UI를 훨씬 쉽게 디자인하고 빌드할 수 있습니다...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/44&quot;&gt;https://gift123.tistory.com/44&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/45?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623907043280&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;안드로이드 개발 (16) Compose Lists&quot; data-og-description=&quot;안녕하세요 안드로이드 개발자 Loner입니다. Compose의 정리를 이어서 진행해보도록 하겠습니다. List 기존 Xml방식으로 List는 주로 리싸이클러 뷰 혹은 리스트뷰로 많이 구현을 해왔습니다. Compose에&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/44&quot; data-og-url=&quot;https://gift123.tistory.com/44&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhkKqR/hyKAi9DgBB/PXSz4eksa6tcuvIh1rvW7k/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/hD3NX/hyKAmxq6BU/Ii8YCrc4ar1teiWs11CFx0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bm8Fim/hyKAti1dXK/WBB533TIiaNDa8o1teielk/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/44&quot; data-source-url=&quot;https://gift123.tistory.com/44&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhkKqR/hyKAi9DgBB/PXSz4eksa6tcuvIh1rvW7k/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/hD3NX/hyKAmxq6BU/Ii8YCrc4ar1teiWs11CFx0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bm8Fim/hyKAti1dXK/WBB533TIiaNDa8o1teielk/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (16) Compose Lists&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 안드로이드 개발자 Loner입니다. Compose의 정리를 이어서 진행해보도록 하겠습니다. List 기존 Xml방식으로 List는 주로 리싸이클러 뷰 혹은 리스트뷰로 많이 구현을 해왔습니다. Compose에&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/45?category=967702&quot;&gt;https://gift123.tistory.com/45?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623907058311&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;안드로이드 개발 (17) Compose Text  1편&quot; data-og-description=&quot;안녕하세요 안드로이드 개발자 Loner입니다. Compose 연구에 푹 빠져사는 요즘 새로운 세계를 맛보고 있어서 즐거운것 같습니다. 이번에는 Compose의 텍스트에 관해 정리를 해봤습니다. Compose 의 텍스&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/45?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/45&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ct6XNJ/hyKAkTW8k3/1ncRkkbZI4KbineJNh6i7K/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/cT1k8l/hyKAlZADX5/FdfH9PpkxhgZYRHLpMaBQ1/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/bi8e8o/hyKBA1TMmQ/mBEZ7c6KFKvoa8reQ2zSzK/img.jpg?width=1280&amp;amp;height=856&amp;amp;face=0_0_1280_856&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/45?category=967702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/45?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ct6XNJ/hyKAkTW8k3/1ncRkkbZI4KbineJNh6i7K/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/cT1k8l/hyKAlZADX5/FdfH9PpkxhgZYRHLpMaBQ1/img.jpg?width=800&amp;amp;height=535&amp;amp;face=0_0_800_535,https://scrap.kakaocdn.net/dn/bi8e8o/hyKBA1TMmQ/mBEZ7c6KFKvoa8reQ2zSzK/img.jpg?width=1280&amp;amp;height=856&amp;amp;face=0_0_1280_856');&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;안드로이드 개발 (17) Compose Text 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 안드로이드 개발자 Loner입니다. Compose 연구에 푹 빠져사는 요즘 새로운 세계를 맛보고 있어서 즐거운것 같습니다. 이번에는 Compose의 텍스트에 관해 정리를 해봤습니다. Compose 의 텍스&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>AndroidCompose</category>
      <category>AndroidComposeText</category>
      <category>ComposeEditText</category>
      <category>ComposeText</category>
      <category>안드로이드</category>
      <category>안드로이드Compose</category>
      <category>안드로이드Text</category>
      <category>안드로이드개발</category>
      <category>안드로이드컴포즈</category>
      <category>안드로이드컴포즈정리</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/46</guid>
      <comments>https://gift123.tistory.com/46#entry46comment</comments>
      <pubDate>Thu, 17 Jun 2021 03:05:21 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (17) Compose Text  1편</title>
      <link>https://gift123.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 &lt;b&gt;안드로이드 개발자 &lt;span style=&quot;color: #ee2323;&quot;&gt;Loner&lt;/span&gt;입니다.&lt;/b&gt; Compose 연구에 푹 빠져사는 요즘 새로운 세계를 맛보고 있어서 즐거운것 같습니다. 이번에는 Compose의 텍스트에 관해 정리를 해봤습니다.&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;Compose 의 텍스트&lt;/b&gt;&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;856&quot; data-filename=&quot;social-media-1989152_1280.jpg&quot; width=&quot;610&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lNcOK/btq7sU72cvm/14lOWr2FZN4KEJE50pX6SK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lNcOK/btq7sU72cvm/14lOWr2FZN4KEJE50pX6SK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lNcOK/btq7sU72cvm/14lOWr2FZN4KEJE50pX6SK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlNcOK%2Fbtq7sU72cvm%2F14lOWr2FZN4KEJE50pX6SK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;856&quot; data-filename=&quot;social-media-1989152_1280.jpg&quot; width=&quot;610&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compose는 텍스트를 표시하고 사용자 입력을 처리하는 기본 구조인&lt;b&gt;&lt;span&gt; &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;BasicText 및 BasicTextField를 제공함.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상위 수준에서 Compose는 Text 및 TextField를 제공하며&lt;b&gt; 머티리얼 디자인를 따름&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(위 Composable은 디자인과 분위기가 Android 사용자에게 적합하며 쉽게 커스텀 할 수 있는 옵션이 포함되어 있으므로사용을 권장함)&lt;/p&gt;
&lt;h2 id=&quot;displaying-text&quot; data-ke-size=&quot;size26&quot; data-text=&quot;텍스트 표시&quot;&gt;&lt;span&gt;&lt;b&gt;1. 기본 텍스트 표시&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;yada-yada-1430679_1280.jpg&quot; width=&quot;528&quot; height=&quot;352&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ux68a/btq7p9YFqL4/oNBo8bYsPMCKZjU3UJrhl0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ux68a/btq7p9YFqL4/oNBo8bYsPMCKZjU3UJrhl0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ux68a/btq7p9YFqL4/oNBo8bYsPMCKZjU3UJrhl0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUx68a%2Fbtq7p9YFqL4%2FoNBo8bYsPMCKZjU3UJrhl0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;yada-yada-1430679_1280.jpg&quot; width=&quot;528&quot; height=&quot;352&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot; data-text=&quot;텍스트 표시&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트를 표시하는 가장 기본적인 방법은&lt;span&gt; &lt;/span&gt;&lt;b&gt;Text&lt;span&gt; Composable&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;예제 1.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623859743466&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;@Composable
fun SimpleText() {
Text(&quot;Hello World&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제 2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;문자열 리소스를 사용하는 예제&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623860221173&quot; class=&quot;html xml&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun StringResourceText() {
Text(stringResource(R.string.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;b&gt;&lt;span&gt;예제1,2 결과&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;195&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BxS48/btq7tYgUvJk/4xmzgB6JD6SrWnczUtTuY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BxS48/btq7tYgUvJk/4xmzgB6JD6SrWnczUtTuY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BxS48/btq7tYgUvJk/4xmzgB6JD6SrWnczUtTuY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBxS48%2Fbtq7tYgUvJk%2F4xmzgB6JD6SrWnczUtTuY1%2Fimg.png&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;195&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;h2 id=&quot;styling-text&quot; data-ke-size=&quot;size26&quot; data-text=&quot;텍스트 스타일 지정&quot;&gt;&lt;b&gt;&lt;span&gt;2. 텍스트 스타일 지정&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;929&quot; data-filename=&quot;colored-42660_1280.png&quot; width=&quot;602&quot; height=&quot;437&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C7Wcz/btq7ybUaj79/RCzR5BA0NPBB6Ui9VMhEW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C7Wcz/btq7ybUaj79/RCzR5BA0NPBB6Ui9VMhEW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C7Wcz/btq7ybUaj79/RCzR5BA0NPBB6Ui9VMhEW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC7Wcz%2Fbtq7ybUaj79%2FRCzR5BA0NPBB6Ui9VMhEW0%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;929&quot; data-filename=&quot;colored-42660_1280.png&quot; width=&quot;602&quot; height=&quot;437&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Text&lt;span&gt; &lt;/span&gt;Composable에는 콘텐츠의 스타일을 지정할 수 있는 여러 매개변수가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 매개변수 중 하나를 설정할 때마다 &lt;b&gt;전체 텍스트 값에 스타일이 적용됩니다.&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;Text의 모든 매개변수를 보려면&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;아래링크를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;참고하시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt;l=88?hl=ko&quot;&gt;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt;l=88?hl=ko&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623860398913&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;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt;l=88?hl=ko&quot; data-og-description=&quot;&quot; data-og-host=&quot;cs.android.com&quot; data-og-source-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt;l=88?hl=ko&quot; data-og-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt;l=88?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt;l=88?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt;l=88?hl=ko&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;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt;l=88?hl=ko&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;cs.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623860313622&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun BlueText() {
Text(&quot;Hello World&quot;, color = Color.Blue)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제1. 결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;195&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDmtVP/btq7snhDOal/lik8x9V7cW0TRv4bDfEAKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDmtVP/btq7snhDOal/lik8x9V7cW0TRv4bDfEAKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDmtVP/btq7snhDOal/lik8x9V7cW0TRv4bDfEAKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDmtVP%2Fbtq7snhDOal%2Flik8x9V7cW0TRv4bDfEAKK%2Fimg.png&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;195&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;예제2. 텍스트 크기 변경&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623860427412&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun BigText() {
Text(&quot;Hello World&quot;, fontSize = 30.sp)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;예제2. 결과&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-height=&quot;111&quot; data-origin-width=&quot;422&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ln0mX/btq7quBknjx/mb5BHSkVEKVeSP7bMCOWn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ln0mX/btq7quBknjx/mb5BHSkVEKVeSP7bMCOWn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ln0mX/btq7quBknjx/mb5BHSkVEKVeSP7bMCOWn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fln0mX%2Fbtq7quBknjx%2Fmb5BHSkVEKVeSP7bMCOWn1%2Fimg.png&quot; data-origin-height=&quot;111&quot; data-origin-width=&quot;422&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;&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;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623860446918&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun ItalicText() {
Text(&quot;Hello World&quot;, fontStyle = FontStyle.Italic)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;예제3. 결과&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;190&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xvcqZ/btq7tY2g0eI/UlFwXTne8lQ0MkGs5KX28K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xvcqZ/btq7tY2g0eI/UlFwXTne8lQ0MkGs5KX28K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xvcqZ/btq7tY2g0eI/UlFwXTne8lQ0MkGs5KX28K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxvcqZ%2Fbtq7tY2g0eI%2FUlFwXTne8lQ0MkGs5KX28K%2Fimg.png&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;190&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;&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;예제4. 스트를 굵게 표시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623860470572&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun BoldText() {
Text(&quot;Hello World&quot;, fontWeight = FontWeight.Bold)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제4. 결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;197&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AbdvY/btq7smQB2pw/5V3T5LdYuAWk9jk3N5kjU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AbdvY/btq7smQB2pw/5V3T5LdYuAWk9jk3N5kjU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AbdvY/btq7smQB2pw/5V3T5LdYuAWk9jk3N5kjU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAbdvY%2Fbtq7smQB2pw%2F5V3T5LdYuAWk9jk3N5kjU1%2Fimg.png&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;197&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;&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;에제5. 텍스트 정렬&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623860510178&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Preview(showBackground = true)
@Composable
fun CenterText() {
Text(&quot;Hello World&quot;, textAlign = TextAlign.Center,
modifier = Modifier.width(150.dp))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- textAlign&lt;span&gt; &lt;/span&gt;매개변수를 사용하면 텍스트의 정렬을 설정할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;기본적으로&lt;span&gt; &lt;/span&gt;Text는 콘텐츠 값에 따라 자연 텍스트 정렬을 선택함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(ex: 라틴어, 키릴어, 한글 등 왼쪽에서 오른쪽으로 쓰는 알파벳을 위한&lt;span&gt; &lt;/span&gt;Text&lt;span&gt; &lt;/span&gt;컨테이너의 왼쪽 가장자리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(ex2: 아랍어, 히브리어 등 오른쪽에서 왼쪽으로 쓰는 알파벳을 위한&lt;span&gt; &lt;/span&gt;Text&lt;span&gt; &lt;/span&gt;컨테이너의 오른쪽 가장자리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-TextAlign.Right, TextAlign.Lift 보다 언어 텍스트 방향에 따라 Text 이동하는&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; TextAlign.Start와 TextAlign.End를 사용하는 것을 권장함&lt;/b&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;&lt;b&gt;예제5. 결과&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;413&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SPZzV/btq7tYnGv3I/TH3D8joFYJKKFVwkEbFYDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SPZzV/btq7tYnGv3I/TH3D8joFYJKKFVwkEbFYDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SPZzV/btq7tYnGv3I/TH3D8joFYJKKFVwkEbFYDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSPZzV%2Fbtq7tYnGv3I%2FTH3D8joFYJKKFVwkEbFYDK%2Fimg.png&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;413&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;h3 data-ke-size=&quot;size23&quot; data-text=&quot;글꼴 작업&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot; data-text=&quot;글꼴 작업&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 id=&quot;fonts&quot; data-ke-size=&quot;size26&quot; data-text=&quot;글꼴 작업&quot;&gt;&lt;b&gt;&lt;span&gt;3. font&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot; data-filename=&quot;alphabet-5979425_1280.png&quot; width=&quot;570&quot; height=&quot;285&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp6rUb/btq7x5ft977/X9Pv6baVROPWC4mQVqtf31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp6rUb/btq7x5ft977/X9Pv6baVROPWC4mQVqtf31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp6rUb/btq7x5ft977/X9Pv6baVROPWC4mQVqtf31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp6rUb%2Fbtq7x5ft977%2FX9Pv6baVROPWC4mQVqtf31%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot; data-filename=&quot;alphabet-5979425_1280.png&quot; width=&quot;570&quot; height=&quot;285&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Text의&lt;span&gt; &lt;/span&gt;fontFamily&lt;span&gt; &lt;/span&gt;매개변수를 사용하면 Composable에 사용되는 글꼴을 관리할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&lt;b&gt; 기본적으로 Serif, Sans-serif,&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;/p&gt;
&lt;pre id=&quot;code_1623860786930&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun DifferentFonts() {
Column {
Text(&quot;Hello World&quot;, fontFamily = FontFamily.Serif)
Text(&quot;Hello World&quot;, fontFamily = FontFamily.SansSerif)
}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-height=&quot;103&quot; data-origin-width=&quot;219&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C80XN/btq7vnmXmDW/T4FBktIKMoJ9HZ1WLQXUQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C80XN/btq7vnmXmDW/T4FBktIKMoJ9HZ1WLQXUQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C80XN/btq7vnmXmDW/T4FBktIKMoJ9HZ1WLQXUQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC80XN%2Fbtq7vnmXmDW%2FT4FBktIKMoJ9HZ1WLQXUQk%2Fimg.png&quot; data-origin-height=&quot;103&quot; data-origin-width=&quot;219&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;fontFamily&lt;span&gt; &lt;/span&gt;속성을 사용하여&lt;span&gt; &lt;/span&gt;res/fonts&lt;span&gt; &lt;/span&gt;폴더에 정의된 맞춤 font로 작업할 수 있음&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;400&quot; data-origin-height=&quot;330&quot; data-origin-width=&quot;486&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OAhmL/btq7sUTRHqk/KfRMDQIBbxGd6mpfYVUEq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OAhmL/btq7sUTRHqk/KfRMDQIBbxGd6mpfYVUEq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OAhmL/btq7sUTRHqk/KfRMDQIBbxGd6mpfYVUEq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOAhmL%2Fbtq7sUTRHqk%2FKfRMDQIBbxGd6mpfYVUEq0%2Fimg.png&quot; width=&quot;400&quot; data-origin-height=&quot;330&quot; data-origin-width=&quot;486&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;/p&gt;
&lt;pre id=&quot;code_1623860803019&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val firaSansFamily = FontFamily(
Font(R.font.firasans_light, FontWeight.Light),
Font(R.font.firasans_regular, FontWeight.Normal),
Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
Font(R.font.firasans_medium, FontWeight.Medium),
Font(R.font.firasans_bold, FontWeight.Bold)
)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623860858155&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Column {
Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Italic)
Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;fontFamily를 Text 컴포저블에 전달할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- fontFamily에 수동으로&lt;b&gt; fontWeight를 설정하여 텍스트에 적합한 가중치를 선택할 수 있습니다.&lt;/b&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;b&gt;결과&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-height=&quot;288&quot; data-origin-width=&quot;204&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eEYJ0H/btq7on3zjhL/3YQbDb3nL4eRDPk3D6E0c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eEYJ0H/btq7on3zjhL/3YQbDb3nL4eRDPk3D6E0c0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eEYJ0H/btq7on3zjhL/3YQbDb3nL4eRDPk3D6E0c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeEYJ0H%2Fbtq7on3zjhL%2F3YQbDb3nL4eRDPk3D6E0c0%2Fimg.png&quot; data-origin-height=&quot;288&quot; data-origin-width=&quot;204&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot; data-text=&quot;텍스트 내 여러 스타일&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 id=&quot;multiple-styles&quot; data-ke-size=&quot;size23&quot; data-text=&quot;텍스트 내 여러 스타일&quot;&gt;&lt;b&gt;&lt;span&gt;4. Text Composable 내의 여러 스타일 설정&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot; data-filename=&quot;quiz-6064477_1280.png&quot; width=&quot;610&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lV472/btq7x48JAaK/mKLodmyflOYnFmkUT1E2Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lV472/btq7x48JAaK/mKLodmyflOYnFmkUT1E2Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lV472/btq7x48JAaK/mKLodmyflOYnFmkUT1E2Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlV472%2Fbtq7x48JAaK%2FmKLodmyflOYnFmkUT1E2Fk%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot; data-filename=&quot;quiz-6064477_1280.png&quot; width=&quot;610&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한&lt;span&gt; &lt;/span&gt;Text&lt;span&gt; &lt;/span&gt;컴포저블 내에서 여러 스타일을 설정하려면 &lt;b&gt;AnnotatedString&lt;/b&gt;을 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AnnotatedString은 다음 항목이 포함된 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;데이터 클래스&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Text&lt;span&gt; &lt;/span&gt;값&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SpanStyleRange&lt;/b&gt;의&lt;span&gt; &lt;/span&gt;List: 텍스트 값 내 위치 범위가 포함되는 인라인 스타일 지정과 동일함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ParagraphStyleRange&lt;/b&gt;의&lt;span&gt; &lt;/span&gt;List: 텍스트 정렬, 텍스트 방향, 행 간격, 텍스트 들여쓰기 스타일 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextStyle은&lt;span&gt; &lt;/span&gt;Text&lt;span&gt; &lt;/span&gt;컴포저블에서 사용되며&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpanStyle&lt;span&gt; &lt;/span&gt;및&lt;span&gt; &lt;/span&gt;ParagraphStyle은&lt;span&gt; &lt;/span&gt;AnnotatedString에서 사용됩니다.&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;SpanStyle과&lt;span&gt; &lt;/span&gt;ParagraphStyle의 차이점은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;b&gt;ParagraphStyle&lt;/b&gt;은 전체 단락에 적용할 수 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;b&gt;SpanStyle&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;예제1. SpanStyle 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623861095600&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MultipleStylesInText() {
Text(buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Blue)) {
append(&quot;H&quot;)
}
append(&quot;ello &quot;)

withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
append(&quot;W&quot;)
}
append(&quot;orld&quot;)
})
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제1.결과&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 alignLeft&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;194&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2nnLB/btq7sXv0D8j/iePeAEJnxcVvAPUdlMdU3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2nnLB/btq7sXv0D8j/iePeAEJnxcVvAPUdlMdU3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2nnLB/btq7sXv0D8j/iePeAEJnxcVvAPUdlMdU3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2nnLB%2Fbtq7sXv0D8j%2FiePeAEJnxcVvAPUdlMdU3K%2Fimg.png&quot; data-origin-height=&quot;52&quot; data-origin-width=&quot;194&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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.ParagraphStyle 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623861195016&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun ParagraphStyle() {
Text(buildAnnotatedString {
withStyle(style = ParagraphStyle(lineHeight = 30.sp)) {
withStyle(style = SpanStyle(color = Color.Blue)) {
append(&quot;Hello\n&quot;)
}
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold,
color = Color.Red)) {
append(&quot;World\n&quot;)
}
append(&quot;Compose&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;-텍스트의 한 부분이 ParagraphStyle로 표시되면 이 부분은 &lt;b&gt;시작과 끝에 줄 바꿈이 있는 것처럼&lt;/b&gt; 나머지 부분과 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;분리&lt;/b&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;b&gt;예제2. 결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-height=&quot;272&quot; data-origin-width=&quot;163&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSre1U/btq7ut11NYs/FGKK6j9sq1wCcdnIk4bJ5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSre1U/btq7ut11NYs/FGKK6j9sq1wCcdnIk4bJ5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSre1U/btq7ut11NYs/FGKK6j9sq1wCcdnIk4bJ5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSre1U%2Fbtq7ut11NYs%2FFGKK6j9sq1wCcdnIk4bJ5K%2Fimg.png&quot; data-origin-height=&quot;272&quot; data-origin-width=&quot;163&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;max-lines&quot; data-ke-size=&quot;size26&quot; data-text=&quot;최대 줄 수&quot;&gt;&lt;b&gt;&lt;span&gt;5. 최대 줄 수&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;sign-160740_1280.png&quot; width=&quot;316&quot; height=&quot;316&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SCJX7/btq7ut2JgQZ/nOIKdKkjA0SQTHDIOfCdBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SCJX7/btq7ut2JgQZ/nOIKdKkjA0SQTHDIOfCdBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SCJX7/btq7ut2JgQZ/nOIKdKkjA0SQTHDIOfCdBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSCJX7%2Fbtq7ut2JgQZ%2FnOIKdKkjA0SQTHDIOfCdBK%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;sign-160740_1280.png&quot; width=&quot;316&quot; height=&quot;316&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Text&lt;span&gt; &lt;/span&gt;Composable에 표시되는 줄 수를 제한하려면&lt;span&gt; &lt;/span&gt;maxLines&lt;span&gt; &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_1623861322729&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun LongText() {
Text(&quot;hello &quot;.repeat(50), maxLines = 2)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;400&quot; data-origin-height=&quot;138&quot; data-origin-width=&quot;825&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI5lyx/btq7slRDrex/M6z2lqdKrvyN5ZxX7xEPvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI5lyx/btq7slRDrex/M6z2lqdKrvyN5ZxX7xEPvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI5lyx/btq7slRDrex/M6z2lqdKrvyN5ZxX7xEPvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI5lyx%2Fbtq7slRDrex%2FM6z2lqdKrvyN5ZxX7xEPvk%2Fimg.png&quot; width=&quot;400&quot; data-origin-height=&quot;138&quot; data-origin-width=&quot;825&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;text-overflow&quot; data-ke-size=&quot;size23&quot; data-text=&quot;텍스트 오버플로&quot;&gt;&lt;b&gt;&lt;span&gt;6. 텍스트 오버플로&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;400&quot; data-origin-height=&quot;97&quot; data-origin-width=&quot;1080&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sVeZO/btq7ucMLp8U/XZgyLbrHcIR3NrPlhECikk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sVeZO/btq7ucMLp8U/XZgyLbrHcIR3NrPlhECikk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sVeZO/btq7ucMLp8U/XZgyLbrHcIR3NrPlhECikk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsVeZO%2Fbtq7ucMLp8U%2FXZgyLbrHcIR3NrPlhECikk%2Fimg.png&quot; width=&quot;400&quot; data-origin-height=&quot;97&quot; data-origin-width=&quot;1080&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 긴 텍스트를 제한할 때 표시된 텍스트가 잘린 경우에만 표시되는 텍스트 오버플로를 표시할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- textOverflow 매개변수를 설정하면됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623861396025&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun OverflowedText() {
Text(&quot;Hello Compose &quot;.repeat(50), maxLines = 2, overflow = TextOverflow.Ellipsis)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. 정리&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;result-3236280_1280.jpg&quot; width=&quot;562&quot; height=&quot;421&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2U3YZ/btq7sXcgT9B/UvJlfKns6tM19IwQ9E6RJK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2U3YZ/btq7sXcgT9B/UvJlfKns6tM19IwQ9E6RJK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2U3YZ/btq7sXcgT9B/UvJlfKns6tM19IwQ9E6RJK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2U3YZ%2Fbtq7sXcgT9B%2FUvJlfKns6tM19IwQ9E6RJK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;result-3236280_1280.jpg&quot; width=&quot;562&quot; height=&quot;421&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;Compose 의 Text Composable은 기본적으로 머티리얼 디자인 가이드에 따라 구현이 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Text Composable의 매개변수로 스타일을 지정할 수 있음 크기, 색깔, 기울기, 정렬, 굵기,최대 줄수, 오버플로우 등등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- RTL, LTR 설정 컨텍스트에 따라 정렬이 자연적으로 달라지게 되어있음 (Left,Right 보다 Start,End 사용을 권장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- font도 fontFamily 패러미터로 쉽게 설정가능, fontWeight 패러미터로 가중치 수동으로 설정가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Text Composable 내의 글자마다 설정을 다르게 하고 싶으면 &lt;b&gt;AnnotatedString&lt;/b&gt;를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; -&amp;gt;ParagraphStyle&lt;/b&gt;은 전체단락&lt;b&gt; , SpanStyle&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;이상 Text 1편 정리입니다. 다음은 2편으로 이어서 작성 예정입니다.&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;&lt;b&gt;이전 Compose 정리 내용&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;https://gift123.tistory.com/33?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623905413612&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;안드로이드 개발 (8) Compose 이해 정리&quot; data-og-description=&quot;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;안드로이드 개발 (8) Compose 이해 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;https://gift123.tistory.com/34?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623905413612&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;안드로이드 개발 (9) Compose 상태 관리&quot; data-og-description=&quot;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078');&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;안드로이드 개발 (9) Compose 상태 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;https://gift123.tistory.com/38?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623905413613&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;안드로이드 개발 (10) Compose Composable Lifecycle&quot; data-og-description=&quot;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/38?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420');&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;안드로이드 개발 (10) Compose Composable Lifecycle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;https://gift123.tistory.com/39?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623905413613&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;안드로이드 개발 (11) Compose Side-effects&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824');&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;안드로이드 개발 (11) Compose Side-effects&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;https://gift123.tistory.com/41&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623905413613&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;안드로이드 개발 (13) Layout in Compose 1편&quot; data-og-description=&quot;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/41&quot; data-og-url=&quot;https://gift123.tistory.com/41&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dml8f7/hyKAqec0Cx/614plyeHa0PYhdk7ndaT3K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bvI9GW/hyKy62Jkgo/AsBT5LySWlNHgIBlmhtttK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/OnT3t/hyKy3EUT8z/GKUhvtCkSqAmEDxllBKHs0/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot; data-source-url=&quot;https://gift123.tistory.com/41&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dml8f7/hyKAqec0Cx/614plyeHa0PYhdk7ndaT3K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bvI9GW/hyKy62Jkgo/AsBT5LySWlNHgIBlmhtttK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/OnT3t/hyKy3EUT8z/GKUhvtCkSqAmEDxllBKHs0/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (13) Layout in Compose 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;https://gift123.tistory.com/42?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623905413614&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;안드로이드 개발 (14) Layout in Compose 2편&quot; data-og-description=&quot;1편 정리 https://gift123.tistory.com/41 안드로이드 개발 (13) Layout in Compose 1편 지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/42?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/42&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHnHfG/hyKAljFdZK/2ECx5Xv8Liz2TCo6a4jK7k/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/bV4tPS/hyKyZ3AMoL/yAKHv6G6HcfNnkHOBoxnt0/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/kK9DB/hyKy9LSbr9/ZH55ZpQzTT0KYubAsIaKb0/img.jpg?width=1280&amp;amp;height=846&amp;amp;face=0_372_254_650&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHnHfG/hyKAljFdZK/2ECx5Xv8Liz2TCo6a4jK7k/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/bV4tPS/hyKyZ3AMoL/yAKHv6G6HcfNnkHOBoxnt0/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/kK9DB/hyKy9LSbr9/ZH55ZpQzTT0KYubAsIaKb0/img.jpg?width=1280&amp;amp;height=846&amp;amp;face=0_372_254_650');&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;안드로이드 개발 (14) Layout in Compose 2편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1편 정리 https://gift123.tistory.com/41 안드로이드 개발 (13) Layout in Compose 1편 지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/43&quot;&gt;https://gift123.tistory.com/43&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623905413614&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;안드로이드 개발 (15) Theming in Compose&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/themes?hl=en Compose의 레이아웃 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers Jetpack Compose를 사용하면 앱의 UI를 훨씬 쉽게 디자인하고 빌드할 수 있습니다...&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/43&quot; data-og-url=&quot;https://gift123.tistory.com/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/buq1xz/hyKAnhegq4/BrqKiDoAsN5xqkd1jbmIBk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bI5bXl/hyKy2ltoWr/OBOtuvr49CwUpHx9nQXkwk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/quoEb/hyKyWeuYAt/lIvRSrXX6xsWEavKCtd9Gk/img.jpg?width=1280&amp;amp;height=960&amp;amp;face=0_0_1280_960&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/43&quot; data-source-url=&quot;https://gift123.tistory.com/43&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/buq1xz/hyKAnhegq4/BrqKiDoAsN5xqkd1jbmIBk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bI5bXl/hyKy2ltoWr/OBOtuvr49CwUpHx9nQXkwk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/quoEb/hyKyWeuYAt/lIvRSrXX6xsWEavKCtd9Gk/img.jpg?width=1280&amp;amp;height=960&amp;amp;face=0_0_1280_960');&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;안드로이드 개발 (15) Theming in Compose&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/themes?hl=en Compose의 레이아웃 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers Jetpack Compose를 사용하면 앱의 UI를 훨씬 쉽게 디자인하고 빌드할 수 있습니다...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/44&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/44&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623905424504&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;안드로이드 개발 (16) Compose Lists&quot; data-og-description=&quot;안녕하세요 안드로이드 개발자 Loner입니다. Compose의 정리를 이어서 진행해보도록 하겠습니다. List 기존 Xml방식으로 List는 주로 리싸이클러 뷰 혹은 리스트뷰로 많이 구현을 해왔습니다. Compose에&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/44&quot; data-og-url=&quot;https://gift123.tistory.com/44&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhkKqR/hyKAi9DgBB/PXSz4eksa6tcuvIh1rvW7k/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/hD3NX/hyKAmxq6BU/Ii8YCrc4ar1teiWs11CFx0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bm8Fim/hyKAti1dXK/WBB533TIiaNDa8o1teielk/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/44&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/44&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhkKqR/hyKAi9DgBB/PXSz4eksa6tcuvIh1rvW7k/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/hD3NX/hyKAmxq6BU/Ii8YCrc4ar1teiWs11CFx0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bm8Fim/hyKAti1dXK/WBB533TIiaNDa8o1teielk/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (16) Compose Lists&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 안드로이드 개발자 Loner입니다. Compose의 정리를 이어서 진행해보도록 하겠습니다. List 기존 Xml방식으로 List는 주로 리싸이클러 뷰 혹은 리스트뷰로 많이 구현을 해왔습니다. Compose에&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>AndroidComposeText</category>
      <category>compose</category>
      <category>ComposeText</category>
      <category>ComposeTextSetting</category>
      <category>ComposeText설정</category>
      <category>안드로이드ComposeText</category>
      <category>안드로이드개발</category>
      <category>안드로이드컴포즈</category>
      <category>안드로이드컴포즈Text</category>
      <category>컴포즈Text</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/45</guid>
      <comments>https://gift123.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 17 Jun 2021 01:37:52 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (16) Compose Lists</title>
      <link>https://gift123.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요&lt;b&gt; 안드로이드 개발자 &lt;span style=&quot;color: #ee2323;&quot;&gt;Loner&lt;/span&gt;입니다.&lt;/b&gt; Compose의 정리를 이어서 진행해보도록 하겠습니다.&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;List&lt;/b&gt;&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;checklist-2470549_1280.jpg&quot; width=&quot;558&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b65gqd/btq7oH0vcqe/GqiPnBz2eE2n6MV5hUIxn0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b65gqd/btq7oH0vcqe/GqiPnBz2eE2n6MV5hUIxn0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b65gqd/btq7oH0vcqe/GqiPnBz2eE2n6MV5hUIxn0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb65gqd%2Fbtq7oH0vcqe%2FGqiPnBz2eE2n6MV5hUIxn0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;checklist-2470549_1280.jpg&quot; width=&quot;558&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Xml방식으로 List는 주로 리싸이클러 뷰 혹은 리스트뷰로 많이 구현을 해왔습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose에서는 List를 어떻게 구현해야할지 살펴보겠습니다.&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;/h4&gt;
&lt;pre id=&quot;code_1623780294730&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MessageList(messages: List&amp;lt;Message&amp;gt;) {
    Column {
        messages.forEach { message -&amp;gt;
            MessageRow(message)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Column 또는 Row를 사용하여 위과 같이 각 아이템의 콘텐츠를 표시할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;verticalScroll()&lt;span&gt;&amp;nbsp;&lt;/span&gt;Modifier를 사용하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;Column을 스크롤 가능하게 만들 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 아이템 갯수만큼 UI가 미리 만들어져 있기 때문에 많은 size의 아이템을 표시해야 하는 경우&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;성능문제가 발생할 수 있음&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;lazy&quot; data-text=&quot;지연 컴포저블&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;1. Lazy Composable&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;856&quot; data-filename=&quot;turtle-1137608_1280.jpg&quot; width=&quot;547&quot; height=&quot;366&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WOgQb/btq7jDyrhY4/QP39CqaeJKKK2OkgYsDQtK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WOgQb/btq7jDyrhY4/QP39CqaeJKKK2OkgYsDQtK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WOgQb/btq7jDyrhY4/QP39CqaeJKKK2OkgYsDQtK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWOgQb%2Fbtq7jDyrhY4%2FQP39CqaeJKKK2OkgYsDQtK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;856&quot; data-filename=&quot;turtle-1137608_1280.jpg&quot; width=&quot;547&quot; height=&quot;366&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 Column를 이용한 심플 구현과 달리 화면상 표시되는 영역만 아이템을 구성하며 배치할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 구성요소에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;LazyColumn&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;LazyRow&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;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;RecyclerView와 동일한 원칙을 따릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) LazyColumn 와 LazyRow&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LazyColumn은 세로로 스크롤되는 목록을 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LazyRow는 가로로 스크롤되는 목록을 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Lazy 구성요소는 Compose의 대부분 레이아웃과 다름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Lazy 구성요소는&lt;span&gt;&amp;nbsp;&lt;/span&gt;@Composable&lt;span&gt;&amp;nbsp;&lt;/span&gt;콘텐츠 블록 구성요소를 수락하고 앱에서 직접 Composable을 내보낼 수 있도록 허용하는 대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;LazyListScope.()&lt;/b&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;lazylistscope&quot; data-text=&quot;LazyListScope DSL&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;2) LazyListScope DSL&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;LazyListScope&lt;span&gt;&amp;nbsp;&lt;/span&gt;블록은 앱에서 항목 콘텐츠를&lt;span&gt;&amp;nbsp;&lt;/span&gt;설명할 수 있는 DSL을 제공합니다.&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;span&gt;DSL은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;도메인별 언어&lt;/b&gt;를 의미함. Compose에서 일부 API용 DSL을 정의하는 방법에 관한 자세한 내용은&lt;span&gt;&amp;nbsp;아래&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문서를 참고할 것&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://developer.android.com/jetpack/compose/kotlin#dsl&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/jetpack/compose/kotlin#dsl&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623780870773&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;Jetpack Compose용 Kotlin &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;Jetpack Compose는 Kotlin을 중심으로 빌드되었습니다. 일부 경우에 Kotlin은 좋은 Compose 코드를 더 쉽게 작성할 수 있게 하는 특수 관용구를 제공합니다. 다른 프로그래밍 언어로 생각하고 그 언어를 Kot&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/jetpack/compose/kotlin#dsl&quot; data-og-url=&quot;https://developer.android.com/jetpack/compose/kotlin?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.android.com/jetpack/compose/kotlin#dsl&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/jetpack/compose/kotlin#dsl&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;Jetpack Compose용 Kotlin &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jetpack Compose는 Kotlin을 중심으로 빌드되었습니다. 일부 경우에 Kotlin은 좋은 Compose 코드를 더 쉽게 작성할 수 있게 하는 특수 관용구를 제공합니다. 다른 프로그래밍 언어로 생각하고 그 언어를 Kot&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LazyListScope의 DSL은 레이아웃의 항목을 설명하는 여러 함수를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;item()은 단일 항목을 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- [items(Int)]는 여러 항목을 추가&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;/p&gt;
&lt;pre id=&quot;code_1623781070785&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LazyColumn {
    // Add a single item
    item {
        Text(text = &quot;First item&quot;)
    }

    // Add 5 items
    items(5) { index -&amp;gt;
        Text(text = &quot;Item: $index&quot;)
    }

    // Add another single item
    item {
        Text(text = &quot;Last item&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;265&quot; data-filename=&quot;화면 캡처 2021-06-16 032140.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byASER/btq7koOvGsQ/qiMo5hDtADKQx6C60JO1k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byASER/btq7koOvGsQ/qiMo5hDtADKQx6C60JO1k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byASER/btq7koOvGsQ/qiMo5hDtADKQx6C60JO1k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyASER%2Fbtq7koOvGsQ%2FqiMo5hDtADKQx6C60JO1k1%2Fimg.png&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;265&quot; data-filename=&quot;화면 캡처 2021-06-16 032140.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;List와 같이 항목 컬렉션을 추가할 수 있는 다양한 확장 함수도 있습니다.&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;/p&gt;
&lt;pre id=&quot;code_1623781281456&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import androidx.compose.foundation.lazy.items

@Composable
fun MessageList(messages: List&amp;lt;Message&amp;gt;) {
    LazyColumn {
        items(messages) { message -&amp;gt;
            MessageRow(message)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 기본 Column과 달리 성능문제가 해결됨&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;span&gt;&amp;nbsp;&lt;/span&gt;itemsIndexed()라고 하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;items()&lt;span&gt;&amp;nbsp;&lt;/span&gt;확장 함수의 버전도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은&lt;span&gt;&amp;nbsp;&lt;/span&gt;아래&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;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListScope&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListScope&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623781587358&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;LazyListScope &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListScope&quot; data-og-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListScope?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ZgLyK/hyKAoUZ4se/PmQVkJOWnpRDljVR4mqgn0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListScope&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListScope&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ZgLyK/hyKAoUZ4se/PmQVkJOWnpRDljVR4mqgn0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;LazyListScope &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-text=&quot;콘텐츠 패딩&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;content-padding&quot; data-text=&quot;콘텐츠 패딩&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;2. 콘텐츠 패딩&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;852&quot; data-filename=&quot;tape-3591242_1280.jpg&quot; width=&quot;495&quot; height=&quot;330&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnu3ck/btq7oGN3Exe/WFN74tnXjzdzhakoNhUBJK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnu3ck/btq7oGN3Exe/WFN74tnXjzdzhakoNhUBJK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnu3ck/btq7oGN3Exe/WFN74tnXjzdzhakoNhUBJK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcnu3ck%2Fbtq7oGN3Exe%2FWFN74tnXjzdzhakoNhUBJK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;852&quot; data-filename=&quot;tape-3591242_1280.jpg&quot; width=&quot;495&quot; height=&quot;330&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘텐츠 가장자리 주변에 패딩을 추가해야 하는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연 구성요소를 사용하면 일부&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;PaddingValues을&lt;span&gt;&amp;nbsp;&lt;/span&gt;contentPadding&lt;span&gt;&amp;nbsp;&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;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623781660984&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LazyColumn(
    contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
    // ...
}&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;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;325&quot; data-filename=&quot;화면 캡처 2021-06-16 032949.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLegPi/btq7mza6Vlm/mZbgv8MZEpiJw0583kLuUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLegPi/btq7mza6Vlm/mZbgv8MZEpiJw0583kLuUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLegPi/btq7mza6Vlm/mZbgv8MZEpiJw0583kLuUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLegPi%2Fbtq7mza6Vlm%2FmZbgv8MZEpiJw0583kLuUK%2Fimg.png&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;325&quot; data-filename=&quot;화면 캡처 2021-06-16 032949.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-text=&quot;콘텐츠 간격&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;content-spacing&quot; data-text=&quot;콘텐츠 간격&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;3. 콘텐츠 간격&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;525&quot; height=&quot;380&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;925&quot; data-filename=&quot;metal-1749027_1280.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uJw2I/btq7qvkYXTk/tqPWUK7XaMRW2Eo3kNcXB1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uJw2I/btq7qvkYXTk/tqPWUK7XaMRW2Eo3kNcXB1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uJw2I/btq7qvkYXTk/tqPWUK7XaMRW2Eo3kNcXB1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuJw2I%2Fbtq7qvkYXTk%2FtqPWUK7XaMRW2Eo3kNcXB1%2Fimg.jpg&quot; width=&quot;525&quot; height=&quot;380&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;925&quot; data-filename=&quot;metal-1749027_1280.jpg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Arrangement.spacedBy()를 사용하면 아이템 마다 간격을 줄 수 있음&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;/p&gt;
&lt;pre id=&quot;code_1623781855191&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LazyColumn(
    verticalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Column은 verticalArrangement만 사용&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623781861175&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LazyRow(
    horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Row는 horizontalArrangement만 사용&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;338&quot; data-filename=&quot;화면 캡처 2021-06-16 033444.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckNIW0/btq7klqOkpH/k0BylQGt9UeCSvkDQ8suTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckNIW0/btq7klqOkpH/k0BylQGt9UeCSvkDQ8suTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckNIW0/btq7klqOkpH/k0BylQGt9UeCSvkDQ8suTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckNIW0%2Fbtq7klqOkpH%2Fk0BylQGt9UeCSvkDQ8suTK%2Fimg.png&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;338&quot; data-filename=&quot;화면 캡처 2021-06-16 033444.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;item-animations&quot; data-text=&quot;항목 애니메이션&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;4. 항목 애니메이션&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-RecyclerView 의경우&lt;span&gt; 아이템 변경시&lt;/span&gt; 자동으로 애니메이션 처리되지만&lt;/b&gt;&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;- 항목을 변경하면 인스턴트 '스냅'이 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 차후 업데이트 가능성이 있음&lt;/p&gt;
&lt;h2 data-text=&quot;고정 헤더(실험용)&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;sticky-headers&quot; data-text=&quot;고정 헤더(실험용)&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;5. 고정 헤더(실험용)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의:&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;실험용 API는 향후 변경되거나 완전히 삭제될 수 있음&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LazyColumn이 있는 고정 헤더를 표시하려면 헤더 콘텐츠를 제공하는 실험용 stickyHeader() 함수를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623782303120&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListWithHeader(items: List&amp;lt;Item&amp;gt;) {
    LazyColumn {
        stickyHeader {
            Header()
        }

        items(items) { item -&amp;gt;
            ItemRow(item)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;500&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;683&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLpWai/btq7o3I10Ji/lDr5kYwjs1eHTFgsuOjjmK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLpWai/btq7o3I10Ji/lDr5kYwjs1eHTFgsuOjjmK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLpWai/btq7o3I10Ji/lDr5kYwjs1eHTFgsuOjjmK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cLpWai/btq7o3I10Ji/lDr5kYwjs1eHTFgsuOjjmK/img.gif&quot; width=&quot;500&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;683&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위예와 같이 여러 헤더를 표시하려면 다음 예제를 참고하세요&lt;/p&gt;
&lt;pre id=&quot;code_1623782645135&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val grouped = contacts.groupBy { it.firstName[0] }

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ContactsList(grouped: Map&amp;lt;Char, List&amp;lt;Contact&amp;gt;&amp;gt;) {
    LazyColumn {
        grouped.forEach { (initial, contactsForInitial) -&amp;gt;
            stickyHeader {
                CharacterHeader(initial)
            }

            items(contactsForInitial) { contact -&amp;gt;
                ContactListItem(contact)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-text=&quot;그리드(실험용)&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;grids&quot; data-text=&quot;그리드(실험용)&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;5. 그리드(실험용)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의:&lt;/b&gt;&amp;nbsp;실험용 API는 향후 변경되거나 완전히 삭제될 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LazyVerticalGrid&lt;span&gt; Composable&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;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623782988991&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PhotoGrid(photos: List&amp;lt;Photo&amp;gt;) {
    LazyVerticalGrid(
        cells = GridCells.Adaptive(minSize = 128.dp)
    ) {
        items(photos) { photo -&amp;gt;
            PhotoItem(photo)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cells&lt;span&gt;&amp;nbsp;&lt;/span&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;GridCells.Fixed의 인스턴스를 대신 제공할 수 있습니다.&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 alignLeft&quot; width=&quot;300&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;1999&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wMyvS/btq7oIrAeMh/ksH4OLBKzafarJYlPwBcL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wMyvS/btq7oIrAeMh/ksH4OLBKzafarJYlPwBcL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wMyvS/btq7oIrAeMh/ksH4OLBKzafarJYlPwBcL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwMyvS%2Fbtq7oIrAeMh%2FksH4OLBKzafarJYlPwBcL1%2Fimg.png&quot; width=&quot;300&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;1999&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;react-to-scroll-position&quot; data-text=&quot;스크롤 위치에 반응&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;6. 스크롤 위치에 반응&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;parchment-2217669_1280.jpg&quot; width=&quot;375&quot; height=&quot;250&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vTVvK/btq7nGA3mPQ/iROkRI49gYbBu1D7vRwfr1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vTVvK/btq7nGA3mPQ/iROkRI49gYbBu1D7vRwfr1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vTVvK/btq7nGA3mPQ/iROkRI49gYbBu1D7vRwfr1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvTVvK%2Fbtq7nGA3mPQ%2FiROkRI49gYbBu1D7vRwfr1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;parchment-2217669_1280.jpg&quot; width=&quot;375&quot; height=&quot;250&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;스크롤 위치와 항목 레이아웃 변경사항에 반응하려면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연 구성요소는&lt;b&gt;LazyListState를 호이스팅하여 사용합니다.&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;예제1.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623783275271&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@OptIn(ExperimentalAnimationApi::class) // AnimatedVisibility
@Composable
fun MessageList(messages: List&amp;lt;Message&amp;gt;) {
    Box {
        val listState = rememberLazyListState()

        LazyColumn(state = listState) {
            // ...
        }
        val showButton by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex &amp;gt; 0
            }
        }

        AnimatedVisibility(visible = showButton) {
            ScrollToTopButton()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;- 사용자가 첫 번째 항목을 지나 스크롤했는지 여부에 따라 버튼을 표시하고 숨기는 예제&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;- &lt;/span&gt;&lt;b&gt;LazyListState는&lt;span&gt;&amp;nbsp;&lt;/span&gt;firstVisibleItemIndex&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;firstVisibleItemScrollOffset&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성을 제공&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; 위의 예에서는&amp;nbsp;derivedStateOf()를 사용하여 불필요한 Composition을 최소화함&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;Composition에서&amp;nbsp; 동일한 컴퍼지션에서 직접 상태를 읽는 기능을 처리할 필요가 없는 시나리오도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;snapshotFlow()&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&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;b&gt;예제2.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623783776174&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -&amp;gt; index &amp;gt; 0 }
        .distinctUntilChanged()
        .filter { it == true }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 이 시나리오의 일반적인 예는 사용자가 특정 지점을 지나 스크롤한 후 분석 이벤트를 보내는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이 시나리오를 효율적으로 처리하기 위해 &lt;b&gt;snapshowFlow()&lt;/b&gt;를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LazyListState는 또한&lt;span&gt;&amp;nbsp;&lt;/span&gt;layoutInfo&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623783964656&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;LazyListLayoutInfo &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo&quot; data-og-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bCEVPH/hyKAr5f3R2/A5vpVkdw7reTipGv2XQerk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bCEVPH/hyKAr5f3R2/A5vpVkdw7reTipGv2XQerk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;LazyListLayoutInfo &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-text=&quot;스크롤 위치 제어&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;control-scroll-position&quot; data-text=&quot;스크롤 위치 제어&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;7. 스크롤 위치 제어&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;scroll-1760402_1280.jpg&quot; width=&quot;522&quot; height=&quot;348&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTlUQb/btq7jzv7Ccv/XozXV9xBYUHIhQuLV8GoeK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTlUQb/btq7jzv7Ccv/XozXV9xBYUHIhQuLV8GoeK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTlUQb/btq7jzv7Ccv/XozXV9xBYUHIhQuLV8GoeK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTlUQb%2Fbtq7jzv7Ccv%2FXozXV9xBYUHIhQuLV8GoeK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;scroll-1760402_1280.jpg&quot; width=&quot;522&quot; height=&quot;348&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LazyListState는 스크롤 위치를 '즉시' 스냅하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;scrollToItem()&lt;span&gt;&amp;nbsp;&lt;/span&gt;및 애니메이션을 사용하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;animateScrollToItem()&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;b&gt;참고:&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;scrollToItem()&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;animateScrollToItem()은 모두 suspend 함수입니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;즉 코루틴에서 호출해야 합니다.&lt;/b&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;pre id=&quot;code_1623784052781&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MessageList(messages: List&amp;lt;Message&amp;gt;) {
    val listState = rememberLazyListState()
    // Remember a CoroutineScope to be able to launch
    val coroutineScope = rememberCoroutineScope()

    LazyColumn(state = listState) {
        // ...
    }

    ScrollToTopButton(
        onClick = {
            coroutineScope.launch {
                // Animate scroll to the first item
                listState.animateScrollToItem(index = 0)
            }
        }
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;large-datasets&quot; data-text=&quot;큰 데이터 세트(페이징)&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;8. 페이징&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;book-3510326_1280.jpg&quot; width=&quot;641&quot; height=&quot;427&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwLbC/btq7kkFtBds/svgHygBCsOuiM2ma4InFb0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwLbC/btq7kkFtBds/svgHygBCsOuiM2ma4InFb0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwLbC/btq7kkFtBds/svgHygBCsOuiM2ma4InFb0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwLbC%2Fbtq7kkFtBds%2FsvgHygBCsOuiM2ma4InFb0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;book-3510326_1280.jpg&quot; width=&quot;641&quot; height=&quot;427&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이템 양이 많으면 Paging 라이브러리를 사용해서 필요한 만큼 아이템을 불러와서 표시할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Paging 3.0 이상에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;androidx.paging:paging-compose&lt;span&gt;&amp;nbsp;&lt;/span&gt;라이브러리를 통해 &lt;b&gt;Compose 지원 기능을 제공합니다.&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;span&gt;&amp;nbsp;&lt;/span&gt;collectAsLazyPagingItems()&lt;span&gt;&amp;nbsp;&lt;/span&gt;확장 함수를 사용한 다음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 반환된&lt;span&gt;&amp;nbsp;&lt;/span&gt;LazyPagingItems를&lt;span&gt;&amp;nbsp;&lt;/span&gt;LazyColumn의&lt;span&gt;&amp;nbsp;&lt;/span&gt;items()에 전달하면 됩니다.&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;span&gt;&amp;nbsp;&lt;/span&gt;item이&lt;span&gt;&amp;nbsp;&lt;/span&gt;null인지 확인하여 데이터가 로드되는 동안 자리표시자를 표시할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623784326885&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items

@Composable
fun MessageList(pager: Pager&amp;lt;Int, Message&amp;gt;) {
    val lazyPagingItems = pager.flow.collectAsLazyPagingItems()

    LazyColumn {
        items(lazyPagingItems) { message -&amp;gt;
            if (message != null) {
                MessageRow(message)
            } else {
                MessagePlaceholder()
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;경고:&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;RemoteMediator를 사용하여 데이터를 가져오는 경우 실제 크기의 자리표시자 아이템을 제공해야합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;RemoteMediator를 사용하는 경우 화면이 콘텐츠로 채워질 때까지 반복적으로 호출되어 새 데이터를 가져옵니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위처럼 하지 않으면 화면이 채워지지 않을 수 있으며 앱에서 많은 데이터 페이지를 가져오게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;item-keys&quot; data-text=&quot;항목 키&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;9. 항목 키&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;keys-525732_1280.jpg&quot; width=&quot;507&quot; height=&quot;338&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drmfxg/btq7lQqAFzj/LurR6BYWkrMLT1esziQbK1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drmfxg/btq7lQqAFzj/LurR6BYWkrMLT1esziQbK1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drmfxg/btq7lQqAFzj/LurR6BYWkrMLT1esziQbK1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdrmfxg%2Fbtq7lQqAFzj%2FLurR6BYWkrMLT1esziQbK1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;keys-525732_1280.jpg&quot; width=&quot;507&quot; height=&quot;338&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 각 아이템의 상태는 List에 있는 아이템의 position를 기준으로 키가 지정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;position을 효율적으로 변경하는 List에 상태가 저장되지 않아 &lt;/b&gt;&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;LazyColumn&lt;span&gt;&amp;nbsp;&lt;/span&gt;내&lt;span&gt;&amp;nbsp;&lt;/span&gt;LazyRow&lt;span&gt;&amp;nbsp;&lt;/span&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;span&gt;&amp;nbsp;&lt;/span&gt;key&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;b&gt;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623784917212&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MessageList(messages: List&amp;lt;Message&amp;gt;) {
    LazyColumn {
        items(
            items = messages,
            key = { message -&amp;gt;
                // Return a stable + unique key for the item
                message.id
            }
        ) { message -&amp;gt;
            MessageRow(message)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;참고:&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;제공된 모든 키를&lt;span&gt;&amp;nbsp;&lt;/span&gt;Bundle&lt;span&gt;&amp;nbsp;&lt;/span&gt;내에 저장할 수 있어야 합니다. &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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;b&gt;10. 정리&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;lamp-4134749_1280.png&quot; width=&quot;560&quot; height=&quot;373&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/26lrj/btq7mxRStp9/yJZvZ13x356n7ps7weurpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/26lrj/btq7mxRStp9/yJZvZ13x356n7ps7weurpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/26lrj/btq7mxRStp9/yJZvZ13x356n7ps7weurpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F26lrj%2Fbtq7mxRStp9%2FyJZvZ13x356n7ps7weurpk%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;lamp-4134749_1280.png&quot; width=&quot;560&quot; height=&quot;373&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 아이템 수가 많지 않을땐 Lazy Composable를 사용하지 않고 심플하게 Column이나 Row로 만들수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Lazy Composable로 구현해야 리사이클러 뷰 처럼 성능상 효율적으로 UI를 배치하고 표시함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Compose에서 List는 매우 만들기 편리함 간단하게 각 아이템마다 간격을 쉽게 넣을 수도 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 스크롤 관련 이벤트나 제어를 쉽게 컨트롤 하게끔 지원해줌&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 페이징 라이브러리는 compose를 공식적으로 지원해주기 떄문에 paging을 사용하기 매우 편리함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- List의 position을 이용해서 해당 아이템을 찾아쓸 때 Compose 상태관리 특징상 문제가 있을 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;이상 Compose의 List에 관해서 정리를 해봤습니다. 개인적으로 RecyclerView에서 어댑터를 따로 만들어 셋팅할 때 보일러플레이트 코드가 너무 많이 발생해서 상당히 불편했는데, 이번 Compose로 이런 문제들이 많이 해결되는것 같습니다.&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;&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 내용 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;https://gift123.tistory.com/33?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623780108179&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;안드로이드 개발 (8) Compose 이해 정리&quot; data-og-description=&quot;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;안드로이드 개발 (8) Compose 이해 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;https://gift123.tistory.com/34?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623780108179&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;안드로이드 개발 (9) Compose 상태 관리&quot; data-og-description=&quot;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078');&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;안드로이드 개발 (9) Compose 상태 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;https://gift123.tistory.com/38?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623780108179&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;안드로이드 개발 (10) Compose Composable Lifecycle&quot; data-og-description=&quot;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/38?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420');&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;안드로이드 개발 (10) Compose Composable Lifecycle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;https://gift123.tistory.com/39?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623780108179&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;안드로이드 개발 (11) Compose Side-effects&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824');&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;안드로이드 개발 (11) Compose Side-effects&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;https://gift123.tistory.com/41&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623780112765&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;안드로이드 개발 (13) Layout in Compose 1편&quot; data-og-description=&quot;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/41&quot; data-og-url=&quot;https://gift123.tistory.com/41&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dml8f7/hyKAqec0Cx/614plyeHa0PYhdk7ndaT3K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bvI9GW/hyKy62Jkgo/AsBT5LySWlNHgIBlmhtttK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/OnT3t/hyKy3EUT8z/GKUhvtCkSqAmEDxllBKHs0/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/41&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dml8f7/hyKAqec0Cx/614plyeHa0PYhdk7ndaT3K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bvI9GW/hyKy62Jkgo/AsBT5LySWlNHgIBlmhtttK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/OnT3t/hyKy3EUT8z/GKUhvtCkSqAmEDxllBKHs0/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (13) Layout in Compose 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;https://gift123.tistory.com/42?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623780198122&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;안드로이드 개발 (14) Layout in Compose 2편&quot; data-og-description=&quot;1편 정리 https://gift123.tistory.com/41 안드로이드 개발 (13) Layout in Compose 1편 지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/42?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/42&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHnHfG/hyKAljFdZK/2ECx5Xv8Liz2TCo6a4jK7k/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/bV4tPS/hyKyZ3AMoL/yAKHv6G6HcfNnkHOBoxnt0/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/kK9DB/hyKy9LSbr9/ZH55ZpQzTT0KYubAsIaKb0/img.jpg?width=1280&amp;amp;height=846&amp;amp;face=0_372_254_650&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHnHfG/hyKAljFdZK/2ECx5Xv8Liz2TCo6a4jK7k/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/bV4tPS/hyKyZ3AMoL/yAKHv6G6HcfNnkHOBoxnt0/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/kK9DB/hyKy9LSbr9/ZH55ZpQzTT0KYubAsIaKb0/img.jpg?width=1280&amp;amp;height=846&amp;amp;face=0_372_254_650');&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;안드로이드 개발 (14) Layout in Compose 2편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1편 정리 https://gift123.tistory.com/41 안드로이드 개발 (13) Layout in Compose 1편 지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/43&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623785474247&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;안드로이드 개발 (15) Theming in Compose&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/themes?hl=en Compose의 레이아웃 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers Jetpack Compose를 사용하면 앱의 UI를 훨씬 쉽게 디자인하고 빌드할 수 있습니다...&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/43&quot; data-og-url=&quot;https://gift123.tistory.com/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/buq1xz/hyKAnhegq4/BrqKiDoAsN5xqkd1jbmIBk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bI5bXl/hyKy2ltoWr/OBOtuvr49CwUpHx9nQXkwk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/quoEb/hyKyWeuYAt/lIvRSrXX6xsWEavKCtd9Gk/img.jpg?width=1280&amp;amp;height=960&amp;amp;face=0_0_1280_960&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/43&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/buq1xz/hyKAnhegq4/BrqKiDoAsN5xqkd1jbmIBk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/bI5bXl/hyKy2ltoWr/OBOtuvr49CwUpHx9nQXkwk/img.jpg?width=800&amp;amp;height=600&amp;amp;face=0_0_800_600,https://scrap.kakaocdn.net/dn/quoEb/hyKyWeuYAt/lIvRSrXX6xsWEavKCtd9Gk/img.jpg?width=1280&amp;amp;height=960&amp;amp;face=0_0_1280_960');&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;안드로이드 개발 (15) Theming in Compose&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/themes?hl=en Compose의 레이아웃 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers Jetpack Compose를 사용하면 앱의 UI를 훨씬 쉽게 디자인하고 빌드할 수 있습니다...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>AndroidCompose</category>
      <category>Android개발</category>
      <category>Android개발자</category>
      <category>compose</category>
      <category>Compose의리스트</category>
      <category>ListInCompose</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>안드로이드개발자로너</category>
      <category>컴포즈List</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/44</guid>
      <comments>https://gift123.tistory.com/44#entry44comment</comments>
      <pubDate>Wed, 16 Jun 2021 04:41:39 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (15) Theming in Compose</title>
      <link>https://gift123.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘도 Compose 내용 정리를 이어서 해보겠습니다.&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;Compose의 테마 설정&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compose는 테마를 적용하여 앱에 일관된 디자인과 분위기를 쉽게 제공을 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본적으로 Compose의&lt;span&gt;&amp;nbsp;&lt;/span&gt;머티리얼 디자인&lt;span&gt;&amp;nbsp;&lt;/span&gt;구현을 만드려는 앱에 맞게 맞춤설정이 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 머티리얼 디자인 뿐만 아니라 다른 Compose의 공개 API를 사용하여 맞춤 디자인 시스템을 만들 수 있음&lt;/p&gt;
&lt;h4 id=&quot;app-wide&quot; data-text=&quot;애플리케이션 전체 테마 설정&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;애플리케이션 전체 테마 설정&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;500&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;688&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cn3W3F/btq7ixx4Jl7/qEczTlGPljydG5q1lTj1Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cn3W3F/btq7ixx4Jl7/qEczTlGPljydG5q1lTj1Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cn3W3F/btq7ixx4Jl7/qEczTlGPljydG5q1lTj1Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcn3W3F%2Fbtq7ixx4Jl7%2FqEczTlGPljydG5q1lTj1Rk%2Fimg.png&quot; width=&quot;500&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;688&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1623737508669&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MaterialTheme(
    colors = &amp;hellip;,
    typography = &amp;hellip;,
    shapes = &amp;hellip;
) {
    // app content
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머티리얼 디자인&lt;span&gt;&amp;nbsp;구성요소&lt;/span&gt;(버튼, 카드, 스위치 등)는 나만의 앱을 효과적으로 반영하기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머티리얼 디자인을 맞춤설정하는 체계적인 방법인&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;&amp;nbsp;&lt;/span&gt;서체&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;도형&lt;span&gt;&amp;nbsp;&lt;/span&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;h2 id=&quot;color&quot; data-text=&quot;색상&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;1. 색상&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;tiles-shapes-2617112_1280.jpg&quot; width=&quot;699&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKuUTp/btq7nHfjtpp/BsMhnYxxobjfwQYYYod2ek/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKuUTp/btq7nHfjtpp/BsMhnYxxobjfwQYYYod2ek/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKuUTp/btq7nHfjtpp/BsMhnYxxobjfwQYYYod2ek/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKuUTp%2Fbtq7nHfjtpp%2FBsMhnYxxobjfwQYYYod2ek%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;tiles-shapes-2617112_1280.jpg&quot; width=&quot;699&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1623738442021&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val Red = Color(0xffff0000)
val Blue = Color(red = 0f, green = 0f, blue = 1f)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;색상은 Compose에서 간단한 데이터 보유 클래스인&lt;span&gt;&amp;nbsp;&lt;/span&gt;Color&lt;span&gt;&amp;nbsp;&lt;/span&gt;클래스로 모델링됩니다.&lt;span&gt;&lt;/span&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; (ex: 싱글톤 내에서 또는 정의된 인라인으로 최상위 상수로 구성)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; (이 방식을 사용하면 여러 테마를 지원할 수 있습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;625&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nQpXh/btq7nF2ty0P/QsMJwrdhjusq9OpFjueNTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nQpXh/btq7nF2ty0P/QsMJwrdhjusq9OpFjueNTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nQpXh/btq7nF2ty0P/QsMJwrdhjusq9OpFjueNTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnQpXh%2Fbtq7nF2ty0P%2FQsMJwrdhjusq9OpFjueNTK%2Fimg.png&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;625&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Colors&lt;span&gt;&amp;nbsp;&lt;/span&gt;클래스를 제공하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;머티리얼 색상 시스템을 모델링합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://material.io/design/color/the-color-system.html#color-usage-and-palettes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://material.io/design/color/the-color-system.html#color-usage-and-palettes&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623737868829&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;Material Design&quot; data-og-description=&quot;Build beautiful, usable products faster. Material Design is an adaptable system&amp;mdash;backed by open-source code&amp;mdash;that helps teams build high quality digital experiences.&quot; data-og-host=&quot;material.io&quot; data-og-source-url=&quot;https://material.io/design/color/the-color-system.html#color-usage-and-palettes&quot; data-og-url=&quot;https://material.io&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJw9Lo/hyKAnuFvKa/bIX6cmPFoRTvMhiibqno61/img.png?width=630&amp;amp;height=630&amp;amp;face=0_0_630_630,https://scrap.kakaocdn.net/dn/HVXaL/hyKArRng1W/pJioMP94rzr1oUPCpSUj50/img.png?width=630&amp;amp;height=630&amp;amp;face=0_0_630_630&quot;&gt;&lt;a href=&quot;https://material.io/design/color/the-color-system.html#color-usage-and-palettes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://material.io/design/color/the-color-system.html#color-usage-and-palettes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJw9Lo/hyKAnuFvKa/bIX6cmPFoRTvMhiibqno61/img.png?width=630&amp;amp;height=630&amp;amp;face=0_0_630_630,https://scrap.kakaocdn.net/dn/HVXaL/hyKArRng1W/pJioMP94rzr1oUPCpSUj50/img.png?width=630&amp;amp;height=630&amp;amp;face=0_0_630_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;Material Design&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Build beautiful, usable products faster. Material Design is an adaptable system&amp;mdash;backed by open-source code&amp;mdash;that helps teams build high quality digital experiences.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;material.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;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) Colors는 다음과 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;밝거나(light)&lt;span&gt;&amp;nbsp;&lt;/span&gt;어두운(dark)&lt;span&gt;&amp;nbsp;&lt;/span&gt;색상 세트를 만드는 함수를 제공&lt;/b&gt;&lt;/h4&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;/p&gt;
&lt;pre id=&quot;code_1623738191084&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private val Yellow200 = Color(0xffffeb46)
    private val Yellow400 =  Color(0xFFFFEE58)
    private val Yellow500 = Color(0xFFFFEB3B)

    private val Blue200 = Color(0xff91a4fc)
    private val Blue700 = Color(0xFFBECAFF)


    private val DarkColors = darkColors(
        primary = Yellow200,
        secondary = Blue200,
    )
    private val LightColors = lightColors(
        primary = Yellow500,
        primaryVariant = Yellow400,
        secondary = Blue700,
    )&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623740567322&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Composable
    private fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme()
    ) {
        MaterialTheme(
            colors = if (darkTheme) DarkColors else LightColors
        ) {
            //머티리얼 테마가 적용되는 영역
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Colors를 정의한 후에는&amp;nbsp;MaterialTheme에 전달할 수 있음&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;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623738625989&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; MaterialTheme(
            colors = if (darkTheme) DarkColors else LightColors
        ) {
            Text(
                text = &quot;Hello theming&quot;,
                color = MaterialTheme.colors.primary
            )
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MaterialTheme.colors&lt;/b&gt;를 사용하면 MaterialTheme Composable에 제공된Colors를 검색할 수 있음&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;결과&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;256&quot; data-filename=&quot;화면 캡처 2021-06-15 160225.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCxxXn/btq7myQh9Kv/CtaetJdZHNctcgEmqTwVx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCxxXn/btq7myQh9Kv/CtaetJdZHNctcgEmqTwVx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCxxXn/btq7myQh9Kv/CtaetJdZHNctcgEmqTwVx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCxxXn%2Fbtq7myQh9Kv%2FCtaetJdZHNctcgEmqTwVx0%2Fimg.png&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;256&quot; data-filename=&quot;화면 캡처 2021-06-15 160225.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;2) 표시 경로 및 콘텐츠 색상&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 구성요소가 한 쌍의 색상 및 '콘텐츠 색상'을 허용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1623740789170&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Surface(
    color: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(color),
    // ...

TopAppBar(
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    // ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- contentColor를 통해 Composable의 색상 설정과 그 안에 포함된 Composable인&lt;span&gt;&amp;nbsp;&lt;/span&gt;콘텐츠의 기본 색상을 제공할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-많은 Composable은 기본적으로 이 콘텐츠 색상을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- contentColorFor() 메서드는 테마 색상에 적절한 '설정' 색상을 검색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; (예를 들어&amp;nbsp;primary&amp;nbsp;배경을 설정하면&amp;nbsp;onPrimary가 콘텐츠 색상으로 설정됩니다.)&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 alignLeft&quot; width=&quot;400&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;378&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bonUUX/btq7iQxoD5N/QbBgaiBFVk9yKksIQo60l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bonUUX/btq7iQxoD5N/QbBgaiBFVk9yKksIQo60l1/img.png&quot; data-alt=&quot;예를 들어&amp;amp;amp;nbsp; Text는 상위 요소의 콘텐츠 색상을 기반으로 색상을 설정하고&amp;amp;amp;nbsp; Icon은 이 색상을 사용하여 색조를 설정합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bonUUX/btq7iQxoD5N/QbBgaiBFVk9yKksIQo60l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbonUUX%2Fbtq7iQxoD5N%2FQbBgaiBFVk9yKksIQo60l1%2Fimg.png&quot; width=&quot;400&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;378&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예를 들어&amp;nbsp; Text는 상위 요소의 콘텐츠 색상을 기반으로 색상을 설정하고&amp;nbsp; Icon은 이 색상을 사용하여 색조를 설정합니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;LocalContentColor 를 사용하여 현재 배경과 대조되는 현재 콘텐츠 색상을 검색합니다.&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;span&gt;&lt;span&gt; &lt;/span&gt;Surface가 적절한 콘텐츠 색상을 설정하기 때문에 &amp;nbsp;Surface를 사용하여 색상을 설정하는 것을 권장&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;(적절한 콘텐츠 색상이 설정되지 않는 직접&lt;span&gt;&amp;nbsp;&lt;/span&gt;Modifier.background()&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;3) 콘텐츠 알파&lt;/span&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;머티리얼 디자인에서는 불투명도를 사용하여 다양한 중요도 수준을 전달하도록&lt;span&gt;&amp;nbsp;&lt;/span&gt;권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 LocalContentAlpha를 사용하면 됩니다.&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;LocalContentAlpha&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CompositionLocal&lt;span&gt;&amp;nbsp;&lt;/span&gt;값을&lt;span&gt;&amp;nbsp;&lt;/span&gt;제공하여 계층 구조의 콘텐츠 알파를 지정할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하위 Composable에서 이 값을 사용함&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;/p&gt;
&lt;pre id=&quot;code_1623741968290&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
    private fun AlphaTest() {
        Column {
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text(&quot;test&quot;)
            }
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
                Icon(painter = painterResource(id = R.drawable.ic_launcher_background), null)
                Text(&quot;asdasdasdasdas&quot;)
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;Text&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;Icon은 기본적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;LocalContentAlpha를 사용하도록 조정된&lt;span&gt;&amp;nbsp;&lt;/span&gt;LocalContentColor&lt;span&gt;&amp;nbsp;&lt;/span&gt;조합을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 머티리얼에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;ContentAlpha&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체에 의해 모델링된 일부 표준 알파 값(high,&lt;span&gt;&amp;nbsp;&lt;/span&gt;medium,&lt;span&gt;&amp;nbsp;&lt;/span&gt;disabled)을 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;MaterialTheme은&lt;span&gt;&amp;nbsp;&lt;/span&gt;LocalContentAlpha의 기본값을&lt;span&gt;&amp;nbsp;&lt;/span&gt;ContentAlpha.high로 설정함&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;결과&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;238&quot; data-filename=&quot;화면 캡처 2021-06-15 163014.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRCT2R/btq7h7fytTP/4ngg1uVVK1UaKxHsyfqeM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRCT2R/btq7h7fytTP/4ngg1uVVK1UaKxHsyfqeM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRCT2R/btq7h7fytTP/4ngg1uVVK1UaKxHsyfqeM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRCT2R%2Fbtq7h7fytTP%2F4ngg1uVVK1UaKxHsyfqeM1%2Fimg.png&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;238&quot; data-filename=&quot;화면 캡처 2021-06-15 163014.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;400&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;271&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YFL14/btq7h70JsMA/PhldKVUkphOAKLJMLuaqj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YFL14/btq7h70JsMA/PhldKVUkphOAKLJMLuaqj0/img.png&quot; data-alt=&quot;중요도가 투명도로 전달이 됨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YFL14/btq7h70JsMA/PhldKVUkphOAKLJMLuaqj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYFL14%2Fbtq7h70JsMA%2FPhldKVUkphOAKLJMLuaqj0%2Fimg.png&quot; width=&quot;400&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;271&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;중요도가 투명도로 전달이 됨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;span&gt;4) 어두운 테마&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;MaterialTheme&lt;span&gt; Composable&lt;/span&gt;에 다양한&lt;span&gt;&amp;nbsp;&lt;/span&gt;Colors&lt;span&gt;&amp;nbsp;&lt;/span&gt;세트를 제공하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테마를 통해 색상을 사용하여 light theme 및 dark theme를 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제1.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623742564744&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; @Composable
    fun MyTheme(
        darkTheme: Boolean = isSystemInDarkTheme(),
        content: @Composable () -&amp;gt; Unit
    ) {
        MaterialTheme(
            colors = if (darkTheme) DarkColors else LightColors,
            content = content
        )
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- isSystemInDarkTheme()로 어두운 테마를 사용할지 여부를 정하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- true 일떄 기기 테마 설정을 쿼리하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;darkTheme의 기본값을 가져옵니다.&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;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623742744616&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val isLightTheme = MaterialTheme.colors.isLight&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- DarkTheme를 구현할 때 현재 Colors가 밝은지 또는 어두운지 확인할 수 있습니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-위 값은&lt;span&gt;&amp;nbsp;&lt;/span&gt;lightColors()&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;darkColors()&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;b&gt;예제3.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623743011887&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Surface(
    elevation = 2.dp,
    color = MaterialTheme.colors.surface, // color will be adjusted for elevation
    /*...*/
) { /*...*/ }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 머티리얼에서 고도가 높은 Dark Theme의 표시 경로는 배경을 밝게 하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;고도 오버레이를 수신합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이러한 오버레이는 어두운 색상을 사용할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;Surface&lt;span&gt;&amp;nbsp;&lt;/span&gt;Composable에 의해 자동 구현됩니다.&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 alignLeft&quot; width=&quot;300&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1999&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAegtv/btq7h6AMyLJ/U3bCRxO395GozrE4GKKavK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAegtv/btq7h6AMyLJ/U3bCRxO395GozrE4GKKavK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAegtv/btq7h6AMyLJ/U3bCRxO395GozrE4GKKavK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAegtv%2Fbtq7h6AMyLJ%2FU3bCRxO395GozrE4GKKavK%2Fimg.png&quot; width=&quot;300&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1999&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카드 및 하단 탐색은 &lt;span&gt;&amp;nbsp;&lt;/span&gt;surface&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;5) 머티리얼 색상 확장&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1623743346919&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  val Colors.snackbarAction: Color
        @Composable get() = if (isLight) Blue200 else Blue700&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 색상 세트를 확장해야 한다면 위와 같이 자체 색상 시스템을 구현하거나 확장을 추가할 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-text=&quot;서체&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-text=&quot;서체&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 id=&quot;typography&quot; data-text=&quot;서체&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;2. 서체&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;calligraphy-3810555_1280.jpg&quot; width=&quot;609&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt5PWD/btq7mzu7FHX/pHa35AyidjVsjgC9UamJEK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt5PWD/btq7mzu7FHX/pHa35AyidjVsjgC9UamJEK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt5PWD/btq7mzu7FHX/pHa35AyidjVsjgC9UamJEK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt5PWD%2Fbtq7mzu7FHX%2FpHa35AyidjVsjgC9UamJEK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;calligraphy-3810555_1280.jpg&quot; width=&quot;609&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;&lt;a href=&quot;https://material.io/design/typography/the-type-system.html#type-scale&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://material.io/design/typography/the-type-system.html#type-scale&lt;/a&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623743461262&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;Material Design&quot; data-og-description=&quot;Build beautiful, usable products faster. Material Design is an adaptable system&amp;mdash;backed by open-source code&amp;mdash;that helps teams build high quality digital experiences.&quot; data-og-host=&quot;material.io&quot; data-og-source-url=&quot;https://material.io/design/typography/the-type-system.html#type-scale&quot; data-og-url=&quot;https://material.io&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gLpb7/hyKzaqfrPh/txSnzbUytVWhtaWftdmluk/img.png?width=630&amp;amp;height=630&amp;amp;face=0_0_630_630,https://scrap.kakaocdn.net/dn/3ZrBT/hyKAkksIxD/Ogj4ifdJY0rsbrkx0BlDx1/img.png?width=630&amp;amp;height=630&amp;amp;face=0_0_630_630&quot;&gt;&lt;a href=&quot;https://material.io/design/typography/the-type-system.html#type-scale&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://material.io/design/typography/the-type-system.html#type-scale&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gLpb7/hyKzaqfrPh/txSnzbUytVWhtaWftdmluk/img.png?width=630&amp;amp;height=630&amp;amp;face=0_0_630_630,https://scrap.kakaocdn.net/dn/3ZrBT/hyKAkksIxD/Ogj4ifdJY0rsbrkx0BlDx1/img.png?width=630&amp;amp;height=630&amp;amp;face=0_0_630_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;Material Design&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Build beautiful, usable products faster. Material Design is an adaptable system&amp;mdash;backed by open-source code&amp;mdash;that helps teams build high quality digital experiences.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;material.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머티리얼 디자인은은&lt;span&gt; 위 링크처럼&amp;nbsp;&lt;/span&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;500&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;624&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eKrKxg/btq7nzacoJM/6uO52LrSTieT5eJBFvNdZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eKrKxg/btq7nzacoJM/6uO52LrSTieT5eJBFvNdZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eKrKxg/btq7nzacoJM/6uO52LrSTieT5eJBFvNdZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeKrKxg%2Fbtq7nzacoJM%2F6uO52LrSTieT5eJBFvNdZk%2Fimg.png&quot; width=&quot;500&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;624&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Typography,&lt;span&gt;&amp;nbsp;&lt;/span&gt;TextStyle&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;글꼴 관련&lt;span&gt;&amp;nbsp;&lt;/span&gt;클래스를 사용하여 유형 시스템을 구현합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Typography&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;pre id=&quot;code_1623744495382&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val Rubik = FontFamily(
    Font(R.font.rubik_regular),
    Font(R.font.rubik_medium, FontWeight.W500),
    Font(R.font.rubik_bold, FontWeight.Bold)
)

val MyTypography = Typography(
    h1 = TextStyle(
        fontFamily = Rubik,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = Rubik,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
    )&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623744542206&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MaterialTheme(typography = MyTypography, /*...*/)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적으로 동일한 글꼴을 사용하려면 다음과 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;defaultFontFamily&lt;span&gt;&amp;nbsp;&lt;/span&gt;매개변수를 지정하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;TextStyle&lt;span&gt;&amp;nbsp;&lt;/span&gt;요소의&lt;span&gt;&amp;nbsp;&lt;/span&gt;fontFamily를 생략합니다.&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;/p&gt;
&lt;pre id=&quot;code_1623745926269&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val typography = Typography(defaultFontFamily = Rubik)
MaterialTheme(typography = typography, /*...*/)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;참고:&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Compose는 현재 Android의&lt;span&gt;&amp;nbsp;&lt;/span&gt;다운로드 가능한 글꼴&lt;span&gt;&amp;nbsp;&lt;/span&gt;기능을 지원하지 않지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;번들 글꼴 리소스&lt;/b&gt;를 지원합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://developer.android.com/guide/topics/resources/font-resource&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/guide/topics/resources/font-resource&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623746116458&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;글꼴 리소스 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;글꼴 리소스는 앱에서 사용할 수 있는 맞춤 글꼴을 정의합니다. 글꼴은 개별 글꼴 파일 또는 글꼴 모음이라고 하는 글꼴 파일 모임일 수 있으며 XML로 정의됩니다. 또한, XML로 글꼴을 정의하는 방&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/guide/topics/resources/font-resource&quot; data-og-url=&quot;https://developer.android.com/guide/topics/resources/font-resource?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eIrCN/hyKy5oWnpx/Dong0fsGT0w6kbdBsbUdQ0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/guide/topics/resources/font-resource&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/guide/topics/resources/font-resource&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eIrCN/hyKy5oWnpx/Dong0fsGT0w6kbdBsbUdQ0/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;글꼴 리소스 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;글꼴 리소스는 앱에서 사용할 수 있는 맞춤 글꼴을 정의합니다. 글꼴은 개별 글꼴 파일 또는 글꼴 모음이라고 하는 글꼴 파일 모임일 수 있으며 XML로 정의됩니다. 또한, XML로 글꼴을 정의하는 방&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;텍스트 스타일 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예와 같이 테마에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;TextStyle을 검색합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623746093469&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Text(
    text = &quot;Subtitle2 styled&quot;,
    style = MaterialTheme.typography.subtitle2
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;300&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;1057&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbYFTl/btq7kkx1FzZ/8rluMqmXjjxIqB63UbyA90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbYFTl/btq7kkx1FzZ/8rluMqmXjjxIqB63UbyA90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbYFTl/btq7kkx1FzZ/8rluMqmXjjxIqB63UbyA90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbYFTl%2Fbtq7kkx1FzZ%2F8rluMqmXjjxIqB63UbyA90%2Fimg.png&quot; width=&quot;300&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;1057&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;shape&quot; data-text=&quot;도형&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;3. 도형&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;784&quot; width=&quot;492&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q1TXl/btq7nHfjAEF/oivLHtxvsAAMOUqwuiFFW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q1TXl/btq7nHfjAEF/oivLHtxvsAAMOUqwuiFFW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q1TXl/btq7nHfjAEF/oivLHtxvsAAMOUqwuiFFW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq1TXl%2Fbtq7nHfjAEF%2FoivLHtxvsAAMOUqwuiFFW1%2Fimg.png&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;784&quot; width=&quot;492&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머티리얼은&lt;span&gt;&amp;nbsp;&lt;/span&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;Compose는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Shapes&lt;span&gt;&amp;nbsp;&lt;/span&gt;클래스를 통해 도형 시스템을 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 클래스를 사용하면 각 카테고리의&lt;span&gt;&amp;nbsp;&lt;/span&gt;CornerBasedShape를 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623746197629&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val Shapes = Shapes(
    small = RoundedCornerShape(percent = 50),
    medium = RoundedCornerShape(0f),
    large = CutCornerShape(
        topStart = 16.dp,
        topEnd = 0.dp,
        bottomEnd = 0.dp,
        bottomStart = 16.dp
    )
)

MaterialTheme(shapes = Shapes, /*...*/)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 많은 구성요소가 기본적으로 이러한 도형을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UI 사이즈 별로 도형 정의가 나뉨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;-&amp;nbsp; small =&amp;gt; Button,TextField 등등.., medium =&amp;gt; AlertDialog 등등.. , large =&amp;gt; ModalDrawer 등등..&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;span&gt;도형 사용&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 테마에서 도형을 검색합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1623746232284&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;400&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;742&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxF9ZM/btq7nG1n7t2/P4egWe17zeKaVteQwGEGnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxF9ZM/btq7nG1n7t2/P4egWe17zeKaVteQwGEGnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxF9ZM/btq7nG1n7t2/P4egWe17zeKaVteQwGEGnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxF9ZM%2Fbtq7nG1n7t2%2FP4egWe17zeKaVteQwGEGnK%2Fimg.png&quot; width=&quot;400&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;742&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그림 6.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;도형을 사용하여 브랜드 또는 상태를 표현할 수 있습니다.&lt;/p&gt;
&lt;h2 id=&quot;component-styles&quot; data-text=&quot;구성요소 스타일&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;4. 구성요소 스타일&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot; data-filename=&quot;tie-690084_1280.jpg&quot; width=&quot;613&quot; height=&quot;409&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHXnBc/btq7nG1Lb18/U4w0fo5cjBE3Ai9oTLwcq1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHXnBc/btq7nG1Lb18/U4w0fo5cjBE3Ai9oTLwcq1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHXnBc/btq7nG1Lb18/U4w0fo5cjBE3Ai9oTLwcq1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHXnBc%2Fbtq7nG1Lb18%2FU4w0fo5cjBE3Ai9oTLwcq1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot; data-filename=&quot;tie-690084_1280.jpg&quot; width=&quot;613&quot; height=&quot;409&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compose에는 구성요소 스타일에 대한 명시적인 개념이 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(자체 Composable을 생성하여 이 기능을 제공하기 때문입니다.)&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;pre id=&quot;code_1623746745820&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun LoginButton(
    onClick: () -&amp;gt; Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -&amp;gt; Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 고유의 Composable 함수로 버튼을 래핑하고 변경하려는 매개변수를 직접 설정하며 포함하는 Composable에 매개변수로 다른 매개변수를 노출합니다.&lt;/p&gt;
&lt;h2 id=&quot;custom-design&quot; data-text=&quot;맞춤 디자인 시스템&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;5. 맞춤 디자인 시스템&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;brushes-1683134_1280.jpg&quot; width=&quot;584&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XK5dB/btq7lQqgpKt/ZiZR2s8wHSrDVuqMJdHVw1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XK5dB/btq7lQqgpKt/ZiZR2s8wHSrDVuqMJdHVw1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XK5dB/btq7lQqgpKt/ZiZR2s8wHSrDVuqMJdHVw1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXK5dB%2Fbtq7lQqgpKt%2FZiZR2s8wHSrDVuqMJdHVw1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;brushes-1683134_1280.jpg&quot; width=&quot;584&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머티리얼은 권장되는 디자인 시스템이지만 이 시스템뿐만 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 방식으로 자체 디자인 시스템을 만드는 것이 전적으로 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서에서는 다룰 수 있는 범주를 벗어낫기 때문에 개발자 스스로가 개척해야함&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;자세한 내용은 아래 링크들 참고해서 커스텀 해볼것&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;a href=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt?hl=ko&quot;&gt;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt?hl=ko&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623746646886&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;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt?hl=ko&quot; data-og-description=&quot;&quot; data-og-host=&quot;cs.android.com&quot; data-og-source-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt?hl=ko&quot; data-og-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt?hl=ko&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;https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/material/material/src/commonMain/kotlin/androidx/compose/material/MaterialTheme.kt?hl=ko&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;cs.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocal?hl=ko&quot;&gt;https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocal?hl=ko&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623746671770&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;CompositionLocal &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocal?hl=ko&quot; data-og-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocal?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uxrrQ/hyKAkkuDbi/MtcXqpZfYBxBb02a6pgSaK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocal?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/CompositionLocal?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uxrrQ/hyKAkkuDbi/MtcXqpZfYBxBb02a6pgSaK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CompositionLocal &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary?hl=ko#CompositionLocalProvider(kotlin.Array,kotlin.Function0)&quot;&gt;https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary?hl=ko#CompositionLocalProvider(kotlin.Array,kotlin.Function0)&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623746698323&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;androidx.compose.runtime &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary?hl=ko#CompositionLocalProvider(kotlin.Array,kotlin.Function0)&quot; data-og-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/FIRAc/hyKAfpXmjb/6JGKiO7ky7awEvDqBZV4Nk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary?hl=ko#CompositionLocalProvider(kotlin.Array,kotlin.Function0)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary?hl=ko#CompositionLocalProvider(kotlin.Array,kotlin.Function0)&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/FIRAc/hyKAfpXmjb/6JGKiO7ky7awEvDqBZV4Nk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;androidx.compose.runtime &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/android/compose-samples/tree/main/Jetsnack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/android/compose-samples/tree/main/Jetsnack&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623746711792&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;android/compose-samples&quot; data-og-description=&quot;Official Jetpack Compose samples. Contribute to android/compose-samples development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/android/compose-samples/tree/main/Jetsnack&quot; data-og-url=&quot;https://github.com/android/compose-samples&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eo4tBj/hyKAfKgj3J/z0RkPXxYrFqqWxRXqLwWx0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/9DdQy/hyKAeR666Y/JmeSS0aAebo03eZsxoYbfK/img.png?width=320&amp;amp;height=569&amp;amp;face=0_0_320_569,https://scrap.kakaocdn.net/dn/mQvAV/hyKAh88ZD5/wqJOGfqWXEcV4wnov9leU0/img.png?width=320&amp;amp;height=569&amp;amp;face=0_0_320_569&quot;&gt;&lt;a href=&quot;https://github.com/android/compose-samples/tree/main/Jetsnack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/android/compose-samples/tree/main/Jetsnack&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eo4tBj/hyKAfKgj3J/z0RkPXxYrFqqWxRXqLwWx0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/9DdQy/hyKAeR666Y/JmeSS0aAebo03eZsxoYbfK/img.png?width=320&amp;amp;height=569&amp;amp;face=0_0_320_569,https://scrap.kakaocdn.net/dn/mQvAV/hyKAh88ZD5/wqJOGfqWXEcV4wnov9leU0/img.png?width=320&amp;amp;height=569&amp;amp;face=0_0_320_569');&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/compose-samples&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Official Jetpack Compose samples. Contribute to android/compose-samples development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6.정리&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;staff-2452807_1280.jpg&quot; width=&quot;606&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oioIW/btq7iO0TT1q/M7GXJYauY8rR0ezAuTqQkK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oioIW/btq7iO0TT1q/M7GXJYauY8rR0ezAuTqQkK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oioIW/btq7iO0TT1q/M7GXJYauY8rR0ezAuTqQkK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoioIW%2Fbtq7iO0TT1q%2FM7GXJYauY8rR0ezAuTqQkK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;staff-2452807_1280.jpg&quot; width=&quot;606&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본적으로 Compose는 머티리얼 디자인을 테마로 자신의 앱을 만들 수 있도록 제공해주는 편임&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;size16&quot;&gt;Compose를 통해서 더 쉽게 머티리얼 디자인을 따라갈 수 있도록 되어가는것 같습니다. 기존 xml로 했을 때 머티리얼 디자인 가이드를 보면서 안드로이드에서 완전 알맞게 지원을 해주는편이라긴 보다 이런 디자인 가이드는 너가 알아서 따라해라 느낌에 가까웠다면 이젠 본격적으로 머티리얼 디자인을 누구나 쉽게 잘 적용할 수 있을 것 같네요.&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;/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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 Compose 내용 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;https://gift123.tistory.com/33?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623736042117&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;안드로이드 개발 (8) Compose 이해 정리&quot; data-og-description=&quot;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;안드로이드 개발 (8) Compose 이해 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;https://gift123.tistory.com/34?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623736042117&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;안드로이드 개발 (9) Compose 상태 관리&quot; data-og-description=&quot;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078');&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;안드로이드 개발 (9) Compose 상태 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;https://gift123.tistory.com/38?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623736042118&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;안드로이드 개발 (10) Compose Composable Lifecycle&quot; data-og-description=&quot;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/38?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420');&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;안드로이드 개발 (10) Compose Composable Lifecycle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;https://gift123.tistory.com/39?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623736042118&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;안드로이드 개발 (11) Compose Side-effects&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824');&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;안드로이드 개발 (11) Compose Side-effects&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;https://gift123.tistory.com/41&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623780125618&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;안드로이드 개발 (13) Layout in Compose 1편&quot; data-og-description=&quot;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/41&quot; data-og-url=&quot;https://gift123.tistory.com/41&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dml8f7/hyKAqec0Cx/614plyeHa0PYhdk7ndaT3K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bvI9GW/hyKy62Jkgo/AsBT5LySWlNHgIBlmhtttK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/OnT3t/hyKy3EUT8z/GKUhvtCkSqAmEDxllBKHs0/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/41&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dml8f7/hyKAqec0Cx/614plyeHa0PYhdk7ndaT3K/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/bvI9GW/hyKy62Jkgo/AsBT5LySWlNHgIBlmhtttK/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/OnT3t/hyKy3EUT8z/GKUhvtCkSqAmEDxllBKHs0/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (13) Layout in Compose 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;https://gift123.tistory.com/42?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623780188542&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;안드로이드 개발 (14) Layout in Compose 2편&quot; data-og-description=&quot;1편 정리 https://gift123.tistory.com/41 안드로이드 개발 (13) Layout in Compose 1편 지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/42?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/42&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHnHfG/hyKAljFdZK/2ECx5Xv8Liz2TCo6a4jK7k/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/bV4tPS/hyKyZ3AMoL/yAKHv6G6HcfNnkHOBoxnt0/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/kK9DB/hyKy9LSbr9/ZH55ZpQzTT0KYubAsIaKb0/img.jpg?width=1280&amp;amp;height=846&amp;amp;face=0_372_254_650&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/42?category=967702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/42?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHnHfG/hyKAljFdZK/2ECx5Xv8Liz2TCo6a4jK7k/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/bV4tPS/hyKyZ3AMoL/yAKHv6G6HcfNnkHOBoxnt0/img.jpg?width=800&amp;amp;height=543&amp;amp;face=0_0_800_543,https://scrap.kakaocdn.net/dn/kK9DB/hyKy9LSbr9/ZH55ZpQzTT0KYubAsIaKb0/img.jpg?width=1280&amp;amp;height=846&amp;amp;face=0_372_254_650');&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;안드로이드 개발 (14) Layout in Compose 2편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1편 정리 https://gift123.tistory.com/41 안드로이드 개발 (13) Layout in Compose 1편 지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android개발</category>
      <category>안드로이드App</category>
      <category>안드로이드Compose</category>
      <category>안드로이드ComposeTheme</category>
      <category>안드로이드UI제작</category>
      <category>안드로이드개발</category>
      <category>안드로이드앱개발</category>
      <category>안드로이드앱제작</category>
      <category>안드로이드컴포즈</category>
      <category>앱개발</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/43</guid>
      <comments>https://gift123.tistory.com/43#entry43comment</comments>
      <pubDate>Tue, 15 Jun 2021 18:06:55 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (14) Layout in Compose 2편</title>
      <link>https://gift123.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1편 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/41&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623676712289&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;안드로이드 개발 (13) Layout in Compose 1편&quot; data-og-description=&quot;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/41&quot; data-og-url=&quot;https://gift123.tistory.com/41&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cMoooX/hyKy2x2Gml/QXVECW2FGLUS8kui1AfUr0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/HkXsW/hyKy4bxwZg/U1hn6Ta19igxvPVrh6iXc0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/dN8Y6R/hyKy3Rgbzh/1ot4YiGeE20jq9M5P0D5j1/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/41&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cMoooX/hyKy2x2Gml/QXVECW2FGLUS8kui1AfUr0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/HkXsW/hyKy4bxwZg/U1hn6Ta19igxvPVrh6iXc0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/dN8Y6R/hyKy3Rgbzh/1ot4YiGeE20jq9M5P0D5j1/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (13) Layout in Compose 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;1편에 이어서 2편도 정리해가보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1편 요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본 레이아웃은 Colum , Row, Box 3가지를 주로 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Modifiers 로 Composable의 설정이나 여러가지를 추가적으로 작업할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Modifiers는 순서가 매우 중요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가중치, 제약조건 사용으로 반응형 레이아웃을 만들 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 슬롯기반 API로 쉽게 머티리얼 디자인을 구성하거나 기본적으로 지원해주는 UI를 빠르게 만들어 낼 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(대표적으로 Scaffold)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;contraintlayout&quot; data-text=&quot;ConstraintLayout&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;1. ConstraintLayout&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;chain-1742948_1280.jpg&quot; width=&quot;608&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crG2yL/btq7lPRDcVG/rJJ8SQViK4g74KprWoGQc1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crG2yL/btq7lPRDcVG/rJJ8SQViK4g74KprWoGQc1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crG2yL/btq7lPRDcVG/rJJ8SQViK4g74KprWoGQc1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrG2yL%2Fbtq7lPRDcVG%2FrJJ8SQViK4g74KprWoGQc1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;chain-1742948_1280.jpg&quot; width=&quot;608&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ConstraintLayout은 더 복잡한 정렬 요구사항이 있는 더 큰 레이아웃을 구현할 때 유용함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 간단한 레이아웃을 만드는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;Column&lt;span&gt; &amp;amp; Row &amp;amp; Box 사용하기를 권장함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Xml에서 플랫 View 계층 구조를 갖는 콘스트레이아웃의 특징은 Compose는 이미 깊은 레이아웃 계층 구조를 효율적으로 처리할 수 있기 때문에 &lt;b&gt;Compose에서 콘스트레이아웃의 장점이 아님&lt;/b&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;Compose의&lt;span&gt;&amp;nbsp;&lt;/span&gt;ConstraintLayout 사용방법&amp;nbsp;&lt;/b&gt;&lt;/h4&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;span&gt;&amp;nbsp;&lt;/span&gt;createRefs()&lt;span&gt; ,&lt;/span&gt;createRefFor()를 사용하여 생성 ConstraintLayout의 각 Composable에는 연결된 참조가 필수&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) 제약 조건은&lt;span&gt;&amp;nbsp;&lt;/span&gt;constrainAs()&lt;span&gt; Modifiers&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;(3) 제약 조건은&lt;span&gt;&amp;nbsp;&lt;/span&gt;linkTo()&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;(4) parent는&lt;span&gt;&amp;nbsp;&lt;/span&gt;ConstraintLayout&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;b&gt;예제&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623678978581&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; @Composable
    fun ConstraintLayoutContent() {
        ConstraintLayout {

            // 1. 참조 생성
            val (button, text) = createRefs()

            Button(onClick = {/*아무말*/ },
                //2. constrainAs Modifier를 사용해서 제약조건을 걸 환경을 제공 시킴
                modifier = Modifier.constrainAs(button) {
                    //3.linkTo 함수로 실제 제약조건을 구체적으로 걸어줌
                    top.linkTo(parent.top, margin = 16.dp)
                }) {
                Text(&quot;Button&quot;)
            }
                //위의 2~3과 같은 내용
            Text(&quot;Text&quot;,modifier = Modifier.constrainAs(text){
                top.linkTo(button.bottom, margin = 8.dp)
            })
        }
    }&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;- 위 예제는 제약 조건은 적용되는 Composable 의 수정자와 함께 인라인으로 지정&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;301&quot; data-filename=&quot;화면 캡처 2021-06-14 225702.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vAMPE/btq7ddzOjlQ/yQcqMXSGYMkaBMXn3tf8Vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vAMPE/btq7ddzOjlQ/yQcqMXSGYMkaBMXn3tf8Vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vAMPE/btq7ddzOjlQ/yQcqMXSGYMkaBMXn3tf8Vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvAMPE%2Fbtq7ddzOjlQ%2FyQcqMXSGYMkaBMXn3tf8Vk%2Fimg.png&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;301&quot; data-filename=&quot;화면 캡처 2021-06-14 225702.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;decoupled&quot; data-text=&quot;분리된 API&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;분리된 API&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 제약 조건은 적용되는 Composable 의 수정자와 함께 인라인으로 지정함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&lt;b&gt;&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;/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;span&gt;&amp;nbsp;&lt;/span&gt;ConstraintLayout을 서로 다른 방식으로 사용할 수 있습니다.&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) ConstraintSet을 파라미터로&lt;span&gt;&amp;nbsp;&lt;/span&gt;ConstraintLayout에 전달함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) layoutId&lt;span&gt;&amp;nbsp;&lt;/span&gt;Modifier를 사용하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;ConstraintSet에 생성된 참조를 Composable에 할당함&lt;/p&gt;
&lt;pre id=&quot;code_1623680246358&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//ConstraintSet을 리턴하는 함수  
    private fun decoupledConstraints(margin: Dp): ConstraintSet {
        return ConstraintSet {
            val button = createRefFor(&quot;button&quot;)
            val text = createRefFor(&quot;text&quot;)

            constrain(button) {
                top.linkTo(parent.top, margin)
            }
            constrain(text) {
                top.linkTo(button.bottom, margin)
            }
        }
    }
    @Preview
    @Composable
    fun DecoupledConstraintLayout() {
        //BoxWithConstraints 를 사용해서 화면의 minWidth를 참고 
        BoxWithConstraints {
            val constrains = if (minWidth &amp;lt; 600.dp)
                decoupledConstraints(16.dp)
            else
                decoupledConstraints(32.dp)

            ConstraintLayout(constrains) {
                Button(
                    onClick = { /*아무말*/ },
                    modifier = Modifier.layoutId(&quot;button&quot;)
                ) {
                    Text(&quot;button&quot;)
                }

                Text(&quot;Text&quot;,Modifier.layoutId(&quot;text&quot;) )
            }


        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-위 예제대로 라면 제약 조건을 변경해야 할 때 다른&lt;span&gt;&amp;nbsp;&lt;/span&gt;ConstraintSet을 전달하거나 수정하면 됨&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;custom-layouts&quot; data-text=&quot;맞춤 레이아웃&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;2. Custom Layout&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;846&quot; data-filename=&quot;crayons-1445053_1280.jpg&quot; width=&quot;597&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CNQba/btq7iuAPFbK/DEP7KWAUOgmAJR8NV5kBM1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CNQba/btq7iuAPFbK/DEP7KWAUOgmAJR8NV5kBM1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CNQba/btq7iuAPFbK/DEP7KWAUOgmAJR8NV5kBM1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCNQba%2Fbtq7iuAPFbK%2FDEP7KWAUOgmAJR8NV5kBM1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;846&quot; data-filename=&quot;crayons-1445053_1280.jpg&quot; width=&quot;597&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Compose 에서 UI를 그리는 순서&lt;/h4&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) Compose에서 UI 요소는 호출될 때 UI 요소를 내보내는 Composable 함수로 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 화면에 렌더링되는 UI 트리에 추가됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; (각 UI 요소에는 하나의 상위 요소와 여러 개의 하위 요소가 있을 수 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 각 요소는 (x, y) 위치로 지정된 상위 요소 내에 배치되며&lt;span&gt;&amp;nbsp;&lt;/span&gt;width&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;height로 크기가 지정됨&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 요소는 하위 요소의 제약조건을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제약 조건은 요소의 최소/최대 width 및 height를 제한 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소에 하위 요소가 있을때, 각 하위요소를 측정해서 요소의 크기를 결정할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Compose UI는 다중 패스 측정을 허용하지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(단일 패스 측정은 성능 측면에서 효율적이므로 Compose가 깊은 UI 트리를 효율적으로 처리할 수 있음)&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&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;(단일 패스 측정 하기전에 정보가 필요하다면 맨 아래에 설명할 내장 기능 측정을 참고하면 됩니다.)&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;h4 id=&quot;layout-modifier&quot; data-text=&quot;레이아웃 수정자 사용&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;(1) Layout Modifier 사용&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1623681514898&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun Modifier.customLayoutModifier(...) =
    this.layout { measurable, constraints -&amp;gt;
        ...
    })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- layout&lt;span&gt;&amp;nbsp;&lt;/span&gt;modifier 를 사용하여 요소가 측정되고 배치되는 방식을 수정할 수 있음.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Layout은 람다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;layout modifier 는 호출하는 Composable만 변경함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파라미터에 측정할 수 있는 요소(measurable로 전달됨) 및 이 Composable의 수신된 제약 조건(constraints로 전달됨)이 포함&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 alignLeft&quot; width=&quot;400&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;449&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qOSLH/btq7gqrPkMm/Xz5bR72nMtvjGtTvfTD78k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qOSLH/btq7gqrPkMm/Xz5bR72nMtvjGtTvfTD78k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qOSLH/btq7gqrPkMm/Xz5bR72nMtvjGtTvfTD78k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqOSLH%2Fbtq7gqrPkMm%2FXz5bR72nMtvjGtTvfTD78k%2Fimg.png&quot; width=&quot;400&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;449&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;layout Modifier를 커스텀해서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에&lt;span&gt;&amp;nbsp;&lt;/span&gt;Text를 표시하고 텍스트 첫 줄의 상단에서 기준선까지의 거리를 제어해 보겠습니다.&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;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623681608306&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = layout { measurable, constraints -&amp;gt;
    val placeable = measurable.measure(constraints)


    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
   
        placeable.placeRelative(0, placeableY)
    }
}&lt;/code&gt;&lt;/pre&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;(1) Modifier의 확장함수&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) measurable.measure(constraints)를 호출하여 측정 가능한 매개변수로 표시되는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Text&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) layout(width, height)&lt;span&gt;로&lt;/span&gt; Composable의 크기를 지정합니다.&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) placeable.place(x, y)로 화면에 래핑된 요소를 배치함&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(y&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;b&gt;실제 사용 &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623726759833&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Preview
    @Composable
    fun TextWithPaddingToBaselinePreview() {
            Text(&quot;Hi there!&quot;, Modifier.firstBaselineToTop(32.dp))
    }
    @Preview
    @Composable
    fun TextWithNormalPaddingPreview() {
            Text(&quot;Hi there!&quot;, Modifier.padding(top = 32.dp))
    }
&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;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;383&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blfh80/btq7fbodZKR/zHeyCgAn2RjVhd1K39wKm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blfh80/btq7fbodZKR/zHeyCgAn2RjVhd1K39wKm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blfh80/btq7fbodZKR/zHeyCgAn2RjVhd1K39wKm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblfh80%2Fbtq7fbodZKR%2FzHeyCgAn2RjVhd1K39wKm0%2Fimg.png&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;383&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;create-custom&quot; data-text=&quot;맞춤 레이아웃 만들기&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;(2) Layout Composable&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1623727437772&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable() () -&amp;gt; Unit
) {
    Layout(
        modifier = modifier,
        children = content
    ) { measurables, constraints -&amp;gt;
        // measure and position children given constraints logic here
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 여러 Composable을 측정하고 배치하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;Layout&lt;span&gt; Composable를&lt;/span&gt; 사용해야함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하위 요소를 수동으로 측정하고 배치할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;( &lt;/span&gt;Column&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;Row와 같은 모든 상위 수준 레이아웃은&lt;span&gt;&amp;nbsp;&lt;/span&gt;Layout&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;b&gt;참고: &lt;/b&gt;기존 &lt;span&gt;&lt;span&gt;View&lt;/span&gt; System에서 맞춤 레이아웃을 생성하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;ViewGroup을 확장하고 측정 및 레이아웃 함수를 구현해야하는것과 달리 &lt;/span&gt;&lt;span&gt;Compose에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Layout&lt;span&gt; Composable&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;&lt;b&gt;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623727970302&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Composable
    fun MyBasicColumn(
        modifier: Modifier = Modifier,
        content: @Composable() () -&amp;gt; Unit
    ) {
        Layout(content = content, modifier = modifier) { measurables, constraints -&amp;gt;
            val placeable = measurables.map { measurable -&amp;gt;
                measurable.measure(constraints)
            }
            layout(constraints.maxWidth, constraints.maxHeight) {
                var y = 0
                placeable.forEach {
                    it.placeRelative(0, y)
                    y += it.height
                }
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;예제 설명&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;-&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;compose layout의 기본 중 하나인 Colum을 직접 만든 코드 입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;-&amp;nbsp;&lt;/span&gt;하위 Composable은 (minHeight&lt;span&gt;&amp;nbsp;&lt;/span&gt;제약 조건 없이)&lt;span&gt;&amp;nbsp;&lt;/span&gt;Layout&lt;span&gt;&amp;nbsp;&lt;/span&gt;제약 조건에 의해 이전 Composable의&lt;span&gt;&amp;nbsp;&lt;/span&gt;y을 기반으로 배치됩니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623728075367&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyBasicColumn(modifier.padding(8.dp)) {
        Text(&quot;MyBasicColumn&quot;)
        Text(&quot;places items&quot;)
        Text(&quot;vertically.&quot;)
        Text(&quot;We've done it by hand!&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;250&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;318&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UBJYM/btq69WZlQ3I/UCBEQWxh2FSiX1muqVmYAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UBJYM/btq69WZlQ3I/UCBEQWxh2FSiX1muqVmYAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UBJYM/btq69WZlQ3I/UCBEQWxh2FSiX1muqVmYAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUBJYM%2Fbtq69WZlQ3I%2FUCBEQWxh2FSiX1muqVmYAk%2Fimg.png&quot; width=&quot;250&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;318&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;layout-direction&quot; data-text=&quot;레이아웃 방향&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;3. 레이아웃 방향&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;compass-5137269_1280.jpg&quot; width=&quot;627&quot; height=&quot;418&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6dgIv/btq7iwyFez1/cj4AYaisF4E2kwlJItrCMk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6dgIv/btq7iwyFez1/cj4AYaisF4E2kwlJItrCMk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6dgIv/btq7iwyFez1/cj4AYaisF4E2kwlJItrCMk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6dgIv%2Fbtq7iwyFez1%2Fcj4AYaisF4E2kwlJItrCMk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;compass-5137269_1280.jpg&quot; width=&quot;627&quot; height=&quot;418&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;LocalLayoutDirection&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Composition 로컬을 변경하여 Composable의 레이아웃 방향을 변경함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Composable 을 수동으로 배치하는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;LayoutDirection은&lt;span&gt;&amp;nbsp;&lt;/span&gt;layout&lt;span&gt;&amp;nbsp;&lt;/span&gt;수정자 또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Layout&lt;span&gt;&amp;nbsp;&lt;/span&gt;컴포저블의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;LayoutScope에 포함되어 있음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- layoutDirection을 사용할 때는 place 를 사용하여 컴포저블을 배치함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;placeRelative&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드와 달리&lt;span&gt;&amp;nbsp;&lt;/span&gt;place는 레이아웃 방향에 따라 변경되지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;intrinsic-measurements&quot; data-text=&quot;내장 측정&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;4. 내장 측정&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;870&quot; data-filename=&quot;plan-2092499_1280.jpg&quot; width=&quot;590&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm4p1d/btq7delAqDv/Q08rA8N0aNRGFCXh280LV0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm4p1d/btq7delAqDv/Q08rA8N0aNRGFCXh280LV0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm4p1d/btq7delAqDv/Q08rA8N0aNRGFCXh280LV0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm4p1d%2Fbtq7delAqDv%2FQ08rA8N0aNRGFCXh280LV0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;870&quot; data-filename=&quot;plan-2092499_1280.jpg&quot; width=&quot;590&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compose는 한번만 측정하기 때문에 측정하기 전에 하위요소에 관한 정보가 필요한 경우에 &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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Composable에 intrinsicWidth 또는 intrinsicHeight를 요청할 수 있음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; -&amp;gt; (min|max)IntrinsicWidth, (min|max)IntrinsicHeight&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;-&amp;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;p data-ke-size=&quot;size16&quot;&gt;구분선으로 구분된 두 텍스트를 표시하는 Composable 을 만든다고 가정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;500&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;57&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYO4LL/btq7kpSJ9yB/paoD1BDwzyzt7L4mOeDIQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYO4LL/btq7kpSJ9yB/paoD1BDwzyzt7L4mOeDIQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYO4LL/btq7kpSJ9yB/paoD1BDwzyzt7L4mOeDIQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYO4LL%2Fbtq7kpSJ9yB%2FpaoD1BDwzyzt7L4mOeDIQK%2Fimg.png&quot; width=&quot;500&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;57&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;/p&gt;
&lt;pre id=&quot;code_1623729863980&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Composable
    fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
        Row(modifier = modifier
            .height(intrinsicSize = IntrinsicSize.Min)
            .padding(2.dp)) {
            Text(
                modifier = Modifier
                    .weight(1f)
                    .padding(start = 4.dp)
                    .wrapContentWidth(Alignment.Start),
                text = text1
            )
            Divider(
                color = Color.Black, modifier = Modifier
                    .fillMaxHeight()
                    .width(1.dp)
            )
            Text(
                modifier = Modifier
                    .weight(1f)
                    .padding(end = 4.dp)
                    .wrapContentWidth(Alignment.End),
                text = text2
            )

        }
    }&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;&lt;span&gt;예제 설명&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- height(IntrinsicSize.Min)는 하위 요소의 크기를 고유한 최소 높이로 강제 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; (이 기능은 반복적이므로 Row 및 하위 minIntrinsicHeight를 쿼리합니다.)&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;Row&lt;span&gt;&amp;nbsp;&lt;/span&gt;컴포저블의&lt;span&gt;&amp;nbsp;&lt;/span&gt;minIntrinsicHeight가 하위 요소의 최대&lt;span&gt;&amp;nbsp;&lt;/span&gt;minIntrinsicHeight입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제약 조건이 지정되지 않은 경우 공간을 차지하지 않으므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;Divider element'sminIntrinsicHeight는 0이고&lt;span&gt;&amp;nbsp;&lt;/span&gt;Text&lt;span&gt;&amp;nbsp;&lt;/span&gt;minIntrinsicHeight는 특정&lt;span&gt;&amp;nbsp;&lt;/span&gt;width가 지정된 경우 텍스트의 높이입니다.&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: #ee2323;&quot;&gt;&lt;b&gt;따라서&amp;nbsp;Row&amp;nbsp;요소의&amp;nbsp;height&amp;nbsp;제약 조건은&amp;nbsp;Text의 최대&amp;nbsp;minIntrinsicHeight입니다. &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;그런 다음&amp;nbsp;Divider가&amp;nbsp;height를&amp;nbsp;Row가 지정한&amp;nbsp;height&amp;nbsp;제약 조건으로 확장합니다.&lt;/b&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;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;256&quot; data-filename=&quot;화면 캡처 2021-06-15 130650.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lSSpy/btq7iywzGan/KeZDwFEwMQdx66jKoKxQE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lSSpy/btq7iywzGan/KeZDwFEwMQdx66jKoKxQE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lSSpy/btq7iywzGan/KeZDwFEwMQdx66jKoKxQE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlSSpy%2Fbtq7iywzGan%2FKeZDwFEwMQdx66jKoKxQE1%2Fimg.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;256&quot; data-filename=&quot;화면 캡처 2021-06-15 130650.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.최종정리&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot; data-filename=&quot;vacuum-cleaner-268179_1280.jpg&quot; width=&quot;629&quot; height=&quot;420&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxxyvm/btq7iOy4wxB/vWTsQxRH60d0E8wt3iq7J1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxxyvm/btq7iOy4wxB/vWTsQxRH60d0E8wt3iq7J1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxxyvm/btq7iOy4wxB/vWTsQxRH60d0E8wt3iq7J1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdxxyvm%2Fbtq7iOy4wxB%2FvWTsQxRH60d0E8wt3iq7J1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot; data-filename=&quot;vacuum-cleaner-268179_1280.jpg&quot; width=&quot;629&quot; height=&quot;420&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;-컴포즈에서 컨스트레이아웃을 사용하려면 연결된 참조를 만들고 제약 연결해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-컴포즈에서 컨스르레이아웃의 제약조건을 constaintSet으로 변경하거나 쉽게 갈아끼울 수 있음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-커스텀 레이아웃은 Layout Modifier 를 사용하거나 (한개의 Composable만 적용)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Layout Composable(여러가지 Composable을 적용할 수 있음) 를 사용하면 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-컴포즈는 UI의 요소를 측정할때 단 한번만 측정을 함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-내장 측정은 UI 요소를 측정하기 전에 미리 쿼리해서 사용할 수 있음&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;이상 Compose Layout in compose 문서 정리 마무리 입니다.&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;&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 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;https://gift123.tistory.com/33?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623676723601&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;안드로이드 개발 (8) Compose 이해 정리&quot; data-og-description=&quot;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;안드로이드 개발 (8) Compose 이해 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;https://gift123.tistory.com/34?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623676723601&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;안드로이드 개발 (9) Compose 상태 관리&quot; data-og-description=&quot;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078');&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;안드로이드 개발 (9) Compose 상태 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;https://gift123.tistory.com/38?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623676723602&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;안드로이드 개발 (10) Compose Composable Lifecycle&quot; data-og-description=&quot;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/38?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420');&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;안드로이드 개발 (10) Compose Composable Lifecycle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;https://gift123.tistory.com/39?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623676723602&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;안드로이드 개발 (11) Compose Side-effects&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot; data-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824');&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;안드로이드 개발 (11) Compose Side-effects&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;https://gift123.tistory.com/41&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;?category=967702&lt;/a&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot;&gt;&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623730819356&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;안드로이드 개발 (13) Layout in Compose 1편&quot; data-og-description=&quot;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/41&quot; data-og-url=&quot;https://gift123.tistory.com/41&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jMbMD/hyKy4QX4Bq/7M3wgQL38pUHwhbmUqkhQ0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/vL10C/hyKAh2ejiM/UPyPQo7mQClYkEm0HIldvk/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/IEwlm/hyKy6OKZK4/P8lsKZ5H7PFQmKZjMpMnyK/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/41&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jMbMD/hyKy4QX4Bq/7M3wgQL38pUHwhbmUqkhQ0/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/vL10C/hyKAh2ejiM/UPyPQo7mQClYkEm0HIldvk/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/IEwlm/hyKy6OKZK4/P8lsKZ5H7PFQmKZjMpMnyK/img.jpg?width=1280&amp;amp;height=853&amp;amp;face=0_0_1280_853');&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;안드로이드 개발 (13) Layout in Compose 1편&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , Composition, recompostion, Sdie-effects 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>AndroidCompose</category>
      <category>AndroidCustomUI</category>
      <category>androidCustomView</category>
      <category>Android컴포즈</category>
      <category>composable</category>
      <category>안드로이드레이아웃</category>
      <category>안드로이드커스텀</category>
      <category>안드로이드커스텀뷰</category>
      <category>안드로이드컴포즈</category>
      <category>컴포즈</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/42</guid>
      <comments>https://gift123.tistory.com/42#entry42comment</comments>
      <pubDate>Tue, 15 Jun 2021 13:20:36 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (13) Layout in Compose 1편</title>
      <link>https://gift123.tistory.com/41</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;지금까지 Compose에 대해 composable 라이프사이클, Compose 내부흐름 , &lt;b&gt;Composition&lt;/b&gt;, &lt;b&gt;recompostion&lt;/b&gt;, &lt;b&gt;Sdie-effects&lt;/b&gt; 활용방법에 대한 원론 방법에 알았다면 이제 실질적으로 Compose로 Layout을 어떻게 구성하는지에 대해 공부를 해봤습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. 개요&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;abstract-1851115_1280.jpg&quot; width=&quot;603&quot; height=&quot;402&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCWmLh/btq7d5g1FpK/LcSxjmdCE8K5rsDfWl75U1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCWmLh/btq7d5g1FpK/LcSxjmdCE8K5rsDfWl75U1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCWmLh/btq7d5g1FpK/LcSxjmdCE8K5rsDfWl75U1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCWmLh%2Fbtq7d5g1FpK%2FLcSxjmdCE8K5rsDfWl75U1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;abstract-1851115_1280.jpg&quot; width=&quot;603&quot; height=&quot;402&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Composable 함수를 통해서 여러 UI요소들을 만들 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하지만 UI요소들에 대한 정렬 기준이 없다면 아래와 같이 생성됨&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623651064747&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun ArtistCard() {
    Text(&quot;Alfred Sisley&quot;)
    Text(&quot;3 minutes ago&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;81&quot; data-filename=&quot;화면 캡처 2021-06-14 151125.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edKWGq/btq7iPRoHlI/ejgox8ULjkWPustvMDzuI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edKWGq/btq7iPRoHlI/ejgox8ULjkWPustvMDzuI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edKWGq/btq7iPRoHlI/ejgox8ULjkWPustvMDzuI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FedKWGq%2Fbtq7iPRoHlI%2Fejgox8ULjkWPustvMDzuI0%2Fimg.png&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;81&quot; data-filename=&quot;화면 캡처 2021-06-14 151125.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃이 없으면 위와같이 UI요소를 여러개 사용할 때 나오기 때문에 필수로 있어야함&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 기본 레이아웃&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;225&quot; data-filename=&quot;화면 캡처 2021-06-14 151420.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd5Otb/btq66jtK1wz/0JwQSrDjYUGND6F9yObXck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd5Otb/btq66jtK1wz/0JwQSrDjYUGND6F9yObXck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd5Otb/btq66jtK1wz/0JwQSrDjYUGND6F9yObXck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd5Otb%2Fbtq66jtK1wz%2F0JwQSrDjYUGND6F9yObXck%2Fimg.png&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;225&quot; data-filename=&quot;화면 캡처 2021-06-14 151420.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 사용되는 레이아웃은 3가지가 있습니다. Column , Row, Box&lt;/p&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;1) Colum, Row&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Colum &lt;/b&gt;은 수직방향으로 UI를 정렬 (리니어레이아웃의 수직 모드와 흡사함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Row &lt;/b&gt;는 수평방향으로 UI를 정렬&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;/p&gt;
&lt;pre id=&quot;code_1623651366849&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Composable
    private fun UserData() {
        Row(verticalAlignment = Alignment.CenterVertically) {
            Image(
                painter = painterResource(id = R.drawable.header),
                contentDescription = null,
                contentScale = ContentScale.Crop,
                modifier = Modifier
                    .padding(8.dp,12.dp,8.dp,12.dp)
                    .size(44.dp)
                    .clip(shape = CircleShape)
            )
            Column {
                Text(&quot;name&quot;)
                Text(
                    &quot;lastSeenOnline&quot;,
                    Modifier.padding(0.dp, 0.dp, 8.dp, 0.dp)
                )
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;401&quot; data-filename=&quot;화면 캡처 2021-06-14 152911.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYZlyv/btq69XjcblO/VEcYQ0kFHzXWvW5hfaXIb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYZlyv/btq69XjcblO/VEcYQ0kFHzXWvW5hfaXIb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYZlyv/btq69XjcblO/VEcYQ0kFHzXWvW5hfaXIb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYZlyv%2Fbtq69XjcblO%2FVEcYQ0kFHzXWvW5hfaXIb0%2Fimg.png&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;401&quot; data-filename=&quot;화면 캡처 2021-06-14 152911.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Row와 Colum을 조합해서 위 같은 UI를 요소를 만들 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;2) Box&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Box는 UI요소를 다른 요소위에 배치가 가능함 (FrameLayout과 흡사하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623652917953&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Composable
    fun AlignInRow() {
        Row(
            modifier = Modifier
                .size(150.dp)
                .background(Color.Yellow),
            horizontalArrangement = Arrangement.End,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Box(
                Modifier
                    .size(50.dp)
                    .background(Color.Red)
            )

            Box(
                Modifier
                    .size(50.dp)
                    .background(Color.Green),
                contentAlignment = Alignment.Center
            ) {
                Box(
                    Modifier
                        .size(30.dp)
                        .background(Color.Blue)
                )
            }

        }
    }&lt;/code&gt;&lt;/pre&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;b&gt;결과.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;352&quot; data-filename=&quot;화면 캡처 2021-06-14 154303.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfiWjM/btq7iu04eu0/Kb5uXFPDfdBRShHvrsBVfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfiWjM/btq7iu04eu0/Kb5uXFPDfdBRShHvrsBVfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfiWjM/btq7iu04eu0/Kb5uXFPDfdBRShHvrsBVfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfiWjM%2Fbtq7iu04eu0%2FKb5uXFPDfdBRShHvrsBVfK%2Fimg.png&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;352&quot; data-filename=&quot;화면 캡처 2021-06-14 154303.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compose는 중첩된 레이아웃을 효율적으로 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기존 XML으로 중첩을 그리는것을 권장하지 않는것과 달리 Compose는 중첩된 레이아웃을 사용해도 성능상 크게 문제가 없음&lt;span style=&quot;color: #01579b;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 오버드로우를 피하기 위해서 XML에서 컨스트레이아웃 사용을 권장했던 것과 달리 코드로 가로 세로 중첩 이 3가지 모드로 UI 정렬 규칙을 정하고 빠른 시간안에 UI요소들을 만들겁니다.&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;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Modifiers (수정자)&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;crystal-2723145_1280.jpg&quot; width=&quot;506&quot; height=&quot;337&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxjAWQ/btq66UgtZGB/VE5vzlpHNukG36rpcPY7M1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxjAWQ/btq66UgtZGB/VE5vzlpHNukG36rpcPY7M1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxjAWQ/btq66UgtZGB/VE5vzlpHNukG36rpcPY7M1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxjAWQ%2Fbtq66UgtZGB%2FVE5vzlpHNukG36rpcPY7M1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;crystal-2723145_1280.jpg&quot; width=&quot;506&quot; height=&quot;337&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 수정자 특징&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제들에서 이미 사용했던 Modifier 에 관한 설명입니다.&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;-Modifiers는 Kotlin 객체&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Modifier.함수()로 사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Modifiers를 통해 다음과 같은 작업들을 할 수 있음&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Composable의 크기, 레이아웃, 동작 및 모양 변경&lt;/li&gt;
&lt;li&gt;접근성 라벨과 같은 정보 추가&lt;/li&gt;
&lt;li&gt;사용자 입력 처리&lt;/li&gt;
&lt;li&gt;UI요소를 클릭 가능, 스크롤 가능, 드래그 가능 또는 확대/축소 가능하게 만드는 것과 같은&lt;b&gt; 높은 수준의 상호작용 추가&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623653953175&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @Composable
    fun ArtistCard(
        onClick: () -&amp;gt; Unit
    ) {
        val padding = 16.dp
        Column(
            Modifier
                .clickable(onClick = onClick)
                .padding(padding)
                .fillMaxWidth()
        ) {
        //userData()는 위에 만들어둔 예제
            UserData()
            Spacer(Modifier.size(padding))

            Card(elevation = 4.dp) {
                Image(
                    painter = painterResource(id = R.drawable.header),
                    contentDescription = null,
                    modifier = Modifier.fillMaxWidth(),
                    contentScale = ContentScale.Crop

                )
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;위 예제를 보면 Colum, Spacer, Image 인자로&amp;nbsp; Modifier를 사용해서 추가적인 속성들을 정의를 한 것을 볼 수 있습니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;592&quot; data-filename=&quot;화면 캡처 2021-06-14 160129.png&quot; width=&quot;391&quot; height=&quot;448&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIF0eN/btq66k7izbT/PwSYoMKbMwtYbCZdLT0Wdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIF0eN/btq66k7izbT/PwSYoMKbMwtYbCZdLT0Wdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIF0eN/btq66k7izbT/PwSYoMKbMwtYbCZdLT0Wdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIF0eN%2Fbtq66k7izbT%2FPwSYoMKbMwtYbCZdLT0Wdk%2Fimg.png&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;592&quot; data-filename=&quot;화면 캡처 2021-06-14 160129.png&quot; width=&quot;391&quot; height=&quot;448&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;*Modifiers의 장점&lt;/b&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;- Modifiers는 때로는 범위에 따라 다르므로 유형 안전성을 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 레이아웃에 사용 및 적용이 가능한 요소를 찾고 이해를 도와줌&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- XML 레이아웃을 사용하면 &lt;b&gt;특정 레이아웃 속성이 지정된 뷰에 적용 가능한지를 확인하기 어려운데,&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Modifiers는 판별이 가능함&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;** Modifiers는 순서가 매우 중요함 **&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.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623654551638&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; @Composable
    fun ArtistCard(
        onClick: () -&amp;gt; Unit
    ) {
        val padding = 16.dp
        Column(
            Modifier
                .clickable(onClick = onClick)
                .padding(padding)
                .fillMaxWidth()
        ) {
      .....
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Colum인자로 Modifier의 함수 체어이닝 순서가 (1) clickable() (2) padding() 순서일 때 clickable이 먼저 적용됨 그래서 아래와 같은 결과가 나옴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;click시 repple영역이 padding 적용전의 범위로 지정됨&amp;nbsp;&lt;/b&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;b&gt;예제2.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623654835561&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; @Composable
    fun ArtistCard(
        onClick: () -&amp;gt; Unit
    ) {
        val padding = 16.dp
        Column(
            Modifier
                .padding(padding)
                .clickable(onClick = onClick)
                .fillMaxWidth()
        ) {
      .....
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-이전과 달리 순서를 바꾸면&amp;nbsp; (1) padding() (2) clickable() 순서일 때 padding이 먼저 적용되고 clickable이 적용됨&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;뷰를 click 시 padding이 적용된 영역까지만 repple이 생김&amp;nbsp;&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) Modifiers 명시적인 순서에 대한 안드로이드 문서 설명&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note:&amp;nbsp;The&amp;nbsp;explicit&amp;nbsp;order&amp;nbsp;helps&amp;nbsp;you&amp;nbsp;to&amp;nbsp;reason&amp;nbsp;about&amp;nbsp;how&amp;nbsp;different&amp;nbsp;modifiers&amp;nbsp;will&amp;nbsp;interact.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compare&amp;nbsp;this&amp;nbsp;to&amp;nbsp;the&amp;nbsp;view-based&amp;nbsp;system&amp;nbsp;where&amp;nbsp;you&amp;nbsp;had&amp;nbsp;to&amp;nbsp;learn&amp;nbsp;the&amp;nbsp;box&amp;nbsp;model,&amp;nbsp;that&amp;nbsp;margins&amp;nbsp;applied&amp;nbsp;&quot;outside&quot;&amp;nbsp;the&amp;nbsp;element&amp;nbsp;but&amp;nbsp;padding&amp;nbsp;&quot;inside&quot;&amp;nbsp;it,&amp;nbsp;and&amp;nbsp;a&amp;nbsp;background&amp;nbsp;element&amp;nbsp;would&amp;nbsp;be&amp;nbsp;sized&amp;nbsp;accordingly.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The&amp;nbsp;modifier&amp;nbsp;design&amp;nbsp;makes&amp;nbsp;this&amp;nbsp;kind&amp;nbsp;of&amp;nbsp;behavior&amp;nbsp;explicit&amp;nbsp;and&amp;nbsp;predictable,&amp;nbsp;and&amp;nbsp;gives&amp;nbsp;you&amp;nbsp;more&amp;nbsp;control&amp;nbsp;to&amp;nbsp;achieve&amp;nbsp;the&amp;nbsp;exact&amp;nbsp;behavior&amp;nbsp;you&amp;nbsp;want.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;It&amp;nbsp;also&amp;nbsp;explains&amp;nbsp;why&amp;nbsp;there&amp;nbsp;is&amp;nbsp;not&amp;nbsp;a&amp;nbsp;margin&amp;nbsp;modifier&amp;nbsp;but&amp;nbsp;only&amp;nbsp;a&amp;nbsp;padding&amp;nbsp;one.&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;&quot;명시적인 순서는 다양한 수정자가 상호작용하는 방식을 추론하는 데 도움이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;박스 모델을 학습해야 했던 뷰 기반 시스템과 이를 비교해 보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;박스 모델의 경우 여백은 요소 '외부'에 적용되었지만 패딩은 '내부'에 적용되었으며 백그라운드 요소는 그에 따라 크기가 조정되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정자 디자인을 사용하면 이러한 종류의 동작이 명확해지고 예측 가능하게 되며 원하는 동작을 정확하게 달성할 수 있도록 추가적으로 제어할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;padding 수정자를 제외한 여백 수정자가 없는 이유도 설명할 수 있습니다.&quot;&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;3) 이외의 modifiers 정보&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;(1) 지정한 크기가 레이아웃의 상위 요소를 무시하고 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&amp;nbsp; &amp;nbsp;Composable&lt;/span&gt;&lt;span style=&quot;color: #202124;&quot;&gt;의 크기를 고정해야 하는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;requiredSize&lt;span style=&quot;color: #202124;&quot;&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;예제.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623656230948&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun FixedSizeComposable() {
    Box(Modifier.size(90.dp, 150.dp).background(Color.Green)) {
        Box(Modifier.requiredSize(100.dp, 100.dp).background(Color.Red))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장 하위에 있는&amp;nbsp; Box가 상위 Box 90.dp 크기를 무시하고 100.dp로 설정됩니다.&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;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;fillMaxSize(), fillMaxWidth(), fillMaxHeight() 등등을 골라서 사용&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;/p&gt;
&lt;pre id=&quot;code_1623656421333&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun FillSizeComposable() {
    Box(Modifier.background(Color.Green).size(50.dp).padding(10.dp)) {
        Box(Modifier.background(Color.Blue).fillMaxSize())
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-fillMaxSize() 사용으로 상위 UI요소 만큼 하위 UI요소의 크기가 지정됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-padding을 넣엇기 때문에 패딩이 10dp가 적용됨&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;(3) &lt;span style=&quot;color: #202124;&quot;&gt;하위 레이아웃을 상위&lt;span&gt;&amp;nbsp;요소&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #202124;&quot;&gt;와 동일한 크기로 설정하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;matchParentSize&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;수정자를 사용합니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623656730421&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Preview
    @Composable
    fun MatchParentSizeComposable() {
        Box {
            Spacer(
                Modifier
                    .matchParentSize()
                    .background(Color.Green))
            Text(&quot;Hello World&quot;)
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) &lt;span style=&quot;color: #202124;&quot;&gt;레이아웃 상단에서 기준선까지 특정 거리가 유지되도록 &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;패딩을 추가하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;paddingFromBaseline&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;수정자를 사용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623656791420&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun TextWithPaddingFromBaseline() {
    Box(Modifier.background(Color.Yellow)) {
        Text(&quot;Hi there!&quot;, Modifier.paddingFromBaseline(top = 32.dp))
    }
}&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;4) Offset&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;- offset 수정자를 추가해서 x축 , y축을 설정해서 사용함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 양수 혹은 음수일 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- offset의 측정값은 변경되지 않음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- offset&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;수정자는 레이아웃 방향에 따라 가로로 적용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- LTR&lt;/b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;컨텍스트에서 양수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;offset&lt;span style=&quot;color: #202124;&quot;&gt;은 요소를 오른쪽으로 이동시킴&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- RTL &lt;/b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;컨텍스트에서는 요소를 오른쪽으로 이동합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;- aboluteOffset은 레이아웃 방향 상관없이 항상 오른쪽으로 이동시킴&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;&lt;span style=&quot;color: #202124;&quot;&gt;예제.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623657184971&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun OffsetComposable() {
    Box(Modifier.background(Color.Yellow).size(width = 150.dp, height = 70.dp)) {
        Text(
            &quot;Layout offset modifier sample&quot;,
            Modifier.offset(x = 15.dp, y = 20.dp)
        )
    }
}&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;결과.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;193&quot; data-filename=&quot;layout-offset.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9Zh4r/btq7bEcIe0V/QrYfe5NZyeDVCmXw5sGIck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9Zh4r/btq7bEcIe0V/QrYfe5NZyeDVCmXw5sGIck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9Zh4r/btq7bEcIe0V/QrYfe5NZyeDVCmXw5sGIck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9Zh4r%2Fbtq7bEcIe0V%2FQrYfe5NZyeDVCmXw5sGIck%2Fimg.png&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;193&quot; data-filename=&quot;layout-offset.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;scrollable&quot; data-text=&quot;스크롤 가능한 레이아웃&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;3. 스크롤 가능한 레이아웃&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/jetpack/compose/gestures?hl=en#scroll-modifiers&quot;&gt;https://developer.android.com/jetpack/compose/gestures?hl=en#scroll-modifiers&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623657696423&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;동작 &amp;nbsp;|&amp;nbsp; Jetpack Compose &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;Compose는 사용자 상호작용에서 생성된 동작을 감지하는 데 도움이 되는 다양한 API를 제공합니다. 이 API는 광범위하게 사용됩니다. 그중 일부는 상위 수준이며 가장 일반적으로 사용되는 동작을 &quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/jetpack/compose/gestures?hl=en#scroll-modifiers&quot; data-og-url=&quot;https://developer.android.com/jetpack/compose/gestures&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.android.com/jetpack/compose/gestures?hl=en#scroll-modifiers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/jetpack/compose/gestures?hl=en#scroll-modifiers&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;동작 &amp;nbsp;|&amp;nbsp; Jetpack Compose &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Compose는 사용자 상호작용에서 생성된 동작을 감지하는 데 도움이 되는 다양한 API를 제공합니다. 이 API는 광범위하게 사용됩니다. 그중 일부는 상위 수준이며 가장 일반적으로 사용되는 동작을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 (블로그 포스팅 예정)&lt;/p&gt;
&lt;h2 data-text=&quot;반응형 레이아웃&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;responsive-layouts&quot; data-text=&quot;반응형 레이아웃&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;4. 반응형 레이아웃&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;850&quot; data-filename=&quot;feedback-1213042_1280.jpg&quot; width=&quot;464&quot; height=&quot;308&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTNlLr/btq7ddM5I4x/dWYeQk1UcPAq2CkNjWTKEk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTNlLr/btq7ddM5I4x/dWYeQk1UcPAq2CkNjWTKEk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTNlLr/btq7ddM5I4x/dWYeQk1UcPAq2CkNjWTKEk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTNlLr%2Fbtq7ddM5I4x%2FdWYeQk1UcPAq2CkNjWTKEk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;850&quot; data-filename=&quot;feedback-1213042_1280.jpg&quot; width=&quot;464&quot; height=&quot;308&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose에서 제공하는 즉시 사용 가능한 몇 가지 메커니즘으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;composable 레이아웃을 다양한 화면 구성에 따라 쉽게 조정할 수 있음&lt;/p&gt;
&lt;h4 id=&quot;weight-in-row-and-column&quot; data-text=&quot;행 및 열의 가중치 수정자&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;1) Row&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;Column의 가중치 수정자&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Composable 크기를 상위 요소 내에서 유연하게 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623657866811&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun FlexibleComposable() {
    Row(Modifier.width(210.dp)) {
        Box(Modifier.weight(2f).height(50.dp).background(Color.Blue))
        Box(Modifier.weight(1f).height(50.dp).background(Color.Red))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 첫 번째 상자의&lt;span&gt;&amp;nbsp;&lt;/span&gt;weight가 두 번째 상자의 두 배로 지정되므로 너비가 두 배로 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;b&gt;첫 번째&lt;span&gt;&amp;nbsp;&lt;/span&gt;Box의 너비는&lt;span&gt;&amp;nbsp;&lt;/span&gt;140.dp,&amp;nbsp; 두 번째는70.dp&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;constraints&quot; data-text=&quot;제약 조건&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;2) 제약 조건&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상위 요소의 제약 조건에 따라 레이아웃을 디자인하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;BoxWithConstraints를 사용&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;b&gt;다양한 화면 구성에 따라 다양한 레이아웃을 구성이 가능&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623658414667&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
    fun WithConstraintsComposable() {
        BoxWithConstraints {
            Text(
                &quot;My minHeight is $maxHeight while my maxWidth is $maxWidth&quot;,
                fontSize = 24.sp
            )
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;322&quot; data-filename=&quot;화면 캡처 2021-06-14 171232.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcgYNq/btq7iPxjfMM/StQjsayekq94dv3e6kFKKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcgYNq/btq7iPxjfMM/StQjsayekq94dv3e6kFKKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcgYNq/btq7iPxjfMM/StQjsayekq94dv3e6kFKKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcgYNq%2Fbtq7iPxjfMM%2FStQjsayekq94dv3e6kFKKk%2Fimg.png&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;322&quot; data-filename=&quot;화면 캡처 2021-06-14 171232.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최대 dp를 알수도 있고, 제약 관련 정보들이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;438&quot; data-filename=&quot;화면 캡처 2021-06-14 171439.png&quot; width=&quot;532&quot; height=&quot;239&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JJ8P8/btq7jxXub8R/wQ1YANKoCRfdGjp5kcuh61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JJ8P8/btq7jxXub8R/wQ1YANKoCRfdGjp5kcuh61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JJ8P8/btq7jxXub8R/wQ1YANKoCRfdGjp5kcuh61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJJ8P8%2Fbtq7jxXub8R%2FwQ1YANKoCRfdGjp5kcuh61%2Fimg.png&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;438&quot; data-filename=&quot;화면 캡처 2021-06-14 171439.png&quot; width=&quot;532&quot; height=&quot;239&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;5. 슬롯 기반 레이아웃&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1999&quot; data-origin-height=&quot;669&quot; data-filename=&quot;layout-appbar-slots.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WjRfx/btq7bDyhIo1/WKyXK1phrWlX5f6cZG4pIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WjRfx/btq7bDyhIo1/WKyXK1phrWlX5f6cZG4pIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WjRfx/btq7bDyhIo1/WKyXK1phrWlX5f6cZG4pIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWjRfx%2Fbtq7bDyhIo1%2FWKyXK1phrWlX5f6cZG4pIk%2Fimg.png&quot; data-origin-width=&quot;1999&quot; data-origin-height=&quot;669&quot; data-filename=&quot;layout-appbar-slots.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI를 쉽게 빌드할 수 있도록 &lt;b&gt;머티리얼 디자인&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;androidx.compose.material:material&lt;span&gt;&amp;nbsp;&lt;/span&gt;종속 항목을 기반으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 다양한 Composable을 제공합니다.&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;- Drawer,&lt;span&gt;&amp;nbsp;&lt;/span&gt;FloatingActionButton&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;TopAppBar와 같은 요소가 모두 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-머티리얼 구성요소는 Composable 위에 맞춤설정 레이어를 배치하기 위해 도입한 패턴인&lt;span&gt;&amp;nbsp;&lt;/span&gt;슬롯 API를 많이 사용&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;-슬롯은 개발자가 원하는 대로 채울 수 있도록 UI에 빈 공간을 남겨둠&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623659271154&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Composable
    fun HomeScreen(/*...*/) {
        Scaffold(
            drawerContent = { Text(&quot;drawer example&quot;) },
            topBar = {
                TopAppBar(
                    title = { Text(&quot;Scaffold 예제&quot;, textAlign = TextAlign.Center) },
                    backgroundColor = Color.Gray
                )
            },
            content = { Text(text = &quot;슬롯 API 테스트&quot;) }
        )
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;- 아주 쉽게 AppBar, drawer 를 만들수 있음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기존 XML 에서 만들던 방식에 비해 상당히 적은 노력이 들어간다는것이 아주 매력적임&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Colum, Row, Box가 기본 레이아웃으로 사용됨&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;- Modifiers로 Composable의 속성들을 바꿀 수 있음 각종 확장 기능들로 다양하게 활용가능&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Modifiers는 순서가 매우 중요 명시적인 순서를 뛰며 순서에 따라 결과가 다름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Offset으로 기본 위치로 부터 절대 측정값을 사용할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반응형 레이아웃으로 유연한 UI그리기 가능 (가중치 적용, 제약조건 이용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 슬롯 기반 레이아웃 지원 API&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;이전 Compose 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;https://gift123.tistory.com/33?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623650748458&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;안드로이드 개발 (8) Compose 이해 정리&quot; data-og-description=&quot;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33?category=967702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/33?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LJWNM/hyKxPfKfMa/7Vm4b1MmQGbXfHpbz4CLw1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/d7Q57Z/hyKy9XY4kh/NN2ktd6ZVp9hGHAataA5Pk/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/8yEAk/hyKxHaXAF3/iJY3kkqoNHebKLpIMhuvfk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;안드로이드 개발 (8) Compose 이해 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;https://gift123.tistory.com/34?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623650759878&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;안드로이드 개발 (9) Compose 상태 관리&quot; data-og-description=&quot;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34?category=967702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/34?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRvucu/hyKzabwu3e/0J1WDOKk9Yz6rwpyNt0fHk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cFVfuR/hyKyUT4L97/HEchMQeihSf2W9MKxLtdRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c38erq/hyKy2YRP5d/vohGr69VKFIZV9kJxJLti1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078');&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;안드로이드 개발 (9) Compose 상태 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;a href=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;https://gift123.tistory.com/38?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623650770550&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;안드로이드 개발 (10) Compose Composable Lifecycle&quot; data-og-description=&quot;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/38?category=967702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/38?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQqCq8/hyKy5adoq2/zvj4CHkNB10XhizukCkKG0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/cFDywf/hyKxCHtW9a/z9tAo5DyKhQxDMhTERr9vK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/bqGwKy/hyKy7eOg7c/YA67cSfRn9ijpanhx3BOV1/img.png?width=1280&amp;amp;height=420&amp;amp;face=0_0_1280_420');&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;안드로이드 개발 (10) Compose Composable Lifecycle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;https://gift123.tistory.com/39?category=967702&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1623650776705&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;안드로이드 개발 (11) Compose Side-effects&quot; data-og-description=&quot;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot; data-og-url=&quot;https://gift123.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/39?category=967702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/39?category=967702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bILTAo/hyKy3KdZT0/JuQPCpACyDZkB1OwAzPL80/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/RlyMn/hyKy587Y6d/DpOefbtyEnDjByv9OQUfWk/img.jpg?width=800&amp;amp;height=426&amp;amp;face=0_0_800_426,https://scrap.kakaocdn.net/dn/b4TO1X/hyKxEZAdyi/SPDtVeM6T6GvaNXIEyBos1/img.jpg?width=1280&amp;amp;height=824&amp;amp;face=0_0_1280_824');&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;안드로이드 개발 (11) Compose Side-effects&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://developer.android.com/jetpack/compose/side-effects#state-effect-use-cases Compose의 부수 효과 &amp;nbsp;| Jetpack Compose &amp;nbsp;| Android Developers 컴포저블에는 부수 효과가 없어야 합니다. 하지만 앱의..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>AndoridCompose</category>
      <category>AndroidDev</category>
      <category>compose</category>
      <category>안드로이드</category>
      <category>안드로이드Compose</category>
      <category>안드로이드LayoutinCompose</category>
      <category>안드로이드UI</category>
      <category>안드로이드개발</category>
      <category>안드로이드컴포즈</category>
      <category>안드로이드컴포즈개발</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/41</guid>
      <comments>https://gift123.tistory.com/41#entry41comment</comments>
      <pubDate>Mon, 14 Jun 2021 18:49:46 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (12) SendBird Chat SDK</title>
      <link>https://gift123.tistory.com/40</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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;SendBird&lt;/b&gt; &lt;/span&gt;를 사용할지도 몰라서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 공부를 해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(알아두면 나쁠건 없으니까)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;SendBird&lt;/b&gt;&lt;/span&gt;는 국내 기업 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현재 10억 5천만 달러의 가치를 인정 받으면서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 국내 B2B 소프트웨어 스타트업 최초로 유니콘 기업이라는 칭호&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;&lt;b&gt;관련기사&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://platum.kr/archives/160778&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://platum.kr/archives/160778&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623413814943&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;센드버드, 1억 달러 투자유치&amp;hellip;엔터프라이즈 SW &amp;lsquo;유니콘&amp;rsquo; 탄생&quot; data-og-description=&quot; &quot; data-og-host=&quot;platum.kr&quot; data-og-source-url=&quot;https://platum.kr/archives/160778&quot; data-og-url=&quot;https://platum.kr/archives/160778&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/blLvi4/hyKxPx3SoE/EySSL2I7moRIOqqTRxBKP1/img.jpg?width=640&amp;amp;height=427&amp;amp;face=145_94_419_269,https://scrap.kakaocdn.net/dn/c8i5HI/hyKwIHjguw/vpkAVtZqUZvGTa4ECEAVKK/img.jpg?width=1200&amp;amp;height=749&amp;amp;face=0_0_1200_749,https://scrap.kakaocdn.net/dn/dFLoKS/hyKxEXBxbe/jea45kLv4xey5djLmVwUz1/img.jpg?width=1073&amp;amp;height=705&amp;amp;face=254_144_710_428,https://scrap.kakaocdn.net/dn/chmF7u/hyKxPSmgkT/dR0PPC6EOh5eDhcgZ0Efy0/img.jpg?width=1073&amp;amp;height=705&amp;amp;face=254_144_710_428&quot;&gt;&lt;a href=&quot;https://platum.kr/archives/160778&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://platum.kr/archives/160778&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/blLvi4/hyKxPx3SoE/EySSL2I7moRIOqqTRxBKP1/img.jpg?width=640&amp;amp;height=427&amp;amp;face=145_94_419_269,https://scrap.kakaocdn.net/dn/c8i5HI/hyKwIHjguw/vpkAVtZqUZvGTa4ECEAVKK/img.jpg?width=1200&amp;amp;height=749&amp;amp;face=0_0_1200_749,https://scrap.kakaocdn.net/dn/dFLoKS/hyKxEXBxbe/jea45kLv4xey5djLmVwUz1/img.jpg?width=1073&amp;amp;height=705&amp;amp;face=254_144_710_428,https://scrap.kakaocdn.net/dn/chmF7u/hyKxPSmgkT/dR0PPC6EOh5eDhcgZ0Efy0/img.jpg?width=1073&amp;amp;height=705&amp;amp;face=254_144_710_428');&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;센드버드, 1억 달러 투자유치&amp;hellip;엔터프라이즈 SW &amp;lsquo;유니콘&amp;rsquo; 탄생&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;platum.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;
&lt;p data-ke-size=&quot;size16&quot;&gt;국내 첫 B2B 스타트업으로 유니콘 기업이라는 칭호를 얻은 SendBird는 채팅 API 플랫폼을 선보이며, 많은 기업에서 이 SendBird의 채팅 플랫폼을 사용하고 있다고 합니다. 어째서 SendBird가 글로벌 채팅 API가 되었는지 궁금하기도 하고, 외주 일로 SendBird의 Android Chat SDK를 다룰 수 있어서,&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;a href=&quot;https://sendbird.com/docs/chat/v3/android/getting-started/about-chat-sdk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sendbird.com/docs/chat/v3/android/getting-started/about-chat-sdk&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623639466875&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;About Chat SDK | Chat Android SDK | Sendbird Docs&quot; data-og-description=&quot;See how Sendbird Chat SDK works and try our sample app for Android.&quot; data-og-host=&quot;sendbird.com&quot; data-og-source-url=&quot;https://sendbird.com/docs/chat/v3/android/getting-started/about-chat-sdk&quot; data-og-url=&quot;https://sendbird.com/docs/chat/v3/android/getting-started/about-chat-sdk&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Tj4qK/hyKxDzwJny/ObzfAoFKOhiJoWkVS1WSU1/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000,https://scrap.kakaocdn.net/dn/Z5bsc/hyKyUTX4fW/MkiTBVuKvrFJuVpj7bnWak/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000&quot;&gt;&lt;a href=&quot;https://sendbird.com/docs/chat/v3/android/getting-started/about-chat-sdk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sendbird.com/docs/chat/v3/android/getting-started/about-chat-sdk&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Tj4qK/hyKxDzwJny/ObzfAoFKOhiJoWkVS1WSU1/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000,https://scrap.kakaocdn.net/dn/Z5bsc/hyKyUTX4fW/MkiTBVuKvrFJuVpj7bnWak/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000');&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;About Chat SDK | Chat Android SDK | Sendbird Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;See how Sendbird Chat SDK works and try our sample app for Android.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sendbird.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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;0. 시작&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://dashboard.sendbird.com/auth/signin?_ga=2.136478896.1207239033.1623639446-672426265.1623058267&quot;&gt;https://dashboard.sendbird.com/auth/signin?_ga=2.136478896.1207239033.1623639446-672426265.1623058267&lt;/a&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623639631293&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;Dashboard | Sendbird&quot; data-og-description=&quot;&quot; data-og-host=&quot;dashboard.sendbird.com&quot; data-og-source-url=&quot;https://dashboard.sendbird.com/auth/signin?_ga=2.136478896.1207239033.1623639446-672426265.1623058267&quot; data-og-url=&quot;https://dashboard.sendbird.com/auth/signin?_ga=2.136478896.1207239033.1623639446-672426265.1623058267&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dashboard.sendbird.com/auth/signin?_ga=2.136478896.1207239033.1623639446-672426265.1623058267&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dashboard.sendbird.com/auth/signin?_ga=2.136478896.1207239033.1623639446-672426265.1623058267&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;Dashboard | Sendbird&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;dashboard.sendbird.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샌드버드는 앞서서 사용하기전에 홈페이지 가입이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가입을 하면 채팅을 총괄하는 관리자 ID를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 가장 먼저 진행을 해야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1801&quot; data-origin-height=&quot;957&quot; data-filename=&quot;화면 캡처 2021-06-14 120101.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xTMnu/btq65sdgdtS/Ls3yEy5XeZUPrQi14mPxnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xTMnu/btq65sdgdtS/Ls3yEy5XeZUPrQi14mPxnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xTMnu/btq65sdgdtS/Ls3yEy5XeZUPrQi14mPxnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxTMnu%2Fbtq65sdgdtS%2FLs3yEy5XeZUPrQi14mPxnK%2Fimg.png&quot; data-origin-width=&quot;1801&quot; data-origin-height=&quot;957&quot; data-filename=&quot;화면 캡처 2021-06-14 120101.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 링크를 통해서 가입을 하고 나면 계정 정보 관련으로 Applications에서 채팅을 적용할 앱을 create + 버튼을 눌러서 등록해주시면 됩니다.&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;625&quot; data-filename=&quot;화면 캡처 2021-06-14 120939.png&quot; width=&quot;653&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CAIOq/btq67NN1C3G/mejGf5m0IW3SnTLKf5U9pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CAIOq/btq67NN1C3G/mejGf5m0IW3SnTLKf5U9pK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CAIOq/btq67NN1C3G/mejGf5m0IW3SnTLKf5U9pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCAIOq%2Fbtq67NN1C3G%2FmejGf5m0IW3SnTLKf5U9pK%2Fimg.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;625&quot; data-filename=&quot;화면 캡처 2021-06-14 120939.png&quot; width=&quot;653&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 클라이언트는 서버에서 DB를 받아 데이터를 UI로 표시를 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 서버는 클라이언트로 부터 언제나 DB를 받습니다. (가입, 로그인, 채팅방 입성, 채팅 초대, 채팅 보내기 등등..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 클라이언트는 DB를 받아서 가공해서 다른 서버의 DB와 같이 연동해서 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 클라이언트에서 서버에서 실시간으로 데이터를 받을 수 있는 Callback 함수들을 사용해서 상대방 채팅을 실시간으로 받거나 이벤트를 받을 수 있습니다.&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;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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 개념&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;notepad-1130743_1280.jpg&quot; width=&quot;617&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0TsF0/btq65T2ZguY/DcdgLnnOkOCK8IYulSoqK0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0TsF0/btq65T2ZguY/DcdgLnnOkOCK8IYulSoqK0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0TsF0/btq65T2ZguY/DcdgLnnOkOCK8IYulSoqK0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0TsF0%2Fbtq65T2ZguY%2FDcdgLnnOkOCK8IYulSoqK0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;notepad-1130743_1280.jpg&quot; width=&quot;617&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 설명한것과 같이 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;SendBird&lt;/b&gt;&lt;/span&gt;는 매우 심플한 구조를 가지고 있으며, SendBird에서 자체 서버를 운영하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SendBird가 기업에서 많이 사용하는 채팅 플랫폼이 된 이유는 &lt;b&gt;문서를 보고 누구든지 SendBird를 사용하기 쉽다는 점이 있습니다.&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;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;SendBird Chat SDK는 여러가지 CallBack 함수를 지원해주면서 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;서버에서 db를 가져오는 각종 쿼리 함수들을 지원해줍니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 채널 유형&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;playmobil-451203_1280.jpg&quot; width=&quot;595&quot; height=&quot;446&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfrla8/btq66jHaw9h/pUHrafGxj6o4DdXfuHwKck/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfrla8/btq66jHaw9h/pUHrafGxj6o4DdXfuHwKck/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfrla8/btq66jHaw9h/pUHrafGxj6o4DdXfuHwKck/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfrla8%2Fbtq66jHaw9h%2FpUHrafGxj6o4DdXfuHwKck%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;playmobil-451203_1280.jpg&quot; width=&quot;595&quot; height=&quot;446&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SendBird는 크게 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;OpenChannel&lt;/b&gt;&lt;/span&gt; 과&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; GroupChannel&lt;/b&gt;&lt;/span&gt;로 나뉩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;OpenChannel&lt;/b&gt;&lt;/span&gt;의 경우 누구나 참여할 수 있고 그렇기 때문에 여러명이 접속이 가능합니다. 열려있는 채팅 공간이고 심플한 기능들을 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응용프로그램을 사용하는 모든 사용자가 접속할 수 있고 최대 1000명까지 지원합니다.&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: #ee2323;&quot;&gt;&lt;b&gt;GroupChannel&lt;/b&gt;&lt;/span&gt;의 경우 참여할 유저를 따로 초대를 해야 채팅에 참여가 가능합니다. 기본적으로 비공개 채팅 공간이고 대표적으로 카카오톡을 생각하면 좋을 것 같습니다. &lt;b&gt;공개 설정을 모든 사용자로 바꿀수도 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 100명까지 사람들이 접속할 수 있습니다.&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;GroupChannel은 1:1 , 1:N, 비공개 단톡방 등등 유저가 다양한 카테고리로 사용할 수 있게끔 SendBird 에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원을 해주고, 답장기능 및 채팅을 읽지 않는 갯수 등등 openChannel에서 사용할 수 없었던 다양한 기능들을 사용할 수 있습니다.&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;b&gt;**SuperGroupChannel**&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;SuperGroupChannel은 GroupChannel보다 더 많은 인원을 수용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2000명까지 수용가능&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;하지만 더 많은 인원을 수용하기 때문에 GroupChannel 보다 제한된 몇몇 기능들이 있습니다.&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;- 읽지않는 채팅을 100개 까지 제한&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;- 미리보기 10명까지 지원&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;채팅 개념은 크게 OpenChannel 과 GroupChannel로 나뉘고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그외 사용자 관련된것들은 SendBird라는 클래스가 존재합니다.&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;Android Chat SDK에 3가지 클래스가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenChannel, GroupChannel, SendBird&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;위 3개의 클래스를 응용하면 원하는 채팅을 만들 수 있을것입니다.&amp;nbsp;&lt;/b&gt;귀찮은 일은 이미 SendBird가 다 처리해놨습니다. 많은 기업이 SendBird의 ChatAPI를 사용하는 이유가 바로 여기있다고 생각합니다. 탄탄한 서버에 클라이언트 개발자가 매우 사용하기 간편하기 때문에 서버에서 DB를 받아 클라이언트 개발자가 원하는데로 커스텀하기 쉽습니다.&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;하지만.. 단점으로 비용인데 자체 서버를 운영하기 때문에 채팅 서버 비용이 만만치 않게 빠져나갑니다. 자본이 부족한 기업이라면 다른 채팅 API나 저렴한 FCM+ 로컬 DB, 파이어베이스 등등 다른것을 고려해보길 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) 유저 유형&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;girl-2696947_1280.jpg&quot; width=&quot;589&quot; height=&quot;589&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvv2bB/btq7h60la6v/vKiepGuZWJvJ3nXXiaeCbK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvv2bB/btq7h60la6v/vKiepGuZWJvJ3nXXiaeCbK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvv2bB/btq7h60la6v/vKiepGuZWJvJ3nXXiaeCbK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbvv2bB%2Fbtq7h60la6v%2FvKiepGuZWJvJ3nXXiaeCbK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;girl-2696947_1280.jpg&quot; width=&quot;589&quot; height=&quot;589&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;유저 유형은 크게 4가지로 나뉩니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #212121;&quot;&gt;User&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;-어플리케이션을 사용중인 일반 User를 뜻합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;b&gt;Participant&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span&gt;-오픈채널에 참여중이고 online 상태중인 User를 뜻합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;Member&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;-&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;특정 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;그룹채널에 참여중인 유저를 뜻합니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Operator&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;채널을 관리하는 운영자 특정 Member 및 Participant를 차단하거나 음소거를 시킬 수 있음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;공식 문서를 읽을 때 유저관련 명칭 정리 정도로 생각하면 좋을것 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Android Chat SDK&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1039&quot; data-filename=&quot;tools-498202_1280.jpg&quot; width=&quot;658&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k181y/btq666nwpHe/T5NfD14TY86AiZSjJPDvfK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k181y/btq666nwpHe/T5NfD14TY86AiZSjJPDvfK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k181y/btq666nwpHe/T5NfD14TY86AiZSjJPDvfK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk181y%2Fbtq666nwpHe%2FT5NfD14TY86AiZSjJPDvfK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1039&quot; data-filename=&quot;tools-498202_1280.jpg&quot; width=&quot;658&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 셋팅&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;처음에 가입을 완료하고 Application까지 등록을 해야 아래설정이 가능합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 개발자가 sdk를 사용할 때 늘 그랫듯이 gradle 설정을 해야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1623642749179&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;allprojects {
    repositories {
        ...
        maven { url &quot;https://repo.sendbird.com/public/maven&quot; }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 url를 설정해주고&lt;/p&gt;
&lt;pre id=&quot;code_1623642786770&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    ...
    implementation 'com.sendbird.sdk:sendbird-android-sdk:3.0.166'
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dependencies에 위와같이 추가해줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 SendBird.init를 사용해줘야합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623642827290&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class App: Application() {
    override fun onCreate() {
        super.onCreate()
        //샌드버드 키 등록 
        SendBird.init(getString(R.string.sendBird_key),applicationContext)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 sendBird에 등록했던 Application의 key를 넣어줍시다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1801&quot; data-origin-height=&quot;957&quot; data-filename=&quot;화면 캡처 2021-06-14 120101.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xTMnu/btq65sdgdtS/Ls3yEy5XeZUPrQi14mPxnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xTMnu/btq65sdgdtS/Ls3yEy5XeZUPrQi14mPxnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xTMnu/btq65sdgdtS/Ls3yEy5XeZUPrQi14mPxnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxTMnu%2Fbtq65sdgdtS%2FLs3yEy5XeZUPrQi14mPxnK%2Fimg.png&quot; data-origin-width=&quot;1801&quot; data-origin-height=&quot;957&quot; data-filename=&quot;화면 캡처 2021-06-14 120101.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Applications의 이미 만든 Application 이름을 선택해주면&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1667&quot; data-origin-height=&quot;800&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1vRhd/btq665V29DY/9PB8GKRDZHPkNMygkkuQdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1vRhd/btq665V29DY/9PB8GKRDZHPkNMygkkuQdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1vRhd/btq665V29DY/9PB8GKRDZHPkNMygkkuQdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1vRhd%2Fbtq665V29DY%2F9PB8GKRDZHPkNMygkkuQdK%2Fimg.png&quot; data-origin-width=&quot;1667&quot; data-origin-height=&quot;800&quot; data-filename=&quot;blob&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application ID가 바로 SendBird Key 입니다.&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;2) 특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android Chat SDK로 지원해주는 메서드는 SendBird.init() 메서드를 빼고 모두&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;비동기&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 클라이언트 입장에서 코딩을 할때 주의해야할점이 콜백지옥으로 빠지는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt; 코딩을 할 때 이점을 유의해서 최대한 콜백지옥으로 빠지지 않게끔 주의하면 좋을것 같습니다.&lt;/b&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;b&gt;예제.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623642610951&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// The CHANNEL_URL below can be retrieved using the openChannel.getUrl().
OpenChannel.getChannel(CHANNEL_URL, new OpenChannel.OpenChannelGetHandler() {
    @Override
    public void onResult(OpenChannel openChannel, SendBirdException e) {
        if (e != null) {
            // Handle error.
        }

        // Call the instance method of the result object in the &quot;openChannel&quot; parameter of the onResult() callback method.
        openChannel.enter(new OpenChannel.OpenChannelEnterHandler() {
            @Override
            public void onResult(SendBirdException e) {
                if (e != null) {
                    // Handle error.
                }
                
                // The current user successfully enters the open channel,
                // and can chat with other users in the channel by using APIs.
                ... 
            }
        });
    }
});&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;- Android Chat SDK는 위 처럼 메서드안에 메서드가 사용되는 형식으로 주로 쓰게 됩니다.&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;3) SDK 설명&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OpenChannel, GroupChannel , SendBird&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 3가지 클래스를 기준으로 많이 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OpenChannel&lt;/b&gt;은 오픈 채널에 관련된 것들을 모두 지원합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오픈채널 관련 채널 생성, 채팅 내역, 채팅 db 검색 등등..&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 alignLeft&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;423&quot; data-filename=&quot;화면 캡처 2021-06-14 132108.png&quot; width=&quot;493&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cALZ43/btq65Tn37Hh/89AyAkedLcXMSkGgGDqM1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cALZ43/btq65Tn37Hh/89AyAkedLcXMSkGgGDqM1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cALZ43/btq65Tn37Hh/89AyAkedLcXMSkGgGDqM1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcALZ43%2Fbtq65Tn37Hh%2F89AyAkedLcXMSkGgGDqM1k%2Fimg.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;423&quot; data-filename=&quot;화면 캡처 2021-06-14 132108.png&quot; width=&quot;493&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GroupChannel&lt;/b&gt;은 그룹 채널에 관련된 것들을 모두 지원합니다.&amp;nbsp;&lt;br /&gt;- 그룹채널 관련 채널 생성, 채팅 내역, 채팅 db 검색 등등..&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;661&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;535&quot; data-filename=&quot;화면 캡처 2021-06-14 132125.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJTnA8/btq66vtXLg8/z44xtUb7kBwrOkOVkyKS11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJTnA8/btq66vtXLg8/z44xtUb7kBwrOkOVkyKS11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJTnA8/btq66vtXLg8/z44xtUb7kBwrOkOVkyKS11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJTnA8%2Fbtq66vtXLg8%2Fz44xtUb7kBwrOkOVkyKS11%2Fimg.png&quot; width=&quot;661&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;535&quot; data-filename=&quot;화면 캡처 2021-06-14 132125.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SendBird&lt;/b&gt;는 유저 로그인 및 가입, 유저정보, 서버로 전체 DB 가져오기, 유저차단, 차단 풀기 등등에 사용합니다&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;- SendBird ChatSdk 셋팅&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;- SendBird와 연결관리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버의 전반적인 DB 가져오기&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;480&quot; data-filename=&quot;화면 캡처 2021-06-14 132046.png&quot; width=&quot;545&quot; height=&quot;326&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MceJA/btq650VgD8Q/EtCdB0EGPReYWGHuUl4Jj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MceJA/btq650VgD8Q/EtCdB0EGPReYWGHuUl4Jj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MceJA/btq650VgD8Q/EtCdB0EGPReYWGHuUl4Jj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMceJA%2Fbtq650VgD8Q%2FEtCdB0EGPReYWGHuUl4Jj1%2Fimg.png&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;480&quot; data-filename=&quot;화면 캡처 2021-06-14 132046.png&quot; width=&quot;545&quot; height=&quot;326&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;Parms 관련 클래스들이 지원됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채널을 만들때나 유저를 생성할때 Parms를 생성해서 여러 추가정보를 입력할 수있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623645448296&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; val params = GroupChannelParams()
            .setPublic(false)
            .setEphemeral(false)
            .setDistinct(true)
            .setSuper(false)
            .addUserIds(userList)
            .setName(getString(R.string.currentUser))
        GroupChannel.createChannel(params) { groupChannel, _ -&amp;gt;
            currentUrl = groupChannel.url
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에 유저관련 parms , 오픈채널 관련 Parms 등등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f89009; color: #ffffff;&quot;&gt;&lt;b&gt;이러한 개념만 알면 필요할때 공식문서 사이트에 들어가 필요한 parms 클래스명이 무엇인지 확인해서 사용하면 됩니다.&amp;nbsp;&lt;/b&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;이제 구체적인 감각을 익히기 위해 예제를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 &lt;b&gt;그룹채팅으로 1:1를 구현한 예제&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1623645784295&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위해서 id 'kotlin-android-extensions' 를 추가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;** 주의 **&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;위 extensions는 deprecated가 되었고 9월부터 지원하지 않습니다. 이 예제는 빠른 테스트를 위해 넣었습니다.&lt;/b&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: #ee2323;&quot;&gt;&lt;b&gt; 그래서 실제 프로젝트에 사용할땐&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;findViewById, Compose, ViewBinding, DataBinding중 하나를 선택해서 사용하시기를 권장합니다.&lt;/b&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: #000000;&quot;&gt;xml 코드입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623645964299&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&amp;gt;

    &amp;lt;TextView
        android:id=&quot;@+id/chat_tv&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;32dp&quot;
        app:layout_constraintBottom_toTopOf=&quot;@+id/chatMessage_et&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot;
        tools:text=&quot;chat&quot; /&amp;gt;

    &amp;lt;EditText
        android:id=&quot;@+id/chatMessage_et&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:ems=&quot;10&quot;
        android:inputType=&quot;textPersonName&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toStartOf=&quot;@+id/button&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot; /&amp;gt;

    &amp;lt;Button
        android:id=&quot;@+id/button&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginEnd=&quot;16dp&quot;
        android:text=&quot;Button&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot; /&amp;gt;

    &amp;lt;Button
        android:id=&quot;@+id/btn_invite&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;16dp&quot;
        android:layout_marginEnd=&quot;16dp&quot;
        android:text=&quot;유저2 초대&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&amp;gt;

    &amp;lt;Button
        android:id=&quot;@+id/btn_exit&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;나가기&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&amp;gt;

&amp;lt;/androidx.constraintlayout.widget.ConstraintLayout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;832&quot; data-filename=&quot;화면 캡처 2021-06-14 134648.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MDOCG/btq7d5tS9ss/wNKzXT8AlCVhQuJx52q1b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MDOCG/btq7d5tS9ss/wNKzXT8AlCVhQuJx52q1b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MDOCG/btq7d5tS9ss/wNKzXT8AlCVhQuJx52q1b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMDOCG%2Fbtq7d5tS9ss%2FwNKzXT8AlCVhQuJx52q1b0%2Fimg.png&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;832&quot; data-filename=&quot;화면 캡처 2021-06-14 134648.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 xml이 그려집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;res-&amp;gt; strings 에 아래와 같이 적읍시다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623646137782&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!--    프로젝트 Run할때 기기마다 아래 유저를 다르게 해서 테스트 해야합니다. --&amp;gt;
    &amp;lt;!-- A기종은 TestUser1 B기종은 TestUser2--&amp;gt;
    &amp;lt;string name=&quot;currentUser&quot;&amp;gt;TestUser1&amp;lt;/string&amp;gt;
    &amp;lt;string name=&quot;opponentUser&quot;&amp;gt;TestUser2&amp;lt;/string&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 MainActivity로 가서 코드를 작성해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1623646402983&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MainActivity : BaseActivity() {
    lateinit var currentUrl: String
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.oneonone_activity)

        SendBird.connect(getString(R.string.currentUser)) { user, e -&amp;gt;
            if(e ==null)
            GroupChannel.createMyGroupChannelListQuery().next { list, _ -&amp;gt;
                if (list.isNullOrEmpty()) {
                    toastShort(&quot;채팅방이 생성 되었습니다.&quot;)
                    createChannel()
                } else {
                    toastShort(&quot;이미 채팅방이 존재합니다.&quot;)
                    currentUrl = list.first().url
                    getGroupUrl(currentUrl) { groupChannel -&amp;gt;
                        prevMsg()                
                    }
                }
            }
        }


        button.setOnClickListener {
//            1.방찾기
            GroupChannel.getChannel(currentUrl) { groupChannel, e -&amp;gt;
//                2.입장 (메시지 보내기 위해서는 필수)
                groupChannel.join { }
//                3.메시지 보내기
                groupChannel.sendUserMessage(chatMessage_et.text.toString()) { msg, e -&amp;gt;
//                4. 메시지를 UI로 표시
                    chat_tv.append(&quot;${msg.message}\n&quot;)
                }
            }
        }
        btn_invite.setOnClickListener {
            val userIds = listOf(getString(R.string.opponentUser))
            getGroupUrl(currentUrl) { groupChannel -&amp;gt;
                groupChannel.inviteWithUserIds(userIds) {}
            }
        }

        btn_exit.setOnClickListener {
            getGroupUrl(currentUrl) { groupChannel -&amp;gt;
                groupChannel.leave { toastShort(&quot;탈퇴&quot;) }
            }
        }
    }

    private fun createChannel() {
        val userList = listOf&amp;lt;String&amp;gt;(getString(R.string.currentUser))

        val params = GroupChannelParams()
            .setPublic(false)
            .setEphemeral(false)
            .setDistinct(true)
            .setSuper(false)
            .addUserIds(userList)
            .setName(getString(R.string.currentUser))
        GroupChannel.createChannel(params) { groupChannel, _ -&amp;gt;
            currentUrl = groupChannel.url
        }
    }

    private fun prevMsg() {
        GroupChannel.createMyGroupChannelListQuery().next { list, e -&amp;gt;
            list.forEach {
                chat_tv.append(&quot;${it.lastMessage.message}\n&quot;)
            }
        }
    }

    private fun getGroupUrl(url: String, afterLogin: (GroupChannel) -&amp;gt; Unit) {
        GroupChannel.getChannel(url) { groupChannel, e -&amp;gt;
            afterLogin(groupChannel)
        }
    }


    override fun onResume() {
        super.onResume()
        SendBird.addChannelHandler(GROUP_HANDLER_ID, object : SendBird.ChannelHandler() {
            override fun onMessageReceived(p0: BaseChannel?, message: BaseMessage?) {
                chat_tv.append(&quot;${message!!.message}\n&quot;)
            }
        })
    }

    companion object {
        const val GROUP_HANDLER_ID = &quot;GROUP_HANDLER_ID&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제 흐름 순서&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*&amp;nbsp;&lt;/b&gt;그룹 채팅으로 1:1방 구현 *&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) 화면이 켜지는순간 유저 명이 이미 존재한다면 로그인 아니라면 가입 -&amp;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;(2) 로그인한 User의 GroupChannel이 하나라도 없을시 채팅방 생성 있을시 생성하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;(이미 존재하는 방일시 이전 채팅내역 다가져오기) -&amp;gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 유저 초대버튼을 누르면 strings의 저장해놓은 상대방 유저를 채팅으로 초대함 -&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;(4) 상대방 채팅을 언제든지 읽을 수 있도록 onResume()에 SendBird.addChannelHandler() 등록&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;4) 테스트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 기기를 사용해서 서로 채팅을 해볼 생각입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 가상기기나 혹은 실기기를 준비해봅시다.&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-origin-width=&quot;1912&quot; data-origin-height=&quot;462&quot; data-filename=&quot;화면 캡처 2021-06-14 141103.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfIv2e/btq69Svo16g/ox129HmdvDYeL6DWDFw2NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfIv2e/btq69Svo16g/ox129HmdvDYeL6DWDFw2NK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfIv2e/btq69Svo16g/ox129HmdvDYeL6DWDFw2NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfIv2e%2Fbtq69Svo16g%2Fox129HmdvDYeL6DWDFw2NK%2Fimg.png&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;462&quot; data-filename=&quot;화면 캡처 2021-06-14 141103.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위해서 웹사이트도 같이 봐줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 Application ID를 보던 페이지에서 Group channels 를 누르면&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;우측 상단에 CreateUser + 를 눌러서 TestUser1, TestUser2 를 만들어 줍시다.&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;716&quot; height=&quot;349&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;935&quot; data-filename=&quot;화면 캡처 2021-06-14 140222.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cM8dqD/btq7d49FRdt/ROKXlAiElKh6S2ANI5Q8Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cM8dqD/btq7d49FRdt/ROKXlAiElKh6S2ANI5Q8Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cM8dqD/btq7d49FRdt/ROKXlAiElKh6S2ANI5Q8Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcM8dqD%2Fbtq7d49FRdt%2FROKXlAiElKh6S2ANI5Q8Rk%2Fimg.png&quot; width=&quot;716&quot; height=&quot;349&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;935&quot; data-filename=&quot;화면 캡처 2021-06-14 140222.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 좌측 사이드에 Group channels 를 눌러서 해당 페이지를 켜놓고 안드로이드 스튜디오로 이동해봅시다.&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;A기종을 먼저 테스트 해봅시다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Run을 해서 A기종에 앱을 만들어봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;831&quot; data-filename=&quot;화면 캡처 2021-06-14 140642.png&quot; width=&quot;473&quot; height=&quot;706&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1zRGV/btq7gqxRM6Q/s4xGgkikzxwDDfQk0zY8IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1zRGV/btq7gqxRM6Q/s4xGgkikzxwDDfQk0zY8IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1zRGV/btq7gqxRM6Q/s4xGgkikzxwDDfQk0zY8IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1zRGV%2Fbtq7gqxRM6Q%2Fs4xGgkikzxwDDfQk0zY8IK%2Fimg.png&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;831&quot; data-filename=&quot;화면 캡처 2021-06-14 140642.png&quot; width=&quot;473&quot; height=&quot;706&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;847&quot; data-filename=&quot;화면 캡처 2021-06-14 140744.png&quot; width=&quot;438&quot; height=&quot;704&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHkGP6/btq67SCp6CW/FvdwvkhLUnBtSp2wrg3cKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHkGP6/btq67SCp6CW/FvdwvkhLUnBtSp2wrg3cKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHkGP6/btq67SCp6CW/FvdwvkhLUnBtSp2wrg3cKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHkGP6%2Fbtq67SCp6CW%2FFvdwvkhLUnBtSp2wrg3cKK%2Fimg.png&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;847&quot; data-filename=&quot;화면 캡처 2021-06-14 140744.png&quot; width=&quot;438&quot; height=&quot;704&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 웹사이트에는 새로운 채팅이 하나가 추가된게 UI로 표시가 되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상대 유저 입장에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 서버로 부터 받는 DB의 쿼리로 list를 가져올 때 채팅이 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;그러니 이 예제에서는 아래와 같은 순서로 해주시길 바랍니다.&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;p data-ke-size=&quot;size16&quot;&gt;(2) 버튼을 누르고&amp;nbsp; 채팅 아무거나 적을것&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(물론 실프로젝트는 위와 같이 안되게 잘 커스텀해주시면 됩니다. 언제나 예제일뿐.)&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;이어서 B기종으로 테스트 해봅시다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;빌드를 하기전에&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B기종으로 빌드를 할때 res-&amp;gt; strings.xml을 아래처럼 바꿉시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(다시 말씀드리지만 테스트를 위해 임시로 짠 방식입니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1623647048591&quot; class=&quot;kotlin&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!--    프로젝트 Run할때 기기마다 아래 유저를 다르게 해서 테스트 해야합니다. --&amp;gt;
    &amp;lt;!-- A기종은 TestUser1 B기종은 TestUser2--&amp;gt;
    &amp;lt;string name=&quot;currentUser&quot;&amp;gt;TestUser2&amp;lt;/string&amp;gt;
    &amp;lt;string name=&quot;opponentUser&quot;&amp;gt;TestUser1&amp;lt;/string&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B기종은 사용자가 TestUser2 상대방이 TestUser1 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(A기종은 사용자가 TestUser1 상대방이 TestUser2)&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 alignLeft&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;872&quot; data-filename=&quot;화면 캡처 2021-06-14 142041.png&quot; width=&quot;397&quot; height=&quot;692&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0AM6B/btq7grcwvs8/CchqnpK2KpP13xWAuoSvEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0AM6B/btq7grcwvs8/CchqnpK2KpP13xWAuoSvEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0AM6B/btq7grcwvs8/CchqnpK2KpP13xWAuoSvEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0AM6B%2Fbtq7grcwvs8%2FCchqnpK2KpP13xWAuoSvEk%2Fimg.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;872&quot; data-filename=&quot;화면 캡처 2021-06-14 142041.png&quot; width=&quot;397&quot; height=&quot;692&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B기종으로 실행한 화면 입니다.&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;아까 켜놨던 웹사이트도 보면 채팅이 생성되고 유저가 2명인걸 알 수가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1647&quot; data-origin-height=&quot;891&quot; data-filename=&quot;화면 캡처 2021-06-14 142325.png&quot; width=&quot;703&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMe2wP/btq7deLxJWT/m2HG6N8DWIjBUhGvGnCufK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMe2wP/btq7deLxJWT/m2HG6N8DWIjBUhGvGnCufK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMe2wP/btq7deLxJWT/m2HG6N8DWIjBUhGvGnCufK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMe2wP%2Fbtq7deLxJWT%2Fm2HG6N8DWIjBUhGvGnCufK%2Fimg.png&quot; data-origin-width=&quot;1647&quot; data-origin-height=&quot;891&quot; data-filename=&quot;화면 캡처 2021-06-14 142325.png&quot; width=&quot;703&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;827&quot; data-filename=&quot;화면 캡처 2021-06-14 142616.png&quot; width=&quot;885&quot; height=&quot;424&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clgbDU/btq67Os5PoK/XgBxNTCRtGqbs5qz1wgF0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clgbDU/btq67Os5PoK/XgBxNTCRtGqbs5qz1wgF0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clgbDU/btq67Os5PoK/XgBxNTCRtGqbs5qz1wgF0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclgbDU%2Fbtq67Os5PoK%2FXgBxNTCRtGqbs5qz1wgF0K%2Fimg.png&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;827&quot; data-filename=&quot;화면 캡처 2021-06-14 142616.png&quot; width=&quot;885&quot; height=&quot;424&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정하면 실시간으로 서로 채팅이 오갈 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디까지나 예제로 적은거라 좀 비효율적인 로직이 있긴한데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드좀 파악하고 큰 흐름에 대해 감이 온다면 SendBird Chat SDK가 워낙 쉽기 때문에 누구든지 원하는 형태로&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;&lt;b&gt;아래는 실제 사용 영상입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&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;채팅을 사용하기 쉽게 만든 SendBird 팀에게 감탄을 하게 되네요 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 자본력이 생기면 그때 SendBird를 적극 사용해볼 생각입니다.&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;등등의 SendBird 공식 문서에 잘 나와 있습니다.&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;이상 안드로이드 SendBird Chat SDK에 알아봤습니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>AndroidChat</category>
      <category>AndroidSendBird</category>
      <category>sendbird</category>
      <category>샌드버드</category>
      <category>안드로이드</category>
      <category>안드로이드SendBird</category>
      <category>안드로이드개발</category>
      <category>안드로이드채팅</category>
      <category>안드로이드채팅API</category>
      <category>안드로이드채팅만드는법</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/40</guid>
      <comments>https://gift123.tistory.com/40#entry40comment</comments>
      <pubDate>Mon, 14 Jun 2021 14:42:49 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (11) Compose Side-effects</title>
      <link>https://gift123.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size18&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;size16&quot;&gt;Compose는 Kotlin으로 만들어진 UI Tool입니다. 코틀린의 Ramda를 최대한 이용한 라이브러리이기 때문에 함수형 언어프로그래밍을 같이 공부해주면 좋습니다. 오늘 내용인 Side-effect(부수 효과) 같은 경우도 함수형 언어를 알아야 이해가 되기 때문에 이 포스팅을 보기전에 아래링크를 보시길 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sojin.io/article/%EC%88%9C%EC%88%98-%ED%95%A8%EC%88%98%EC%99%80-%EB%B6%80%EC%88%98-%ED%9A%A8%EA%B3%BC/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sojin.io/article/%EC%88%9C%EC%88%98-%ED%95%A8%EC%88%98%EC%99%80-%EB%B6%80%EC%88%98-%ED%9A%A8%EA%B3%BC/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623386531949&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;sojin.io&quot; data-og-source-url=&quot;https://sojin.io/article/%EC%88%9C%EC%88%98-%ED%95%A8%EC%88%98%EC%99%80-%EB%B6%80%EC%88%98-%ED%9A%A8%EA%B3%BC/&quot; data-og-url=&quot;https://sojin.io/article/%EC%88%9C%EC%88%98-%ED%95%A8%EC%88%98%EC%99%80-%EB%B6%80%EC%88%98-%ED%9A%A8%EA%B3%BC&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/EmKon/hyKwAvtr4c/9KjMgr71kHkSKNMX1SZG01/img.jpg?width=233&amp;amp;height=228&amp;amp;face=0_0_233_228&quot;&gt;&lt;a href=&quot;https://sojin.io/article/%EC%88%9C%EC%88%98-%ED%95%A8%EC%88%98%EC%99%80-%EB%B6%80%EC%88%98-%ED%9A%A8%EA%B3%BC/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sojin.io/article/%EC%88%9C%EC%88%98-%ED%95%A8%EC%88%98%EC%99%80-%EB%B6%80%EC%88%98-%ED%9A%A8%EA%B3%BC/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/EmKon/hyKwAvtr4c/9KjMgr71kHkSKNMX1SZG01/img.jpg?width=233&amp;amp;height=228&amp;amp;face=0_0_233_228');&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;sojin.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Side-effect(부수효과)란&amp;nbsp; 함수가 결괏값을 반환하는 것 이외에 다른 일을 할 때 그 함수는 Side-effect를 가진다고 한다.&lt;/b&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;그리고 이 글에는 알아야할 사전 지식들이 있습니다. composable, composition, recomposition, remember 등등을 안다는 가정하에 정리를 한 내용들이라서 이해가 안된다면 이전 포스팅들을 참고하면 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/33&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623387394839&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;안드로이드 개발 (8) Compose 이해 정리&quot; data-og-description=&quot;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/33&quot; data-og-url=&quot;https://gift123.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/YyMIs/hyKxNUi1nA/HSVkj4DKRZGGV9FL5Lcck0/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/WoQ7M/hyKxDRGJn7/R6zYbz0vi6pJ49O59Ak6A1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/CzeZq/hyKxMVoOnM/AP02Q6VcZPBdyUP3E9iJI0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/33&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/YyMIs/hyKxNUi1nA/HSVkj4DKRZGGV9FL5Lcck0/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/WoQ7M/hyKxDRGJn7/R6zYbz0vi6pJ49O59Ak6A1/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/CzeZq/hyKxMVoOnM/AP02Q6VcZPBdyUP3E9iJI0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;안드로이드 개발 (8) Compose 이해 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/34&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623387382797&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;안드로이드 개발 (9) Compose 상태 관리&quot; data-og-description=&quot;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/34&quot; data-og-url=&quot;https://gift123.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJ4Xxa/hyKwAhZjSC/Z7y8MJRXnNFT3aK9iKK8vK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bDvquB/hyKxGt6Xzt/U8aryhhKGuukHaKaFAGynK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cRAnRo/hyKwA966cs/OKnpkWtlSvFO6oZlNKf6N1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/34&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJ4Xxa/hyKwAhZjSC/Z7y8MJRXnNFT3aK9iKK8vK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bDvquB/hyKxGt6Xzt/U8aryhhKGuukHaKaFAGynK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cRAnRo/hyKwA966cs/OKnpkWtlSvFO6oZlNKf6N1/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078');&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;안드로이드 개발 (9) Compose 상태 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/38&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623387407458&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;안드로이드 개발 (10) Compose Composable Lifecycle&quot; data-og-description=&quot;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/38&quot; data-og-url=&quot;https://gift123.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/O9RFb/hyKxPkhHhB/0lxCAkjTSWKqbC6kyQ6iE0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/zsmdQ/hyKxAUXNUQ/PSGeemKaZKfPk94MmkVmdK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/3X65i/hyKwz4rn6i/ixJ7WbruTYBoppFVsk50X0/img.png?width=1280&amp;amp;height=414&amp;amp;face=0_0_1280_414&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/38&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/O9RFb/hyKxPkhHhB/0lxCAkjTSWKqbC6kyQ6iE0/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/zsmdQ/hyKxAUXNUQ/PSGeemKaZKfPk94MmkVmdK/img.png?width=800&amp;amp;height=343&amp;amp;face=0_0_800_343,https://scrap.kakaocdn.net/dn/3X65i/hyKwz4rn6i/ixJ7WbruTYBoppFVsk50X0/img.png?width=1280&amp;amp;height=414&amp;amp;face=0_0_1280_414');&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;안드로이드 개발 (10) Compose Composable Lifecycle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다. 아래 문서를 정리한 내용입니다. https://developer.android.com/jetpack/compose/lifecycle?hl=en 컴포저블 수명 주..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Compose의 SideEffects&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;682&quot; data-filename=&quot;drop-1759703_1280.jpg&quot; width=&quot;561&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YVp8Q/btq61JY9nDg/MhVKDoZ2Dix4jrS48kGTM1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YVp8Q/btq61JY9nDg/MhVKDoZ2Dix4jrS48kGTM1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YVp8Q/btq61JY9nDg/MhVKDoZ2Dix4jrS48kGTM1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYVp8Q%2Fbtq61JY9nDg%2FMhVKDoZ2Dix4jrS48kGTM1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;682&quot; data-filename=&quot;drop-1759703_1280.jpg&quot; width=&quot;561&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본적으로 Composable&lt;span&gt; 에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;SideEffects가 없어야함&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;. &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 필요한 경우 Composable의 Lifecycle 를 인식하는 관리된 환경에서 SideEffect를 호출해야함&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;2. 상태 및 effect 사용 사례&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;art-therapy-227585_1280.jpg&quot; width=&quot;516&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q43Y7/btq64gPwTPU/LoXNqSMQYn4qaxYDNDv3P1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q43Y7/btq64gPwTPU/LoXNqSMQYn4qaxYDNDv3P1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q43Y7/btq64gPwTPU/LoXNqSMQYn4qaxYDNDv3P1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq43Y7%2Fbtq64gPwTPU%2FLoXNqSMQYn4qaxYDNDv3P1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;art-therapy-227585_1280.jpg&quot; width=&quot;516&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-앱 상태를 변경해야 하는 경우 SideEfffects 가 예측 가능한 방식으로 실행하게끔 &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Effect API를 사용해야 함&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;-&lt;b&gt;effect&lt;/b&gt;&lt;/span&gt;는 UI를 내보내지 않으며 Composition이 완료될 때 SideEffects 를 실행하는 Composable 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;-&lt;b&gt;effect&lt;/b&gt;에서 실행하는 작업이 UI와 관련되고 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;단방향 데이터 흐름&lt;/b&gt;&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;b&gt;참고:&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;반응형 UI는 본질적으로 비동기이며 Compose는 콜백을 사용하는 대신 API 수준에서 &lt;b&gt;코루틴을 이용함&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;launchedeffect&quot; data-text=&quot;LaunchedEffect: 컴포저블의 범위에서 정지 함수 실행&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;LaunchedEffect: Composable의 Scope에서 suspend 함수 실행&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Composable 내에서 안전하게 suspend 함수를 호출하려면&lt;b&gt;&lt;span&gt; LaunchedEffect&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Composable&lt;/b&gt;을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LaunchedEffect는 Composable 함수이므로 다른 Composable&amp;nbsp; 함수 내에서만 사용가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LaunchedEffect가 Composable 을 시작하면 매개변수로 전달된 코드 블록으로 코루틴이 실행됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LaunchedEffect가 Composition을 종료하면 코루틴이 취소됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;LaunchedEffect가 다른 키로 recomposition 되면 기존 코루틴이 취소되고 새 코루틴에서 새 suspend 함수가 실행&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;/p&gt;
&lt;pre id=&quot;code_1623241021620&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MyScreen(
    state: UiState&amp;lt;List&amp;lt;Movie&amp;gt;&amp;gt;,
    scaffoldState: ScaffoldState = rememberScaffoldState()
) {

    // If the UI state contains an error, show snackbar
    if (state.hasError) {

        // `LaunchedEffect` will cancel and re-launch if
        // `scaffoldState.snackbarHostState` changes
        LaunchedEffect(scaffoldState.snackbarHostState) {
            // Show snackbar using a coroutine, when the coroutine is cancelled the
            // snackbar will automatically dismiss. This coroutine will cancel whenever
            // `state.hasError` is false, and only start when `state.hasError` is true
            // (due to the above if-check), or if `scaffoldState.snackbarHostState` changes.
            scaffoldState.snackbarHostState.showSnackbar(
                message = &quot;Error message&quot;,
                actionLabel = &quot;Retry message&quot;
            )
        }
    }

    Scaffold(scaffoldState = scaffoldState) {
        /* ... */
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;예제 설명&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-LaunchedEffect&lt;span&gt;&amp;nbsp;&lt;/span&gt;호출 사이트가 if 문 내에서 조건이 거짓일 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;LaunchedEffect가 composition 에 있으면 삭제되고 따라서 코루틴이 취소&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;remembercoroutinescope&quot; data-text=&quot;rememberCoroutineScope: 컴포지션 인식 범위를 확보하여 컴포저블 외부에서 코루틴 실행&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;rememberCoroutineScope: Compsoable 인식 범위를 확보하여 Composable 외부에서 코루틴 실행&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1623241933923&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MoviesScreen(scaffoldState: ScaffoldState = rememberScaffoldState()) {

    // Creates a CoroutineScope bound to the MoviesScreen's lifecycle
    val scope = rememberCoroutineScope()

    Scaffold(scaffoldState = scaffoldState) {
        Column {
            /* ... */
            Button(
                onClick = {
                    // Create a new coroutine in the event handler
                    // to show a snackbar
                    scope.launch {
                        scaffoldState.snackbarHostState
                            .showSnackbar(&quot;Something happened!&quot;)
                    }
                }
            ) {
                Text(&quot;Press me&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;- Composable 외부에 있지만 Composition을 종료한 후 자동으로 취소되도록 범위가 지정된 코루틴을 실행하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;rememberCoroutineScope를 사용&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;- rememberCoroutineScope는 코루틴 하나 이상의 Lifecycle를 수동으로 관리해야 할 때 사용&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;- rememberCoroutineScope는 호출되는 Composition의 지점에 바인딩된&lt;span&gt;&amp;nbsp;&lt;/span&gt;CoroutineScope를 반환하는 Composable함수 (호출이 Composable을 종료하면 Scope가 취소됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;rememberupdatedstate&quot; data-text=&quot;rememberUpdatedState: 값이 변경되는 경우 다시 시작되지 않아야 하는 효과에서 값 참조&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;rememberUpdatedState: 값이 변경되는 경우 다시 시작되지 않아야 하는 effect에서 값 참조&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- rememberUpdatedState는 경우에 따라 effect에서 값이 변경되면 effect를 다시 시작하지 않을 값을 캡처할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;- rememberUpdatedState는 캡처하고 업데이트할 수 있는 이 값의 참조를 만들어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 비용이 많이 들거나 &lt;b&gt;다시 만들고 다시 시작할 수 없도록&lt;/b&gt; 오래 지속되는 작업이 포함된 effect에 유용&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;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623241130203&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun LandingScreen(onTimeout: () -&amp;gt; Unit) {

    // This will always refer to the latest onTimeout function that
    // LandingScreen was recomposed with
    val currentOnTimeout by rememberUpdatedState(onTimeout)

    // Create an effect that matches the lifecycle of LandingScreen.
    // If LandingScreen recomposes, the delay shouldn't start again.
    LaunchedEffect(true) {
        delay(SplashWaitTimeMillis)
        currentOnTimeout()
    }

    /* Landing screen content */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&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;span&gt;&amp;nbsp;&lt;/span&gt;LandingScreen이 있다고 가정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서 LandingScreen이 recomposition 되는 경우에도 일정 시간 동안 대기하고&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;1) 호출 사이트의 수명 주기와 일치하는 효과를 만들기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;Unit&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;true와&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같이 변경되지 않는 상수가 매개변수로 전달&lt;span&gt; LaunchedEffect(true)&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;2) &lt;/span&gt;onTimeout&lt;span&gt;&amp;nbsp;&lt;/span&gt;람다에&lt;span&gt;&amp;nbsp;&lt;/span&gt;LandingScreen이 recomposition된 최신 값이&lt;span&gt;&amp;nbsp;&lt;/span&gt;항상&lt;span&gt;&amp;nbsp;&lt;/span&gt;포함되도록 하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rememberUpdatedState로&lt;span&gt;&amp;nbsp;&lt;/span&gt;onTimeout을 래핑해야 함&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;letter-spacing: 0px;&quot;&gt;3) 코드에서 반환된&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;State,&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;currentOnTimeout은 effect에서 사용해야 할것&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;주의:&lt;/b&gt;&lt;span&gt;&lt;span&gt; 위 예제에서 &lt;/span&gt;LaunchedEffect(true)는&lt;span&gt;&amp;nbsp;&lt;/span&gt;while(true)처럼 작동될 수 있음. 예제는 예제일 뿐,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;항상&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;h4 id=&quot;disposableeffect&quot; data-text=&quot;DisposableEffect: 정리가 필요한 효과&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;DisposableEffect: 정리가 필요한 효과&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Key가 변경되거나 Composable이 Composition을 종료한 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;정리해야 하는 side-effect 의 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;DisposableEffect를 사용&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;-DisposableEffect&lt;span&gt;&amp;nbsp;&lt;/span&gt;키가 변경되면 Composable이 현재 effect를&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623241140418&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -&amp;gt; Unit) {

    // Safely update the current `onBack` lambda when a new one is provided
    val currentOnBack by rememberUpdatedState(onBack)

    // Remember in Composition a back callback that calls the `onBack` lambda
    val backCallback = remember {
        // Always intercept back events. See the SideEffect for
        // a more complete version
        object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                currentOnBack()
            }
        }
    }

    // If `backDispatcher` changes, dispose and reset the effect
    DisposableEffect(backDispatcher) {
        // Add callback to the backDispatcher
        backDispatcher.addCallback(backCallback)

        // When the effect leaves the Composition, remove the callback
        onDispose {
            backCallback.remove()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&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;1) effect가 저장된&lt;span&gt;&amp;nbsp;&lt;/span&gt;backCallback을&lt;span&gt;&amp;nbsp;&lt;/span&gt;backDispatcher에 추가-&amp;gt; backDispatcher가 변경되면 effect가 삭제되고 다시 시작&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)DisposableEffect는&amp;nbsp;onDispose&amp;nbsp;절을 코드 블록의 최종 문으로 필수 포함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(onDispose가 없으면 IDE에 빌드 시간 오류가 표시됨)&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;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;onDispose에 빈 블록을 포함하는 것은 권장하지 않음&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;sideeffect-publish&quot; data-text=&quot;SideEffect: Compose 상태를 비 Compose 코드에 게시&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;3. Side-Effect: Compose 상태를 비 Compose 코드에 게시&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1002&quot; data-filename=&quot;balloon-3206530_1280.jpg&quot; width=&quot;547&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmtAPY/btq64v6LjOB/BmcN5NJVOH44BHKISHqmk0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmtAPY/btq64v6LjOB/BmcN5NJVOH44BHKISHqmk0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmtAPY/btq64v6LjOB/BmcN5NJVOH44BHKISHqmk0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmtAPY%2Fbtq64v6LjOB%2FBmcN5NJVOH44BHKISHqmk0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1002&quot; data-filename=&quot;balloon-3206530_1280.jpg&quot; width=&quot;547&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose 상태를 Compose에서 관리하지 않는 객체와 공유하려면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;recomposition 성공 시마다 호출되는&lt;span&gt;&amp;nbsp;&lt;/span&gt;SideEffect&lt;span&gt; composable을 사용할 것&amp;nbsp;&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;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623241147923&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun BackHandler(
    backDispatcher: OnBackPressedDispatcher,
    enabled: Boolean = true, // Whether back events should be intercepted or not
    onBack: () -&amp;gt; Unit
) {
    /* ... */
    val backCallback = remember { /* ... */ }

    // On every successful composition, update the callback with the `enabled` value
    // to tell `backCallback` whether back events should be intercepted or not
    SideEffect {
        backCallback.isEnabled = enabled
    }

    /* Rest of the code */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;예제설명&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-BackHandler &lt;/b&gt;함수와 같이 콜백을 사용 설정해야 하는지 전달하려면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;SideEffect를 사용하여 값을 업데이트합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;producestate&quot; data-text=&quot;produceState: 비 Compose 상태를 Compose 상태로 변환&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;produceState: 비 Compose 상태를 Compose 상태로 변환&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-produceState은 반환된 State로 값을 푸시할 수 있는 Composition으로 Scope가 지정된 코루틴을 실행&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상태를 Compose상태 변환하려면 Flow,LiveData, RxJava와 같은 구독 기반 State를 Composition으로 변환하려면 produceState를 사용&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;-produceState이 Composition을 시작시 프로듀서가 실행 -&amp;gt; Composition을 종료시 프로듀서가 취소&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(return 된 state은 합성됨, 동일한 값을 설정해도 recompostion이 실행되지 않음)&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;- produceState가 코루틴을 만드는 경우에 정지되지 않는 데이터 소스를 관찰하는 데 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 소스의 구독을 삭제하려면 awaitDispose 함수를 사용)&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;produceState는 다른 효과를 사용함&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;- remember { mutableStateOf(initialValue) }를 사용하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;result&lt;span&gt;&amp;nbsp;&lt;/span&gt;변수를 보유하며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; LaunchedEffect에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;producer&lt;span&gt;&amp;nbsp;&lt;/span&gt;블록을 실행 후&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; producer&lt;span&gt;&amp;nbsp;&lt;/span&gt;블록에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;value가 업데이트될 때마다&lt;span&gt;&amp;nbsp;&lt;/span&gt;result&lt;span&gt;&amp;nbsp;&lt;/span&gt;상태가 새 값으로 업데이트됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; (기존 API 위에 자신만의 효과를 쉽게 만들 수 있음)&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;/p&gt;
&lt;pre id=&quot;code_1623241155811&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun loadNetworkImage(
    url: String,
    imageRepository: ImageRepository
): State&amp;lt;Result&amp;lt;Image&amp;gt;&amp;gt; {

    // Creates a State&amp;lt;T&amp;gt; with Result.Loading as initial value
    // If either `url` or `imageRepository` changes, the running producer
    // will cancel and will be re-launched with the new keys.
    return produceState(initialValue = Result.Loading, url, imageRepository) {

        // In a coroutine, can make suspend calls
        val image = imageRepository.load(url)

        // Update State with either an Error or Success result.
        // This will trigger a recomposition where this State is read
        value = if (image == null) {
            Result.Error
        } else {
            Result.Success(image)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;예제 설명&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- produceState를 사용하여 네트워크에서 이미지를 로드하는 방법을 보여줌&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- loadNetworkImage 함수는 다른 Composable에서 사용할 수 있는 State를 return&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;주의: &lt;/b&gt;return&lt;span&gt; type이 있는 Composable은 일반 Kotlin 함수의 이름을 지정하는 방식에 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;따라 소문자로 시작하는 이름을 지정함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(기존 Composable 함수는 모두 대문자로 시작.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;derivedstateof&quot; data-text=&quot;derivedStateOf: 하나 이상의 상태 객체를 다른 상태로 변환&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;derivedStateOf: 하나 이상의 상태 객체를 다른 상태로 변환&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다른 상태 객체에서 특정 상태가 계산되거나 파생되는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;derivedStateOf를 사용&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;b&gt;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623241163055&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun TodoList(
    highPriorityKeywords: List&amp;lt;String&amp;gt; = listOf(&quot;Review&quot;, &quot;Unblock&quot;, &quot;Compose&quot;)
) {
    val todoTasks = remember { mutableStateListOf&amp;lt;String&amp;gt;() }

    // Calculate high priority tasks only when the todoTasks or
    // highPriorityKeywords change, not on every recomposition
    val highPriorityTasks by remember(todoTasks, highPriorityKeywords) {
        derivedStateOf {
            todoTasks.filter { it.containsWord(highPriorityKeywords) }
        }
    }

    Box(Modifier.fillMaxSize()) {
        LazyColumn {
            items(highPriorityTasks) { /* ... */ }
            items(todoTasks) { /* ... */ }
        }
        /* Rest of the UI where users can add elements to the list */
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&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;0) 위 예에서는 우선순위가 높은 사용자 정의 키워드가 있는 작업이 먼저 표시되는 기본 할 일 목록을 보여줌&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;span&gt;&amp;nbsp;&lt;/span&gt;derivedStateOf는&lt;span&gt;&amp;nbsp;&lt;/span&gt;todoTasks&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;highPriorityKeywords가 변경될 때마다&lt;span&gt;&amp;nbsp;&lt;/span&gt;highPriorityTasks&lt;span&gt;&amp;nbsp;&lt;/span&gt;계산이 실행&amp;nbsp; &amp;nbsp; &amp;nbsp;되고 그에 따라 UI가 업데이트되도록 보장&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) highPriorityTasks를 계산하기 위한 필터링은 비용이 많이 들 수 있으므로 매 recomposition 실행될 때가 아니라 목록이 변경될 때만 실행&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) derivedStateOf에 의해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;상태가 업데이트되어도 업데이트가 선언된 Composable이 recomposition이 되지 않습니다.&lt;/b&gt;&lt;/span&gt; Compose는 예의&lt;span&gt;&amp;nbsp;&lt;/span&gt;LazyColumn&lt;span&gt;&amp;nbsp;&lt;/span&gt;내에서 반환된 상태를 읽는 위치의 composable만 recomposition합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;snapshotFlow&quot; data-text=&quot;snapshotFlow: Compose의 상태를 Flow로 변환&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;snapshotFlow: Compose의 상태를 Flow로 변환&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- snapshotFlow를 사용하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;State&amp;lt;T&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체를 콜드 Flow로 변환&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&gt;snapshotFlow는 수집될 때 블록을 실행하고 읽은&lt;span&gt;&amp;nbsp;&lt;/span&gt;State&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;- &lt;/span&gt;snapshotFlow&lt;span&gt;&amp;nbsp;&lt;/span&gt;블록 내에서 읽은&lt;span&gt;&amp;nbsp;&lt;/span&gt;State&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체의 하나가 변경되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 값이 이전에 내보낸 값과&lt;span&gt;&amp;nbsp;&lt;/span&gt;같지&lt;span&gt;&amp;nbsp;&lt;/span&gt;않은 경우 Flow에서 새 값을 수집기에 내보냄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 동작은&lt;span&gt;&amp;nbsp;&lt;/span&gt;Flow.distinctUntilChanged의 동작과 비슷함).&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;/p&gt;
&lt;pre id=&quot;code_1623241170760&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -&amp;gt; index &amp;gt; 0 }
        .distinctUntilChanged()
        .filter { it == true }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&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;span&gt;1) 예제는 사용자가 목록에서 첫 번째 항목을 지나 분석까지 스크롤할 때 기록되는 부작용을 보여줌&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) listState.firstVisibleItemIndex는 Flow 연산자의 이점을 활용할 수 있는 Flow로 변환&lt;/p&gt;
&lt;h2 id=&quot;restarting-effects&quot; data-text=&quot;효과 다시 시작&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;4. effect 다시 시작&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;824&quot; data-filename=&quot;change-4056014_1280.jpg&quot; width=&quot;494&quot; height=&quot;318&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdMX3W/btq60NnhN1S/RtstnwsI7Rxc0JKheRiKLK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdMX3W/btq60NnhN1S/RtstnwsI7Rxc0JKheRiKLK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdMX3W/btq60NnhN1S/RtstnwsI7Rxc0JKheRiKLK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdMX3W%2Fbtq60NnhN1S%2FRtstnwsI7Rxc0JKheRiKLK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;824&quot; data-filename=&quot;change-4056014_1280.jpg&quot; width=&quot;494&quot; height=&quot;318&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Compose의 일부 effect&lt;b&gt;(LaunchedEffect,&lt;span&gt;&amp;nbsp;&lt;/span&gt;produceState,&lt;span&gt;&amp;nbsp;&lt;/span&gt;DisposableEffect)&lt;/b&gt;에서 실행 중인 effect를 취하는 데 사용되는 가변적인 수의 인수를 취하고 새 key로 새 effect를 시작&lt;/p&gt;
&lt;pre id=&quot;code_1623386023405&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;- 효과를 다시 시작하는 데 사용되는 매개변수가 올바른 매개변수가 아닌 경우 문제가 발생할 수 있음&lt;/span&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 대체적으로 효과 코드 블록에 사용되는 &lt;b&gt;변경할 수 있는 변수&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;와 &lt;/span&gt;&lt;b&gt;변경할 수 없는 변수&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;는 effect composable에 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp; 매개변수로&amp;nbsp; 추가해야 함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; (이 매개변수 외에 effect를 강제로 다시 시작하도록 더 많은 매개변수를 추가할 수 있음)&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;rememberUpdatedState에 래핑해야 함&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;remember에 래핑되어 변경되지 않으면 변수를 효과에 키로 전달할 필요가 없음&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;span&gt;&lt;span&gt; effect&lt;/span&gt;에 사용되는 변수는 effect composable의 매개변수로 추가하거나&lt;span&gt;&amp;nbsp;&lt;/span&gt;rememberUpdatedState를 사용해야 함&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;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623241178177&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun BackHandler(backDispatcher: OnBackPressedDispatcher, onBack: () -&amp;gt; Unit) {
    /* ... */
    val backCallback = remember { /* ... */ }

    DisposableEffect(backDispatcher) {
        backDispatcher.addCallback(backCallback)
        onDispose {
            backCallback.remove()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;예제 설명&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;0) &lt;/span&gt;DisposableEffect&lt;span&gt;&amp;nbsp;&lt;/span&gt;코드에서 블록의&lt;span&gt;&amp;nbsp;&lt;/span&gt;backDispatcher가 변경되면 효과가 다시 시작되어야 하므로 effect에서 이를 매개&amp;nbsp; &amp;nbsp; &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;1) backCallback은 컴포지션에서 값이 변경되지 않으므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;DisposableEffect&lt;span&gt;&amp;nbsp;&lt;/span&gt;키로 필요하지 않으며 키가 없는&lt;span&gt;&amp;nbsp;&lt;/span&gt;remember&amp;nbsp; &amp;nbsp; &amp;nbsp;에 래핑됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2) &lt;/span&gt;backDispatcher가 매개변수로 전달되지 않고 변경되면&lt;span&gt;&amp;nbsp;&lt;/span&gt;BackHandler는 recomposition되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&lt;/span&gt;DisposableEffect는 삭제되지 않고 다시 시작됨. 따라서 이 시점부터 잘못된&lt;span&gt;&amp;nbsp;&lt;/span&gt;backDispatcher가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;사용되기 때문에 문제가 발생&lt;/p&gt;
&lt;h4 data-text=&quot;키로 사용되는 상수&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 id=&quot;constants-as-keys&quot; data-text=&quot;키로 사용되는 상수&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;키로 사용되는 상수&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- true와 같은 상수를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;호출 사이트의 수명 주기를 추적&lt;/b&gt;하는 효과 키로 사용할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위 예제에서 본&lt;span&gt;&amp;nbsp;&lt;/span&gt;LaunchedEffect(true)&lt;span&gt;&amp;nbsp;&lt;/span&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;result-3236285_1280.jpg&quot; width=&quot;495&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pZbIn/btq64hOtXhV/ndKa3KuEyf4KRY3SdBHkfK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pZbIn/btq64hOtXhV/ndKa3KuEyf4KRY3SdBHkfK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pZbIn/btq64hOtXhV/ndKa3KuEyf4KRY3SdBHkfK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpZbIn%2Fbtq64hOtXhV%2FndKa3KuEyf4KRY3SdBHkfK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;result-3236285_1280.jpg&quot; width=&quot;495&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상 Compose Side Effect에 관한 정리였습니다. 역시나 안드로이드 개발문서는 상당히 친절하면서 꽤 시크하다는 느낌을 받네요 요즘 곳곳에서 Compose 사용 후기가 들려오고 있습니다. 벌써 Compose에 심취한 사람들은 xml은 이제 쓰기 불편하다고도 하지요&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의 미래가 xml을 모두 대체 할것인지, xml과 Compose와 같이 쓰는형태로 자리매김 할것인지..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기대해볼만 합니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>compose</category>
      <category>compose부수효과</category>
      <category>SideEffect</category>
      <category>안드로이드</category>
      <category>안드로이드Compose</category>
      <category>안드로이드개발</category>
      <category>안드로이드컴포즈</category>
      <category>컴포즈</category>
      <category>컴포즈sideeffect</category>
      <category>컴포즈부수효과</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/39</guid>
      <comments>https://gift123.tistory.com/39#entry39comment</comments>
      <pubDate>Fri, 11 Jun 2021 14:12:52 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (10) Compose Composable Lifecycle</title>
      <link>https://gift123.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner 입니다. 오늘은 Compose의 컴포저블 라이프사이클 공부를 정리해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;읽기전에 알아야할 개념-&lt;/b&gt;&lt;/h1&gt;
&lt;h1&gt;&lt;b&gt;Composition, ReComposition&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://gift123.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/34&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1622982086307&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;안드로이드 개발 (9) Compose 상태 관리&quot; data-og-description=&quot;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/34&quot; data-og-url=&quot;https://gift123.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/U9Jhb/hyKuw6v2pz/JSMeOcmSnlRJ3Kpt1Am18k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/q7BCy/hyKum3TNK1/KBWcbo2q6kGz50wehz7d70/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/EWOVJ/hyKuxdhaPu/5rENFv70jv1o30gclJHakk/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/34&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/U9Jhb/hyKuw6v2pz/JSMeOcmSnlRJ3Kpt1Am18k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/q7BCy/hyKum3TNK1/KBWcbo2q6kGz50wehz7d70/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/EWOVJ/hyKuxdhaPu/5rENFv70jv1o30gclJHakk/img.png?width=958&amp;amp;height=1078&amp;amp;face=0_0_958_1078');&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;안드로이드 개발 (9) Compose 상태 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;jetpack compose 에 한창 포스팅 중입니다. https://gift123.tistory.com/33 안드로이드 개발 (8) Compose 이해 정리 이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.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;위 링크에서 언급한대로 Composition은 UI를 기술하는 컴포저블의 트리 구조입니다.&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;Compose 최초 Composition 시의 순서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)&amp;nbsp;처음으로 컴포저블을 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) UI를 기술하기 위해 호출하는 컴포저블을 추적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 앱 상태가 변경되면 Jetpack Compose는&lt;span&gt;&amp;nbsp;&lt;/span&gt;recomposition을 예약&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) recompostion은 Compose가 상태 변경사항에 따라 Composable을 다시 실행해서 컴포지션을 업데이트&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: #ee2323;&quot;&gt;&lt;b&gt;*composition은 초기 composition을 통해서만 생성, recomposition을 통해서만 업데이트 가능*&lt;/b&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;h1&gt;&lt;b&gt;1. Composable Lifecycle&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Composable의 라이프 사이클은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 컴포지션 시작, 0회 이상 recomposition 및 컴포지션 종료 이벤트로 정의&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;500&quot; data-origin-width=&quot;1191&quot; data-origin-height=&quot;512&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yz3er/btq6GotHZnF/k939XTKL1xXGdiwW7qAk6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yz3er/btq6GotHZnF/k939XTKL1xXGdiwW7qAk6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yz3er/btq6GotHZnF/k939XTKL1xXGdiwW7qAk6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYz3er%2Fbtq6GotHZnF%2Fk939XTKL1xXGdiwW7qAk6K%2Fimg.png&quot; width=&quot;500&quot; data-origin-width=&quot;1191&quot; data-origin-height=&quot;512&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순서:&lt;/b&gt; Composable이 Composition을 시작 -&amp;gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 0회 이상 recomposition -&amp;gt;&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; &amp;nbsp;comosition을 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composable 의 Lifecycle는 View, Activity 및 Fragment의 Lifecycle보다 간단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composable 은 Lifecycle 이 더 복잡한 외부 리소스를 관리하거나 이와 상호작용해야 하는 경우 효과를 사용 해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composable이 여러 번 호출되면 Composition에 여러 인스턴스가 배치됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition 각 호출에는 자체 Lifecycle가 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622983474131&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MyComposable() {
    Column {
        Text(&quot;Hello&quot;)
        Text(&quot;World&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;500&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;749&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ms3mu/btq6AQFcWvI/jKmMD21ylMalU4aSkx1Xjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ms3mu/btq6AQFcWvI/jKmMD21ylMalU4aSkx1Xjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ms3mu/btq6AQFcWvI/jKmMD21ylMalU4aSkx1Xjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fms3mu%2Fbtq6AQFcWvI%2FjKmMD21ylMalU4aSkx1Xjk%2Fimg.png&quot; width=&quot;500&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;749&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recomposition은 일반적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;State&amp;lt;T&amp;gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;객체가 변경되면 트리거됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose는 이러한 객체를 추적합니다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1)Composition에서 특정&lt;span&gt;&amp;nbsp;&lt;/span&gt;State&amp;lt;T&amp;gt;를 읽는 모든 Composable&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2)호출하는 Composable&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;h2 id=&quot;composition-anatomy&quot; data-text=&quot;컴포지션 내 컴포저블의 분석&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;2. 컴포지션 내 컴포저블의 분석&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;897&quot; data-filename=&quot;tunnel-3385624_1280.jpg&quot; width=&quot;501&quot; height=&quot;351&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ucqk3/btq6AQeglAp/Bke3uQ7kwczJFoDtIhgfc1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ucqk3/btq6AQeglAp/Bke3uQ7kwczJFoDtIhgfc1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ucqk3/btq6AQeglAp/Bke3uQ7kwczJFoDtIhgfc1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fucqk3%2Fbtq6AQeglAp%2FBke3uQ7kwczJFoDtIhgfc1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;897&quot; data-filename=&quot;tunnel-3385624_1280.jpg&quot; width=&quot;501&quot; height=&quot;351&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;*호출 사이트&lt;/b&gt;는 컴포저블이 호출되는 소스 코드 위치*&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Composition 내 composable 의 인스턴스는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;호출 사이트&lt;/b&gt;로 식별&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;-Compose 컴파일러는 각 호출 사이트를 고유한 것으로 간주&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;-&lt;/span&gt;여러 호출 사이트에서 컴포저블을 호출하면 컴포지션에 컴포저블의 여러 인스턴스가 생성&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;recompostion 시 composable이 이전 recomposition 시 호출한 것과 다른 composable을 호출하는 경우&lt;/b&gt;&lt;/h4&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) Compose는&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;&lt;b&gt;호출되거나 호출되지 않은 composable을 식별&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 두 Compostion 모두에서 호출된 composable의 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;입력이 변경되지 않은 경우 recomposition 하지않음&lt;/b&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;부수 효과를 composable과 연결하기 위해서는 ID를 유지하는 것이 중요함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(recomposition마다&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;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622984306761&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;LoginScreen은&lt;span&gt;&amp;nbsp;&lt;/span&gt;LoginError Composable을 조건부로 호출하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상&lt;span&gt;&amp;nbsp;&lt;/span&gt;LoginInput Composable을 호출합니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;277&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buj0W9/btq6HP5Gdxx/Fhgme3mZkR8Mep9LCowWz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buj0W9/btq6HP5Gdxx/Fhgme3mZkR8Mep9LCowWz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buj0W9/btq6HP5Gdxx/Fhgme3mZkR8Mep9LCowWz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbuj0W9%2Fbtq6HP5Gdxx%2FFhgme3mZkR8Mep9LCowWz1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;277&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태가 변경되고 recomposition이 발생할 때 composition 내&lt;span&gt;&amp;nbsp;&lt;/span&gt;LoginScreen의 표현.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(색상이 동일하면 recomposition되지 않았음을 의미합니다.)&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 style=&quot;color: #ee2323;&quot;&gt;LoginInput이 첫 번째로 호출되었다가 두 번째로 호출되었지만&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;LoginInput&amp;nbsp;인스턴스는 여러 recomposition에 걸쳐 유지됩니다. &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;&lt;span style=&quot;color: #ee2323;&quot;&gt;또한&amp;nbsp;LoginInput에는 recomposition 간에 변경된 매개변수가 없으므로&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; Compose가&amp;nbsp;LoginInput&amp;nbsp;호출을 건너뜁니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-text=&quot;스마트 리컴포지션에 도움이 되는 정보 추가&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 id=&quot;add-info-smart-recomposition&quot; data-text=&quot;스마트 리컴포지션에 도움이 되는 정보 추가&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;2-1.스마트 recomposition에 도움이 되는 정보 추가&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;768&quot; data-filename=&quot;light-bulbs-1875384_1280.jpg&quot; width=&quot;633&quot; height=&quot;380&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DZOss/btq6JnBbxPg/Y6ZuSwmKFzPgq3awwx5BZ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DZOss/btq6JnBbxPg/Y6ZuSwmKFzPgq3awwx5BZ0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DZOss/btq6JnBbxPg/Y6ZuSwmKFzPgq3awwx5BZ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDZOss%2Fbtq6JnBbxPg%2FY6ZuSwmKFzPgq3awwx5BZ0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;768&quot; data-filename=&quot;light-bulbs-1875384_1280.jpg&quot; width=&quot;633&quot; height=&quot;380&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-동일한 호출 사이트에서 composable을 여러 번 호출하는 경우 Compose가 인스턴스를 구분하기 위해 호출 사이트 외에 실행 순서가 사용됨&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;letter-spacing: 0px;&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;b&gt;예제.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622991420656&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MoviesScreen(movies: List&amp;lt;Movie&amp;gt;) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Compose는 호출 사이트 외에 실행 순서를 사용하여 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Composition 에서 인스턴스를 구분합니다.&lt;/b&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;&amp;nbsp;&lt;/span&gt;movie가&lt;b&gt; 리스트의&lt;span&gt;&amp;nbsp;&lt;/span&gt;하단에 추가된 경우&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex: list.add(movie)&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: #ee2323;&quot;&gt;&lt;b&gt;Compose는 인스턴스의 리스트&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;내&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 위치가 변경되지 않았고&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;인스턴스의&amp;nbsp;movie&amp;nbsp;입력이 동일하므로&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;컴포지션에 이미 있는 인스턴스를 재사용할 수 있습니다.&lt;/b&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;letter-spacing: 0px;&quot;&gt;(ex: {1,2,3}일때 list.add(4) 를 하면&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;{1,2,3,4} 라서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;이전과 비교해서 4를 제외한 1,2,3 값과 index 위치가 그대로 있는거라면 )&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;420&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E12FC/btq6BFpH1QE/6zVlIWzry6mYamJK8TYdJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E12FC/btq6BFpH1QE/6zVlIWzry6mYamJK8TYdJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E12FC/btq6BFpH1QE/6zVlIWzry6mYamJK8TYdJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE12FC%2Fbtq6BFpH1QE%2F6zVlIWzry6mYamJK8TYdJ1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;420&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림처럼 composition 의&amp;nbsp;MovieOverview&amp;nbsp;Composable은 재사용이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(색상이 동일하면 recomposition되지 않았음을 의미합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&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;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: #ee2323;&quot;&gt;&lt;b&gt;리스트의&amp;nbsp;상단 아이템 추가, 리스트의&amp;nbsp;가운데 아이템 추가, 항목을 삭제, 재정렬&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;등등&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;movies&lt;span&gt;&amp;nbsp;&lt;/span&gt;목록이 변경되면 목록에서 입력 매개변수의 위치가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 변경된 모든&lt;span&gt;&amp;nbsp;&lt;/span&gt;MovieOverview Composable&lt;span&gt;&amp;nbsp;&lt;/span&gt;호출에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;recomposition&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;span&gt;&amp;nbsp;&lt;/span&gt;MovieOverview가 부수 효과를 사용하여 네트워크에서 이미지를&amp;nbsp;가져오는 경우 매우 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;효과가 적용되는 동안 recomposition 이 발생하면 효과가 취소되고 다시 시작됩니다.&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;예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622992550638&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;414&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5vCca/btq6Bba5vMu/xlCV7Z1tdUmLJlBlsKdrr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5vCca/btq6Bba5vMu/xlCV7Z1tdUmLJlBlsKdrr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5vCca/btq6Bba5vMu/xlCV7Z1tdUmLJlBlsKdrr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5vCca%2Fbtq6Bba5vMu%2FxlCV7Z1tdUmLJlBlsKdrr1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;414&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림은 ex:list.add(0,movie) 를 했을때의 예시입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(색상이 동일하지 않으면 recomposition 되었음을 의미합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 재사용 없이 다시 recompostion이 됨&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;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;그렇다면 위와 상황에서 recomposition 없이 재사용을 사용할려면&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;이상적으로&amp;nbsp;MovieOverview&amp;nbsp;인스턴스의 ID는 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;인스턴스에 전달된&amp;nbsp;movie의 ID에 연결되는것이 좋습니다.&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;Compose에서 런타임에 트리의 특정 부분을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식별하는 데 사용할 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Key&lt;/b&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;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;key&amp;nbsp;&lt;/span&gt;값은&lt;span&gt;&amp;nbsp;&lt;/span&gt;전체적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;고유하지 않아도 되며 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;호출 사이트에서의 Composable 호출 간에만 고유하면 됩니다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(앱의 다른 위치에 있는 다른 Composable과 이 key를 공유하는 것은 괜찮습니다.)&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;/p&gt;
&lt;pre id=&quot;code_1622992836297&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MoviesScreen(movies: List&amp;lt;Movie&amp;gt;) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}&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;movies의 요소가 변경되더라도 Compose는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key별로&lt;span&gt;&amp;nbsp;&lt;/span&gt;MovieOverview()&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;409&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1ObXi/btq6HRbljM0/sepYWCR8thWhBGSOTd4tu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1ObXi/btq6HRbljM0/sepYWCR8thWhBGSOTd4tu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1ObXi/btq6HRbljM0/sepYWCR8thWhBGSOTd4tu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1ObXi%2Fbtq6HRbljM0%2FsepYWCR8thWhBGSOTd4tu0%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;409&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key를 사용했기 때문에 고유 식별자가 생겨서 recomposition 없이 재사용이 가능해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(색상이 동일하면 recomposition되지 않았음을 의미합니다.)&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;일부 Composable에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;key&lt;span&gt;&amp;nbsp;&lt;/span&gt;Composable 지원 기능이 내장되어 있습니다.&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;/p&gt;
&lt;pre id=&quot;code_1622994691592&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MoviesScreen(movies: List&amp;lt;Movie&amp;gt;) {
    LazyColumn {
        items(movies, key = { movie -&amp;gt; movie.id }) { movie -&amp;gt;
            MovieOverview(movie)
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를&amp;nbsp;들어&amp;nbsp;LazyColumn의&amp;nbsp;경우&amp;nbsp;items&amp;nbsp;DSL에&amp;nbsp;맞춤&amp;nbsp;key를&amp;nbsp;지정할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;skipping&quot; data-text=&quot;입력이 변경되지 않은 경우 건너뛰기&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;2-2.입력이 변경되지 않은 경우 건너뛰기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;board-3317498_1280.jpg&quot; width=&quot;602&quot; height=&quot;401&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b44AWs/btq6DR391li/1Za3cYSF9bQ41cYy1w1LgK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b44AWs/btq6DR391li/1Za3cYSF9bQ41cYy1w1LgK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b44AWs/btq6DR391li/1Za3cYSF9bQ41cYy1w1LgK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb44AWs%2Fbtq6DR391li%2F1Za3cYSF9bQ41cYy1w1LgK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;board-3317498_1280.jpg&quot; width=&quot;602&quot; height=&quot;401&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Composition에 이미 Composable이 있는 경우 모든 input이 안정적이고 변경되지 않았으면 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;recompostion을 건너뛸 수 있습니다.&lt;/b&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;Stable 타입은 다음을 준수해야 합니다.&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;span&gt;&amp;nbsp;&lt;/span&gt;equals&lt;span&gt;&amp;nbsp;&lt;/span&gt;결과가 동일한 두 인스턴스의 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;항상&lt;span&gt;&amp;nbsp;&lt;/span&gt;동일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 타입의 Public 속성이 변경되면 Composition에 알림이 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 모든 Public 속성 유형도 안정적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예외로 안정적이지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;변경할 수 있는 한 가지 중요한 타입은 Compose의&lt;span&gt;&amp;nbsp;&lt;/span&gt;MutableState&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;value가&amp;nbsp;&lt;/span&gt;MutableState로 유지되는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;State의&lt;span&gt;&amp;nbsp;&lt;/span&gt;.value&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성이 변경되면 Compose에 알림이 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&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;Composable에 매개변수로 전달된 모든 타입이이 안정적인 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 트리 내 Composition 위치를 기반으로 매개변수 값이 동일한지 비교합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이전 호출 이후 모든 값이 변경되지 않은 경우 recompostion 을 건너뜀)&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;@Stable&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose는 증명할 수 있는 경우에만 유형을 안정적인 것으로 취급&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(ex: 인터페이스는 일반적으로 안정적이지 않은 것 취급되며 구현을&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;Compose가 유형이 안정적이라고 추론할 수 없을때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안정적인 것으로 간주하도록 하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/Stable&quot;&gt;@Stable&lt;/a&gt;&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;b&gt;예제.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622994240944&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState&amp;lt;T : Result&amp;lt;T&amp;gt;&amp;gt; {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}&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;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;하지만 @Stable&amp;nbsp;주석을 추가하면 Compose가 이 유형이 안정적임을 알게 되고 스마트 recomposition을 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;선호하게 됨&lt;/b&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose가 모든 구현을 안정적인 것으로 간주함&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: #ee2323;&quot;&gt;&lt;b&gt;@Stable&amp;nbsp;주석을 사용하여 안정적이라고 명시되지 않더라도&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Compose 컴파일러가 안정적인 것으로 간주하며 이 계약에 포함되는 중요한 일반 타입이 있습니다.&lt;/b&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;1) 모든 원시 값 타입:&lt;span&gt;&amp;nbsp;&lt;/span&gt;Boolean,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Int,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Long,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Float,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Char&lt;span&gt;&amp;nbsp;&lt;/span&gt;등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 문자열&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 모든 함수 타입(람다)&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;- 이 타입은 모두 변경할 수 없으므로 stable 계약을 준수함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 변경할 수 없는 유형은 절대 변경되지 않으므로 Composition에 변경사항을 알리지 않아도 됨&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;이상 Composable의 Lifecycle 이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 툴은 항상 더 좋은 편리함을 위해 발전합니다. Compose 는 더 좋은 성능을 낼 수 있는 UI 제작 툴이라 생각드네요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 더 Compose를 깊게 알아가야겠습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android개발</category>
      <category>composable</category>
      <category>composablelifecycle</category>
      <category>compose</category>
      <category>개발</category>
      <category>라이프사이클</category>
      <category>안드로이드</category>
      <category>안드로이드개발</category>
      <category>안드로이드컴포즈</category>
      <category>컴포즈</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/38</guid>
      <comments>https://gift123.tistory.com/38#entry38comment</comments>
      <pubDate>Mon, 7 Jun 2021 01:12:13 +0900</pubDate>
    </item>
    <item>
      <title>앱 제작 (4) 스타크래프트 실력 측정기</title>
      <link>https://gift123.tistory.com/37</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 Loner입니다. 바빠서 안드로이드 개발 정리보다 옛날에 만들었던 앱을 하나 더 open 해볼까합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(안드로이드 개발란에 Compose 이야기를 연속적으로 적어내고 싶어서 다른 안드로이드 개발 얘기는 일단 지양중입니다.)&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://play.google.com/store/apps/details?id=kr.loner.starcarftpoll&quot;&gt;https://play.google.com/store/apps/details?id=kr.loner.starcarftpoll&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1622907862997&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;스타크래프트 고인물 측정기 - Google Play 앱&quot; data-og-description=&quot;당신이 20 년 이상의 역사를 가진 스타 크래프트 유저라면 한번 쯤 해보시길 바랍니다.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=kr.loner.starcarftpoll&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=kr.loner.starcarftpoll&amp;amp;hl=ko&amp;amp;gl=JP&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Zsnmm/hyKtgp9zNK/uxI0KY68WEfZnpKLx65Ark/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/mFZDc/hyKs6A4XL6/Wa6BaGJkLlUYlnhaNZLJY1/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=kr.loner.starcarftpoll&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=kr.loner.starcarftpoll&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Zsnmm/hyKtgp9zNK/uxI0KY68WEfZnpKLx65Ark/img.png?width=512&amp;amp;height=250&amp;amp;face=0_0_512_250,https://scrap.kakaocdn.net/dn/mFZDc/hyKs6A4XL6/Wa6BaGJkLlUYlnhaNZLJY1/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&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;스타크래프트 고인물 측정기 - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;당신이 20 년 이상의 역사를 가진 스타 크래프트 유저라면 한번 쯤 해보시길 바랍니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 소개&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;687&quot; data-filename=&quot;once-upon-a-time-719174_1280.jpg&quot; width=&quot;676&quot; height=&quot;363&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edu1DN/btq6BeyEoPp/zdraMNlxnnmKWAsiI3MDv0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edu1DN/btq6BeyEoPp/zdraMNlxnnmKWAsiI3MDv0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edu1DN/btq6BeyEoPp/zdraMNlxnnmKWAsiI3MDv0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fedu1DN%2Fbtq6BeyEoPp%2FzdraMNlxnnmKWAsiI3MDv0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;687&quot; data-filename=&quot;once-upon-a-time-719174_1280.jpg&quot; width=&quot;676&quot; height=&quot;363&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션 효과좀 테스트 해볼려고 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;장난 삼아 만들었던 추억의 앱&lt;/b&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;개인 취미로 개발문서 읽기 , 사이드협업 하기, 알고리즘 풀기가 있는데 이 3가지가 질리면 오랜만에&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;한번 내가 좋아하는 주제로 앱을 만들어보면 어떨까 생각해서 장난삼아 만들어봤던 앱입니다.&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 style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 다른 메인 프로젝트 개발를 한창 할때라서, 토이앱을 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;신경쓰지 않기 위해 임시로 마무리 했었습니다.&lt;/b&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;/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;2. 앱의 기능 &lt;/b&gt;&lt;/h2&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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&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;/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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 스타크래프트 게임 실력 측정 문제 풀기&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;정답시 점수 +1 오답시 점수 -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;특이점으로 뒷 배경 그림이 풀었던 문제 수가 어느정도 지나면 계속 변합니다.&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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&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;결과가 측정됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과가 바로 나오지 않고 A,B,C,D,E 가 여러가지 겹치다가 최종 결과가 나옵니다.&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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&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;페이스북과 카카오톡에 공유가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오톡은 마음먹으면 앱 설치가 안되었을 시 웹으로 들어가게 할 수 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가볍게 만든거라 굳이 분기처리는 안했었던걸로 기억합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 앱 제작 과정&lt;/b&gt;&lt;/h2&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 alignLeft&quot; data-origin-width=&quot;1465&quot; data-origin-height=&quot;920&quot; data-filename=&quot;화면 캡처 2021-06-06 010418.png&quot; width=&quot;586&quot; height=&quot;368&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzwKzt/btq6zzRpsOH/diIeismREGykXkNPGrEfX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzwKzt/btq6zzRpsOH/diIeismREGykXkNPGrEfX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzwKzt/btq6zzRpsOH/diIeismREGykXkNPGrEfX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzwKzt%2Fbtq6zzRpsOH%2FdiIeismREGykXkNPGrEfX0%2Fimg.png&quot; data-origin-width=&quot;1465&quot; data-origin-height=&quot;920&quot; data-filename=&quot;화면 캡처 2021-06-06 010418.png&quot; width=&quot;586&quot; height=&quot;368&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-navigtion&lt;/b&gt;&lt;b&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;386&quot; data-filename=&quot;화면 캡처 2021-06-06 010813.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bieAEj/btq6Bek4owx/3rkBEiPVbOvTRohP2muTQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bieAEj/btq6Bek4owx/3rkBEiPVbOvTRohP2muTQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bieAEj/btq6Bek4owx/3rkBEiPVbOvTRohP2muTQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbieAEj%2Fbtq6Bek4owx%2F3rkBEiPVbOvTRohP2muTQ0%2Fimg.png&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;386&quot; data-filename=&quot;화면 캡처 2021-06-06 010813.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-SingleActivity&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Navigation 를 사용하는 이상 싱글 액티비티 구조로 가는것이 가장 좋았습니다.&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 alignLeft&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;732&quot; data-filename=&quot;화면 캡처 2021-06-06 011107.png&quot; width=&quot;734&quot; height=&quot;472&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2DvAL/btq6FtBsy8F/ojf8KQpPY8rUiBXwYUcTrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2DvAL/btq6FtBsy8F/ojf8KQpPY8rUiBXwYUcTrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2DvAL/btq6FtBsy8F/ojf8KQpPY8rUiBXwYUcTrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2DvAL%2Fbtq6FtBsy8F%2Fojf8KQpPY8rUiBXwYUcTrk%2Fimg.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;732&quot; data-filename=&quot;화면 캡처 2021-06-06 011107.png&quot; width=&quot;734&quot; height=&quot;472&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 화면 이동 담당 클래스 제작&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 GoToAction이라는 네비게이션간의 이동을 책임지는 클래스를 만들었습니다.&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;875&quot; data-filename=&quot;화면 캡처 2021-06-06 011127.png&quot; width=&quot;729&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HKiqp/btq6DDEq1uN/UBfZwdzZqOx6KA6G3ztlmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HKiqp/btq6DDEq1uN/UBfZwdzZqOx6KA6G3ztlmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HKiqp/btq6DDEq1uN/UBfZwdzZqOx6KA6G3ztlmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHKiqp%2Fbtq6DDEq1uN%2FUBfZwdzZqOx6KA6G3ztlmk%2Fimg.png&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;875&quot; data-filename=&quot;화면 캡처 2021-06-06 011127.png&quot; width=&quot;729&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;애니메이션 로직이나 게임 로직을 담당할 클래스를 미리 하나 만들어뒀습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 앱 규모도 작고 복잡하게 연결되어있는 부분이 없으니 object를 하나 생성해 버렸습니다.&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;s&gt;파라미터로 View 자체를 넘기진 않는 편인데 토이앱에선 넘겨버렸습니다.&lt;br /&gt;&lt;br /&gt;&lt;/s&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 alignLeft&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;345&quot; data-filename=&quot;화면 캡처 2021-06-06 011204.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BkPVj/btq6zzjyevn/C0kfP0dqs27ZtRboatWbI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BkPVj/btq6zzjyevn/C0kfP0dqs27ZtRboatWbI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BkPVj/btq6zzjyevn/C0kfP0dqs27ZtRboatWbI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBkPVj%2Fbtq6zzjyevn%2FC0kfP0dqs27ZtRboatWbI0%2Fimg.png&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;345&quot; data-filename=&quot;화면 캡처 2021-06-06 011204.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;623&quot; data-filename=&quot;화면 캡처 2021-06-06 011253.png&quot; width=&quot;567&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JSthj/btq6DSIgyIJ/g1KZRI8SowWcJ1I31b7FD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JSthj/btq6DSIgyIJ/g1KZRI8SowWcJ1I31b7FD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JSthj/btq6DSIgyIJ/g1KZRI8SowWcJ1I31b7FD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJSthj%2Fbtq6DSIgyIJ%2Fg1KZRI8SowWcJ1I31b7FD1%2Fimg.png&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;623&quot; data-filename=&quot;화면 캡처 2021-06-06 011253.png&quot; width=&quot;567&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션의 경우 xml에 정의하는 애니메이션과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 정의하는 애니메이션을 둘다 사용했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;208&quot; data-filename=&quot;화면 캡처 2021-06-06 011332.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFnbvp/btq6AdtFAxE/dhcLYxRpozratOIbiYE4i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFnbvp/btq6AdtFAxE/dhcLYxRpozratOIbiYE4i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFnbvp/btq6AdtFAxE/dhcLYxRpozratOIbiYE4i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFnbvp%2Fbtq6AdtFAxE%2FdhcLYxRpozratOIbiYE4i1%2Fimg.png&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;208&quot; data-filename=&quot;화면 캡처 2021-06-06 011332.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;282&quot; data-filename=&quot;화면 캡처 2021-06-06 011347.png&quot; width=&quot;725&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qKU4k/btq6ARwZEuQ/LVEAkRjESWAkFjBEA1gaik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qKU4k/btq6ARwZEuQ/LVEAkRjESWAkFjBEA1gaik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qKU4k/btq6ARwZEuQ/LVEAkRjESWAkFjBEA1gaik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqKU4k%2Fbtq6ARwZEuQ%2FLVEAkRjESWAkFjBEA1gaik%2Fimg.png&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;282&quot; data-filename=&quot;화면 캡처 2021-06-06 011347.png&quot; width=&quot;725&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;258&quot; data-filename=&quot;화면 캡처 2021-06-06 011536.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wzMM4/btq6AdtFAyM/OCxHHKi57fGkljAKVqiALK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wzMM4/btq6AdtFAyM/OCxHHKi57fGkljAKVqiALK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wzMM4/btq6AdtFAyM/OCxHHKi57fGkljAKVqiALK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwzMM4%2Fbtq6AdtFAyM%2FOCxHHKi57fGkljAKVqiALK%2Fimg.png&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;258&quot; data-filename=&quot;화면 캡처 2021-06-06 011536.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;이 앱은 네트워크에서 db를 가져오는게 아니라서 마음 편하게 미리 고정된 정답과 답을&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 기본 resource로 지정해놓고 사용하면 됬었습니다.&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;안드로이드 스튜디오 string.xml 에 문제와 정답들을 string Array형식으로 다 담아놓고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fragment에서 해당 리소스들을 가져와 사용했습니다.&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;4.정리&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;377&quot; data-origin-height=&quot;696&quot; data-filename=&quot;화면 캡처 2021-06-06 012856.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ug10r/btq6CBUp7pF/E5EnR1UzDvQJE3WaEifqx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ug10r/btq6CBUp7pF/E5EnR1UzDvQJE3WaEifqx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ug10r/btq6CBUp7pF/E5EnR1UzDvQJE3WaEifqx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fug10r%2Fbtq6CBUp7pF%2FE5EnR1UzDvQJE3WaEifqx0%2Fimg.png&quot; data-origin-width=&quot;377&quot; data-origin-height=&quot;696&quot; data-filename=&quot;화면 캡처 2021-06-06 012856.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네비게이션을 사용하니까 화면간 이동을 설정하기가 편했음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네비게이션xml 을 통해서 화면이 어디로 이동하는지 한눈에 볼 수 있어서 좋았음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 문제 로직 만들기만 하면 나머지는 손쉽게 풀림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 애니메이션 정의를 class에서 할수도 있고 xml에서도 할 수 있는데 xml으로 정의하는 편이&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레그먼트가 깔끔해지고 보기 좋았음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>앱 제작 App</category>
      <category>Android</category>
      <category>app</category>
      <category>Starcraft</category>
      <category>스타1</category>
      <category>스타크래프트</category>
      <category>스타크래프트리마스터</category>
      <category>스타크래프트앱</category>
      <category>안드로이드개발</category>
      <category>안드로이드앱개발</category>
      <category>앱제작</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/37</guid>
      <comments>https://gift123.tistory.com/37#entry37comment</comments>
      <pubDate>Sun, 6 Jun 2021 01:32:25 +0900</pubDate>
    </item>
    <item>
      <title>kotlin (6) - lateinit 과 by lazy 용도 간단 정리</title>
      <link>https://gift123.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;내일 토요일을 맞기 전에 java, kotlin , oop, solid , mvvm,mvc,mvp , AAC, android 등등을 한번에 복습을 해봤습니다. 과거에 공부했던 것들을 다시 보니 안보였던 부분들이 다시 보이기도하고 감회가 참 새로운거 같습니다. 이래서 영화를 여러번 돌려보나봅니다.&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;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. 지연 초기화 (lazy initialization)&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;network-3966619_1280.jpg&quot; width=&quot;465&quot; height=&quot;311&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RTgU0/btq6CBGfieG/79rM0zYp2lcGtQbbUnXKGk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RTgU0/btq6CBGfieG/79rM0zYp2lcGtQbbUnXKGk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RTgU0/btq6CBGfieG/79rM0zYp2lcGtQbbUnXKGk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRTgU0%2Fbtq6CBGfieG%2F79rM0zYp2lcGtQbbUnXKGk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;network-3966619_1280.jpg&quot; width=&quot;465&quot; height=&quot;311&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;kotlin에서는 2가지 지연 초기화를 지원합니다. lateinit 과 by lazy입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;066b&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. lateinit&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;network-4045624_1280.jpg&quot; width=&quot;453&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmadBs/btq6zyRWtEK/gWXqQ8oqhZuvndUjWAt0Zk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmadBs/btq6zyRWtEK/gWXqQ8oqhZuvndUjWAt0Zk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmadBs/btq6zyRWtEK/gWXqQ8oqhZuvndUjWAt0Zk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmadBs%2Fbtq6zyRWtEK%2FgWXqQ8oqhZuvndUjWAt0Zk%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;network-4045624_1280.jpg&quot; width=&quot;453&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- var 프로퍼티에서 사용가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- null을 허용하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- get() set() 사용이 불가능함&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;코틀린 같은 경우 null 허용 표시를 골라서 해야되기 때문에 변수가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null 허용 객체라면 항상 변수!! 혹은 변수? 이런식으로 사용해야합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1622818930345&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MyTest {
    var subject: TestSubject?

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject!!.method()  // dereference directly
    }
}
&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;항상 변수!! or 변수? 를 표시하는것이 번거로울 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴때 lateinit을 사용하는게 차라리 편합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1622818900802&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // dereference directly
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 null를 허용하지 않으면서 null 허용표시 없이 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;또한 lateinit var 객체는 초기화되기 전에&lt;span&gt;&amp;nbsp;프로퍼티를 사용&lt;/span&gt;하면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;프로퍼티가&lt;/span&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;isInitialized&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;isInitialized&lt;/b&gt;를 사용해서 프로퍼티가 초기화 되었는지 확인 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622819383915&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (foo::bar.isInitialized) {
    println(foo.bar)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. by lazy&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;341&quot; data-filename=&quot;mockup-4824767_1280.jpg&quot; width=&quot;578&quot; height=&quot;154&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uJen5/btq6zU8kMk1/eGhG7Z4DGQNzfkMjr0rUO1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uJen5/btq6zU8kMk1/eGhG7Z4DGQNzfkMjr0rUO1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uJen5/btq6zU8kMk1/eGhG7Z4DGQNzfkMjr0rUO1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuJen5%2Fbtq6zU8kMk1%2FeGhG7Z4DGQNzfkMjr0rUO1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;341&quot; data-filename=&quot;mockup-4824767_1280.jpg&quot; width=&quot;578&quot; height=&quot;154&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- val 에서 사용 가능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- get() set() 을 지원하지 않음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 널 허용&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 클래스 생성자에서 사용 불가&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622819700941&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MyTest {
     val subject by lazy{TestSubject()}

    @Test fun test() {
        subject.method() 
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;by lazy()는 늦게 초기화할 내용을 미리 적어놓고 해당 by lazy를 선언한 프로퍼티를 사용시 초기화가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 subject.method()가 첫 초기화를 해주는 부분 이면서 method()를 사용하는 부분 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lateinit 과 달리 null을 허용하기 때문에 까먹고 사용하지 않아도 null예외가 발생하지 않습니다.&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. by lazy를 사용하는 경우 (vs lateinit)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;argument-4057664_1280.png&quot; width=&quot;391&quot; height=&quot;532&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sQLTV/btq6ARwhyZh/Ohb77IkbtCE9m8QTsIoFBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sQLTV/btq6ARwhyZh/Ohb77IkbtCE9m8QTsIoFBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sQLTV/btq6ARwhyZh/Ohb77IkbtCE9m8QTsIoFBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsQLTV%2Fbtq6ARwhyZh%2FOhb77IkbtCE9m8QTsIoFBK%2Fimg.png&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;argument-4057664_1280.png&quot; width=&quot;391&quot; height=&quot;532&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;1) 반드시 초기화를 안해도 되는 경우 (null 허용)&lt;/p&gt;
&lt;pre id=&quot;code_1622821154712&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val test by lazy { &quot;test&quot; }
fun main() {
    println(&quot;초기화 안함&quot;)
}
//출력: &quot;초기화 안함&quot;  -&amp;gt; null point 예외는 일어나지 않습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;2) 최초 초기화 후 다시 초기화 할일이 없을 때 (val)&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;lateinit의 경우 아래와 같이 var (mutable) 이기 때문에 여러번 재 초기화가 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622821618448&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lateinit var lateTest:String
fun main() {
    lateTest = &quot;1번째 초기화&quot;
    println(&quot;1 :$lateTest&quot;)
    lateTest= &quot;2번째 초기화&quot;
    println(&quot;2 :$lateTest&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;반면 by lazy{}의 경우 재 초기화가 안됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;305&quot; data-filename=&quot;화면 캡처 2021-06-05 004036.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfl3XT/btq6Avtr8vp/jfXoKklRfdvyXMTLq35Ck1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfl3XT/btq6Avtr8vp/jfXoKklRfdvyXMTLq35Ck1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfl3XT/btq6Avtr8vp/jfXoKklRfdvyXMTLq35Ck1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfl3XT%2Fbtq6Avtr8vp%2FjfXoKklRfdvyXMTLq35Ck1%2Fimg.png&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;305&quot; data-filename=&quot;화면 캡처 2021-06-05 004036.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;이상 간단한 지연 초기화 정리였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;원래 by lazy{} 가 작동하는 원리에 대해 포스팅 하려고 했으나 상세히 적으려면&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&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;</description>
      <category>코틀린 Kotlin</category>
      <category>bylazy</category>
      <category>koltin</category>
      <category>kotlinbylazy</category>
      <category>kotlinlazy</category>
      <category>kotlinlazyinit</category>
      <category>lateinit</category>
      <category>Lazy</category>
      <category>지연초기화</category>
      <category>코틀린</category>
      <category>코틀린지연초기화</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/36</guid>
      <comments>https://gift123.tistory.com/36#entry36comment</comments>
      <pubDate>Sat, 5 Jun 2021 00:56:25 +0900</pubDate>
    </item>
    <item>
      <title>kotlin (5) Object 와 Companion Object</title>
      <link>https://gift123.tistory.com/35</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 loner 입니다. compose 관련 작성을 열심히 준비하다가 오랜만에 Kotlin 관련글로 돌아왔습니다.&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;a href=&quot;https://jcchu.medium.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jcchu.medium.com/&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1622690756444&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;profile&quot; data-og-title=&quot;JCChu &amp;ndash; Medium&quot; data-og-description=&quot;Read writing from JCChu on Medium. &amp;ldquo;기본부터&amp;rdquo;라는 말을 좋아하지만 정작 기본이 없는&amp;hellip; 기본을 쌓아나가려고 합니다. 안드로이드 개발자로 성장하기 위해 열심히 책상앞에 앉아 두들겨보겠습니다. E&quot; data-og-host=&quot;jcchu.medium.com&quot; data-og-source-url=&quot;https://jcchu.medium.com/&quot; data-og-url=&quot;https://jcchu.medium.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bCKscz/hyKrDENFd9/KHe1mhsWz4rX0ihoBschaK/img.png?width=96&amp;amp;height=96&amp;amp;face=0_0_96_96,https://scrap.kakaocdn.net/dn/chPWR1/hyKrFo45Pc/3aTmUWW6rvl68CaU1eYV2k/img.png?width=262&amp;amp;height=262&amp;amp;face=0_0_262_262&quot;&gt;&lt;a href=&quot;https://jcchu.medium.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jcchu.medium.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bCKscz/hyKrDENFd9/KHe1mhsWz4rX0ihoBschaK/img.png?width=96&amp;amp;height=96&amp;amp;face=0_0_96_96,https://scrap.kakaocdn.net/dn/chPWR1/hyKrFo45Pc/3aTmUWW6rvl68CaU1eYV2k/img.png?width=262&amp;amp;height=262&amp;amp;face=0_0_262_262');&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;JCChu &amp;ndash; Medium&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Read writing from JCChu on Medium. &amp;ldquo;기본부터&amp;rdquo;라는 말을 좋아하지만 정작 기본이 없는&amp;hellip; 기본을 쌓아나가려고 합니다. 안드로이드 개발자로 성장하기 위해 열심히 책상앞에 앉아 두들겨보겠습니다. E&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jcchu.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;/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;p data-ke-size=&quot;size16&quot;&gt;Kotlin부터 java, os지식까지 차근히 정리해가보도록 하겠습니다.&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;Object&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/object-declarations.html#semantic-difference-between-object-expressions-and-declarations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlinlang.org/docs/object-declarations.html#semantic-difference-between-object-expressions-and-declarations&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1622693435956&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;Object expressions and declarations | Kotlin&quot; data-og-description=&quot; &quot; data-og-host=&quot;kotlinlang.org&quot; data-og-source-url=&quot;https://kotlinlang.org/docs/object-declarations.html#semantic-difference-between-object-expressions-and-declarations&quot; data-og-url=&quot;https://www.jetbrains.com/idea/object-declarations.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/object-declarations.html#semantic-difference-between-object-expressions-and-declarations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinlang.org/docs/object-declarations.html#semantic-difference-between-object-expressions-and-declarations&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;Object expressions and declarations | Kotlin&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;kotlinlang.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kotlin 공식문서를 참고해서 작성했습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;02f5&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;object 는 크게 2가지로 사용합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;- 객체 표현식&lt;/span&gt;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;- 객체 선언&lt;/span&gt;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;- 동반 객체 (객체 선언을 클래스 내부에서 사용한것)&lt;/span&gt;&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;object-expressions&quot; data-toc=&quot;object-declarations#object-expressions&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;1. 객체 표현식&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1115&quot; data-filename=&quot;picture-frame-3042585_1280.jpg&quot; width=&quot;522&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfy4cE/btq6u2dFEDG/P0YkcWjICxBXoxNUcedh0k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfy4cE/btq6u2dFEDG/P0YkcWjICxBXoxNUcedh0k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfy4cE/btq6u2dFEDG/P0YkcWjICxBXoxNUcedh0k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfy4cE%2Fbtq6u2dFEDG%2FP0YkcWjICxBXoxNUcedh0k%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1115&quot; data-filename=&quot;picture-frame-3042585_1280.jpg&quot; width=&quot;522&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p id=&quot;25b99ffa&quot; data-ke-size=&quot;size16&quot;&gt;객체 표현식은&lt;span&gt;&amp;nbsp;&lt;/span&gt;익명 클래스의 객체, 즉 명시적으로 선언되지 않은&lt;span&gt;&amp;nbsp;&lt;/span&gt;class를만듭니다.&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 익명 객체 사용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;ace-1869825_1280.jpg&quot; width=&quot;485&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/elqlFc/btq6vTAFvls/0CR6VucUCPGMx0mufOckC0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/elqlFc/btq6vTAFvls/0CR6VucUCPGMx0mufOckC0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/elqlFc/btq6vTAFvls/0CR6VucUCPGMx0mufOckC0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FelqlFc%2Fbtq6vTAFvls%2F0CR6VucUCPGMx0mufOckC0%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot; data-filename=&quot;ace-1869825_1280.jpg&quot; width=&quot;485&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 표현식은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;object키워드&lt;/b&gt;로&lt;span&gt;&amp;nbsp;&lt;/span&gt;시작&lt;span&gt;&amp;nbsp;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p id=&quot;5d51cec4&quot; data-ke-size=&quot;size16&quot;&gt;특별한 상속없이 object 만 필요한 경우 아래과 같이 중괄호로 구문을&amp;nbsp; 작성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622734973555&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val helloWorld = object {
    val hello = &quot;Hello&quot;
    val world = &quot;World&quot;
    // object expressions extend Any, so `override` is required on `toString()`
    override fun toString() = &quot;$hello $world&quot; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-toc=&quot;object-declarations#inheriting-anonymous-objects-from-supertypes&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 id=&quot;inheriting-anonymous-objects-from-supertypes&quot; data-toc=&quot;object-declarations#inheriting-anonymous-objects-from-supertypes&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;2) 익명 class 상속&lt;/span&gt;해서 사용하기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1622735365653&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { /*...*/ }

    override fun mouseEntered(e: MouseEvent) { /*...*/ }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속되는 익명 클래스의 객체를 만들려면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;object: 클래스()&lt;span&gt;&amp;nbsp;&lt;/span&gt;&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;&amp;nbsp;&lt;/span&gt;에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;상속하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;것처럼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 클래스의 멤버 함수를 구현하거나 오버라이드가 가능합니다.&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1622735526375&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class A(x: Int) {
    public open val y: Int = x
}

interface B { /*...*/ }

val ab: A = object : A(1), B {
    override val y = 15
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-부모 클래스에 생성자가 있으면 파라미터를 전달해야합니다.&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;-object : 뒤에 쉼표(,)로 구분해서 여러 인터페이스를 추가로 상속받아 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;using-anonymous-object-as-return-and-value-types&quot; data-toc=&quot;object-declarations#using-anonymous-object-as-return-and-value-types&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;3)return 및 value Type으로 익명 객체 사용&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot; data-filename=&quot;anonymous-657195_1280.jpg&quot; width=&quot;542&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FcHOC/btq6wxjPY8H/kaKOLQKMQa42j6TbZMD19k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FcHOC/btq6wxjPY8H/kaKOLQKMQa42j6TbZMD19k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FcHOC/btq6wxjPY8H/kaKOLQKMQa42j6TbZMD19k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFcHOC%2Fbtq6wxjPY8H%2FkaKOLQKMQa42j6TbZMD19k%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot; data-filename=&quot;anonymous-657195_1280.jpg&quot; width=&quot;542&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익명 객체가&lt;span&gt; inline&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;선언 (함수 또는 속성)이&lt;span&gt; 아니면서 &lt;/span&gt;&lt;span&gt;private &lt;/span&gt;로 사용할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익명 객체를 return하는 함수 또는 변수를 통해 모든 멤버함수에서 접근 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622735754892&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class C {
    private fun getObject() = object {
        val x: String = &quot;x&quot;
    }

    fun printX() {
        println(getObject().x)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익명 객체를 return하는 함수 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;또는 변수가 public 또는 private inline 인 경우 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;color: #27282c;&quot;&gt;actual type&lt;/span&gt;은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul id=&quot;748cbab1&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;613f09a3&quot;&gt;Any&lt;span&gt;&amp;nbsp;&lt;/span&gt;익명 객체에 선언 된 super Type이 없는 경우&lt;/li&gt;
&lt;li id=&quot;6f9af4d9&quot;&gt;Type이 정확히 하나있는 경우 익명 객체의 선언 된 Super Type&lt;/li&gt;
&lt;li id=&quot;dd5d7813&quot;&gt;선언 된 Type이 둘 이상있는 경우 명시적으로 선언 된 Type&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;b646098e&quot; data-ke-size=&quot;size16&quot;&gt;위 모든 경우에 익명 객체에 추가 된 멤버에 액세스 할 수 없습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 또는 속성의 &lt;span style=&quot;color: #27282c;&quot;&gt;actual type&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_1622736117727&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface A {
    fun funFromA() {}
}
interface B

class C {
    // The return type is Any. x is not accessible
    fun getObject() = object {
        val x: String = &quot;x&quot;
    }

    // The return type is A; x is not accessible
    fun getObjectA() = object: A {
        override fun funFromA() {}
        val x: String = &quot;x&quot;
    }

    // The return type is B; funFromA() and x are not accessible
    fun getObjectB(): B = object: A, B { // explicit return type is required
        override fun funFromA() {}
        val x: String = &quot;x&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;accessing-variables-from-anonymous-objects&quot; data-toc=&quot;object-declarations#accessing-variables-from-anonymous-objects&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;4) 익명 객체에서 변수 접근&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;horses-1378409_1280.jpg&quot; width=&quot;403&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UxkiW/btq6q1mU6le/gG7sK5sxHtqCtL2jTEC1MK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UxkiW/btq6q1mU6le/gG7sK5sxHtqCtL2jTEC1MK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UxkiW/btq6q1mU6le/gG7sK5sxHtqCtL2jTEC1MK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUxkiW%2Fbtq6q1mU6le%2FgG7sK5sxHtqCtL2jTEC1MK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot; data-filename=&quot;horses-1378409_1280.jpg&quot; width=&quot;403&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p id=&quot;c8dd6ca0&quot; data-ke-size=&quot;size16&quot;&gt;Object 표현식의 코드는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;괄호 {} 를 둘러싸는 범위에서 {}안에 있는 변수에 접근 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622737010475&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }

        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 객체 선언&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;915&quot; data-filename=&quot;testament-229778_1280.jpg&quot; width=&quot;511&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJ2eo/btq6ue6G8EP/da28agOUWKzhS4nVnkblX1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJ2eo/btq6ue6G8EP/da28agOUWKzhS4nVnkblX1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJ2eo/btq6ue6G8EP/da28agOUWKzhS4nVnkblX1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJ2eo%2Fbtq6ue6G8EP%2Fda28agOUWKzhS4nVnkblX1%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;915&quot; data-filename=&quot;testament-229778_1280.jpg&quot; width=&quot;511&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리가 1번만 호출되는 싱글톤 용도로 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1622737167699&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection&amp;lt;DataProvider&amp;gt;
        get() = // ...
}&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;span&gt;&amp;nbsp;&lt;/span&gt;이라고하며&lt;span&gt;&amp;nbsp;&lt;/span&gt;항상&lt;span&gt;&amp;nbsp;&lt;/span&gt;object 뒤에 객체 이름이 있습니다.&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 id=&quot;2ed09dd4&quot; data-ke-size=&quot;size16&quot;&gt;객체 선언의 초기화는 스레드로부터 안전합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 Object명.으로 접근이 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622737528218&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DataProviderManager.registerDataProvider(...)&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;ad1c1d4a&quot; data-ke-size=&quot;size16&quot;&gt;혹은 상속을 받아 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622737617177&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { ... }

    override fun mouseEntered(e: MouseEvent) { ... }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 *중첩 object선언이 가능합니다. (static inner class)&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;2-2. 동반 객체 (Companion Object)&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;785&quot; data-filename=&quot;sunset-1807524_1280.jpg&quot; width=&quot;414&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBFOEH/btq6vSV4GbR/Wz9hZpLZUVUVLYP9Tx5i8K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBFOEH/btq6vSV4GbR/Wz9hZpLZUVUVLYP9Tx5i8K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBFOEH/btq6vSV4GbR/Wz9hZpLZUVUVLYP9Tx5i8K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBFOEH%2Fbtq6vSV4GbR%2FWz9hZpLZUVUVLYP9Tx5i8K%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;785&quot; data-filename=&quot;sunset-1807524_1280.jpg&quot; width=&quot;414&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 내부의 객체 선언은&lt;span&gt;&amp;nbsp;&lt;/span&gt;companion object 키워드로 표시 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622737911042&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #27282c;&quot;&gt;companion object 의 멤버함수는 단순히 생성자 없이 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #27282c;&quot;&gt;클래스 이름을 사용해서 호출 할 수 있습니다.&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_1622737995514&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val instance = MyClass.create()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #27282c;&quot;&gt;companion object&lt;/span&gt;의 이름은 생략 할 수 있습니다. 이 경우 이름은&lt;span&gt;&amp;nbsp;&lt;/span&gt;Companion이 사용됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1622738096666&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyClass {
    companion object { }
}

val x = MyClass.Companion&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;클래스 멤버는 해당 Companion object의 비공개 필드에 접근 할 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;21c34ca7&quot; 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;클래스 내부의 Companion object 에 대한 참조 역할을합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1622738124554&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyClass1 {
    companion object Named { }
}

val x = MyClass1

class MyClass2 {
    companion object { }
}

val y = MyClass2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #27282c;&quot;&gt;companion object 의 멤버는 다른 언어에서 static 멤버처럼 보이지만 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #27282c;&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: #27282c;&quot;&gt; 예를 들어 인터페이스를 구현할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622738529082&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Factory&amp;lt;T&amp;gt; {
    fun create(): T
}

class MyClass {
    companion object : Factory&amp;lt;MyClass&amp;gt; {
        override fun create(): MyClass = MyClass()
    }
}

val f: Factory&amp;lt;MyClass&amp;gt; = MyClass&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;span style=&quot;color: #27282c;&quot;&gt;그러나&amp;nbsp;JVM에서는&amp;nbsp;@JvmStatic주석&amp;nbsp;을&amp;nbsp;사용하는&amp;nbsp;경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #27282c;&quot;&gt;static 메소드 및 필드로 생성 된 companion object의 멤버를 가질 수 있습니다 .&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;semantic-difference-between-object-expressions-and-declarations&quot; data-toc=&quot;object-declarations#semantic-difference-between-object-expressions-and-declarations&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;3. 객체 표현식과 객체 선언의 차이&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;834&quot; data-filename=&quot;opposites-3808487_1280.jpg&quot; width=&quot;445&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cS7dh7/btq6tTImlxG/kwYHcz0cbqcV7IUYZIF6tK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cS7dh7/btq6tTImlxG/kwYHcz0cbqcV7IUYZIF6tK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cS7dh7/btq6tTImlxG/kwYHcz0cbqcV7IUYZIF6tK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcS7dh7%2Fbtq6tTImlxG%2FkwYHcz0cbqcV7IUYZIF6tK%2Fimg.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;834&quot; data-filename=&quot;opposites-3808487_1280.jpg&quot; width=&quot;445&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p id=&quot;994dd4a9&quot; data-ke-size=&quot;size16&quot;&gt;객체 표현식과 객체 선언의 차이는 다음과 같습니다.&lt;/p&gt;
&lt;ul id=&quot;6fd1d85f&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li id=&quot;28ff5705&quot;&gt;객체 표현식은&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용되는 곳에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;즉시&lt;span&gt;&amp;nbsp;&lt;/span&gt;실행 (및 초기화)&lt;span&gt;&amp;nbsp;&lt;/span&gt;됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;li id=&quot;f56d186f&quot;&gt;객체 선언은&lt;span&gt;&amp;nbsp;&lt;/span&gt;처음 접근 할 때&lt;span&gt; &lt;/span&gt;&lt;b&gt;늦은&lt;span&gt;&amp;nbsp;&lt;/span&gt;초기화&lt;span&gt;&amp;nbsp;&lt;/span&gt;됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;li id=&quot;5979c067&quot;&gt;companion object 는 해당 class가 load 될 때 초기화되며 &lt;span style=&quot;color: #27282c;&quot;&gt;Java static initializer &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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ditt/Kotlin-Object-%ED%82%A4%EC%9B%8C%EB%93%9C&quot;&gt;https://velog.io/@ditt/Kotlin-Object-%ED%82%A4%EC%9B%8C%EB%93%9C&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1622738882649&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;[Kotlin] Object 키워드&quot; data-og-description=&quot;Kotlin의 Object 키워드&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@ditt/Kotlin-Object-%ED%82%A4%EC%9B%8C%EB%93%9C&quot; data-og-url=&quot;https://velog.io/@ditt/Kotlin-Object-키워드&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bLy4d8/hyKrxSNibl/sP95oNTq8LOcD1m11TPz6k/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500&quot;&gt;&lt;a href=&quot;https://velog.io/@ditt/Kotlin-Object-%ED%82%A4%EC%9B%8C%EB%93%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@ditt/Kotlin-Object-%ED%82%A4%EC%9B%8C%EB%93%9C&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bLy4d8/hyKrxSNibl/sP95oNTq8LOcD1m11TPz6k/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500');&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;[Kotlin] Object 키워드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin의 Object 키워드&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩은 기본기가 중요하다고 생각합니다. 항상 잊을만하면 복습하는 것은 필수 인것 같네요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상 kotlin object에 관한 정리 였습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>코틀린 Kotlin</category>
      <category>copanionobject</category>
      <category>Kotlin</category>
      <category>kotlinCompanionObject</category>
      <category>kotlinObject</category>
      <category>object</category>
      <category>객체</category>
      <category>익명객체</category>
      <category>익명클래스</category>
      <category>코틀린</category>
      <category>코틀린object</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/35</guid>
      <comments>https://gift123.tistory.com/35#entry35comment</comments>
      <pubDate>Fri, 4 Jun 2021 01:57:20 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 (9) Compose 상태 관리</title>
      <link>https://gift123.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;jetpack compose 에 한창 포스팅 중입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gift123.tistory.com/33&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1622636655040&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;안드로이드 개발 (8) Compose 이해 정리&quot; data-og-description=&quot;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&quot; data-og-host=&quot;gift123.tistory.com&quot; data-og-source-url=&quot;https://gift123.tistory.com/33&quot; data-og-url=&quot;https://gift123.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/J8aj1/hyKryJUepl/QpANKydFtv9lHSqFNzhypK/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/cOk1eo/hyKrHtjLGe/WgYvarKOBrPrUsIHuPb9R0/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/CoQT8/hyKrFWyXrV/L17gZTN7Fv64BvmDOVIcB0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/33&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gift123.tistory.com/33&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/J8aj1/hyKryJUepl/QpANKydFtv9lHSqFNzhypK/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/cOk1eo/hyKrHtjLGe/WgYvarKOBrPrUsIHuPb9R0/img.png?width=800&amp;amp;height=405&amp;amp;face=0_0_800_405,https://scrap.kakaocdn.net/dn/CoQT8/hyKrFWyXrV/L17gZTN7Fv64BvmDOVIcB0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&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;안드로이드 개발 (8) Compose 이해 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅부터 Compose에 대해 차근히 파헤쳐 가보겠습니다. Android Compose 공식 문서를 보면서 정리한 내용들 입니다. https://developer.android.com/jetpack/compose/mental-model?hl=en Compose 이해 &amp;nbsp;|..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gift123.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;&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 상태관리를 알아보도록 하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Compose에서의 상태&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;it-business-722950_1280.png&quot; width=&quot;480&quot; height=&quot;480&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDMFzO/btq6sj6VJGM/cfQYnNxfZTDtmPsisKkgdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDMFzO/btq6sj6VJGM/cfQYnNxfZTDtmPsisKkgdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDMFzO/btq6sj6VJGM/cfQYnNxfZTDtmPsisKkgdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDMFzO%2Fbtq6sj6VJGM%2FcfQYnNxfZTDtmPsisKkgdK%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;it-business-722950_1280.png&quot; width=&quot;480&quot; height=&quot;480&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;(ex: &lt;span style=&quot;color: #202124;&quot;&gt;Room 데이터베이스부터 클래스 변수까지 모든 변할 수 있는값을 뜻함)&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: #ee2323;&quot;&gt;&lt;b&gt;상태개념은 Compose에서 핵심&lt;/b&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;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;/li&gt;
&lt;li&gt;사용자가 이미지 위에 그릴 수 있는 스티커&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-Jetpack Compose 는 상태를 저장하거나 사용하는 위치와 방법들을 명시적으로 나타냄&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;composition-and-recomposition&quot; data-text=&quot;컴포지션 및 리컴포지션&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;컴포지션 및 리컴포지션&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;741&quot; data-filename=&quot;wrench-2659675_1280.png&quot; width=&quot;473&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pl6yK/btq6rvs0sfb/zvyJUxhgbsvKZON5gjzzIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pl6yK/btq6rvs0sfb/zvyJUxhgbsvKZON5gjzzIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pl6yK/btq6rvs0sfb/zvyJUxhgbsvKZON5gjzzIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPl6yK%2Fbtq6rvs0sfb%2FzvyJUxhgbsvKZON5gjzzIk%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;741&quot; data-filename=&quot;wrench-2659675_1280.png&quot; width=&quot;473&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴포지션:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Jetpack Compose가 컴포저블을 실행할 때 빌드한 UI에 관한 설명&lt;/p&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;/p&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;/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;-컴포지션은 UI를 기술하는 역할을 하며 컴포저블을 실행하면 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(컴포지션은 UI를 기술하는 컴포저블의 트리 구조)&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;컴포지션 흐름&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 초기 컴포지션&lt;span&gt;&amp;nbsp;&lt;/span&gt;시 Compose는 컴포지션에서 호출하는 컴포저블을 추적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 추적 후 다음 앱 상태가 변경되면 Jetpack Compose는&lt;span&gt;&amp;nbsp;&lt;/span&gt;리컴포지션을 예약&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 리컴포지션에서는 상태 변경에 관한 응답으로 변할 수 있는 컴포저블을 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) Jetpack Compose는 변경사항을 반영하도록 컴포지션을 업데이트&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 style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;-컴포지션을 수정하는 유일한 방법은 리컴포지션을 통하는 것입니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;introducing-state&quot; data-text=&quot;상태 소개&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;상태 소개&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1020&quot; data-filename=&quot;book-2022465_1280.png&quot; width=&quot;570&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSsnk5/btq6mGbLzv1/uKtpqlAL4ibWbIRHtJ0WQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSsnk5/btq6mGbLzv1/uKtpqlAL4ibWbIRHtJ0WQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSsnk5/btq6mGbLzv1/uKtpqlAL4ibWbIRHtJ0WQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSsnk5%2Fbtq6mGbLzv1%2FuKtpqlAL4ibWbIRHtJ0WQ1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1020&quot; data-filename=&quot;book-2022465_1280.png&quot; width=&quot;570&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;예제 1. 텍스트를 적어도 아무것도 실행되지않는 Composable함수&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622638273508&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun HelloContent() {
   Column(modifier = Modifier.padding(16.dp)) {
       Text(
           text = &quot;Hello!&quot;,
           modifier = Modifier.padding(bottom = 8.dp),
           style = MaterialTheme.typography.h5
       )
       OutlinedTextField(
           value = &quot;&quot;,
           onValueChange = { },
           label = { Text(&quot;Name&quot;) }
       )
   }
}&lt;/code&gt;&lt;/pre&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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예제2. 이벤트가 작동하는 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622649648684&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   @Composable
    fun HelloContent() {
        Column(modifier = Modifier.padding(16.dp)) {
            var name by remember { mutableStateOf(&quot;&quot;) }
            Text(
                text = &quot;Hello&quot;,
                modifier = Modifier.padding(bottom = 8.dp),
                style = MaterialTheme.typography.h5
            )
            OutlinedTextField(
                value = name,
                onValueChange = { name = it },
                label = { Text(&quot;Name&quot;) }
            )
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;Composable함수를 업데이트를 하려면&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;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;remember { mutableStateOf() }&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;span style=&quot;color: #202124;&quot;&gt;OutlinedTextField의 상태를 나타내는 값을 전달하고,&amp;nbsp; (value)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;OutlinedTextField의 값이 변경될 때 상태를 업데이트하는 코드를 추가합니다. (onValueChance)&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: #202124;&quot;&gt;그러면 Composable함수를 업데이트를 하기 때문에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;아래와 같이&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;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;Comosable 함수는&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;remember Composable을 사용해서 메모리에 단일 객체를 저장할 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;remember에 의해 계산된 값은 초기 컴포지션 중에 컴포지션에 저장되고&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;497&quot; data-filename=&quot;화면 캡처 2021-06-03 011312.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi6z5G/btq6p0NZ6cv/wNhT7La7YVCXR9r8Q0btrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi6z5G/btq6p0NZ6cv/wNhT7La7YVCXR9r8Q0btrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi6z5G/btq6p0NZ6cv/wNhT7La7YVCXR9r8Q0btrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi6z5G%2Fbtq6p0NZ6cv%2FwNhT7La7YVCXR9r8Q0btrK%2Fimg.png&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;497&quot; data-filename=&quot;화면 캡처 2021-06-03 011312.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-remember&lt;/b&gt;는 객체를 컴포지션에 저장하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-remember&lt;/b&gt;를 호출한 컴포저블이 컴포지션에서 삭제되면 그 객체를 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-remember&lt;/b&gt;는 변경 가능한 객체뿐만 아니라 변경할 수 없는 객체를 저장하는 데에도 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-remember&lt;/b&gt;는 리컴포지션(재구성)에서 상태를 보존하는 데 도움이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(위 예제에서 remember를 함께 사용하지 않고&lt;span&gt;&amp;nbsp;&lt;/span&gt;mutableStateOf를 사용하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;HelloContent&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;size18&quot;&gt;&lt;b&gt;mutableStateOf&lt;/b&gt;&lt;/p&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;span style=&quot;color: #000000;&quot;&gt;mutableStateOf는 Compose에서 &lt;b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;observable&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;type&lt;/b&gt;인 MutableState을 생성합니다.&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622651952325&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface MutableState&amp;lt;T&amp;gt; : State&amp;lt;T&amp;gt; {
   override var value: T
}&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;위 value가 변경되면 그 값을 읽는 Comosable 함수의 리컴포지션(재구성)이 생깁니다.&lt;/b&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;아래와 같은 3가지 유형으로 mutableStateOf를 사용할 수 있습니다.&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;val mutableState = remember { mutableStateOf(default) }&lt;/li&gt;
&lt;li&gt;var value by remember { mutableStateOf(default) }&lt;/li&gt;
&lt;li&gt;val (value, setValue) = remember { mutableStateOf(default) }&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;remember와 같이 사용하면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기억된 값을 다른 Composable의 매개변수로 사용하거나 구문의 로직으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하여 표시할 Composable을 변경할 수 있습니다.&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 id=&quot;code_1622651092007&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun HelloContent() {
   Column(modifier = Modifier.padding(16.dp)) {
       var name by remember { mutableStateOf(&quot;&quot;) }
       if (name.isNotEmpty()) {
           Text(
               text = &quot;Hello, $name!&quot;,
               modifier = Modifier.padding(bottom = 8.dp),
               style = MaterialTheme.typography.h5
           )
       }
       OutlinedTextField(
           value = name,
           onValueChange = { name = it },
           label = { Text(&quot;Name&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;if문으로 name변수가 비어 있지 않는 경우 인사말을 표시하는 분기처리도 가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래처럼 name에 text가 비어있으면 위의 인사말이 사라졌다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;text가 글이 있으면 인사말이 표시 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&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;remember&lt;span style=&quot;color: #202124;&quot;&gt;가 리컴포지션(재구성) 과정 전체에서 상태를 유지하는 데 도움은 되지만&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt; 구성 변경시에 유지가 되지 않습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt;( &lt;/span&gt;the state is not retained across configuration changes.)&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: #202124;&quot;&gt;이 경우에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;rememberSaveable&lt;/b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;을 사용해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp;rememberSaveable은&amp;nbsp;Bundle에 저장할 수 있는 모든 값을 자동으로 저장합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;다른 값의 경우에는 맞춤 Saver 객체를 전달할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622652314110&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; //위 예제에서 var name by remember {mutableStateOf(&quot;&quot;)} 이걸 지우고 아래껄 사용하면 
 //구성 변경 환경에서도 상태가 저장됨
 var name by rememberSaveable { mutableStateOf(&quot;&quot;) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;예시)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성 변경이나 전화 통화 같은 중단 상황이 발생하면 내부 상태를 유지할지 선택해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지해야 하는 경우라면&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;rememberSaveable&lt;/b&gt;을 사용하여 상태를 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;stateful-vs-stateless&quot; data-text=&quot;Stateful versus stateless&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;b&gt;Stateful&amp;nbsp; 와&lt;/b&gt;&amp;nbsp;&lt;b&gt;stateless&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;scales-310962_1280.png&quot; width=&quot;454&quot; height=&quot;455&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O2pVd/btq6rn2NEAL/xNYZmk2wYCS7ekKPjan48K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O2pVd/btq6rn2NEAL/xNYZmk2wYCS7ekKPjan48K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O2pVd/btq6rn2NEAL/xNYZmk2wYCS7ekKPjan48K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO2pVd%2Fbtq6rn2NEAL%2FxNYZmk2wYCS7ekKPjan48K%2Fimg.png&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;scales-310962_1280.png&quot; width=&quot;454&quot; height=&quot;455&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;remember객체를 저장하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;데 사용하는 Composable은 자체적으로 상태를 생성하여 Composable을&lt;b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Stateful&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 호출자가 상태를 제어 할 필요가없고 상태를 직접 관리하지 않고도 사용할 수있는 상황에서 유용 할 수 있습니다.&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;b&gt;그러나 Composable이 &lt;span style=&quot;color: #202124;&quot;&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;span&gt;재사용과 테스트를 쉽게 하려면 &lt;b&gt;상태를 갖지않는 Composable인 Stateless Composable도 만들어야 합니다.&lt;/b&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: #202124;&quot;&gt;재사용 가능한 Composable를 개발할 때 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;동일한 컴포저블의 스테이트풀(Stateful) 버전과 스테이트리스(Stateless) 버전을 모두 노출하고자 할 수 있습니다.&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;&lt;span style=&quot;color: #202124;&quot;&gt;스테이트풀(Stateful) 버전은 상태를 염두에 두지 않는 호출자에 편리하며,&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #202124;&quot;&gt; 스테이트리스(Stateless) 버전은 상태를 제어하거나 끌어올려야 하는 호출자에 필요합니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;state-hoisting&quot; data-text=&quot;State hoisting&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;상태 끌어올리기 State Hosting&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;mountaineer-4276010_1280.png&quot; width=&quot;451&quot; height=&quot;503&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFeNrc/btq6qhWkgoq/A3rmhFdZVIkKoQpACrQ720/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFeNrc/btq6qhWkgoq/A3rmhFdZVIkKoQpACrQ720/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFeNrc/btq6qhWkgoq/A3rmhFdZVIkKoQpACrQ720/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFeNrc%2Fbtq6qhWkgoq%2FA3rmhFdZVIkKoQpACrQ720%2Fimg.png&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;mountaineer-4276010_1280.png&quot; width=&quot;451&quot; height=&quot;503&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;상태 끌어올리기&lt;/b&gt;는 구성요소를 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;스테이트리스(Stateless)로 만들기 위해 상태를 트리 위로 옮기는 패턴입니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴이 컴포저블에 적용되는 경우 컴포저블에 매개변수 2개가 추가될 때가 많습니다.&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;value: T&lt;/b&gt;: 표시할 현재 값&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onValueChange: (T) -&amp;gt; Unit&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;T가 제안된 새 값인 경우 값을 변경하도록 요청하는 이벤트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2개만 사용한다는 얘기는 아닙니다. 필요하다면 파라미터를 더 만들어서 쓰면 됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예제.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622654700074&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf(&quot;&quot;) }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -&amp;gt; Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = &quot;Hello, $name&quot;,
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = name,
            onValueChange = onNameChange,
            label = { Text(&quot;Name&quot;) }
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;위 예에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;HelloContent&lt;/b&gt;에서&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;name&lt;span style=&quot;color: #202124;&quot;&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;onValueChange&lt;span style=&quot;color: #202124;&quot;&gt;를 추출한 다음, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;이 항목을 트리 상단에 &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;HelloContent&lt;span style=&quot;color: #202124;&quot;&gt;를 호출하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;HelloScreen&lt;/b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;b&gt;&lt;span&gt; Composable&lt;/span&gt;&lt;/b&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;HelloContent는 변경할 수 없는&lt;span&gt;&amp;nbsp;&lt;/span&gt;String&lt;span&gt;&amp;nbsp;&lt;/span&gt;매개변수의 형태로 상태에 액세스할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 상태 변경을 요청하고자 할 때 호출할 수 있는 람다&lt;span&gt;&amp;nbsp;&lt;/span&gt;onNameChange에도 액세스할 수 있습니다.&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;람다는 Composable의 이벤트를 설명하는 가장 일반적인 방법입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 예에서 &lt;/span&gt;(String) -&amp;gt; Unit를 사용해서&lt;span&gt;&amp;nbsp;&lt;/span&gt;onNameChange&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;HelloContent에서 상태를 끌어올리면 더 쉽게 Composable을 추론하고&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;HelloContent는 상태의 저장 방식과 분리됩니다. &lt;/b&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;&amp;nbsp;&lt;/span&gt;HelloScreen을 수정하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교체할 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;HelloContent의 구현 방식을 변경할 필요가 없다는 의미입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;1078&quot; data-filename=&quot;udf-hello-screen.png&quot; width=&quot;546&quot; height=&quot;614&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2m5yE/btq6rmpiWnH/kL3O6LfQnB95hu1Rld7v11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2m5yE/btq6rmpiWnH/kL3O6LfQnB95hu1Rld7v11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2m5yE/btq6rmpiWnH/kL3O6LfQnB95hu1Rld7v11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2m5yE%2Fbtq6rmpiWnH%2FkL3O6LfQnB95hu1Rld7v11%2Fimg.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;1078&quot; data-filename=&quot;udf-hello-screen.png&quot; width=&quot;546&quot; height=&quot;614&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;상태가 내려가고 이벤트가 올라가는 패턴을&amp;nbsp;단방향 데이터 흐름이라고 합니다. &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;이 경우 상태는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;HelloScreen&lt;span style=&quot;color: #202124;&quot;&gt;에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;HelloContent&lt;span style=&quot;color: #202124;&quot;&gt;로 내려가고 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;이&lt;/span&gt;&lt;span style=&quot;color: #202124;&quot;&gt;벤트는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;HelloContent&lt;span style=&quot;color: #202124;&quot;&gt;에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;HelloScreen&lt;span style=&quot;color: #202124;&quot;&gt;으로 올라갑니다.&lt;/span&gt;&lt;span style=&quot;color: #202124;&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: #202124;&quot;&gt;단방향 데이터 흐름을 따르면 UI에 상태를 표시하는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;Composable과 상태를 저장하고 변경하는 앱 부분을 서로 분리할 수 있습니다.&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;size18&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;b&gt;또 다른 예제.&lt;/b&gt;&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_1622655782442&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun ExpandingCard(title: String, body: String) {
    var expanded by remember { mutableStateOf(false) }
    ExpandingCard(
        title = title,
        body = body,
        expanded = expanded,
        onExpand = { expanded = true },
        onCollapse = { expanded = false }
    )
}

@Composable
fun ExpandingCard(
    title: String,
    body: String,
    expanded: Boolean,
    onExpand: () -&amp;gt; Unit,
    onCollapse: () -&amp;gt; Unit
) {
    Card {
        Column(
            Modifier
                .width(280.dp)
                .animateContentSize() // automatically animate size when it changes
                .padding(top = 16.dp, start = 16.dp, end = 16.dp)
        ) {
            Text(title)
            if (expanded) {
                Spacer(Modifier.height(8.dp))
                Text(body)
                IconButton(onClick = onCollapse, Modifier.fillMaxWidth()) {
                    Icon(imageVector = Icons.Default.ExpandLess, contentDescription = &quot;Expand less&quot;)
                }
            } else {
                IconButton(onClick = onExpand, Modifier.fillMaxWidth()) {
                    Icon(imageVector = Icons.Default.ExpandMore, contentDescription = &quot;Expand more&quot;)
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;단일 소스 저장소:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;상태를 복제하지 않고 위로 옮겼기 때문에expanded의 소스 저장소가 하나만 있습니다. 버그 방지에 도움이 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캡슐화:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;스테이트풀(Stateful)&lt;span&gt;&amp;nbsp;&lt;/span&gt;ExpandingCard만 상태를 수정할 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공유 가능:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;끌어올린 상태를 여러 Composable과 공유 가능합니다. (예를 들어&lt;span&gt;&amp;nbsp;&lt;/span&gt;Card가 펼쳐질 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;Fab&lt;span&gt;&amp;nbsp;&lt;/span&gt;버튼을 숨기려고 한다면 끌어올리기를 통해 그 작업이 가능합니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가로채기 가능:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;스테이트리스(Stateless)&lt;span&gt;&amp;nbsp;&lt;/span&gt;ExpandingCard의 호출자는 상태를 변경하기 전에 이벤트를 무시할지 수정할지 결정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분리됨:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;스테이트리스(Stateless)&lt;span&gt;&amp;nbsp;&lt;/span&gt;ExpandingCard의 상태는 어디에나 저장할 수 있습니다. 예를 들어 이제는&lt;span&gt;&amp;nbsp;&lt;/span&gt;title,&lt;span&gt;&amp;nbsp;&lt;/span&gt;body,&lt;span&gt;&amp;nbsp;&lt;/span&gt;expanded를&lt;span&gt;&amp;nbsp;&lt;/span&gt;ViewModel로 옮길 수 있습니다.&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;&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;상태는 스테이트풀(Stateful) 컴포저블에서 아래로 전달되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트는 스테이트리스(Stateless) 컴포저블에서 위로 흐릅니다.&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;&lt;span&gt;&amp;nbsp;*&lt;/span&gt;상태를 끌어올릴 때 상태의 이동 위치를 쉽게 파악할 수 있는 세 가지 규칙이 있습니다.*&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;적어도&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;그 상태를 사용하는 모든 컴포저블의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;가장 낮은 공통 상위 요소&lt;/b&gt;로 끌어올려야 합니다(읽기).&lt;/li&gt;
&lt;li&gt;상태는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;최소한&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;변경될 수 있는 가장 높은 수준&lt;/b&gt;으로 끌어올려야 합니다(쓰기).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동일한 이벤트에 대한 응답으로 두 상태가 변경&lt;/b&gt;되는 경우 두 상태를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;함께 끌어올려야&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;use-other-types-of-state-in-jetpack-compose&quot; data-text=&quot;Jetpack Compose에서 다른 유형의 상태 사용&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;Compose에서 다른 유형의 상태 사용&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;971&quot; data-filename=&quot;binoculars-159363_1280.png&quot; width=&quot;501&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DZq11/btq6lmY7E2H/kdM7tO5kOkEkAqE20HnXb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DZq11/btq6lmY7E2H/kdM7tO5kOkEkAqE20HnXb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DZq11/btq6lmY7E2H/kdM7tO5kOkEkAqE20HnXb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDZq11%2Fbtq6lmY7E2H%2FkdM7tO5kOkEkAqE20HnXb0%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;971&quot; data-filename=&quot;binoculars-159363_1280.png&quot; width=&quot;501&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;Compose&lt;/b&gt;에서는 상태를 보존하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;MutableState&amp;lt;T&amp;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;Compose는 &lt;b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;observable&lt;/span&gt; type&lt;/b&gt;을 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;observable&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;type을 읽으려면 상태를&lt;span&gt;&amp;nbsp;&lt;/span&gt;State&amp;lt;T&amp;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;Compose에는 이미 안드로이드에서 자주 사용 하는&lt;span style=&quot;color: #202124;&quot;&gt;observable&lt;/span&gt;&amp;nbsp;type에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;State&amp;lt;T&amp;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;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/livedata/package-summary&quot;&gt;LiveData&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#collectAsState(kotlinx.coroutines.flow.StateFlow,kotlin.coroutines.CoroutineContext)&quot;&gt;Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/rxjava2/package-summary&quot;&gt;RxJava2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;앱에 &lt;span style=&quot;color: #202124;&quot;&gt;observable한&lt;/span&gt;&amp;nbsp;커스텀 클래스를 사용하는 경우 &lt;span style=&quot;color: #202124;&quot;&gt;observable&lt;/span&gt; type을 읽어오기 위한 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Compose용 확장 함수를 빌드할 수 있습니다.&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;모든 변경사항을 수신하도록 Compose에 허용하는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 객체를&lt;span&gt;&amp;nbsp;&lt;/span&gt;State&amp;lt;T&amp;gt;로 변환하고 Composable을 통해 읽어올 수 있습니다.&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는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/compose/runtime/State&quot;&gt;State&amp;lt;T&amp;gt;&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체를 읽어오면서 자동으로 리컴포지션(재구성)됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;LiveData&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 다른 &lt;b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;observable&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;type&lt;/b&gt;을 사용할 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composable에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;LiveData&amp;lt;T&amp;gt;.observeAsState()&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 Composable 함수를 사용하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 type을 읽어오려면 &lt;b&gt;유형을&lt;span&gt;&amp;nbsp;&lt;/span&gt;State&amp;lt;T&amp;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;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Compose에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;ArrayList&amp;lt;T&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;mutableListOf()&lt;span&gt;&amp;nbsp;&lt;/span&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;span&gt;&amp;nbsp;&lt;/span&gt;ArrayList&amp;lt;T&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는 변경 가능한 데이터 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 observe가 불가능한 객체는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 객체가 변경될 때 컴포지션을 트리거하도록 Compose에서 관찰할 수 없습니다.&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;observe가 불가능하면서 변경 가능한 객체를 사용하는 대신에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;State&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;및 &lt;b&gt;변경 불가능한&lt;span&gt;&amp;nbsp;&lt;/span&gt;listOf()&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 관찰 가능 데이터 홀더를 사용하는 것이 좋습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-text=&quot;Stateful versus stateless&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;ViewModel 및 상태&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;call-3613071_1280.png&quot; width=&quot;453&quot; height=&quot;453&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgbxBm/btq6pVzqR2E/i5LYu0N3IBrgcQukLl8KkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgbxBm/btq6pVzqR2E/i5LYu0N3IBrgcQukLl8KkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgbxBm/btq6pVzqR2E/i5LYu0N3IBrgcQukLl8KkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgbxBm%2Fbtq6pVzqR2E%2Fi5LYu0N3IBrgcQukLl8KkK%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot; data-filename=&quot;call-3613071_1280.png&quot; width=&quot;453&quot; height=&quot;453&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;ViewModel을 사용하면 상태를 &lt;b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;observable&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;type&lt;/b&gt;(&lt;span&gt;&amp;nbsp;&lt;/span&gt;LiveData,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Flow,Rx java 등등)에&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;예제.&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;ViewModel&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622656743993&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HelloViewModel : ViewModel() {
    
    private val _name = MutableLiveData(&quot;&quot;)
    val name: LiveData&amp;lt;String&amp;gt; = _name
    
    fun onNameChange(newName: String) {
        _name.value = newName
    }
}
&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;b&gt;Activity&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622656681897&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
    val name: String by helloViewModel.name.observeAsState(&quot;&quot;)
    HelloContent(name = name, onNameChange = { helloViewModel.onNameChange(it) })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -&amp;gt; Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = &quot;Hello, $name&quot;,
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = name,
            onValueChange = onNameChange,
            label = { Text(&quot;Name&quot;) }
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;observeAsState&lt;/b&gt;는&amp;nbsp;LiveData&amp;lt;T&amp;gt;를&amp;nbsp;관찰하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveData가&amp;nbsp;변경될&amp;nbsp;때마다&amp;nbsp;업데이트되는&amp;nbsp;State&amp;lt;T&amp;gt;&amp;nbsp;객체를&amp;nbsp;반환합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State&amp;lt;T&amp;gt;는 J Compose가 직접 사용할 수 있는 관찰 가능한 유형입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;observeAsState는&amp;nbsp;컴포지션에&amp;nbsp;있는&amp;nbsp;동안에만&amp;nbsp;LiveData를&amp;nbsp;관찰합니다.&lt;/b&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;pre id=&quot;code_1622657255436&quot; class=&quot;kotlin&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val name: String by helloViewModel.name.observeAsState(&quot;&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observeAsState에서 반환된 상태 객체를 자동으로 래핑 해제하는 구문 슈가입니다.&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;&lt;b&gt;이 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;String&lt;span&gt;&amp;nbsp;&lt;/span&gt;대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;State&amp;lt;String&amp;gt;이 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1622657277039&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val nameState: State&amp;lt;String&amp;gt; = helloViewModel.name.observeAsState(&quot;&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;512&quot; data-filename=&quot;state-and-event-flow.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7VxyO/btq6lYRmvFk/rDaiHRw2R7GgknTBpyCgIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7VxyO/btq6lYRmvFk/rDaiHRw2R7GgknTBpyCgIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7VxyO/btq6lYRmvFk/rDaiHRw2R7GgknTBpyCgIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7VxyO%2Fbtq6lYRmvFk%2FrDaiHRw2R7GgknTBpyCgIK%2Fimg.png&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;512&quot; data-filename=&quot;state-and-event-flow.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewModel 사용시,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;상태는&amp;nbsp;HelloViewModel에서 아래로 흐르고 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이벤트는&amp;nbsp;HelloScreen에서 위로 흐르는 단방향 데이터 흐름 패턴을 따릅니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol 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;span&gt;&amp;nbsp;&lt;/span&gt;onValueChange가 호출&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태 업데이트&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;HelloViewModel.onNameChange가 처리를 진행 후 변경 가능한&lt;span&gt;&amp;nbsp;&lt;/span&gt;LiveData,&lt;span&gt;&amp;nbsp;&lt;/span&gt;_name의 상태를 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태 표시&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;HelloViewmodel.name의 값이 변경 -&amp;gt; observeAsState에서 관찰됩니다. 그러면&lt;span&gt;&amp;nbsp;&lt;/span&gt;HelloScreen이&lt;span&gt;&amp;nbsp;&lt;/span&gt;name의 새 값에 따라 UI를 기술하도록 다시 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;restore-ui-state&quot; data-text=&quot;활동 또는 프로세스 재생성 후 UI 상태 복원&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;Compose 상태 복원&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;894&quot; data-filename=&quot;spanner-311765_1280.png&quot; width=&quot;464&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPt4Ob/btq6pZn35yj/giLsKJg4eqpEFdbkJemL00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPt4Ob/btq6pZn35yj/giLsKJg4eqpEFdbkJemL00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPt4Ob/btq6pZn35yj/giLsKJg4eqpEFdbkJemL00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPt4Ob%2Fbtq6pZn35yj%2FgiLsKJg4eqpEFdbkJemL00%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;894&quot; data-filename=&quot;spanner-311765_1280.png&quot; width=&quot;464&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;- 액티비티 또는 프로세스가 다시 생성된 이후&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;rememberSaveable&lt;/b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;을 사용하여 UI 상태를 복원&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- rememberSaveable&lt;/b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;은 재구성 과정 전체에서 상태를 유지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;/span&gt;&lt;b&gt;rememberSaveable&lt;/b&gt;&lt;span style=&quot;color: #202124;&quot;&gt;은 활동 및 프로세스 재생성 전반에 걸쳐 상태를 유지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;ways-to-store&quot; data-text=&quot;Ways to store state&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;상태를 저장하는 방법&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622657872361&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MyExample() {
    var selectedId by rememberSaveable&amp;lt;String?&amp;gt; { mutableStateOf(null) }
    /*...*/
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bundle에 추가되는 모든 데이터 유형은 자동으로 저장됩니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bundle에 추가할 수 없는 항목을 저장하려는 경우 몇 가지 옵션이 있습니다.&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;a href=&quot;https://github.com/Kotlin/KEEP/blob/master/proposals/extensions/android-parcelable.md&quot;&gt;@Parcelize&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;주석을 추가하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 객체가 parcelable이 되며 번들로 제공될 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1622657923121&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Parcelize
data class City(val name: String, val country: String)

@Composable
fun MyExample() {
  var selectedCity = rememberSaveable { mutableStateOf(City(&quot;Madrid&quot;, &quot;Spain&quot;)) }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;MapSaver&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;&lt;span style=&quot;color: #202124;&quot;&gt;어떤 이유로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;@Parcelize&lt;span style=&quot;color: #202124;&quot;&gt;가 적합하지 않을 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;mapSaver&lt;span style=&quot;color: #202124;&quot;&gt;를 사용하여 시스템이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Bundle&lt;span style=&quot;color: #202124;&quot;&gt;에 저장할 수 있는 값 집합으로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202124;&quot;&gt;객체를 변환하는 고유한 규칙을 정의할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622657986791&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class City(val name: String, val country: String)

val CitySaver = run {
    val nameKey = &quot;Name&quot;
    val countryKey = &quot;Country&quot;
    mapSaver(
        save = { mapOf(nameKey to it.name, countryKey to it.country) },
        restore = { City(it[nameKey] as String, it[countryKey] as String) }
    )
}

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City(&quot;Madrid&quot;, &quot;Spain&quot;))
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;ListSaver&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵에 대한 키를 정의 할 필요가 없도록&lt;span&gt;&amp;nbsp;&lt;/span&gt;listSaver&lt;span&gt;&amp;nbsp;&lt;/span&gt;인덱스를 키로&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용&lt;span&gt;&amp;nbsp;&lt;/span&gt;하고 사용할&lt;span&gt;&amp;nbsp;&lt;/span&gt;수도 있습니다&lt;span&gt;&amp;nbsp;&lt;/span&gt;.&lt;/p&gt;
&lt;pre id=&quot;code_1622658008455&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class City(val name: String, val country: String)

val CitySaver = listSaver&amp;lt;City, Any&amp;gt;(
    save = { listOf(it.name, it.country) },
    restore = { City(it[0] as String, it[1] as String) }
)

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City(&quot;Madrid&quot;, &quot;Spain&quot;))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포즈에 대해 알면 알수록 더 빠져드는것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상 Compose 상태관리에 대한 정리였습니다.&lt;/p&gt;</description>
      <category>안드로이드 Android</category>
      <category>Android</category>
      <category>AndroidCompose</category>
      <category>Android개발</category>
      <category>compose</category>
      <category>안드로이드</category>
      <category>안드로이드Compose</category>
      <category>안드로이드Compose사용법</category>
      <category>안드로이드UI</category>
      <category>안드로이드개발</category>
      <category>컴포즈</category>
      <author>SEOBI서비</author>
      <guid isPermaLink="true">https://gift123.tistory.com/34</guid>
      <comments>https://gift123.tistory.com/34#entry34comment</comments>
      <pubDate>Thu, 3 Jun 2021 03:35:21 +0900</pubDate>
    </item>
  </channel>
</rss>