<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>SWEE IT</title>
    <link>https://sweetburble.tistory.com/</link>
    <description>개발자를 꿈꿉니다</description>
    <language>ko</language>
    <pubDate>Tue, 7 Jul 2026 18:31:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>달거달거</managingEditor>
    <image>
      <title>SWEE IT</title>
      <url>https://tistory1.daumcdn.net/tistory/5380716/attach/e766acbee022415f9a9a6d4097466330</url>
      <link>https://sweetburble.tistory.com</link>
    </image>
    <item>
      <title>[IntelliJ 또는 Android Studio] 콘솔에서 한글 출력이 깨질 때</title>
      <link>https://sweetburble.tistory.com/303</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;25년 2월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT나 인터넷 검색에서 나오는 여러 방법들을 써봐도, 여전히 한글이 깨진다면 아래 문제일 수도 있다.&lt;/p&gt;
&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;JRE 버전을 18미만(17, 11...)으로 맞춰야 한다.&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. JRE 버전을 17로 지정하는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 JDK 17은 다운로드되어 있어야 하고, 설치된 위치도 알아야 할 때도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1725&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kyRmr/btsMkxyalbV/xqzzziqzN65TWtPLElISo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kyRmr/btsMkxyalbV/xqzzziqzN65TWtPLElISo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kyRmr/btsMkxyalbV/xqzzziqzN65TWtPLElISo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkyRmr%2FbtsMkxyalbV%2FxqzzziqzN65TWtPLElISo0%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;1725&quot; height=&quot;231&quot; data-origin-width=&quot;1725&quot; data-origin-height=&quot;231&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: #006dd7;&quot;&gt;&lt;b&gt;Edit Configurations 클릭&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-origin-width=&quot;1051&quot; data-origin-height=&quot;861&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJQYLD/btsMlFILCrf/yHoIeKuZrHA7qXGAqxlQYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJQYLD/btsMlFILCrf/yHoIeKuZrHA7qXGAqxlQYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJQYLD/btsMlFILCrf/yHoIeKuZrHA7qXGAqxlQYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJQYLD%2FbtsMlFILCrf%2FyHoIeKuZrHA7qXGAqxlQYK%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;1051&quot; height=&quot;861&quot; data-origin-width=&quot;1051&quot; data-origin-height=&quot;861&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: #006dd7;&quot;&gt;&lt;b&gt;기본적으로 프로젝트를 생성하면, JRE가 위처럼 아무것도 설정되어 있지 않을것이다.&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-origin-width=&quot;700&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cN3WS4/btsMlOZRg64/PXRMNKU1Yuk65Ke1SD4Er0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cN3WS4/btsMlOZRg64/PXRMNKU1Yuk65Ke1SD4Er0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cN3WS4/btsMlOZRg64/PXRMNKU1Yuk65Ke1SD4Er0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcN3WS4%2FbtsMlOZRg64%2FPXRMNKU1Yuk65Ke1SD4Er0%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;700&quot; height=&quot;296&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;296&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;설치되어 있는 JDK나 JBR 중 아무거나 18버전 미만인 것을 골라서, 설정한다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qMGyF/btsMmMNKB8R/W91pFqgqsRfxfOkHsWwuok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qMGyF/btsMmMNKB8R/W91pFqgqsRfxfOkHsWwuok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qMGyF/btsMmMNKB8R/W91pFqgqsRfxfOkHsWwuok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqMGyF%2FbtsMmMNKB8R%2FW91pFqgqsRfxfOkHsWwuok%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;527&quot; height=&quot;339&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;정상적으로 한글이 출력된다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. [실패] 새로운 Android 프로젝트를 생성할 때도, JRE 버전을 17로 설정하도록 하기&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;907&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kbz7t/btsMkJrEtgi/iotRdhHVIkVkNU4ECyixbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kbz7t/btsMkJrEtgi/iotRdhHVIkVkNU4ECyixbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kbz7t/btsMkJrEtgi/iotRdhHVIkVkNU4ECyixbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKbz7t%2FbtsMkJrEtgi%2FiotRdhHVIkVkNU4ECyixbk%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;1188&quot; height=&quot;907&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;907&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;865&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beBj35/btsMlDYxg7o/iZC3MaNq7IoSQWcc3zAkR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beBj35/btsMlDYxg7o/iZC3MaNq7IoSQWcc3zAkR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beBj35/btsMlDYxg7o/iZC3MaNq7IoSQWcc3zAkR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeBj35%2FbtsMlDYxg7o%2FiZC3MaNq7IoSQWcc3zAkR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1086&quot; height=&quot;865&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;865&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;&quot;Edit configuration template&quot;에서 이렇게 설정해도 안된다...&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;</description>
      <category>개발 환경/Kotlin</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/303</guid>
      <comments>https://sweetburble.tistory.com/303#entry303comment</comments>
      <pubDate>Mon, 17 Feb 2025 18:20:08 +0900</pubDate>
    </item>
    <item>
      <title>Docker 이모저모</title>
      <link>https://sweetburble.tistory.com/302</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;25년 1월을 기준으로 작성되었습니다&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Dockerfile이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;Dockerfile은 도커 이미지를 생성하기 위한 설계도 또는 명세서의 역할을 합니다. 주요 특징과 동작 방식을 설명해드리겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;기본 역할&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;Dockerfile은 도커 이미지를 자동으로 빌드하기 위한 모든 명령어들을 포함하는 텍스트 파일이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: oklch(0.304 0.04 213.681); 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;h4 style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;동작 방식&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;&lt;b&gt;이미지 빌드 시점에만 사용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Dockerfile은 오직&amp;nbsp;docker build&amp;nbsp;명령어를 실행할 때만 참조된다.&lt;/li&gt;
&lt;li&gt;빌드 과정에서 Dockerfile의 각 명령어는 새로운 레이어를 생성한다.&lt;/li&gt;
&lt;li&gt;컨테이너 실행 시점에는 더 이상 Dockerfile을 참조하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;&lt;b&gt;레이어 구조&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주요 장점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;&lt;b&gt;재현성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일한 Dockerfile로 언제든지 같은 환경의 이미지를 생성할 수 있다.&lt;/li&gt;
&lt;li&gt;이미지 생성 과정이 문서화되어 유지보수가 용이하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;&lt;b&gt;자동화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수동으로 컨테이너를 구성하고 커밋하는 방식보다 효율적이다.&lt;/li&gt;
&lt;li&gt;CI/CD 파이프라인에 쉽게 통합할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;따라서 Dockerfile은 이미지를 빌드하는 시점에만 사용되며, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;컨테이너 실행 시에는 이미 빌드된 이미지만을 참조한다는 점을 명확히 이해하는 것이 중요!&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발 환경</category>
      <category>docker</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/302</guid>
      <comments>https://sweetburble.tistory.com/302#entry302comment</comments>
      <pubDate>Sat, 18 Jan 2025 12:13:27 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 소소한 팁</title>
      <link>https://sweetburble.tistory.com/301</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 11월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. APK 파일 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1732684907156&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter build apk --release --target-platform=android-arm64
flutter build apk --debug --target-platform=android-arm64&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;fvm을 사용하면, 가장 앞에 fvm만 붙이면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;release :&amp;nbsp; 디버깅 및 런타임 오류 보고 기능 비활성화, 앱 사이즈가 작다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;debug : 디버깅 및 런타임 오류 보고 기능 활성화, 앱 사이즈가 크다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dart와 Flutter</category>
      <category>Flutter</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/301</guid>
      <comments>https://sweetburble.tistory.com/301#entry301comment</comments>
      <pubDate>Wed, 27 Nov 2024 14:23:14 +0900</pubDate>
    </item>
    <item>
      <title>AntiForgery 프로젝트 분석</title>
      <link>https://sweetburble.tistory.com/300</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 11월을 기준으로 작성되었습니다&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/AbstractTeen/AntiForgery&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/AbstractTeen/AntiForgery&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731940127332&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 - AbstractTeen/AntiForgery&quot; data-og-description=&quot;Contribute to AbstractTeen/AntiForgery development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/AbstractTeen/AntiForgery&quot; data-og-url=&quot;https://github.com/AbstractTeen/AntiForgery&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cxbUxK/hyXzVNpWf4/jBlw8F2vPLaR77DazgPjd1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/olhm7/hyXzWFxZG7/AcrUef9hisDInf3DpMdtiK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/AbstractTeen/AntiForgery&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/AbstractTeen/AntiForgery&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cxbUxK/hyXzVNpWf4/jBlw8F2vPLaR77DazgPjd1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/olhm7/hyXzWFxZG7/AcrUef9hisDInf3DpMdtiK/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 - AbstractTeen/AntiForgery&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to AbstractTeen/AntiForgery 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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 간단한 논문 정리&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;b&gt;연구 배경 및 문제점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- DeepFake&lt;/span&gt;는 개인의 프라이버시와 정치적 안보에 실질적인 위협이 되고 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;기존의 수동적&lt;span&gt; DeepFake &lt;/span&gt;탐지 방식은 사후 대응에 불과하며&lt;span&gt;, &lt;/span&gt;허위정보 확산을 사전에 막지 못함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&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;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;핵심 아이디어&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Lab &lt;/span&gt;색공간에서 지각적으로 자연스러운 교란을 생성하여&lt;span&gt; DeepFake &lt;/span&gt;생성을 방해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;기존&lt;span&gt; RGB &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;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. &lt;/span&gt;시각적 자연스러움 유지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. &lt;/span&gt;입력 변환&lt;span&gt;(&lt;/span&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&gt;3. &lt;/span&gt;다양한&lt;span&gt; DeepFake &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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;b&gt;실험 결과&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;세 가지 유형의&lt;span&gt; DeepFake(&lt;/span&gt;속성 편집&lt;span&gt;, &lt;/span&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&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 data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로젝트 의의&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. &lt;/span&gt;지각적 적대적 공격에 대한 새로운 연구 방향 제시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. DeepFake &lt;/span&gt;방어를 위한 실용적이고 강건한 해결책 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3. &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;특히&lt;span&gt; DeepFake &lt;/span&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. utils.py 분석&lt;/b&gt;&lt;/h2&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;이 파일은 AntiForgery 프로젝트의 유틸리티 함수들을 포함하고 있으며,&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;span style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot;&gt;&lt;b&gt;핵심 유틸리티 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;모델 가중치 로딩&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이미지 정규화/역정규화&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;레이블 처리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이미지 품질 평가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;LAB 색공간 기반 적대적 공격&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;핵심 함수들&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 데이터 전처리 함수&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- `load_model_weights()`: 사전 학습된 모델 가중치를 로드하며, 'preprocessing' 키를 제외한 가중치만 선택적으로 로드한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- `denorm()`: [-1, 1] 범위의 값을 [0, 1] 범위로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- `label2onehot()`: 레이블 인덱스를 원-핫 인코딩 벡터로 변환&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;2. 레이블 처리 함수&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- `create_labels()`: CelebA나 RaFD 데이터셋에 대한 타겟 도메인 레이블 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;-&lt;b&gt;&amp;gt; &lt;span&gt;CelebA의 경우 헤어 컬러 속성을 특별히 처리&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;size18&quot;&gt;&lt;b&gt;3. 이미지 변환/비교 함수 (&lt;span&gt;최소 이미지 크기(7x7) 보장을 위한 리사이징 로직 포함)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- `compare()`: 두 이미지 간의 SSIM과 PSNR 계산&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;5. 적대적 공격 함수&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;`lab_attack()`:&amp;nbsp;LAB&amp;nbsp;색공간에서&amp;nbsp;적대적&amp;nbsp;공격을&amp;nbsp;수행하는&amp;nbsp;핵심&amp;nbsp;함수&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;lab_attack 함수 상세 분석&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1731939273773&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def lab_attack(X_nat, c_trg, model, epsilon=0.05, iter=100):&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;주요 매개변수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- X_nat : 원본 이미지 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;- c_trg : 타겟 조건 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;- model : 대상 모델 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;- epsilon : 교란의 최대 크기 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;- iter : 반복 횟수&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;size18&quot;&gt;&lt;b&gt;동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;초기화&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;a와 b 채널에 대한 섭동(perturbation) 초기화&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Adam 옵티마이저 설정&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;반복적 공격 과정&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;RGB &amp;rarr; LAB 변환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;섭동 적용 및 클리핑&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;LAB &amp;rarr; RGB 역변환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;StarGAN을 통한 이미지 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;손실 계산 및 역전파&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;안정성 확보&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;NaN 값 검사&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;그래디언트 리셋&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;예외 처리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;이 함수는 논문에서 제안한 &quot;perceptual-aware perturbations&quot; 생성의 핵심 구현부라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. color_space.py 분석&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;b&gt;주요 기능&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일은&lt;span&gt; RGB&lt;/span&gt;와&lt;span&gt; Lab &lt;/span&gt;색공간 간의 변환을 위한 핵심 함수들을 구현하고 있다&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;색공간 변환은&lt;span&gt; RGB &amp;rarr; XYZ &amp;rarr; Lab &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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;색공간 변환 함수들&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;1) RGB &amp;harr; XYZ &lt;/span&gt;변환&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1731939408948&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def rgb2xyz(rgb):&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;&lt;span&gt;감마 보정을 적용하여 비선형 RGB 값을 선형화&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;RGB 채널별 가중치를 적용하여 XYZ 좌표계로 변환&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;출력값을 [0,1] 범위로 클리핑&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;pre id=&quot;code_1731939425829&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def xyz2rgb(xyz):&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;&lt;span&gt;XYZ 좌표계에서 RGB 좌표계로의 역변환 행렬 적용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;결과값을 [0,1] 범위로 클리핑하여 유효한 RGB 값 보장&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;2) XYZ &amp;harr; Lab &lt;/span&gt;변환&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1731939452885&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def xyz2lab(xyz):&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;&lt;span&gt;D65 표준 광원 기준 화이트포인트(0.95047, 1.0, 1.08883) 적용&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;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;L*(명도), a*(녹-적), b*(청-황) 채널로 변환&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;pre id=&quot;code_1731939462585&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def lab2xyz(lab):&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;- Lab&lt;/span&gt;에서&lt;span&gt; XYZ&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;- D65 &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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;3) RGB &amp;harr; Lab &lt;/span&gt;직접 변환&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1731939478947&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def rgb2lab(rgb):&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;- RGB&lt;/span&gt;를&lt;span&gt; Lab&lt;/span&gt;으로 변환하는 편의 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- L &lt;/span&gt;채널을&lt;span&gt; [-50,50] &lt;/span&gt;범위로&lt;span&gt;, ab &lt;/span&gt;채널을&lt;span&gt; [-110,110] &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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;pre id=&quot;code_1731939491136&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def lab2rgb(lab_rs):&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;정규화된&lt;span&gt; Lab &lt;/span&gt;값을&lt;span&gt; RGB&lt;/span&gt;로 역변환하는 편의 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- L &lt;/span&gt;채널을&lt;span&gt; [0,100], ab &lt;/span&gt;채널을&lt;span&gt; [-110,110] &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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구현 특징&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. PyTorch &lt;/span&gt;텐서 연산 사용으로&lt;span&gt; GPU &lt;/span&gt;가속 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. &lt;/span&gt;배치 처리 지원&lt;span&gt; (B x C x H x W &lt;/span&gt;형태의 텐서 처리&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3. &lt;/span&gt;수치적 안정성을 위한 마스킹과 클리핑 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;4. D65 &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;이 구현은&lt;span&gt; AntiForgery &lt;/span&gt;프로젝트에서&lt;span&gt; Lab &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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. data_loader.py 분석&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;b&gt;주요 구성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일은&lt;span&gt; CelebA &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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;CelebA &lt;/span&gt;클래스&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1731939574760&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CelebA(data.Dataset):&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; PyTorch&lt;/span&gt;의&lt;span&gt; Dataset &lt;/span&gt;클래스를 상속받아&lt;span&gt; CelebA &lt;/span&gt;데이터셋을 처리한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주요 메서드들&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;1. &lt;/span&gt;초기화 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731939607868&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def __init__(self, image_dir, attr_path, selected_attrs, transform, mode):&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;- image_dir: &lt;/span&gt;이미지 디렉토리 경로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- attr_path: &lt;/span&gt;속성 파일 경로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- selected_attrs: &lt;/span&gt;선택된 속성들의 리스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- transform: &lt;/span&gt;이미지 변환을 위한 함수들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- mode: 'train' &lt;/span&gt;또는&lt;span&gt; 'test' &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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;2. &lt;/span&gt;전처리 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731939625275&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def preprocess(self):&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;속성 파일을 읽어서 속성 이름과 인덱스 매핑 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;데이터셋을&lt;span&gt; train/test&lt;/span&gt;로 분할&lt;span&gt; (&lt;/span&gt;처음&lt;span&gt; 2000&lt;/span&gt;개는 테스트용&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;랜덤 시드&lt;span&gt; 1234&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;3. &lt;/span&gt;데이터 접근 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731939637576&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def __getitem__(self, index):&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;인덱스에 해당하는 이미지와 레이블 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;이미지는&lt;span&gt; PIL Image&lt;/span&gt;로 로드 후&lt;span&gt; transform &lt;/span&gt;적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;레이블은&lt;span&gt; FloatTensor&lt;/span&gt;로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 로더 함수&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1731939675403&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def get_loader(image_dir, attr_path, selected_attrs, crop_size=178, image_size=256,

               batch_size=1, dataset='CelebA', mode='test', num_workers=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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이미지 변환 파이프라인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. &lt;/span&gt;학습 모드일 경우 랜덤 수평 뒤집기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. &lt;/span&gt;중앙 크롭&lt;span&gt; (crop_size)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3. &lt;/span&gt;이미지 크기 조정&lt;span&gt; (image_size)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;4. &lt;/span&gt;텐서 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;5. &lt;/span&gt;정규화&lt;span&gt; (mean=0.5, std=0.5)&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;데이터 로더 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;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 data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;멀티프로세싱을 위한&lt;span&gt; worker &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;이 구현은&lt;span&gt; DeepFake &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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. model.py 분석&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;b&gt;주요 구성요소&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일은&lt;span&gt; GAN(Generative Adversarial Network) &lt;/span&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&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;1. ResidualBlock &lt;/span&gt;클래스&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1731939772762&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ResidualBlock(nn.Module):&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;잔차 연결&lt;span&gt;(residual connection)&lt;/span&gt;을 포함한 기본 블록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Instance Normalization&lt;/span&gt;과&lt;span&gt; ReLU &lt;/span&gt;활성화 함수 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- 3x3 &lt;/span&gt;컨볼루션 레이어&lt;span&gt; 2&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;2. Generator &lt;/span&gt;클래스&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1731939786394&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Generator(nn.Module):&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;- U-Net &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;&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- &lt;/span&gt;입력 레이어&lt;span&gt; (7x7 &lt;/span&gt;컨볼루션&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- &lt;/span&gt;다운샘플링 레이어&lt;span&gt; (2&lt;/span&gt;회&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- &lt;/span&gt;병목 레이어&lt;span&gt; (ResidualBlock &lt;/span&gt;반복&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- &lt;/span&gt;업샘플링 레이어&lt;span&gt; (2&lt;/span&gt;회&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- &lt;/span&gt;출력 레이어&lt;span&gt; (Tanh &lt;/span&gt;활성화&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;3. Discriminator &lt;/span&gt;클래스&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1731939806156&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Discriminator(nn.Module):&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;- PatchGAN &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;&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;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- LeakyReLU &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;소스 판별&lt;span&gt;(out_src)&lt;/span&gt;과 클래스 분류&lt;span&gt;(out_cls) &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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;4. AvgBlurGenerator &lt;/span&gt;클래스&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1731939830610&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AvgBlurGenerator(nn.Module):&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;- Generator&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;- &lt;/span&gt;기본&lt;span&gt; Generator&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;1. &lt;/span&gt;정규화 기법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Instance Normalization &lt;/span&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&gt;- affine=True&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;size18&quot;&gt;&lt;b&gt;&lt;span&gt;2. &lt;/span&gt;조건부 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;도메인 정보&lt;span&gt;(c)&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;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;3. &lt;/span&gt;특징 맵 추출&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Generator&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;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;4. &lt;/span&gt;블러 처리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731939869624&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def avg_smoothing_filter(channels, kernel_size):&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;평균 스무딩 필터 구현&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;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구현은&lt;span&gt; DeepFake &lt;/span&gt;방지를 위한 적대적 학습에 초점을 맞추고 있으며&lt;span&gt;,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 생성자 네트워크에서 중간 특징 맵의 추출과 블러 처리 기능이 중요한 역할을 하는 것으로 보인다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. main.py 분석&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;b&gt;주요 기능&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일은&lt;span&gt; AntiForgery &lt;/span&gt;프로젝트의 메인 실행 파일로&lt;span&gt;, StarGAN&lt;/span&gt;을 이용한&lt;span&gt; DeepFake &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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구조 분석&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;1. &lt;/span&gt;설정 및 초기화&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1731939932050&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def main():
    parser = argparse.ArgumentParser()&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;모델 설정&lt;span&gt; (G/D &lt;/span&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&gt;- &lt;/span&gt;데이터 설정&lt;span&gt; (&lt;/span&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&gt;- &lt;/span&gt;경로 설정&lt;span&gt; (&lt;/span&gt;이미지&lt;span&gt;, &lt;/span&gt;모델&lt;span&gt;, &lt;/span&gt;결과 디렉토리&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/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;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1. 모델 구성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- c_dim: &lt;/span&gt;도메인 레이블 차원&lt;span&gt; (&lt;/span&gt;기본값&lt;span&gt;: 5)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- image_size: &lt;/span&gt;이미지 해상도&lt;span&gt; (256x256)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;g_conv_dim/d_conv_dim: 생성자/판별자의 첫 레이어 필터 수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;g_repeat_num/d_repeat_num: 생성자/판별자의 반복 블록 수&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: #409d00;&quot;&gt;&lt;b&gt;2. 데이터 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- batch_size: 미니배치 크기 (기본값: 1)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- attack_iters: &lt;/span&gt;적대적 공격 반복 횟수&lt;span&gt; (100)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- selected_attrs: CelebA &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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;2. &lt;/span&gt;모델 설정&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1731939961392&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;G = Generator(config.g_conv_dim, config.c_dim, config.g_repeat_num)

D = Discriminator(config.image_size, config.d_conv_dim, config.c_dim, config.d_repeat_num)&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;- Generator&lt;/span&gt;와&lt;span&gt; Discriminator &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;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;pre id=&quot;code_1731939976391&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for i, (x_real, c_org) in enumerate(celeba_loader):&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;각 이미지에 대해서,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; 1. Lab &lt;/span&gt;색공간에서 적대적 공격 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; 2. &lt;/span&gt;원본과 변조된 이미지에 대한&lt;span&gt; StarGAN &lt;/span&gt;변환 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; 3. &lt;/span&gt;메트릭 계산&lt;span&gt; (L2 &lt;/span&gt;오차&lt;span&gt;, SSIM, PSNR, 왜곡된 이미지 비율)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; 4. 결과 이미지 저장&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;pre id=&quot;code_1731940001364&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;x_concat = torch.cat(x_fake_list, dim=3)

save_image(denorm(x_concat.data.cpu()), result_path, nrow=1, padding=0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- L2 &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;- SSIM (&lt;/span&gt;구조적 유사도&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- PSNR (&lt;/span&gt;최대 신호 대 잡음비&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &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;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;1. &lt;/span&gt;실험 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;배치 크기&lt;span&gt; 1&lt;/span&gt;로 설정하여 이미지별 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;첫&lt;span&gt; 50&lt;/span&gt;개 이미지에 대해서만 실험 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- GPU &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;h4 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;- &lt;/span&gt;원본&lt;span&gt; DeepFake&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;- &lt;/span&gt;왜곡 임계값&lt;span&gt; 0.05 &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;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;span&gt;- &lt;/span&gt;원본&lt;span&gt;, &lt;/span&gt;변조된 입력&lt;span&gt;, &lt;/span&gt;각각의&lt;span&gt; DeepFake &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;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>머신러닝</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/300</guid>
      <comments>https://sweetburble.tistory.com/300#entry300comment</comments>
      <pubDate>Mon, 18 Nov 2024 23:28:05 +0900</pubDate>
    </item>
    <item>
      <title>강의 내용 정리 - [Flutter로 메신저 앱 만들기]</title>
      <link>https://sweetburble.tistory.com/299</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 10월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. [섹션 8_로딩 화면] GlobalLoaderOverlay 위젯 사용 중 수정사항&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1730100419293&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  return GlobalLoaderOverlay(
    overlayWidgetBuilder: null, // useDefaultLoaing: false 대체
    child: MaterialApp.router(
      // navigatorKey: App.navigatorKey, // GoRouter를 사용하면 이 부분을 삭제하고, GoRouter 정의부 key 파라미터에 넣는다
      debugShowCheckedModeBanner: false,
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      title: 'THECommunity',
      theme: context.themeType.themeData,
      // home: const OTPScreen(),
      routerConfig: router,
    ),
  );&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;GlobalLoaderOverlay 위젯에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;useDefaultLoaing 옵션이 Deprecated&lt;/b&gt;&lt;/span&gt; 되었으므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의 내용처럼 &lt;u&gt;&lt;b&gt;overlayWidgetBuilder 옵션&lt;/b&gt;&lt;/u&gt;에 나만의 overlayWidget을 설정하든지, 아니면 null을 넣어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 본 강의에서 사용하는 패키지들&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1. permission_handler&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패키지는 권한을 요청하고 상태를 확인하는 크로스 플랫폼(iOS, Android) API를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 사용자가 권한을 부여할 수 있도록 기기의 앱 설정을 열 수도 있다.&lt;br /&gt;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;a href=&quot;https://pub.dev/packages/permission_handler&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/permission_handler&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730703928093&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;permission_handler | Flutter package&quot; data-og-description=&quot;Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/permission_handler&quot; data-og-url=&quot;https://pub.dev/packages/permission_handler&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baquON/hyXsUAMMyN/FQwZmFvu8v0iQWIerhMA4K/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cXlaZm/hyXsT9Hcz2/zZ7VFJnFS9UFoxinvVXb41/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/permission_handler&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/permission_handler&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baquON/hyXsUAMMyN/FQwZmFvu8v0iQWIerhMA4K/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cXlaZm/hyXsT9Hcz2/zZ7VFJnFS9UFoxinvVXb41/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;permission_handler | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-2. flutter_contacts&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: start;&quot;&gt;Android 및 iOS에서 기본 연락처를 읽고, 만들고, 업데이트하고, 삭제하고, 관찰할 수 있는 플러터 플러그인으로, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: start;&quot;&gt;그룹 지원, vCard 지원, 연락처 권한 처리 기능을 제공한다.&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: #4a4a4a; text-align: start;&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/flutter_contacts&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/flutter_contacts&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730703919605&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;flutter_contacts | Flutter package&quot; data-og-description=&quot;Flutter plugin to read, create, update, delete and observe native contacts on Android and iOS, with group support, vCard support, and contact permission handling&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/flutter_contacts&quot; data-og-url=&quot;https://pub.dev/packages/flutter_contacts&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dji1pW/hyXsZa1RKK/SaRMBlKX0OqCuKnBmedQt0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/qWW6s/hyXsX5lXMM/6QQjDe3LNE3n4I2KnhRTKk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/flutter_contacts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/flutter_contacts&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dji1pW/hyXsZa1RKK/SaRMBlKX0OqCuKnBmedQt0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/qWW6s/hyXsX5lXMM/6QQjDe3LNE3n4I2KnhRTKk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;flutter_contacts | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flutter plugin to read, create, update, delete and observe native contacts on Android and iOS, with group support, vCard support, and contact permission handling&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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 폴더의&lt;b&gt; AndroidManifest.xml&lt;/b&gt;에 다음과 같은 코드를 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1730704113299&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;com.bandi.THECommunity&quot;&amp;gt;

    &amp;lt;uses-permission android:name=&quot;android.permission.READ_CONTACTS&quot;/&amp;gt;
    &amp;lt;uses-permission android:name=&quot;android.permission.WRITE_CONTACTS&quot;/&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dart와 Flutter</category>
      <category>DART</category>
      <category>Flutter</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/299</guid>
      <comments>https://sweetburble.tistory.com/299#entry299comment</comments>
      <pubDate>Mon, 28 Oct 2024 16:28:16 +0900</pubDate>
    </item>
    <item>
      <title>Provider와 Riverpod</title>
      <link>https://sweetburble.tistory.com/298</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 10월을 기준으로 작성된 글입니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Riverpod 상태관리 패키지&lt;/span&gt;&lt;span&gt;는&lt;span&gt; 기존 Provider 패키지&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; R&amp;eacute;mi Rousselet&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;1. BuildContext&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;에 의존하지 않는 설계&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;기존 Provider 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 한계점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Provider&lt;/span&gt;&lt;span&gt;는 위젯 트리에 접근하기 위해&lt;span&gt;&amp;nbsp;BuildContext&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&gt;&lt;span&gt;위젯 재구성 과정에서&lt;span&gt;&amp;nbsp;BuildContext&lt;/span&gt;의 유효성이 변경될 수 있어&lt;span&gt;, &lt;/span&gt;예상치 못한 에러가 발생할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Riverpod 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 개선점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Riverpod&lt;/span&gt;&lt;span&gt;는&lt;span&gt;&amp;nbsp;BuildContext&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&gt;&lt;span&gt;이는 테스트 작성 시에도 유용하며&lt;span&gt;, &lt;/span&gt;상태를 독립적으로 관리하고자 할 때 큰 이점을 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;2. &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;전역적이며 안정적인 상태 관리&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;기존 Provider 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 한계점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Provider&lt;/span&gt;&lt;span&gt;는 상태를 제공하기 위해 위젯 트리에 반드시 포함되어야 한다&lt;span&gt;. &lt;/span&gt;이는 상태의 범위를 제한하고&lt;span&gt;, &lt;/span&gt;전역적인 상태 관리에 어려움을 준다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;상태를 공유하기 위해 위젯 트리 상에서 올바른 위치에&lt;span&gt; Provider&lt;/span&gt;를 배치해야 하며&lt;span&gt;, &lt;/span&gt;이는 복잡한 앱에서 관리가 어려울 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Riverpod 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 개선점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Riverpod&lt;/span&gt;&lt;span&gt;는 프로바이더를 전역적으로 선언하고 관리할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;상태는 위젯 트리와 분리되어 있으므로&lt;span&gt;, &lt;/span&gt;앱 어디에서나 동일한 상태에 접근할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;이는 코드 구조를 단순화하고&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;상태 관리의 일관성을 높여준다&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;3. &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;span&gt;기존 Provider 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 한계점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Provider&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&gt;&lt;span&gt;상태 간의 의존성을 명시적으로 표현하기 어렵다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Riverpod 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 개선점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Riverpod&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&gt;&lt;span&gt;Provider&amp;nbsp;&lt;/span&gt;&lt;span&gt;외에도&lt;span&gt;&amp;nbsp;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;StateNotifierProvider,&amp;nbsp;FutureProvider,&amp;nbsp;StreamProvider,&amp;nbsp;ChangeNotifierProvider&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;등 다양한 프로바이더 타입을 제공하여 다양한 상태 관리 패턴을 지원한다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;family&lt;/b&gt;&lt;/span&gt;&lt;span&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;color: #f89009;&quot;&gt;&lt;b&gt;&amp;nbsp;autoDispose&lt;/b&gt;&lt;/span&gt;&amp;nbsp;&lt;/span&gt;등의 기능을 통해 프로바이더를 매개변수화하거나 자동으로 자원을 해제할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;4. &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;향상된 성능과 메모리 관리&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;기존 Provider 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 한계점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Provider&lt;/span&gt;&lt;span&gt;는 필요하지 않은 상태도 메모리에 유지할 수 있으며&lt;span&gt;, &lt;/span&gt;이는 곧 메모리 낭비로 이어졌&lt;/span&gt;&lt;span&gt;다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;상태의 수명 주기를 세밀하게 관리하기 어렵다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Riverpod 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 개선점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Riverpod&lt;/span&gt;&lt;span&gt;는&lt;span&gt;&amp;nbsp;autoDispose&amp;nbsp;&lt;/span&gt;기능을 통해 사용되지 않는 상태를 자동으로 폐기하여 메모리 효율성을 높인다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;프로바이더의 수명 주기를 명확하게 설정할 수 있어 성능 최적화에 유리하다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;5. &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;향상된 개발자 경험&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;기존 Provider 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 한계점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Provider &lt;/span&gt;&lt;span&gt;사용 시 코드의 가독성과 유지보수가 어려워질 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;컴파일 타임에 오류를 감지하기 어렵고&lt;span&gt;, &lt;/span&gt;런타임 에러로 이어질 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Riverpod 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 개선점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Riverpod&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&gt;&lt;span&gt;프로바이더를 전역 변수로 선언하고 사용하므로&lt;span&gt;, &lt;/span&gt;코드의 가독성이 향상된다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;IDE&lt;/span&gt;&lt;span&gt;의 자동 완성 기능과 더욱 잘 통합되어 개발 생산성을 높인다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;기존 Provider 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 한계점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;BuildContext&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&gt;&lt;span&gt;상태를 Mocking하거나 대체하기 위한 추가 작업이 필요하다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Riverpod 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 개선점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;상태와 로직이 위젯 트리와 분리되어 있어&lt;span&gt;, &lt;/span&gt;단위 테스트가 용이하다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;프로바이더를 쉽게 Mocking하거나 오버라이드할 수 있어&lt;span&gt;, &lt;/span&gt;다양한 테스트 시나리오를 구현할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;7. &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;동시성 및 비동기 작업 지원&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;기존 Provider 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 한계점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;비동기 상태 관리에 제한이 있으며&lt;span&gt;, Future&lt;/span&gt;나&lt;span&gt; Stream&lt;/span&gt;을 처리하기 위해 추가적인 코드가 필요하다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Riverpod 패키지&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;의 개선점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;FutureProvider,&amp;nbsp;StreamProvider&lt;/b&gt;&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;등을 통해 비동기 작업을 간단하게 관리할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;상태의 변경에 따른 재빌드와 업데이트가 자동으로 처리된다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;8. Hooks&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;와의 통합&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Riverpod&lt;/span&gt;&lt;span&gt;는&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; flutter_hooks 패키지&lt;/b&gt;&lt;/span&gt;와 자연스럽게 통합되어&lt;span&gt;, Hook &lt;/span&gt;기반의 코드를 더욱 효율적으로 작성할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이 Hooks&lt;/span&gt;&lt;span&gt;를 사용하여, 상태 관리 로직을 재사용 가능하고 간결하게 작성할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/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>Dart와 Flutter</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/298</guid>
      <comments>https://sweetburble.tistory.com/298#entry298comment</comments>
      <pubDate>Fri, 11 Oct 2024 01:26:32 +0900</pubDate>
    </item>
    <item>
      <title>콩나물밥 - [유지만 식]</title>
      <link>https://sweetburble.tistory.com/297</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&lt;/b&gt;&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;양념장이 꽤나 괜찮았다.&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;edited_20240812_콩나물밥_유지만.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jcj78/btsI03NsEg6/ciBlI34yDem19ES9EYoKRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jcj78/btsI03NsEg6/ciBlI34yDem19ES9EYoKRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jcj78/btsI03NsEg6/ciBlI34yDem19ES9EYoKRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJcj78%2FbtsI03NsEg6%2FciBlI34yDem19ES9EYoKRK%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;500&quot; height=&quot;375&quot; data-filename=&quot;edited_20240812_콩나물밥_유지만.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/shorts/aLZEwkm4tGU&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/shorts/aLZEwkm4tGU&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/shorts/aLZEwkm4tGU&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/daElsq/hyWOlSYbhy/RkSBEwOMfHmOasLRT0dqyk/img.jpg?width=405&amp;amp;height=720&amp;amp;face=0_0_405_720,https://scrap.kakaocdn.net/dn/bpU8l4/hyWOiPucYW/kDvKY5rsq2s0wt1E3PlEWk/img.jpg?width=405&amp;amp;height=720&amp;amp;face=0_0_405_720&quot; data-video-width=&quot;405&quot; data-video-height=&quot;720&quot; data-video-origin-width=&quot;405&quot; data-video-origin-height=&quot;720&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;자취생들이 좋아할 영상&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/aLZEwkm4tGU&quot; width=&quot;405&quot; height=&quot;720&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/297</guid>
      <comments>https://sweetburble.tistory.com/297#entry297comment</comments>
      <pubDate>Mon, 12 Aug 2024 16:13:52 +0900</pubDate>
    </item>
    <item>
      <title>로제 리조또 - [프리한 요리 식]</title>
      <link>https://sweetburble.tistory.com/295</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240731_로제리조또_프리한요리.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coaDeB/btsISLZbpi9/1igZSIcSsM9NMeghApRSWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coaDeB/btsISLZbpi9/1igZSIcSsM9NMeghApRSWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coaDeB/btsISLZbpi9/1igZSIcSsM9NMeghApRSWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoaDeB%2FbtsISLZbpi9%2F1igZSIcSsM9NMeghApRSWk%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;500&quot; height=&quot;383&quot; data-filename=&quot;edited_20240731_로제리조또_프리한요리.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=TvSPL9tjcj8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=TvSPL9tjcj8&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=TvSPL9tjcj8&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bfz7S6/hyWG0WH91A/YcjKxt8PwrFBa1zE8snKm1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;로제 리조또(Ros&amp;eacute; risotto) 맨날 먹고 싶은 한그릇요리&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/TvSPL9tjcj8&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/295</guid>
      <comments>https://sweetburble.tistory.com/295#entry295comment</comments>
      <pubDate>Wed, 31 Jul 2024 10:28:12 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 소소한 팁</title>
      <link>https://sweetburble.tistory.com/294</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;24년 7월을 기준으로 작성되었습니다.&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Firebase에 사용할 SHA-1 / SHA-256 인증서 얻는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 프로젝트라면 -&amp;gt; 안드로이드 프로젝트 폴더에서, 안드로이드 프로젝트라면 -&amp;gt; 자체 폴더에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령 프롬프트를 열고, 아래의 명령어를 입력한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;gradlew signingReport&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. AVD is already running 오류 해결하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mainia.tistory.com/6768&quot;&gt;https://mainia.tistory.com/6768&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723447673931&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;안드로이드 스튜디오 AVD is already running 오류 해결하기&quot; data-og-description=&quot;안드로이드 에뮬레이터에 문제가 있어서 초기화하거나 다시 시작하는 과정에서 발생하는 AVD is already running 에러입니다. 물론 AVD Manager 에서 해당 에뮬레이터를 종료하면 되지만 최신 버전에는 s&quot; data-og-host=&quot;mainia.tistory.com&quot; data-og-source-url=&quot;https://mainia.tistory.com/6768&quot; data-og-url=&quot;https://mainia.tistory.com/6768&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/M8FxQ/hyWOqtd47R/Bedblom3Cz4HpP1KrUKJbK/img.png?width=800&amp;amp;height=515&amp;amp;face=0_0_800_515,https://scrap.kakaocdn.net/dn/wgZhk/hyWOpOCz72/KkmkdwfGk7dtpq0fZaSFnK/img.png?width=800&amp;amp;height=515&amp;amp;face=0_0_800_515,https://scrap.kakaocdn.net/dn/ByelK/hyWOfyrXWg/T9UTCZq34L7LoAzMmhBSDK/img.png?width=1250&amp;amp;height=931&amp;amp;face=0_0_1250_931&quot;&gt;&lt;a href=&quot;https://mainia.tistory.com/6768&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mainia.tistory.com/6768&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/M8FxQ/hyWOqtd47R/Bedblom3Cz4HpP1KrUKJbK/img.png?width=800&amp;amp;height=515&amp;amp;face=0_0_800_515,https://scrap.kakaocdn.net/dn/wgZhk/hyWOpOCz72/KkmkdwfGk7dtpq0fZaSFnK/img.png?width=800&amp;amp;height=515&amp;amp;face=0_0_800_515,https://scrap.kakaocdn.net/dn/ByelK/hyWOfyrXWg/T9UTCZq34L7LoAzMmhBSDK/img.png?width=1250&amp;amp;height=931&amp;amp;face=0_0_1250_931');&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;안드로이드 스튜디오 AVD is already running 오류 해결하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 에뮬레이터에 문제가 있어서 초기화하거나 다시 시작하는 과정에서 발생하는 AVD is already running 에러입니다. 물론 AVD Manager 에서 해당 에뮬레이터를 종료하면 되지만 최신 버전에는 s&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mainia.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 내 핸드폰에 와이파이로 디버깅 연결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ohdbjj.tistory.com/63&quot;&gt;https://ohdbjj.tistory.com/63&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1723447696710&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 Studio] USB 케이블이 없을 때 와이파이로 디버깅 연결하기&quot; data-og-description=&quot;들어가는 글.. 전에 ADB 무선 연결과 관련하여 포스팅을 작성한 적이 있습니다 https://ohdbjj.tistory.com/41 [Android Studio] adb 무선 연결(adb tcpip &amp;amp; adb connect) USB선을 통해 안드로이드 디버깅을 하는 경우, &quot; data-og-host=&quot;ohdbjj.tistory.com&quot; data-og-source-url=&quot;https://ohdbjj.tistory.com/63&quot; data-og-url=&quot;https://ohdbjj.tistory.com/63&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jW9yw/hyWOqzX9IO/eweHSiJrIcURceTzsNqCN1/img.png?width=737&amp;amp;height=754&amp;amp;face=0_0_737_754,https://scrap.kakaocdn.net/dn/YhVpj/hyWOgc3JcY/JjDbFiXBk4K13WCbE0Lp1k/img.png?width=737&amp;amp;height=754&amp;amp;face=0_0_737_754,https://scrap.kakaocdn.net/dn/DdF8k/hyWOdHqqA8/IK2nZsoCRGNvuStPgSAurK/img.jpg?width=1080&amp;amp;height=2309&amp;amp;face=0_0_1080_2309&quot;&gt;&lt;a href=&quot;https://ohdbjj.tistory.com/63&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ohdbjj.tistory.com/63&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jW9yw/hyWOqzX9IO/eweHSiJrIcURceTzsNqCN1/img.png?width=737&amp;amp;height=754&amp;amp;face=0_0_737_754,https://scrap.kakaocdn.net/dn/YhVpj/hyWOgc3JcY/JjDbFiXBk4K13WCbE0Lp1k/img.png?width=737&amp;amp;height=754&amp;amp;face=0_0_737_754,https://scrap.kakaocdn.net/dn/DdF8k/hyWOdHqqA8/IK2nZsoCRGNvuStPgSAurK/img.jpg?width=1080&amp;amp;height=2309&amp;amp;face=0_0_1080_2309');&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 Studio] USB 케이블이 없을 때 와이파이로 디버깅 연결하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가는 글.. 전에 ADB 무선 연결과 관련하여 포스팅을 작성한 적이 있습니다 https://ohdbjj.tistory.com/41 [Android Studio] adb 무선 연결(adb tcpip &amp;amp; adb connect) USB선을 통해 안드로이드 디버깅을 하는 경우,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ohdbjj.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 현재 편집 중인 파일 바로/항상 탐색기에서 접근하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://holika.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%8C%81-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-%ED%83%90%EC%83%89%EA%B8%B0%EC%97%90%EC%84%9C-%ED%98%84%EC%9E%AC-%ED%8E%B8%EC%A7%91-%EC%A4%91%EC%9D%B8-%ED%8C%8C%EC%9D%BC-%EB%B0%94%EB%A1%9C%ED%95%AD%EC%83%81-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://holika.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%8C%81-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-%ED%83%90%EC%83%89%EA%B8%B0%EC%97%90%EC%84%9C-%ED%98%84%EC%9E%AC-%ED%8E%B8%EC%A7%91-%EC%A4%91%EC%9D%B8-%ED%8C%8C%EC%9D%BC-%EB%B0%94%EB%A1%9C%ED%95%AD%EC%83%81-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723447727242&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;안드로이드 스튜디오에서 파일 편집을 하다가, 왼쪽 탐색기에서 지금 보고 있는 파일의 경로를 찾고 싶을 때가 있다. 프로젝트 &amp;gt; 앱 &amp;gt; 패키지 &amp;gt; 내부 패키지 &amp;gt; ... &amp;gt; 파일 의 경로를 직접 찾아 들어&quot; data-og-host=&quot;holika.tistory.com&quot; data-og-source-url=&quot;https://holika.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%8C%81-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-%ED%83%90%EC%83%89%EA%B8%B0%EC%97%90%EC%84%9C-%ED%98%84%EC%9E%AC-%ED%8E%B8%EC%A7%91-%EC%A4%91%EC%9D%B8-%ED%8C%8C%EC%9D%BC-%EB%B0%94%EB%A1%9C%ED%95%AD%EC%83%81-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0&quot; data-og-url=&quot;https://holika.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%8C%81-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-%ED%83%90%EC%83%89%EA%B8%B0%EC%97%90%EC%84%9C-%ED%98%84%EC%9E%AC-%ED%8E%B8%EC%A7%91-%EC%A4%91%EC%9D%B8-%ED%8C%8C%EC%9D%BC-%EB%B0%94%EB%A1%9C%ED%95%AD%EC%83%81-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JpAbo/hyWKvpqeSY/0nuY4gjRndHx7BSdwyGrXk/img.png?width=800&amp;amp;height=279&amp;amp;face=0_0_800_279,https://scrap.kakaocdn.net/dn/d81E29/hyWOj8Hu6Z/aI0reMOyvNYPdVNYtynmJk/img.png?width=800&amp;amp;height=279&amp;amp;face=0_0_800_279,https://scrap.kakaocdn.net/dn/G4xVb/hyWKuKO1u5/1BeSbSpQzCrGGaTKz6tKXk/img.png?width=822&amp;amp;height=415&amp;amp;face=0_0_822_415&quot;&gt;&lt;a href=&quot;https://holika.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%8C%81-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-%ED%83%90%EC%83%89%EA%B8%B0%EC%97%90%EC%84%9C-%ED%98%84%EC%9E%AC-%ED%8E%B8%EC%A7%91-%EC%A4%91%EC%9D%B8-%ED%8C%8C%EC%9D%BC-%EB%B0%94%EB%A1%9C%ED%95%AD%EC%83%81-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://holika.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%8C%81-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-%ED%83%90%EC%83%89%EA%B8%B0%EC%97%90%EC%84%9C-%ED%98%84%EC%9E%AC-%ED%8E%B8%EC%A7%91-%EC%A4%91%EC%9D%B8-%ED%8C%8C%EC%9D%BC-%EB%B0%94%EB%A1%9C%ED%95%AD%EC%83%81-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JpAbo/hyWKvpqeSY/0nuY4gjRndHx7BSdwyGrXk/img.png?width=800&amp;amp;height=279&amp;amp;face=0_0_800_279,https://scrap.kakaocdn.net/dn/d81E29/hyWOj8Hu6Z/aI0reMOyvNYPdVNYtynmJk/img.png?width=800&amp;amp;height=279&amp;amp;face=0_0_800_279,https://scrap.kakaocdn.net/dn/G4xVb/hyWKuKO1u5/1BeSbSpQzCrGGaTKz6tKXk/img.png?width=822&amp;amp;height=415&amp;amp;face=0_0_822_415');&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;안드로이드 스튜디오에서 파일 편집을 하다가, 왼쪽 탐색기에서 지금 보고 있는 파일의 경로를 찾고 싶을 때가 있다. 프로젝트 &amp;gt; 앱 &amp;gt; 패키지 &amp;gt; 내부 패키지 &amp;gt; ... &amp;gt; 파일 의 경로를 직접 찾아 들어&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;holika.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Flutter App 이름 변경하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blueming-dev.tistory.com/39&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blueming-dev.tistory.com/39&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723447789089&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;flutter app 이름 변경하기&quot; data-og-description=&quot;처음부터 다시 배운다는 마음으로 열심히 flutter에 대해서 공부하고 혹시 다음에도 또 찾을 것 같은 것들을 기록하기로 마음 먹었습니다. 전에도 다 했던 것들인데 다시 열어보니 정말 ??? 물음표&quot; data-og-host=&quot;blueming-dev.tistory.com&quot; data-og-source-url=&quot;https://blueming-dev.tistory.com/39&quot; data-og-url=&quot;https://blueming-dev.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQ7SQT/hyWKu49vSp/ssjde9sjD5B4j5h0Iuus9K/img.png?width=394&amp;amp;height=47&amp;amp;face=0_0_394_47,https://scrap.kakaocdn.net/dn/beTD5p/hyWKwIDUWg/mdKUIazRmMiUIkR9rME9PK/img.png?width=394&amp;amp;height=47&amp;amp;face=0_0_394_47,https://scrap.kakaocdn.net/dn/e53k4/hyWOnJ0xgH/ck3Dg8f7QSdmpHAuGbuWI0/img.png?width=511&amp;amp;height=891&amp;amp;face=0_0_511_891&quot;&gt;&lt;a href=&quot;https://blueming-dev.tistory.com/39&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blueming-dev.tistory.com/39&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQ7SQT/hyWKu49vSp/ssjde9sjD5B4j5h0Iuus9K/img.png?width=394&amp;amp;height=47&amp;amp;face=0_0_394_47,https://scrap.kakaocdn.net/dn/beTD5p/hyWKwIDUWg/mdKUIazRmMiUIkR9rME9PK/img.png?width=394&amp;amp;height=47&amp;amp;face=0_0_394_47,https://scrap.kakaocdn.net/dn/e53k4/hyWOnJ0xgH/ck3Dg8f7QSdmpHAuGbuWI0/img.png?width=511&amp;amp;height=891&amp;amp;face=0_0_511_891');&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;flutter app 이름 변경하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 다시 배운다는 마음으로 열심히 flutter에 대해서 공부하고 혹시 다음에도 또 찾을 것 같은 것들을 기록하기로 마음 먹었습니다. 전에도 다 했던 것들인데 다시 열어보니 정말 ??? 물음표&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blueming-dev.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드</category>
      <category>안드로이드</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/294</guid>
      <comments>https://sweetburble.tistory.com/294#entry294comment</comments>
      <pubDate>Fri, 19 Jul 2024 16:39:02 +0900</pubDate>
    </item>
    <item>
      <title>내용 정리 Part. 2</title>
      <link>https://sweetburble.tistory.com/292</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 7월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxS8VC/btsIwR7jgZV/ySErciB8hd3yKH9npCZP1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxS8VC/btsIwR7jgZV/ySErciB8hd3yKH9npCZP1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxS8VC/btsIwR7jgZV/ySErciB8hd3yKH9npCZP1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxS8VC%2FbtsIwR7jgZV%2FySErciB8hd3yKH9npCZP1K%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;518&quot; height=&quot;247&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;247&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Testing (테스트)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 앱 개발에서, 꼼꼼한 테스트는 고품질 앱을 만드는 필수적인 과정이다. 다행히 Flutter, React Native, Swift 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;Testing은 크게 3종류로 분류할 수 있다. &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;Unit Test, Widget Test, Integration Test&lt;/b&gt;&lt;/span&gt;의 개념을 간단히 알아보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Unit Test : 가장 작은 단위의 코드를 검증&lt;/b&gt;&lt;/h3&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;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;  장점 3가지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 빠른 실행 속도로 개발 단계에서 잦은 코드 변경에도 부담 없이 테스트를 실행할 수 있다. &lt;br /&gt;2. 코드의 특정 부분만 테스트하기 때문에 버그 발생 시 원인 파악이 용이하다. &lt;br /&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;size18&quot;&gt;&lt;b&gt;▶️ 3가지 예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 입력 값에 대한 함수의 반환 값 검증 &lt;br /&gt;2. 클래스 메서드의 예외 처리 로직 검증 &lt;br /&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unit Test 단계에서는 이러한 외부 요소들을 &lt;b&gt;Mocking(더미 객체)&lt;/b&gt; 하여 순수한 로직만 테스트하는 것이 중요하다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Flutter에서는 &quot;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;mockito&quot; 패키지&lt;/b&gt;&lt;/span&gt; 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/mockito&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/mockito&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721110854086&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;mockito | Dart package&quot; data-og-description=&quot;A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/mockito&quot; data-og-url=&quot;https://pub.dev/packages/mockito&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dEgmIS/hyWCPGiwqc/OS4Q9mCSbg233lEEJaGSsk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cgz2u8/hyWCA3rH89/RDV6TLnl0LMqsCUT5vkk9K/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/mockito&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/mockito&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dEgmIS/hyWCPGiwqc/OS4Q9mCSbg233lEEJaGSsk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cgz2u8/hyWCA3rH89/RDV6TLnl0LMqsCUT5vkk9K/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;mockito | Dart package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A mock framework inspired by Mockito with APIs for Fakes, Mocks, behavior verification, and stubbing.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ 테스트 input 값은 어떻게 정하는 게 좋을까?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;1. Edge Cases&lt;/b&gt;&lt;/span&gt; : 경계 값 (최댓값/최솟값)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;2. Corner Cases&lt;/b&gt;&lt;/span&gt; : Edge Case는 모두 통과하지만, 외부 요인 또는 특정 조건에서 문제가 발생할 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Widget Test : UI 요소의 동작과 속성을 검증&lt;/b&gt;&lt;/h3&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;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;  장점 3가지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 실제 기기나 에뮬레이터에서 UI를 시각적으로 확인하면서 테스트할 수 있다다. &lt;br /&gt;2. 사용자 인터랙션에 대한 테스트를 통해 앱의 사용성을 높일 수 있다. &lt;br /&gt;3. 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;▶️ 3가지 예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 버튼 클릭 시 특정 위젯이 화면에 표시되는지 검증 &lt;br /&gt;2. 텍스트 필드에 입력된 값이 다른 위젯에 반영되는지 검증 &lt;br /&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;모바일 환경에서, Widget Test는 다양한 화면 크기와 해상도에 대한 테스트가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 플랫폼별 UI 차이점을 고려하여 Android와 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;Flutter에서는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;flutter_test&quot; 패키지&lt;/b&gt;&lt;/span&gt;를 사용해서 테스트한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WidgetTester 를 활용하여 테스트 환경에서 위젯들 간의 상호작용을 검증한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Unit Test : test()&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Widget Test : testWidgets() &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Finder 클래스 활용하여 위젯을 search한다. Matcher를 활용하여 검증한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Integration Test : 여러 컴포넌트 간의 상호 작용을 검증&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;  장점 3가지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 실제 사용자 환경과 유사한 환경에서 테스트를 수행하여 앱의 안정성을 높일 수 있다. &lt;br /&gt;2. 여러 컴포넌트 간의 예상치 못한 문제를 사전에 발견하고 해결할 수 있다. &lt;br /&gt;3. Unit Test와 Widget Test로는 커버할 수 없는 복잡한 시나리오를 테스트할 수 있다.&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;▶️ 3가지 예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 로그인 기능 테스트 (로그인 입력, 서버 통신, 응답 처리, UI 업데이트) &lt;br /&gt;2. 데이터베이스 연동 테스트 (데이터 저장, 조회, 수정, 삭제) &lt;br /&gt;3. 결제 기능 테스트 (결제 요청, 승인, 취소, 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;/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;Flutter에서는&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&amp;nbsp;&quot;integration test&quot; 패키지&lt;/b&gt;&lt;/span&gt;를 사용해서 테스트한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; ✅ 정리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Unit Test는 핵심 로직의 안정성을 확보하는 기반을 마련한다. &lt;/b&gt;&lt;br /&gt;&lt;b&gt;Widget Test는 사용자 인터페이스의 정확성과 사용성을 보장한다. &lt;/b&gt;&lt;br /&gt;&lt;b&gt;Integration Test는 복잡한 앱의 안정적인 동작을 검증한다. &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;테스트 자동화 도구와 CI/CD 파이프라인을 활용하면 더욱 효율적이고 지속적인 테스트 환경을 구축할 수 있다!&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: #1a5490;&quot;&gt; Golden test&lt;/span&gt;는 특정 실제 위젯과 image 파일을 비교하는 테스트를 의미한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;더 나아가서, 특정 실제 위젯과 &lt;span style=&quot;color: #1a5490;&quot;&gt;snapshot(screenshot) test&lt;/span&gt;는 하나의 화면까지 확장해나간다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/golden_toolkit&quot;&gt;golden_toolkit | Flutter package (pub.dev)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721111296499&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;golden_toolkit | Flutter package&quot; data-og-description=&quot;Common patterns for screenshot-based widget testing using Goldens.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/golden_toolkit&quot; data-og-url=&quot;https://pub.dev/packages/golden_toolkit&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJkNXu/hyWCHhcglg/0y1vuFzdi9Ubj7WHokQjt0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/lwWrO/hyWCGWSVV6/UH6odtWcL9KbGpOdIkYQSk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cVffNa/hyWzCaKwHs/bGBqKfnf6xGMqhhRqbU5Pk/img.png?width=3251&amp;amp;height=4176&amp;amp;face=0_0_3251_4176&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/golden_toolkit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/golden_toolkit&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJkNXu/hyWCHhcglg/0y1vuFzdi9Ubj7WHokQjt0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/lwWrO/hyWCGWSVV6/UH6odtWcL9KbGpOdIkYQSk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cVffNa/hyWzCaKwHs/bGBqKfnf6xGMqhhRqbU5Pk/img.png?width=3251&amp;amp;height=4176&amp;amp;face=0_0_3251_4176');&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;golden_toolkit | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Common patterns for screenshot-based widget testing using Goldens.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Debugging (디버깅)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-1. logger 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  &lt;span style=&quot;color: #0593d3;&quot;&gt;장점 2가지&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. stack trace가 기본으로 제공되며, 아주 잘된다.&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;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;&lt;a href=&quot;https://pub.dev/packages/logger&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/logger&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721114992918&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;logger | Dart package&quot; data-og-description=&quot;Small, easy to use and extensible logger which prints beautiful logs.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/logger&quot; data-og-url=&quot;https://pub.dev/packages/logger&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qA9po/hyWCNBIxHO/tY8NhlXtlM2y4dWyKzykM1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/pzeGW/hyWzBCVZmJ/GLK8zUwQ0QLz61QWP59bs0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bCI5Da/hyWCGihWJj/KoFyjpueveV5hpk0LXI0lk/img.png?width=658&amp;amp;height=529&amp;amp;face=0_0_658_529&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/logger&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/logger&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qA9po/hyWCNBIxHO/tY8NhlXtlM2y4dWyKzykM1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/pzeGW/hyWzBCVZmJ/GLK8zUwQ0QLz61QWP59bs0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bCI5Da/hyWCGihWJj/KoFyjpueveV5hpk0LXI0lk/img.png?width=658&amp;amp;height=529&amp;amp;face=0_0_658_529');&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;logger | Dart package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Small, easy to use and extensible logger which prints beautiful logs.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-2. Network Log를 위한 &quot;Alice&quot; 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 log 패키지도 많지만, Alice가 다양한 &lt;b&gt;Dart http client plugin&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://pub.dev/packages/alice&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/alice&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721115140344&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;alice | Flutter package&quot; data-og-description=&quot;Alice is an HTTP Inspector tool which helps debugging http requests. It catches and stores http requests and responses, which can be viewed via simple UI.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/alice&quot; data-og-url=&quot;https://pub.dev/packages/alice&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eyDdZI/hyWzCaKZ6K/8fwBzbTBR1Z02kj6UkBWZ1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bz1lfb/hyWCMpg8Hy/DSwah1YbOKXeYQVEh1qMQk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/alice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/alice&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eyDdZI/hyWzCaKZ6K/8fwBzbTBR1Z02kj6UkBWZ1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bz1lfb/hyWCMpg8Hy/DSwah1YbOKXeYQVEh1qMQk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;alice | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Alice is an HTTP Inspector tool which helps debugging http requests. It catches and stores http requests and responses, which can be viewed via simple UI.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. DevTools&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현업 개발자는 &lt;u&gt;&lt;b&gt;성능 개선 목적(Performance View)으로 Frames 체크, 메모리 이슈 체크&lt;/b&gt;&lt;/u&gt; 등에 주로 사용한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;&lt;u&gt;위젯 트리, UI 디버깅을 위해 Flutter Inspector&lt;/u&gt;&lt;/b&gt;를 사용한 것 제외하고는 자주 사용해 본 적은 없다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 다른 개발자들은 &lt;u&gt;&lt;b&gt;네트워크 확인용&lt;/b&gt;&lt;/u&gt;으로도 많이 쓴다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;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;&lt;b&gt;Hot Reload, Hot Restart, Logging&lt;/b&gt; 기능들이 꽤나 강력하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flutter Inspector(브라우저에도 제공하는 기능)&lt;/b&gt;가 복잡한 화면에서는 느리다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 무거워질수록 DevTools도 무거워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 개발 경력이 쌓일수록, 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;a href=&quot;https://docs.flutter.dev/tools/devtools&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/tools/devtools&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721118680535&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;Flutter and Dart DevTools&quot; data-og-description=&quot;How to use Flutter DevTools with Flutter.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/tools/devtools&quot; data-og-url=&quot;https://docs.flutter.dev/tools/devtools&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8ZOi2/hyWzwhliwv/4m0rBctjpMmvzfUB7K4GSk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/tools/devtools&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/tools/devtools&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8ZOi2/hyWzwhliwv/4m0rBctjpMmvzfUB7K4GSk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461');&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;Flutter and Dart DevTools&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How to use Flutter DevTools with Flutter.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Firebase&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-1. Firebase Local Emulator Suite&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase의 다양한 서비스를 구글 클라우드에서 돌리는 것이 아니라, 사용자의 로컬 컴퓨터 자체에서 돌리는 것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;▶️ 해당되는 서비스&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;279&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8DbXt/btsIDui8rJi/xb1Km53FpzTHclbfwstpQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8DbXt/btsIDui8rJi/xb1Km53FpzTHclbfwstpQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8DbXt/btsIDui8rJi/xb1Km53FpzTHclbfwstpQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8DbXt%2FbtsIDui8rJi%2Fxb1Km53FpzTHclbfwstpQk%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;279&quot; height=&quot;242&quot; data-origin-width=&quot;279&quot; data-origin-height=&quot;242&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;  사용하는 이유&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5nAlm/btsIDfNmEVW/32Eej4aEUFr00ofCDWfwA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5nAlm/btsIDfNmEVW/32Eej4aEUFr00ofCDWfwA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5nAlm/btsIDfNmEVW/32Eej4aEUFr00ofCDWfwA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5nAlm%2FbtsIDfNmEVW%2F32Eej4aEUFr00ofCDWfwA0%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;726&quot; height=&quot;186&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;186&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;▶️ 포트 구성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ovI39/btsIDHiiJgJ/dLR974QWRyJzXO0H01Imq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ovI39/btsIDHiiJgJ/dLR974QWRyJzXO0H01Imq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ovI39/btsIDHiiJgJ/dLR974QWRyJzXO0H01Imq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FovI39%2FbtsIDHiiJgJ%2FdLR974QWRyJzXO0H01Imq1%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;413&quot; height=&quot;306&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IREcL/btsIDwgYx7P/3DzFmwAnYojoDp4lPss2I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IREcL/btsIDwgYx7P/3DzFmwAnYojoDp4lPss2I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IREcL/btsIDwgYx7P/3DzFmwAnYojoDp4lPss2I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIREcL%2FbtsIDwgYx7P%2F3DzFmwAnYojoDp4lPss2I1%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;502&quot; height=&quot;218&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ 데이터를 저장하고 &amp;amp; 읽어오는 방법&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgNdk2/btsICXlPavI/GNB2YHpLuNcd9MZw07CtpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgNdk2/btsICXlPavI/GNB2YHpLuNcd9MZw07CtpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgNdk2/btsICXlPavI/GNB2YHpLuNcd9MZw07CtpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgNdk2%2FbtsICXlPavI%2FGNB2YHpLuNcd9MZw07CtpK%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;360&quot; height=&quot;212&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;212&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. injectable 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;Get It&quot; 패키지&lt;/b&gt;의 의존성 등록 기능을&lt;b&gt; &lt;/b&gt;&lt;u&gt;&lt;b&gt;Annotation 기반 Code Generation을 통해 자동화&lt;/b&gt;&lt;/u&gt; 시켜주는 패키지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/injectable&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/injectable&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722926897825&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;injectable | Dart package&quot; data-og-description=&quot;Injectable is a convenient code generator for get_it. Inspired by Angular DI, Guice DI and inject.dart.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/injectable&quot; data-og-url=&quot;https://pub.dev/packages/injectable&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d2ZfqM/hyWKEleO8m/C9dX11y7VQuKoG2qwNP7UK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dPHVkd/hyWKGcg6y1/ygKlAfP7ydMb7swCcqDQ91/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/injectable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/injectable&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d2ZfqM/hyWKEleO8m/C9dX11y7VQuKoG2qwNP7UK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dPHVkd/hyWKGcg6y1/ygKlAfP7ydMb7swCcqDQ91/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;injectable | Dart package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Injectable is a convenient code generator for get_it. Inspired by Angular DI, Guice DI and inject.dart.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dq0Irp/btsIVF6ITvt/a8hg6liKhKgKbTCgAeEmp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dq0Irp/btsIVF6ITvt/a8hg6liKhKgKbTCgAeEmp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dq0Irp/btsIVF6ITvt/a8hg6liKhKgKbTCgAeEmp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdq0Irp%2FbtsIVF6ITvt%2Fa8hg6liKhKgKbTCgAeEmp1%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;475&quot; height=&quot;355&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGuuqy/btsIWvbgkFM/V4Dya6oPujFbg8Rwa65jDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGuuqy/btsIWvbgkFM/V4Dya6oPujFbg8Rwa65jDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGuuqy/btsIWvbgkFM/V4Dya6oPujFbg8Rwa65jDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGuuqy%2FbtsIWvbgkFM%2FV4Dya6oPujFbg8Rwa65jDK%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;660&quot; height=&quot;317&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;223&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNEeig/btsIWjhG3zD/Pl7KfHHj5ZmMwTGdeXDiwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNEeig/btsIWjhG3zD/Pl7KfHHj5ZmMwTGdeXDiwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNEeig/btsIWjhG3zD/Pl7KfHHj5ZmMwTGdeXDiwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNEeig%2FbtsIWjhG3zD%2FPl7KfHHj5ZmMwTGdeXDiwk%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;704&quot; height=&quot;223&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;223&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRrngm/btsIWRkKtSm/0fbiK1YlPOxZnYk6LHrFtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRrngm/btsIWRkKtSm/0fbiK1YlPOxZnYk6LHrFtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRrngm/btsIWRkKtSm/0fbiK1YlPOxZnYk6LHrFtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRrngm%2FbtsIWRkKtSm%2F0fbiK1YlPOxZnYk6LHrFtk%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;750&quot; height=&quot;282&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;282&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>Dart와 Flutter/15개 프로젝트</category>
      <category>DART</category>
      <category>Flutter</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/292</guid>
      <comments>https://sweetburble.tistory.com/292#entry292comment</comments>
      <pubDate>Fri, 12 Jul 2024 16:11:54 +0900</pubDate>
    </item>
    <item>
      <title>푸딩 계란찜 - [1분요리 뚝딱이형 식]</title>
      <link>https://sweetburble.tistory.com/291</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240712_계란찜_1분요리뚝딱이형.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;495&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UJySC/btsIx0vDZdS/3NaGzzu6lCwF0i85zTKC11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UJySC/btsIx0vDZdS/3NaGzzu6lCwF0i85zTKC11/img.png&quot; data-alt=&quot;푸딩 계란찜&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UJySC/btsIx0vDZdS/3NaGzzu6lCwF0i85zTKC11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUJySC%2FbtsIx0vDZdS%2F3NaGzzu6lCwF0i85zTKC11%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;500&quot; height=&quot;495&quot; data-filename=&quot;edited_20240712_계란찜_1분요리뚝딱이형.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;495&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;푸딩 계란찜&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=X6Wp38mVT74&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bQsPTE/hyWztcM3NP/9K2WKMycmniKyU5Ylsx9sK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;710원&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/X6Wp38mVT74&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/291</guid>
      <comments>https://sweetburble.tistory.com/291#entry291comment</comments>
      <pubDate>Fri, 12 Jul 2024 13:56:13 +0900</pubDate>
    </item>
    <item>
      <title>스팸 두부 두루치기 - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/290</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 스팸 클래식 200g을 사용했다.&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240710_스팸두부두루치기_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kGl9V/btsIuGRIY5N/qrCmEkn1ZWhwiGkxuppDW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kGl9V/btsIuGRIY5N/qrCmEkn1ZWhwiGkxuppDW0/img.png&quot; data-alt=&quot;스팸 두부 두루치기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kGl9V/btsIuGRIY5N/qrCmEkn1ZWhwiGkxuppDW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkGl9V%2FbtsIuGRIY5N%2FqrCmEkn1ZWhwiGkxuppDW0%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;500&quot; height=&quot;375&quot; data-filename=&quot;edited_20240710_스팸두부두루치기_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스팸 두부 두루치기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=D0IAbgLAEVY&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=D0IAbgLAEVY&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=D0IAbgLAEVY&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/dpgJAI/hyWvU3SIYW/1JTNMh1JST2TPwSX5KYkSk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;제발 여기에 돼지고기 넣지 마세요!&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/D0IAbgLAEVY&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/290</guid>
      <comments>https://sweetburble.tistory.com/290#entry290comment</comments>
      <pubDate>Wed, 10 Jul 2024 14:21:22 +0900</pubDate>
    </item>
    <item>
      <title>콩나물 덮밥 - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/289</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240621_콩나물덮밥_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cg5t6t/btsH7AMp2JQ/tWGroZg38HJwdkkdPRt6MK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cg5t6t/btsH7AMp2JQ/tWGroZg38HJwdkkdPRt6MK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cg5t6t/btsH7AMp2JQ/tWGroZg38HJwdkkdPRt6MK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcg5t6t%2FbtsH7AMp2JQ%2FtWGroZg38HJwdkkdPRt6MK%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;500&quot; height=&quot;403&quot; data-filename=&quot;edited_20240621_콩나물덮밥_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=5fi5PSK32qM&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=5fi5PSK32qM&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=5fi5PSK32qM&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bF58GC/hyWoNiNG6G/A1oAkX74BkmL5p8VQSAV7k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;와 이거 레시피 만드신 분 절 받으세요&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/5fi5PSK32qM&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/289</guid>
      <comments>https://sweetburble.tistory.com/289#entry289comment</comments>
      <pubDate>Fri, 21 Jun 2024 18:41:04 +0900</pubDate>
    </item>
    <item>
      <title>Flutter 개발 중 발생할 수 있는 에러 정리</title>
      <link>https://sweetburble.tistory.com/288</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 6월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. &lt;/b&gt;&lt;b&gt;ext.kotlin_version 에러 (안드로이드에서만 발생)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;플러터 버전 혹은 플러터에서 사용하는 버전과 Kotlin 버전이 호환되지 않을 때&lt;/span&gt;,&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;흔히 아래와 같은 에러를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JRAXE/btsHU9NOWnI/J5hVH7A3eaBhPnncpHxJhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JRAXE/btsHU9NOWnI/J5hVH7A3eaBhPnncpHxJhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JRAXE/btsHU9NOWnI/J5hVH7A3eaBhPnncpHxJhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJRAXE%2FbtsHU9NOWnI%2FJ5hVH7A3eaBhPnncpHxJhk%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;538&quot; height=&quot;187&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;span style=&quot;color: #006dd7;&quot;&gt;&amp;nbsp;&lt;b&gt;프로젝트의 android/build.gradle (프로젝트 gradle 파일)에서 ext.kotlin_version이 적혀 있는 코드를 최신 버전으로 업데이트&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;239&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AQh4W/btsHUAFaypM/wbXcoGzX9QdpDBKeMX6Qx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AQh4W/btsHUAFaypM/wbXcoGzX9QdpDBKeMX6Qx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AQh4W/btsHUAFaypM/wbXcoGzX9QdpDBKeMX6Qx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAQh4W%2FbtsHUAFaypM%2FwbXcoGzX9QdpDBKeMX6Qx1%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;239&quot; height=&quot;120&quot; data-origin-width=&quot;239&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;size18&quot;&gt;&lt;b&gt;※ Kotlin 버전 정보&lt;/b&gt;&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://kotlinlang.org/docs/releases.html#release-details&quot;&gt;https://kotlinlang.org/docs/releases.html#release-details&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719033888607&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 releases | Kotlin&quot; data-og-description=&quot; &quot; data-og-host=&quot;kotlinlang.org&quot; data-og-source-url=&quot;https://kotlinlang.org/docs/releases.html#release-details&quot; data-og-url=&quot;https://kotlinlang.org/docs/releases.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bfN7WI/hyWoCu2Hl0/oNUoU96gkXok7ugB3LTKHK/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/releases.html#release-details&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinlang.org/docs/releases.html#release-details&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bfN7WI/hyWoCu2Hl0/oNUoU96gkXok7ugB3LTKHK/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;Kotlin releases | 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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Multidex error (안드로이드에서만 발생)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드에서 앱 개발을 하다 코드가 많아지고 라이브러리도 늘어나면서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&amp;gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;한 프로젝트에서 약 64,000개 이상의 앱을 정의했다면,&amp;nbsp;Multidex를 활성화&lt;/b&gt;&lt;/span&gt;해줘야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TWBm6/btsHTIqo4gu/287mMtK0IOjUEr9CjRXUt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TWBm6/btsHTIqo4gu/287mMtK0IOjUEr9CjRXUt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TWBm6/btsHTIqo4gu/287mMtK0IOjUEr9CjRXUt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTWBm6%2FbtsHTIqo4gu%2F287mMtK0IOjUEr9CjRXUt1%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;562&quot; height=&quot;552&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Android SDK 버전 에러&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F34pA/btsHVGqSTeo/jFZ2eOCaXpBaQSiYNPzIw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F34pA/btsHVGqSTeo/jFZ2eOCaXpBaQSiYNPzIw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F34pA/btsHVGqSTeo/jFZ2eOCaXpBaQSiYNPzIw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF34pA%2FbtsHVGqSTeo%2FjFZ2eOCaXpBaQSiYNPzIw0%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;564&quot; height=&quot;614&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. FVM으로 Flutter SDK 설치시 발생할 수 있는 에러&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ghdgjs0621/flutter-doctor-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%AA%A8%EC%9D%8C%EC%B0%BD-%EB%8B%AB%ED%9E%98-%EB%B0%8F-Error-Unable-to-find-git-in-your-PATH&quot;&gt;https://velog.io/@ghdgjs0621/flutter-doctor-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%AA%A8%EC%9D%8C%EC%B0%BD-%EB%8B%AB%ED%9E%98-%EB%B0%8F-Error-Unable-to-find-git-in-your-PATH&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718763125018&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;flutter doctor 오류 해결 모음(창 닫힘 및 Error: Unable to find git in your PATH)&quot; data-og-description=&quot;flutter를 설치하며 지속적으로 문제가 발생하였는데 이 부분을 정리해두면 도움이 될 거 같아 작성해두었다.참고로 나는 윈도우에서 설치를 진행하였다.(맥북은 안보시는 게 나을 거 같음)우선 &quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@ghdgjs0621/flutter-doctor-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%AA%A8%EC%9D%8C%EC%B0%BD-%EB%8B%AB%ED%9E%98-%EB%B0%8F-Error-Unable-to-find-git-in-your-PATH&quot; data-og-url=&quot;https://velog.io/@ghdgjs0621/flutter-doctor-오류-해결-모음창-닫힘-및-Error-Unable-to-find-git-in-your-PATH&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cwIo5v/hyWldiuLTS/7nI5fApfKp7BXXjVQkDLp1/img.png?width=1318&amp;amp;height=254&amp;amp;face=0_0_1318_254,https://scrap.kakaocdn.net/dn/d0hglT/hyWoPmIhuS/YaMV3exbk7bu9ePkktoIz0/img.png?width=1318&amp;amp;height=254&amp;amp;face=0_0_1318_254,https://scrap.kakaocdn.net/dn/uhTFm/hyWoHhUDbg/53sHRKJ4zLrKT4hWjKEcY0/img.png?width=1318&amp;amp;height=254&amp;amp;face=0_0_1318_254&quot;&gt;&lt;a href=&quot;https://velog.io/@ghdgjs0621/flutter-doctor-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%AA%A8%EC%9D%8C%EC%B0%BD-%EB%8B%AB%ED%9E%98-%EB%B0%8F-Error-Unable-to-find-git-in-your-PATH&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@ghdgjs0621/flutter-doctor-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%AA%A8%EC%9D%8C%EC%B0%BD-%EB%8B%AB%ED%9E%98-%EB%B0%8F-Error-Unable-to-find-git-in-your-PATH&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cwIo5v/hyWldiuLTS/7nI5fApfKp7BXXjVQkDLp1/img.png?width=1318&amp;amp;height=254&amp;amp;face=0_0_1318_254,https://scrap.kakaocdn.net/dn/d0hglT/hyWoPmIhuS/YaMV3exbk7bu9ePkktoIz0/img.png?width=1318&amp;amp;height=254&amp;amp;face=0_0_1318_254,https://scrap.kakaocdn.net/dn/uhTFm/hyWoHhUDbg/53sHRKJ4zLrKT4hWjKEcY0/img.png?width=1318&amp;amp;height=254&amp;amp;face=0_0_1318_254');&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;flutter doctor 오류 해결 모음(창 닫힘 및 Error: Unable to find git in your PATH)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;flutter를 설치하며 지속적으로 문제가 발생하였는데 이 부분을 정리해두면 도움이 될 거 같아 작성해두었다.참고로 나는 윈도우에서 설치를 진행하였다.(맥북은 안보시는 게 나을 거 같음)우선&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 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;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;Git 찾기 불가&quot;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;&lt;/span&gt;에러는 추가로 &lt;b&gt;Powershell 관리자 모드에서 아래 명령어를 실행&lt;/b&gt;했다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;git config --global --add safe.directory '*'&lt;/b&gt;&lt;/blockquote&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://codingapple.com/forums/topic/flutter-%EC%84%A4%EC%B9%98%EC%8B%9C-flutter-doctor-%EC%B3%A4%EB%8D%94%EB%8B%88-%EA%B3%84%EC%86%8D-%EA%B0%99%EC%9D%80-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%9C%B9%EB%8B%88%EB%8B%A4/&quot;&gt;https://codingapple.com/forums/topic/flutter-%EC%84%A4%EC%B9%98%EC%8B%9C-flutter-doctor-%EC%B3%A4%EB%8D%94%EB%8B%88-%EA%B3%84%EC%86%8D-%EA%B0%99%EC%9D%80-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%9C%B9%EB%8B%88%EB%8B%A4/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718763133396&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;flutter doctor 쳤더니 Unable to find git in your PATH - 코딩애플 온라인 강좌&quot; data-og-description=&quot;flutter doctor 쳤더니 Unable to find git in your PATH 이 주제에는 9개 답변, 6명 참여가 있으며 홍헌가 10 월 전에 전에 마지막으로 업데이트했습니다. &amp;amp;flutter doctor Error: Unable to find git in your PATH. &amp;amp;git --version &quot; data-og-host=&quot;codingapple.com&quot; data-og-source-url=&quot;https://codingapple.com/forums/topic/flutter-%EC%84%A4%EC%B9%98%EC%8B%9C-flutter-doctor-%EC%B3%A4%EB%8D%94%EB%8B%88-%EA%B3%84%EC%86%8D-%EA%B0%99%EC%9D%80-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%9C%B9%EB%8B%88%EB%8B%A4/&quot; data-og-url=&quot;https://codingapple.com/forums/topic/flutter-%EC%84%A4%EC%B9%98%EC%8B%9C-flutter-doctor-%EC%B3%A4%EB%8D%94%EB%8B%88-%EA%B3%84%EC%86%8D-%EA%B0%99%EC%9D%80-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%9C%B9%EB%8B%88%EB%8B%A4/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://codingapple.com/forums/topic/flutter-%EC%84%A4%EC%B9%98%EC%8B%9C-flutter-doctor-%EC%B3%A4%EB%8D%94%EB%8B%88-%EA%B3%84%EC%86%8D-%EA%B0%99%EC%9D%80-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%9C%B9%EB%8B%88%EB%8B%A4/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codingapple.com/forums/topic/flutter-%EC%84%A4%EC%B9%98%EC%8B%9C-flutter-doctor-%EC%B3%A4%EB%8D%94%EB%8B%88-%EA%B3%84%EC%86%8D-%EA%B0%99%EC%9D%80-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%9C%B9%EB%8B%88%EB%8B%A4/&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;flutter doctor 쳤더니 Unable to find git in your PATH - 코딩애플 온라인 강좌&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;flutter doctor 쳤더니 Unable to find git in your PATH 이 주제에는 9개 답변, 6명 참여가 있으며 홍헌가 10 월 전에 전에 마지막으로 업데이트했습니다. &amp;amp;flutter doctor Error: Unable to find git in your PATH. &amp;amp;git --version&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codingapple.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. [Flutter 설치 오류] - Error : ADB exited with exit code 1 Performing Streamed Install&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beuLjR/btsH4YEXY83/KHPYEPZKV5ouqy8frBBcDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beuLjR/btsH4YEXY83/KHPYEPZKV5ouqy8frBBcDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beuLjR/btsH4YEXY83/KHPYEPZKV5ouqy8frBBcDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeuLjR%2FbtsH4YEXY83%2FKHPYEPZKV5ouqy8frBBcDk%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;72&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;72&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;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;그리고 에러 메시지에는 다음과 같이 나올 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;'INSTALL_FAILED_INSUFFICIENT_STORAGE&lt;/b&gt;' &lt;span style=&quot;color: #000000;&quot;&gt;-&amp;gt; &lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;따라서 내 앱을 실행할&lt;b&gt; AVD의 스토리지(데이터)를 &quot;Wipe Data&quot; 로 정리&lt;/b&gt;해주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBxa7L/btsH2HZppFe/1BD7dxxf3MZ1qC35aw8YK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBxa7L/btsH2HZppFe/1BD7dxxf3MZ1qC35aw8YK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBxa7L/btsH2HZppFe/1BD7dxxf3MZ1qC35aw8YK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBxa7L%2FbtsH2HZppFe%2F1BD7dxxf3MZ1qC35aw8YK0%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;520&quot; height=&quot;386&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;386&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. velocity_x 사용 중 발생하는 에러&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. &lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;LayoutBuilder does not support returning intrinsic dimensions.&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 에러가 발생할 때가 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;======== Exception caught by rendering library =====&lt;/b&gt;&lt;br /&gt;&lt;b&gt;The following assertion was thrown during performLayout(): &lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;LayoutBuilder does not support returning intrinsic dimensions. &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;b&gt;Calculating the intrinsic dimensions would require running the layout callback speculatively, which might mutate the live render object tree.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 코드를 작성할 때 발생했던 에러였다.&lt;/p&gt;
&lt;pre id=&quot;code_1721293916601&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:velocity_x/velocity_x.dart';

  PopupMenuButton(
    itemBuilder: (context) {
      /// 제품 리뷰 다이얼로그
      return [
        PopupMenuItem(
          child: &quot;리뷰 등록&quot;.text.make(),
          onTap: () {},
        ),
      ];
    },
  ),&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;처음에는 레이아웃 크기를 지정해주지 않아서 발생하는 에러인 줄 알았으나 (onTap에 showDialog()를 구현했었음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고보니 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;builder 함수에 velocity_x를 사용한 text 위젯이 들어가 있어서 발생했던 문제&lt;/b&gt;&lt;/span&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;따라서 velocity_x를 사용하지 않고 -&amp;gt; builder 함수 내부에서는 일반적인 플러터 위젯을 사용해야 한다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 이유는 모르겠지만, 아마 velocity_x로 만드는 위젯은 lazy하니까, 당장 builder 함수에서 사용할 수 없기 때문에 에러가 발생했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;velocity_x를 사용하던 강의에서 아마 이런 위험사항을 알려줬을 텐데, 대충대충 듣다보니 이런 문제를 쉽게 해결하지 못했던 것 같다 ㅠㅠ&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. &quot;flutterfire configure&quot; 로 프로젝트에 플러터파이어를 설정할 때 발생하는 에러&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-logly-title=&quot;true&quot; data-ke-size=&quot;size16&quot;&gt;한창 설치 중 이런 메시지가 뜰때가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;69&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cN3UJC/btsI0x0IjdF/4HZWHC8DdCSphM94hwrBx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cN3UJC/btsI0x0IjdF/4HZWHC8DdCSphM94hwrBx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cN3UJC/btsI0x0IjdF/4HZWHC8DdCSphM94hwrBx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcN3UJC%2FbtsI0x0IjdF%2F4HZWHC8DdCSphM94hwrBx1%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;849&quot; height=&quot;69&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;69&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;파란색 글씨처럼 명령어를 작성해도 되고, AndroidManifest.xml 에 패키지 이름을 추가해주면 되기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723179591011&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&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;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;com.your.orgname.my_app_name&quot;&amp;gt; // 여기에 추가

    &amp;lt;application
        android:label=&quot;com.your.orgname.my_app_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;a href=&quot;https://qiita.com/asarashi/items/90a5af762e1ee626837f&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://qiita.com/asarashi/items/90a5af762e1ee626837f&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723179607491&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;flutterfire configureがERROR: Package name for Android app cannot be emptyとなる場合の対処法 - Qiita&quot; data-og-description=&quot;環境OSwindowsIDEAndroidStuido対象コマンドflutterFire configure対象エラー Which Android application id (or&amp;hellip;&quot; data-og-host=&quot;qiita.com&quot; data-og-source-url=&quot;https://qiita.com/asarashi/items/90a5af762e1ee626837f&quot; data-og-url=&quot;https://qiita.com/asarashi/items/90a5af762e1ee626837f&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cgYXcq/hyWOqsLH3S/hxlr2L169yVJo8K4leKQ1K/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://qiita.com/asarashi/items/90a5af762e1ee626837f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://qiita.com/asarashi/items/90a5af762e1ee626837f&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cgYXcq/hyWOqsLH3S/hxlr2L169yVJo8K4leKQ1K/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;flutterfire configureがERROR: Package name for Android app cannot be emptyとなる場合の対処法 - Qiita&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;環境OSwindowsIDEAndroidStuido対象コマンドflutterFire configure対象エラー Which Android application id (or&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;qiita.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. 개발 환경 구축 시&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;'Android toolchain'&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 발생할 수 있는 2가지 문제&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 'cmdline-tools component is missing' 문제 해결하기&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caVr9y/btsIZv36p8m/BeajfMtmTgiI9rw7IB90n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caVr9y/btsIZv36p8m/BeajfMtmTgiI9rw7IB90n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caVr9y/btsIZv36p8m/BeajfMtmTgiI9rw7IB90n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaVr9y%2FbtsIZv36p8m%2FBeajfMtmTgiI9rw7IB90n1%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;460&quot; height=&quot;304&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOoojg/btsI0JUiWbF/8kTsMEfuSFjbaFVPmDLrs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOoojg/btsI0JUiWbF/8kTsMEfuSFjbaFVPmDLrs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOoojg/btsI0JUiWbF/8kTsMEfuSFjbaFVPmDLrs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOoojg%2FbtsI0JUiWbF%2F8kTsMEfuSFjbaFVPmDLrs1%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;452&quot; height=&quot;378&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) 'Android license status unknown' 문제 해결하기&lt;/b&gt;&lt;/h3&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pmn94/btsIZoRGJYA/ekf6imfA6r5IeTcKtLkJm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pmn94/btsIZoRGJYA/ekf6imfA6r5IeTcKtLkJm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pmn94/btsIZoRGJYA/ekf6imfA6r5IeTcKtLkJm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpmn94%2FbtsIZoRGJYA%2Fekf6imfA6r5IeTcKtLkJm1%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;496&quot; height=&quot;153&quot; data-origin-width=&quot;496&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;9. 앱 실행 도중 Error:Execution failed for task ':app:compileDebugKotlin' 가 발생할 때&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) Error: Unknown Kotlin JVM target: 21(다른 숫자가 될 수도 있음)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;프로젝트 Android 폴더 -&amp;gt; app 폴더 -&amp;gt; app 폴더 내부의 build.gradle 파일을 다음과 같이 추가하거나, 수정&lt;/b&gt;&lt;/u&gt;한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728659346016&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android {

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8
    }
}&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;kotlinOptions를 추가&lt;/b&gt;&lt;/span&gt;해야 해서, &lt;u&gt;&lt;b&gt;기존 compileOptions에 맞게 java 8로 추가&lt;/b&gt;&lt;/u&gt;해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;10. Vertical viewport was given unbounded height 에러&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;ListView 위젯이나 SingleChildScrollView 위젯&lt;/b&gt;&lt;/u&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;기본적인 원인은&lt;/span&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;gt; 따라서 내부 위젯의 &lt;span style=&quot;color: #006dd7;&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;blockquote data-ke-style=&quot;style3&quot;&gt;======== Exception caught by rendering library ==================&lt;br /&gt;The following assertion was thrown during performResize(): &lt;br /&gt;Vertical viewport was given unbounded height. &lt;br /&gt;&lt;br /&gt;Viewports expand in the scrolling direction to fill their container. &lt;br /&gt;In this case, a vertical viewport was given an unlimited amount of vertical space in which to expand. &lt;br /&gt;This situation typically happens when a scrollable widget is nested inside another scrollable widget. &lt;br /&gt;If this widget is always nested in a scrollable widget there is no need to use a viewport because there will always be enough vertical space for the children. &lt;br /&gt;In this case, consider using a Column instead. &lt;br /&gt;Otherwise, consider using the &quot;shrinkWrap&quot; property (or a ShrinkWrappingViewport) to size the height of the viewport to the sum of the heights of its children.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) shrinkWrap 옵션 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729069362911&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        ListView(
          shrinkWrap: true, // 추가
          children: [
              Container(color: Colors.amber, height: 200,),
              Container(color: Colors.cyan, height: 400,),
              Container(color: Colors.orange, height: 200,),
          ],
        )
      ],
    );
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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_1729069401125&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        SizedBox( // 부모 영역 지정
          height: MediaQuery.of(context).size.height,
          child: ListView(
            children: [
              Container(color: Colors.amber, height: 200,),
              Container(color: Colors.cyan, height: 400,),
              Container(color: Colors.orange, height: 200,),
            ],
          ),
        )
      ],
    );
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) Expanded 위젯 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729069434341&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.max,
      children: [
        Expanded( // Expanded 위젯 사용
          child: ListView(
            children: [
              Container(color: Colors.amber, height: 200,),
              Container(color: Colors.cyan, height: 400,),
              Container(color: Colors.orange, height: 200,),
            ],
          ),
        )
      ],
    );
  }&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dart와 Flutter</category>
      <category>DART</category>
      <category>Flutter</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/288</guid>
      <comments>https://sweetburble.tistory.com/288#entry288comment</comments>
      <pubDate>Wed, 19 Jun 2024 11:18:05 +0900</pubDate>
    </item>
    <item>
      <title>내용 정리 Part.1</title>
      <link>https://sweetburble.tistory.com/287</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 6월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. go_router 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 페이지 간 이동을 간단하고, 더 정교하게 하기 위해서 사용하는 외부 패키지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/go_router&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/go_router&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718694429385&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;go_router | Flutter package&quot; data-og-description=&quot;A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/go_router&quot; data-og-url=&quot;https://pub.dev/packages/go_router&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dj7q3Y/hyWoMpQ2xu/eIiz1Hv3k40hcFTK00HgI0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dnEWCP/hyWk91kN8a/sIAFUZ7pGM5W5Pak3lo82K/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/go_router&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/go_router&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dj7q3Y/hyWoMpQ2xu/eIiz1Hv3k40hcFTK00HgI0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dnEWCP/hyWk91kN8a/sIAFUZ7pGM5W5Pak3lo82K/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;go_router | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;간단한 예제 코드이다. 자세한 go_router 사용법은 추가 학습이 필요하다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;▶️ main.dart&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718694540086&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:widget_practice/screen/new_page.dart';

void main() {
  runApp(MaterialApp.router(
    routerConfig: GoRouter(
      initialLocation: '/',
      routes: [
        GoRoute(
          path: '/',
          name: 'home',
          builder: (context, _) =&amp;gt; HomeScreen(),
        ),
        GoRoute(
          path: '/new',
          name: 'new',
          builder: (context, _) =&amp;gt; NewPage(),
        ),
        GoRoute(
          path: '/new2',
          name: 'new2',
          builder: (context, _) =&amp;gt; NewPage2(),
        ),
      ],
    ),
  ));
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State&amp;lt;HomeScreen&amp;gt; createState() =&amp;gt; _HomeScreenState();
}

class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('화면 이동하기 2'),
      ),
      body: Center(
        child: TextButton(
          child: Text('Go to Page'),
          onPressed: () {
            context.pushNamed('new');
          },
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;▶️ new_page.dart&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718694592609&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class NewPage extends StatelessWidget {
  const NewPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Welcome New Page!'),
      ),
      body: Center(
        child: Column(
          children: [
            TextButton(
              child: Text('Go to Main page'),
              onPressed: () {
                context.pop();
              },
            ),
            TextButton(
              child: Text('Go to New Page2'),
              onPressed: () {
                context.pushNamed('new2');
              },
            ),
          ],
        ),
      ),
    );
  }
}

class NewPage2 extends StatelessWidget {
  const NewPage2({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Welcome New Page 222!'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            TextButton(
              child: Text('Go to Back'),
              onPressed: () {
                context.pop();
              },
            ),
            TextButton(
              child: Text('Go to Home'),
              onPressed: () {
                context.goNamed('home');
              }
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징은 &lt;u&gt;&lt;b&gt;이동하고 싶은 페이지(라우트)를 runApp 시점에 routerConfig 파라미터에 모두 등록&lt;/b&gt;&lt;/u&gt;한 다음,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존 Navigator.push() / pop()&lt;/b&gt;에서 -&amp;gt; &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;context() 함수&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 라이선스 확인하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pub.dev에서 외부 패키지를 추가할 때, 라이선스를 꼭 확인해야 한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ 주요 라이선스&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Aeay/btsH5OhUCZo/TEhlE6AeV9rgFeVmuV1jT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Aeay/btsH5OhUCZo/TEhlE6AeV9rgFeVmuV1jT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Aeay/btsH5OhUCZo/TEhlE6AeV9rgFeVmuV1jT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Aeay%2FbtsH5OhUCZo%2FTEhlE6AeV9rgFeVmuV1jT1%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;608&quot; height=&quot;420&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;420&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;GNU GPL License&lt;/b&gt;와 &lt;b&gt;Mozila Public License 2.0&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 3장에서만 사용하는 파일 네이밍 규칙&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caXgjc/btsH5wCpRsM/KjMtnTNAqgApHqE85pD1kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caXgjc/btsH5wCpRsM/KjMtnTNAqgApHqE85pD1kK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caXgjc/btsH5wCpRsM/KjMtnTNAqgApHqE85pD1kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaXgjc%2FbtsH5wCpRsM%2FKjMtnTNAqgApHqE85pD1kK%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;775&quot; height=&quot;427&quot; data-origin-width=&quot;775&quot; data-origin-height=&quot;427&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ 헝가리안 명명법이라고도 하며, 현재는 IDE의 발달로 잘 사용하지 않는다고 한다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. timeago 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/timeago&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/timeago&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718861251354&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;timeago | Dart package&quot; data-og-description=&quot;A library useful for creating fuzzy timestamps. (e.g. &amp;quot;15 minutes ago&amp;quot;)&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/timeago&quot; data-og-url=&quot;https://pub.dev/packages/timeago&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zgNkA/hyWoOhlJf8/IYaklNhjWp4IyXxS1lMJ2K/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/3mQmn/hyWoFSfQBs/6l6MfVM2oPo7tMd8QvrLL1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/timeago&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/timeago&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zgNkA/hyWoOhlJf8/IYaklNhjWp4IyXxS1lMJ2K/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/3mQmn/hyWoFSfQBs/6l6MfVM2oPo7tMd8QvrLL1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;timeago | Dart package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A library useful for creating fuzzy timestamps. (e.g. &quot;15 minutes ago&quot;)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;여러 애플리케이션의 게시글이나 유튜브 동영상을 보다보면 종종 발견하는 '3초전', '10분전', '하루 전' 등등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;시간 경과를 알려주는 로직을 쉽게 작성&lt;/b&gt;&lt;/u&gt;하는 외부 패키지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Drawing based Animation 2가지 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) Rive&lt;/b&gt;&lt;/h3&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://pub.dev/packages/rive&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/rive&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718956830875&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;rive | Flutter package&quot; data-og-description=&quot;Rive Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/rive&quot; data-og-url=&quot;https://pub.dev/packages/rive&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b5dwSQ/hyWoH3VDi0/cTDTnNRwWOuiRchTfso9hK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/QGHxH/hyWoAwYZsL/yreAoohLudeCfieNRei6j1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dVty85/hyWoIV5Mdr/a3K4aguKUhSIKWmjURUSDK/img.png?width=2000&amp;amp;height=800&amp;amp;face=0_0_2000_800&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/rive&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/rive&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b5dwSQ/hyWoH3VDi0/cTDTnNRwWOuiRchTfso9hK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/QGHxH/hyWoAwYZsL/yreAoohLudeCfieNRei6j1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dVty85/hyWoIV5Mdr/a3K4aguKUhSIKWmjURUSDK/img.png?width=2000&amp;amp;height=800&amp;amp;face=0_0_2000_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;rive | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Rive Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;다른 실력자들이 Rive로 만든 다양한 애니메이션을 보고 사용할 수 있고 (저작권 주의!),&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 직접 만들 수도 있다. Figma랑 비슷한 디자인 느낌...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://rive.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://rive.app/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719896402411&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;Rive - Build interactive motion graphics that run anywhere&quot; data-og-description=&quot;Complex designer-developer handoff is a thing of the past. Build functional graphics with motion and interactivity that run in products, apps, sites, and games.&quot; data-og-host=&quot;rive.app&quot; data-og-source-url=&quot;https://rive.app/&quot; data-og-url=&quot;https://rive.app/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fRSfH/hyWvU80F9R/obj9aeCRrYhUvVp8LYuGFk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cILqQz/hyWrVV4jTT/9KTVTuWe8YJdlREyCrAm31/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://rive.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://rive.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fRSfH/hyWvU80F9R/obj9aeCRrYhUvVp8LYuGFk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cILqQz/hyWrVV4jTT/9KTVTuWe8YJdlREyCrAm31/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;Rive - Build interactive motion graphics that run anywhere&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Complex designer-developer handoff is a thing of the past. Build functional graphics with motion and interactivity that run in products, apps, sites, and games.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;rive.app&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rive 기반의 애니메이션은 벡터 기반의 애니메이션이기 때문에, 반드시&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;u&gt;&lt;b&gt;SizedBox()와 같은 위젯으로 감싸 크기를 지정&lt;/b&gt;&lt;/u&gt;해줘야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) Lottie&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옛날 안드로이드, iOS 개발부터 유명했던 패키지이다. 따라서 기존에 만들어진 애니메이션 예제가 많다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ 그러나, 최근에는 Rive가 지원하는 기능이 훨씬 많기 때문에 Rive 사용을 추천한다.&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;a href=&quot;https://airbnb.io/lottie/#/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://airbnb.io/lottie/#/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719896446122&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;Lottie Docs&quot; data-og-description=&quot;&quot; data-og-host=&quot;airbnb.io&quot; data-og-source-url=&quot;https://airbnb.io/lottie/#/&quot; data-og-url=&quot;https://airbnb.io/lottie/#/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://airbnb.io/lottie/#/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://airbnb.io/lottie/#/&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;Lottie Docs&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;airbnb.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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. Code based Animation 4가지 방법&lt;/b&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;b&gt;1) 커스텀 패키지 이용&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ flutter_animate 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/flutter_animate&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/flutter_animate&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718957084308&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;flutter_animate | Flutter package&quot; data-og-description=&quot;Add beautiful animated effects &amp;amp; builders in Flutter, via an easy, customizable, unified API.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/flutter_animate&quot; data-og-url=&quot;https://pub.dev/packages/flutter_animate&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bktsPQ/hyWoMKWUZa/ePaGGzgqRzrzmyISE13PWk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/rXzbl/hyWoCBzmeE/Tt8u8COxfMsqGzGhTnM6qk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/5v5hk/hyWoDmWzIL/cxNVjBvfE12UahgntVr94k/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/flutter_animate&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/flutter_animate&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bktsPQ/hyWoMKWUZa/ePaGGzgqRzrzmyISE13PWk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/rXzbl/hyWoCBzmeE/Tt8u8COxfMsqGzGhTnM6qk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/5v5hk/hyWoDmWzIL/cxNVjBvfE12UahgntVr94k/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200');&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;flutter_animate | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Add beautiful animated effects &amp;amp; builders in Flutter, via an easy, customizable, unified API.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;※&amp;nbsp; &lt;span style=&quot;color: #1a5490;&quot;&gt;app_base 프로젝트&lt;/span&gt;의 num_duration_extension 파일은 외부 패키지 flutter_animate의 방식을 차용했으므로, &lt;span style=&quot;color: #ee2323;&quot;&gt;flutter_animate를 사용하려면 삭제&lt;/span&gt;해야 한다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ animations 패키지&lt;/b&gt;&lt;/h3&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://pub.dev/packages/animations&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/animations&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718958615445&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;animations | Flutter package&quot; data-og-description=&quot;Fancy pre-built animations that can easily be integrated into any Flutter application.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/animations&quot; data-og-url=&quot;https://pub.dev/packages/animations&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bofVIG/hyWoNJSYmS/p3wCVOR75Swq8MMBXwPkfK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/drDiQU/hyWoK7sIWC/1eJkG1yC4IqfnOCDcgI1wK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/animations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/animations&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bofVIG/hyWoNJSYmS/p3wCVOR75Swq8MMBXwPkfK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/drDiQU/hyWoK7sIWC/1eJkG1yC4IqfnOCDcgI1wK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;animations | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Fancy pre-built animations that can easily be integrated into any Flutter application.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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_1718959217665&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;OpenContainer(
  closedColor: Colors.transparent,
  openColor: Colors.transparent,
  transitionType: ContainerTransitionType.fade,
  openBuilder: (BuildContext context, VoidCallback _) {
    return StockDetail(stockName: e.name);
  },
  closedBuilder: (BuildContext context, VoidCallback action) {
    return Row(
      children: [
        (index + 1).text.bold.white.size(16).make(),
        width20,
        e.name.text.bold.white.size(16).make(),
        emptyExpanded,
        e.todayPercentageString.text
            .color(e.getTodayPercentageColor(context))
            .size(16)
            .make(),
      ],
    ).pSymmetric(h: 20, v: 20);
  },
))&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) Implicit Animation (암시적 애니메이션)&lt;/b&gt;&lt;/h3&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;1.&amp;nbsp; AnimatedContainer 위젯&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sweetburble.tistory.com/283#%E2%9C%85%20AnimatedContainer%20%EC%9C%84%EC%A0%AF-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sweetburble.tistory.com/283#%E2%9C%85%20AnimatedContainer%20%EC%9C%84%EC%A0%AF-1&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718970084698&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;24년 6월을 기준으로 작성되었습니다.&amp;nbsp;0. 기본 위젯 알아보기※ Flutter는 RichText 위젯과 Paragraph와 같은 클래스도 제공한다.https://docs.flutter.dev/ui/widgets/text&amp;nbsp;Text widgetsA catalog of Flutter's widgets for display&quot; data-og-host=&quot;sweetburble.tistory.com&quot; data-og-source-url=&quot;https://sweetburble.tistory.com/283#%E2%9C%85%20AnimatedContainer%20%EC%9C%84%EC%A0%AF-1&quot; data-og-url=&quot;https://sweetburble.tistory.com/283&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dLVNg5/hyWoFZpUCd/ubXMBVtOLjkBigCnSa1CTK/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/0zvhJ/hyWoE7gCOi/bBTtZKoKs2aeI7DdKTL2Pk/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/lYBDZ/hyWoAX7RQl/FnK7mFMjoVSqTYf1wGtJX1/img.png?width=565&amp;amp;height=794&amp;amp;face=0_0_565_794&quot;&gt;&lt;a href=&quot;https://sweetburble.tistory.com/283#%E2%9C%85%20AnimatedContainer%20%EC%9C%84%EC%A0%AF-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sweetburble.tistory.com/283#%E2%9C%85%20AnimatedContainer%20%EC%9C%84%EC%A0%AF-1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dLVNg5/hyWoFZpUCd/ubXMBVtOLjkBigCnSa1CTK/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/0zvhJ/hyWoE7gCOi/bBTtZKoKs2aeI7DdKTL2Pk/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/lYBDZ/hyWoAX7RQl/FnK7mFMjoVSqTYf1wGtJX1/img.png?width=565&amp;amp;height=794&amp;amp;face=0_0_565_794');&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;24년 6월을 기준으로 작성되었습니다.&amp;nbsp;0. 기본 위젯 알아보기※ Flutter는 RichText 위젯과 Paragraph와 같은 클래스도 제공한다.https://docs.flutter.dev/ui/widgets/text&amp;nbsp;Text widgetsA catalog of Flutter's widgets for display&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sweetburble.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) Custom Implicit Animation (TweenAnimatedBuilder 예제)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 파일의 색깔을 스크롤 위치에 따라 초록색에서 -&amp;gt; 오랜지색으로 자연스럽게 변화시키는 코드&lt;/p&gt;
&lt;pre id=&quot;code_1719894903660&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;child: TweenAnimationBuilder&amp;lt;Color?&amp;gt;(
  duration: 1000.ms,
  tween: ColorTween(
    begin: Colors.green,
    end: isTriggered ? Colors.orange : Colors.green,
  ),
  builder: (context, value, child) =&amp;gt; ColorFiltered(
    colorFilter: ColorFilter.mode(
      value ?? Colors.green,
      BlendMode.modulate,
    ),
    child: child,
  ),
  child: Image.asset(
    &quot;$basePath/icon/map_point.png&quot;,
    height: 60,
  ),
),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) Explicit Animation (명시적, 커스텀 애니메이션)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AnimationController를 사용해서 커스텀 애니메이션을 직접 구현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1719896123153&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _SettingScreenState extends State&amp;lt;SettingScreen&amp;gt; with SingleTickerProviderStateMixin {
  final scrollController = ScrollController();

  // 명시적 애니메이션을 위한 컨트롤러
  late final animationController = AnimationController(vsync: this, duration: 2000.ms,);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          ListView(
            controller: scrollController,
            padding: const EdgeInsets.only(top: 150),
            children: [
              // 슬라이더
              Obx(
                () =&amp;gt; Slider(
                  value: Prefs.sliderPosition.get(),
                  onChanged: (value) {
                    animationController.animateTo(value, duration: 0.ms);
                    Prefs.sliderPosition.set(value);
                    // Prefs.sliderPosition(value); -&amp;gt; 이 방법도 가능하다
                  },
                ),
              ),

              BigButton(
                '애니메이션 forward',
                onTap: () {
                  animationController.forward();
                },
              ),
              BigButton(
                '애니메이션 reverse',
                onTap: () {
                  animationController.reverse();
                },
              ),
              BigButton(
                '애니메이션 repeat',
                onTap: () {
                  animationController.repeat();
                },
              ),
              BigButton(
                '애니메이션 reset',
                onTap: () {
                  animationController.reset();
                },
              ),
            ],
          ),
          AnimatedAppBar('설정', scrollController: scrollController, animationController: animationController,)
        ],
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;w_animated_app_bar.dart 에서는&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1719896313210&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _AnimatedAppBarState extends State&amp;lt;AnimatedAppBar&amp;gt; {
  final duration = 100.ms;
  double scrollPosition = 0;

  // Explicit Animation 구현하기
  late CurvedAnimation animation = CurvedAnimation(
      parent: widget.animationController, curve: Curves.bounceInOut);

  @override
  void initState() {
    widget.animationController.addListener(() {
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      color: context.backgroundColor,
      child: SafeArea(
        child: Stack(
          children: [
            Positioned(
              left: animation.value * 200, // value값이 double로 0~1 값이다
              child: TweenAnimationBuilder&amp;lt;Color?&amp;gt;(
                duration: 1000.ms,
                tween: ColorTween(
                  begin: Colors.green,
                  end: isTriggered ? Colors.orange : Colors.green,
                ),
                builder: (context, value, child) =&amp;gt;
                    ColorFiltered(
                      colorFilter: ColorFilter.mode(
                        value ?? Colors.green,
                        BlendMode.modulate,
                      ),
                      child: child,
                    ),
                child: Image.asset(
                  &quot;$basePath/icon/map_point.png&quot;,
                  height: 60,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. live_background 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 만들어 놓은 &lt;span style=&quot;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;&lt;a href=&quot;https://pub.dev/packages/live_background&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/live_background&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718958452637&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;live_background | Flutter package&quot; data-og-description=&quot;Provide nice moving particle bokeh effect wall paper on your background.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/live_background&quot; data-og-url=&quot;https://pub.dev/packages/live_background&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dlVnFI/hyWoEsChju/EAOT1C0SP4CILupKOXwP5k/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/Hxpa3/hyWoDtHW49/5nA84jqKmNjlkjUPmWZnSK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/live_background&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/live_background&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dlVnFI/hyWoEsChju/EAOT1C0SP4CILupKOXwP5k/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/Hxpa3/hyWoDtHW49/5nA84jqKmNjlkjUPmWZnSK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;live_background | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Provide nice moving particle bokeh effect wall paper on your background.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ 개인이 시험삼아 만든 것이기 때문에, 업데이트가 더 이상 없을 확률이 높다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. Deprecated된 코드 정리&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ Part 3 -&amp;gt; Ch 05 -&amp;gt; 2. 상태 관리 없이 앱 만들어보기...&lt;/b&gt;&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;MaterialStateProperty -&amp;gt; WidgetStateProperty&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ Part 3 -&amp;gt; Ch 13 -&amp;gt; 2. easy_localization 적용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;w_menu_drawer.dart 파일에서&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;describeEnum(currentLanguage)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 아래와 같은 getter로 바꿔주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;currentLanguage.name&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;9. freezed 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 Dart로 클래스를 생성할 때, &lt;b&gt;반복작업으로 작성해야 하는&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 생성자, copywith(), equals(), hashCode(), toString() 같은 메서드들을 &quot;자동 완성&quot;&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://github.com/rrousselGit/freezed/blob/master/resources/translations/ko_KR/README.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/rrousselGit/freezed/blob/master/resources/translations/ko_KR/README.md&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719993333563&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;freezed/resources/translations/ko_KR/README.md at master &amp;middot; rrousselGit/freezed&quot; data-og-description=&quot;Code generation for immutable classes that has a simple syntax/API without compromising on the features. - rrousselGit/freezed&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/rrousselGit/freezed/blob/master/resources/translations/ko_KR/README.md&quot; data-og-url=&quot;https://github.com/rrousselGit/freezed/blob/master/resources/translations/ko_KR/README.md&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/n2upj/hyWrPItw4Q/65cuoBgULbMnoqkKUDCFak/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_150_1078_262&quot;&gt;&lt;a href=&quot;https://github.com/rrousselGit/freezed/blob/master/resources/translations/ko_KR/README.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/rrousselGit/freezed/blob/master/resources/translations/ko_KR/README.md&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/n2upj/hyWrPItw4Q/65cuoBgULbMnoqkKUDCFak/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_150_1078_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;freezed/resources/translations/ko_KR/README.md at master &amp;middot; rrousselGit/freezed&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Code generation for immutable classes that has a simple syntax/API without compromising on the features. - rrousselGit/freezed&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9-1. json_serializable 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;freezed는 대게 json_serializable 패키지도 같이 사용한다. 이 패키지도 역시 fromJson(), toJson()과 같이 json 데이터를 처리하는 데 필요한 메서드들을 &quot;자동 완성&quot; 시켜주는 패키지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/json_serializable&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/json_serializable&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722494560727&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;json_serializable | Dart package&quot; data-og-description=&quot;Automatically generate code for converting to and from JSON by annotating Dart classes.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/json_serializable&quot; data-og-url=&quot;https://pub.dev/packages/json_serializable&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tNzlI/hyWGSkgZNe/QXFN3TxSQRaHvnkOZ6d4hk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/xSwz4/hyWGOa82lW/MkVBSDYA2VUngNHOqVw3k0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/json_serializable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/json_serializable&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tNzlI/hyWGSkgZNe/QXFN3TxSQRaHvnkOZ6d4hk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/xSwz4/hyWGOa82lW/MkVBSDYA2VUngNHOqVw3k0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;json_serializable | Dart package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Automatically generate code for converting to and from JSON by annotating Dart classes.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9-2. Android Studio 플러그인 &quot;Flutter Frezeed Snippets&quot;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 freezed 파일과 json 파일을 생성하기 위해 패키지가 요구하는 형식에 맞게 코드를 작성해야 하는데,&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;freezed / json 파일 생성 터미널 명령어는 아래와 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;(fvm) dart run build_runner watch (-&amp;gt; 지속 감시와 갱신)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;(fvm) dart run build_runner build (-&amp;gt; 한 번만 빌드)&lt;/b&gt; &lt;br /&gt;&lt;/b&gt;&lt;/blockquote&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: #ee2323;&quot;&gt;&lt;b&gt;※ 이 명령어를 실행하기 위해서는 당연히 &quot;build_runner&quot; 패키지가 dev_dependencies에 추가되어 있어야 한다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;10. fl_chart 패키지&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/fl_chart&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/fl_chart&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720439464144&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;fl_chart | Flutter package&quot; data-og-description=&quot;A highly customizable Flutter chart library that supports Line Chart, Bar Chart, Pie Chart, Scatter Chart, and Radar Chart.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/fl_chart&quot; data-og-url=&quot;https://pub.dev/packages/fl_chart&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/v9mGQ/hyWztcfs0C/c9Y840RVcMsYwtb4KrcUtK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/XpbqJ/hyWzELBwZF/Zk9kkGZOa67rglEHYj0K7K/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cO6eMb/hyWvV9kn8v/CLO9K64tmPgLOCZO0CLbnK/img.png?width=1952&amp;amp;height=466&amp;amp;face=0_0_1952_466&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/fl_chart&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/fl_chart&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/v9mGQ/hyWztcfs0C/c9Y840RVcMsYwtb4KrcUtK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/XpbqJ/hyWzELBwZF/Zk9kkGZOa67rglEHYj0K7K/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cO6eMb/hyWvV9kn8v/CLO9K64tmPgLOCZO0CLbnK/img.png?width=1952&amp;amp;height=466&amp;amp;face=0_0_1952_466');&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;fl_chart | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A highly customizable Flutter chart library that supports Line Chart, Bar Chart, Pie Chart, Scatter Chart, and Radar Chart.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;11. 간단한 더미 이미지 제공 사이트&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://picsum.photos/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://picsum.photos/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720446781328&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;Lorem Picsum&quot; data-og-description=&quot;Lorem Ipsum... but for photos&quot; data-og-host=&quot;picsum.photos&quot; data-og-source-url=&quot;https://picsum.photos/&quot; data-og-url=&quot;https://picsum.photos&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SvuSe/hyWzyLqiUY/J2SNNknQ5iBfvp9Y2WrgdK/img.jpg?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250,https://scrap.kakaocdn.net/dn/sWIuQ/hyWzp1ZSc8/1JO1HvuVlt9XbiOoXPWDM1/img.jpg?width=536&amp;amp;height=354&amp;amp;face=0_0_536_354&quot;&gt;&lt;a href=&quot;https://picsum.photos/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://picsum.photos/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SvuSe/hyWzyLqiUY/J2SNNknQ5iBfvp9Y2WrgdK/img.jpg?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250,https://scrap.kakaocdn.net/dn/sWIuQ/hyWzp1ZSc8/1JO1HvuVlt9XbiOoXPWDM1/img.jpg?width=536&amp;amp;height=354&amp;amp;face=0_0_536_354');&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;Lorem Picsum&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Lorem Ipsum... but for photos&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;picsum.photos&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;span style=&quot;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;결과로 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;{id/id값}&lt;/b&gt;&lt;/span&gt;이 붙은 사이트로 리다이렉션된다. 이 id값을 기억하면, 그 이미지를 그대로 보여준다.&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;blockquote data-ke-style=&quot;style3&quot;&gt;https://picsum.photos/id/552/200/300&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;12. smooth_page_indicator 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스타 같이 &lt;u&gt;&lt;b&gt;어떤 포스트나 사진을 좌우로 스크롤&lt;/b&gt;&lt;/u&gt;할 때가 있다. 플러터에서는 이 형태를 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;PageView&quot;&lt;/b&gt; &lt;/span&gt;라는 위젯으로 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외부 패키지는 PageView 내부에서, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;현재 어떤 페이지에 있는 지 보여주는 indicator&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;a href=&quot;https://pub.dev/packages/smooth_page_indicator&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/smooth_page_indicator&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720507450510&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;smooth_page_indicator | Flutter package&quot; data-og-description=&quot;Customizable animated page indicator with a set of built-in effects.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/smooth_page_indicator&quot; data-og-url=&quot;https://pub.dev/packages/smooth_page_indicator&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pKtoe/hyWzyEJq42/eMp4OXWQmiNzowpi4dK9K1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/b0ftzn/hyWzzKp6wG/HYZSYMR4KV6Cgm8bxkjqhk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/smooth_page_indicator&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/smooth_page_indicator&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pKtoe/hyWzyEJq42/eMp4OXWQmiNzowpi4dK9K1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/b0ftzn/hyWzzKp6wG/HYZSYMR4KV6Cgm8bxkjqhk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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_page_indicator | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Customizable animated page indicator with a set of built-in effects.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;13. app_settings 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;permission_handler 패키지&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;반면 app_settings 패키지는 거의 모든 권한에 대하여, &lt;u&gt;&lt;b&gt;직접 설정 화면으로 보내는 기능을 제공&lt;/b&gt;&lt;/u&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/app_settings&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/app_settings&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720592938577&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;app_settings | Flutter package&quot; data-og-description=&quot;A Flutter plugin for opening iOS and Android phone settings from an app.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/app_settings&quot; data-og-url=&quot;https://pub.dev/packages/app_settings&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dLYiSV/hyWzsLqOJ7/odyTDJUdRHAPKsSvKOjb01/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/VQmjO/hyWzv9eoDx/ynYjaR5ZETrtXQzjZ3MmLK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/app_settings&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/app_settings&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dLYiSV/hyWzsLqOJ7/odyTDJUdRHAPKsSvKOjb01/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/VQmjO/hyWzv9eoDx/ynYjaR5ZETrtXQzjZ3MmLK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;app_settings | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Flutter plugin for opening iOS and Android phone settings from an app.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;14. nav 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의자께서 기존 플러터의 공식 nav 패키지를 래핑해서 수정한 패키지이며, 여러가지 추가 기능을 넣었다고 한다.&lt;/p&gt;
&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://pub.dev/packages/nav&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/nav&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720593312711&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;nav | Flutter package&quot; data-og-description=&quot;Provide easy way to navigate. Includes lots of routers. You can use this library on Android &amp;amp; iOS.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/nav&quot; data-og-url=&quot;https://pub.dev/packages/nav&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bt2Q2X/hyWvV9z6wa/L02fJn0pWd748PRlJs78c0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/jasQX/hyWzwf0jpL/Kw0ofLJEn9iPEcuILREZh1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/pW9H2/hyWzwtv3gg/cF9I1ZGVWIkoIHuSLcQue1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/nav&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/nav&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bt2Q2X/hyWvV9z6wa/L02fJn0pWd748PRlJs78c0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/jasQX/hyWzwf0jpL/Kw0ofLJEn9iPEcuILREZh1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/pW9H2/hyWzwtv3gg/cF9I1ZGVWIkoIHuSLcQue1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;nav | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Provide easy way to navigate. Includes lots of routers. You can use this library on Android &amp;amp; iOS.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;14-1. nav와 go_router 패키지를 동시에 사용하는 법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 App에 NavigatorState를 담은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;GlobalKey를 생성했을 것이다. &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;그 변수를 GoRouter를 생성할 때,&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; &quot;navigatorKey 파라미터&quot;&lt;/b&gt;&lt;/span&gt; 에 그대로 넣어주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721379389043&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class App extends StatefulWidget {
  static final GlobalKey&amp;lt;NavigatorState&amp;gt; navigatorKey = GlobalKey();

  const App({super.key});

  @override
  State&amp;lt;App&amp;gt; createState() =&amp;gt; AppState();
}

class AppState extends State&amp;lt;App&amp;gt; with Nav, WidgetsBindingObserver {
  final router = GoRouter(
    navigatorKey: App.navigatorKey,
    initialLocation: &quot;/&quot;,
    routes: [
      GoRoute(
        path: &quot;/&quot;,
        builder: (context, state) =&amp;gt; HomeScreen(),
      ),
    ],
  );

  @override
  GlobalKey&amp;lt;NavigatorState&amp;gt; get navigatorKey =&amp;gt; App.navigatorKey;
}&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;15. auto_route 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 부상하고 있는 라우트 패키지이다. go_route가 초반 설정이 복잡하고 귀찮았다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;auto_route는 안드로이드 스튜디오에서 몇번의 클릭만으로 라우팅을 연결해줄 수 있다는 장점이 있다.&lt;/p&gt;
&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;※ 하지만 대부분의 기업들은 go_route를 아직 사용하고 있기 때문에, 장단점을 잘 비교해서 사용해야 한다.&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;a href=&quot;https://pub.dev/packages/auto_route&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/auto_route&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720596905901&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;auto_route | Flutter package&quot; data-og-description=&quot;AutoRoute is a declarative routing solution, where everything needed for navigation is automatically generated for you.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/auto_route&quot; data-og-url=&quot;https://pub.dev/packages/auto_route&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/biKrWr/hyWzC8mFnp/6ZnhQ9kx2Nn9DqCQ4Tf4W1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/eH1cW/hyWzxzdAyT/Ynz3sbHa15XKFafdP1mwW1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/1Opfp/hyWvOWSZLT/z4DmyjnLwNIzF1crwg0Ne1/img.png?width=878&amp;amp;height=553&amp;amp;face=0_0_878_553&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/auto_route&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/auto_route&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/biKrWr/hyWzC8mFnp/6ZnhQ9kx2Nn9DqCQ4Tf4W1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/eH1cW/hyWzxzdAyT/Ynz3sbHa15XKFafdP1mwW1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/1Opfp/hyWvOWSZLT/z4DmyjnLwNIzF1crwg0Ne1/img.png?width=878&amp;amp;height=553&amp;amp;face=0_0_878_553');&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;auto_route | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AutoRoute is a declarative routing solution, where everything needed for navigation is automatically generated for you.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;16. Deep Link &amp;amp; Firebase dynamic link 서비스 종료에 대한 대안&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;➡️ Deep Link 란?&lt;/b&gt;&lt;/h3&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;&lt;span style=&quot;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;&lt;b&gt;링크 클릭 &lt;/b&gt;: 사용자가 웹 페이지, 이메일, 광고 등에서 딥링크를 클릭한다. &lt;br /&gt;&lt;b&gt;&amp;nbsp;-&amp;gt; 앱 실행&lt;/b&gt; : 해당 앱이 설치되어 있다면, 딥링크는 앱을 실행하고 특정 콘텐츠로 연결한다. &lt;br /&gt;&lt;b&gt;&amp;nbsp;-&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;a href=&quot;https://firebase.google.com/support/dynamic-links-faq?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebase.google.com/support/dynamic-links-faq?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720597237404&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;동적 링크 지원 중단 FAQ &amp;nbsp;|&amp;nbsp; Firebase&quot; data-og-description=&quot;의견 보내기 동적 링크 지원 중단 FAQ 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 참고: Firebase 동적 링크는 지원 중단되었으므로 새 프로젝트에서 사용해&quot; data-og-host=&quot;firebase.google.com&quot; data-og-source-url=&quot;https://firebase.google.com/support/dynamic-links-faq?hl=ko&quot; data-og-url=&quot;https://firebase.google.com/support/dynamic-links-faq?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://firebase.google.com/support/dynamic-links-faq?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://firebase.google.com/support/dynamic-links-faq?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;동적 링크 지원 중단 FAQ &amp;nbsp;|&amp;nbsp; Firebase&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;의견 보내기 동적 링크 지원 중단 FAQ 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 참고: Firebase 동적 링크는 지원 중단되었으므로 새 프로젝트에서 사용해&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;firebase.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Airbridge&lt;/b&gt;&lt;/h3&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://help.airbridge.io/ko/developers/flutter-sdk&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://help.airbridge.io/ko/developers/flutter-sdk&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720597563808&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;Flutter SDK | Airbridge Help Center&quot; data-og-description=&quot;airbrdige_flutter_sdk pub.dev pubspec.yaml&amp;nbsp;파일의&amp;nbsp;dependencies&amp;nbsp;블럭 안에 다음과 같은 라인을 추가해 주세요.&quot; data-og-host=&quot;help.airbridge.io&quot; data-og-source-url=&quot;https://help.airbridge.io/ko/developers/flutter-sdk&quot; data-og-url=&quot;https://help.airbridge.io/ko/developers/flutter-sdk&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://help.airbridge.io/ko/developers/flutter-sdk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://help.airbridge.io/ko/developers/flutter-sdk&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;Flutter SDK | Airbridge Help Center&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;airbrdige_flutter_sdk pub.dev pubspec.yaml&amp;nbsp;파일의&amp;nbsp;dependencies&amp;nbsp;블럭 안에 다음과 같은 라인을 추가해 주세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;help.airbridge.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 안드로이드의 App Link &amp;amp; iOS의 Universal Link&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하기 위해서는 본인의 웹 서비스(웹 서버 + 도메인 주소)가 존재해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 지속적인 운영 비용 발생!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/comJj8/btsIvdBNGKJ/VeJ7NOT4go6X9v1CfRhnK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/comJj8/btsIvdBNGKJ/VeJ7NOT4go6X9v1CfRhnK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/comJj8/btsIvdBNGKJ/VeJ7NOT4go6X9v1CfRhnK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcomJj8%2FbtsIvdBNGKJ%2FVeJ7NOT4go6X9v1CfRhnK1%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;587&quot; height=&quot;313&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;373&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1. 안드로이드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/cookbook/navigation/set-up-app-links&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/cookbook/navigation/set-up-app-links&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720597762751&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;Set up app links for Android&quot; data-og-description=&quot;How set up universal links for an iOS application built with Flutter&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/cookbook/navigation/set-up-app-links&quot; data-og-url=&quot;https://docs.flutter.dev/cookbook/navigation/set-up-app-links&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cJzBZu/hyWzBuQ7Oh/IaT9O4iFfncH59k7EuMCP1/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/csUMwG/hyWzxsrOWg/LhPuBAkOogz49PxPK6TpKK/img.png?width=1440&amp;amp;height=3120&amp;amp;face=0_0_1440_3120&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/cookbook/navigation/set-up-app-links&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/cookbook/navigation/set-up-app-links&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cJzBZu/hyWzBuQ7Oh/IaT9O4iFfncH59k7EuMCP1/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/csUMwG/hyWzxsrOWg/LhPuBAkOogz49PxPK6TpKK/img.png?width=1440&amp;amp;height=3120&amp;amp;face=0_0_1440_3120');&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;Set up app links for Android&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How set up universal links for an iOS application built with Flutter&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;2. iOS&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/cookbook/navigation/set-up-universal-links&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/cookbook/navigation/set-up-universal-links&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720597787831&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;Set up universal links for iOS&quot; data-og-description=&quot;How set up universal links for an iOS application built with Flutter&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/cookbook/navigation/set-up-universal-links&quot; data-og-url=&quot;https://docs.flutter.dev/cookbook/navigation/set-up-universal-links&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cyzNMD/hyWvV2OfqE/PAkc6v1w6JnJ8uX5KPFW41/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/cC8XAE/hyWzDlS63K/vaX1LKiSnOSZnDRErW1kd1/img.png?width=1999&amp;amp;height=1570&amp;amp;face=0_0_1999_1570,https://scrap.kakaocdn.net/dn/m2CH3/hyWzwNPRaF/pr0hRUytrdR19ZYzFqLpD0/img.png?width=1040&amp;amp;height=2036&amp;amp;face=0_0_1040_2036&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/cookbook/navigation/set-up-universal-links&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/cookbook/navigation/set-up-universal-links&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cyzNMD/hyWvV2OfqE/PAkc6v1w6JnJ8uX5KPFW41/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/cC8XAE/hyWzDlS63K/vaX1LKiSnOSZnDRErW1kd1/img.png?width=1999&amp;amp;height=1570&amp;amp;face=0_0_1999_1570,https://scrap.kakaocdn.net/dn/m2CH3/hyWzwNPRaF/pr0hRUytrdR19ZYzFqLpD0/img.png?width=1040&amp;amp;height=2036&amp;amp;face=0_0_1040_2036');&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;Set up universal links for iOS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How set up universal links for an iOS application built with Flutter&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ 공식 유튜브 튜토리얼&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6RxuDcs6jVw&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=6RxuDcs6jVw&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=6RxuDcs6jVw&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/BjqqB/hyWvUJCJ1H/xnqNbewQ9N4jPfvKEhEmCk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=936_128_1046_552&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Deep dive into Flutter deep linking&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/6RxuDcs6jVw&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;17. FCM Push를 위한 firebase_messaging 연동하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/firebase_messaging&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/firebase_messaging&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720599171730&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;firebase_messaging | Flutter package&quot; data-og-description=&quot;Flutter plugin for Firebase Cloud Messaging, a cross-platform messaging solution that lets you reliably deliver messages on Android and iOS.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/firebase_messaging&quot; data-og-url=&quot;https://pub.dev/packages/firebase_messaging&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hyQW2/hyWvSyg6Ot/VHx3IK4WVvbRIHSBjcAOA1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/4Bqlh/hyWvJ2pyp5/uBaFjKukjiK8ZBk83Xgv71/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/firebase_messaging&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/firebase_messaging&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hyQW2/hyWvSyg6Ot/VHx3IK4WVvbRIHSBjcAOA1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/4Bqlh/hyWvJ2pyp5/uBaFjKukjiK8ZBk83Xgv71/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;firebase_messaging | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flutter plugin for Firebase Cloud Messaging, a cross-platform messaging solution that lets you reliably deliver messages on Android and iOS.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;18. Localizing (다국어 처리)를 위한 &quot;easy_localization&quot; 패키지&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/easy_localization&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/easy_localization&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720675437244&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;easy_localization | Flutter package&quot; data-og-description=&quot;Easy and Fast internationalizing and localization your Flutter Apps, this package simplify the internationalizing process .&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/easy_localization&quot; data-og-url=&quot;https://pub.dev/packages/easy_localization&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tKhhH/hyWzzKH8Oc/0tBTKI4s8MrOJ2EfJr3ezk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/IVi0T/hyWvPIqnON/KKE9Hvrszo1qha18y6E2IK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/easy_localization&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/easy_localization&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tKhhH/hyWzzKH8Oc/0tBTKI4s8MrOJ2EfJr3ezk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/IVi0T/hyWvPIqnON/KKE9Hvrszo1qha18y6E2IK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;easy_localization | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Easy and Fast internationalizing and localization your Flutter Apps, this package simplify the internationalizing process .&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 하지만 easy_localization 패키지를 사용하더라도 json 파일을 수동으로 수정하고 작성하는 것은 귀찮은 일이다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;➡️ 더 쉽고 편하게 도와주는 &quot;sheet_loader_localization&quot; 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSV 파일을 다운로드하고 easy_localization&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;easy_localization_loader&lt;span&gt;&amp;nbsp;&lt;/span&gt;와 함께 작동하도록 온라인 Google 시트에서 지역화 키를 생성한다고 한다.&lt;/p&gt;
&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://pub.dev/packages/sheet_loader_localization&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/sheet_loader_localization&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720678665855&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;sheet_loader_localization | Flutter package&quot; data-og-description=&quot;Download an CSV file and generates the localization keys from an online Google Sheet.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/sheet_loader_localization&quot; data-og-url=&quot;https://pub.dev/packages/sheet_loader_localization&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ccNiT4/hyWvOJw7YM/1KjF4BYkKCPcU2ywbLJdEK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/AjGjO/hyWzzRtxH6/nNpXJV297htJNKZ5sBKw90/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bSY7aN/hyWvSd5oPL/DeKWpNqP5BnCGvsMKi9kV1/img.png?width=1204&amp;amp;height=575&amp;amp;face=0_0_1204_575&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/sheet_loader_localization&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/sheet_loader_localization&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ccNiT4/hyWvOJw7YM/1KjF4BYkKCPcU2ywbLJdEK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/AjGjO/hyWzzRtxH6/nNpXJV297htJNKZ5sBKw90/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bSY7aN/hyWvSd5oPL/DeKWpNqP5BnCGvsMKi9kV1/img.png?width=1204&amp;amp;height=575&amp;amp;face=0_0_1204_575');&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;sheet_loader_localization | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Download an CSV file and generates the localization keys from an online Google Sheet.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dart와 Flutter/15개 프로젝트</category>
      <category>DART</category>
      <category>Flutter</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/287</guid>
      <comments>https://sweetburble.tistory.com/287#entry287comment</comments>
      <pubDate>Mon, 17 Jun 2024 15:41:11 +0900</pubDate>
    </item>
    <item>
      <title>어묵볶음 - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/286</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240617_어묵볶음_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JyAFR/btsH0BLtftd/NPB68mvsoT0G9F1qx0RMe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JyAFR/btsH0BLtftd/NPB68mvsoT0G9F1qx0RMe0/img.png&quot; data-alt=&quot;어묵볶음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JyAFR/btsH0BLtftd/NPB68mvsoT0G9F1qx0RMe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJyAFR%2FbtsH0BLtftd%2FNPB68mvsoT0G9F1qx0RMe0%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;500&quot; height=&quot;376&quot; data-filename=&quot;edited_20240617_어묵볶음_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어묵볶음&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6Ww9EVQ5eWs&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=6Ww9EVQ5eWs&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=6Ww9EVQ5eWs&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bMlCjA/hyWoKrRHen/MwGNyiGkFIxeVq0CW8Ekwk/img.jpg?width=640&amp;amp;height=480&amp;amp;face=0_0_640_480&quot; data-video-width=&quot;640&quot; data-video-height=&quot;480&quot; data-video-origin-width=&quot;640&quot; data-video-origin-height=&quot;480&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;흰밥에 이것만 올려 드세요.&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/6Ww9EVQ5eWs&quot; width=&quot;640&quot; height=&quot;480&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/286</guid>
      <comments>https://sweetburble.tistory.com/286#entry286comment</comments>
      <pubDate>Mon, 17 Jun 2024 14:17:56 +0900</pubDate>
    </item>
    <item>
      <title>계란 볶음밥 ver.2 - [하루한끼 식]</title>
      <link>https://sweetburble.tistory.com/285</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240614_계란볶음밥ver.2_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;501&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uPj9r/btsH0khQ4QD/C9rCn9BwssnSVK6Y876bKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uPj9r/btsH0khQ4QD/C9rCn9BwssnSVK6Y876bKK/img.png&quot; data-alt=&quot;계란 볶음밥 ver.2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uPj9r/btsH0khQ4QD/C9rCn9BwssnSVK6Y876bKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuPj9r%2FbtsH0khQ4QD%2FC9rCn9BwssnSVK6Y876bKK%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;500&quot; height=&quot;501&quot; data-filename=&quot;edited_20240614_계란볶음밥ver.2_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;501&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;계란 볶음밥 ver.2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=KM-E4KpoLFU&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=KM-E4KpoLFU&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=KM-E4KpoLFU&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bnIk3l/hyWlcJQhvb/5gUW3M6kRQ4aJku6YAE9ck/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;계란볶음밥 만들기&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/KM-E4KpoLFU&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/285</guid>
      <comments>https://sweetburble.tistory.com/285#entry285comment</comments>
      <pubDate>Fri, 14 Jun 2024 21:41:42 +0900</pubDate>
    </item>
    <item>
      <title>내용 정리 Part. 2 - [코드팩토리의 플러터 프로그래밍]</title>
      <link>https://sweetburble.tistory.com/284</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 6월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. [13장] 영상 통화 : WebRTC, 내비게이션, 아고라 API&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-1. camera 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;camera 패키지&lt;/b&gt;&lt;/span&gt;를 사용하면, 카메라 기능을 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장에서는 아고라 패키지를 사용하지만, 플러터 공식 패키지인 camera 패키지를 사용하는 법을 배우자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blW4aw/btsHVWUOWMg/wmxZgZurieDs6mdDwmnxJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blW4aw/btsHVWUOWMg/wmxZgZurieDs6mdDwmnxJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blW4aw/btsHVWUOWMg/wmxZgZurieDs6mdDwmnxJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblW4aw%2FbtsHVWUOWMg%2FwmxZgZurieDs6mdDwmnxJ0%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;368&quot; height=&quot;157&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;157&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_1718093515700&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:camera/camera.dart';

late List&amp;lt;CameraDescription&amp;gt; _cameras;

Future&amp;lt;void&amp;gt; main() async {
  // Flutter 앱이 실행될 준비가 됐는지 확인
  WidgetsFlutterBinding.ensureInitialized();

  // 핸드폰에 있는 카메라들을 가져온다
  _cameras = await availableCameras();
  runApp(const CameraApp());
}

class CameraApp extends StatefulWidget {
  const CameraApp({super.key});

  @override
  State&amp;lt;CameraApp&amp;gt; createState() =&amp;gt; _CameraAppState();
}

class _CameraAppState extends State&amp;lt;CameraApp&amp;gt; {
  // 카메라를 제어할 수 있는 컨트롤러 선언
  late CameraController controller;
  
  @override
  void initState() {
    super.initState();
    initializeCamera();
  }

  initializeCamera() async {
    try {
      // 가장 첫번째 카메라로 카메라 컨트롤러를 설정한다
      controller = CameraController(_cameras[0], ResolutionPreset.max);

      // 카메라 초기화
      await controller.initialize();

      setState(() {});
    } catch (e) {
      // 에러났을 때 출력
      if (e is CameraException) {
        switch (e.code) {
          case 'CameraAccessDenied':
            print('User denied camera access!');
            break;
          default:
            print('Handle other errors.');
            break;
        }
      }
    }
  }

  @override
  void dispose() {
    // 컨트롤러 삭제
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // 카메라 초기화 상태 확인
    if (!controller.value.isInitialized) {
      return Center(
          child: CircularProgressIndicator()
      );
    }

    return MaterialApp(
      home: CameraPreview(controller),
    );
  }
}&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;&lt;b&gt;➡️ 특이사항&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;1. WidgetsFlutterBinding.ensureInitialized()&lt;/b&gt; &lt;/span&gt;: material.dart에서 제공된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main() 함수의 첫 실행값이 runApp()이 아닐 경우, 꼭 제일 먼저 실행해줘야 한다.&lt;/p&gt;
&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: #f89009;&quot;&gt;&lt;b&gt;2. await availableCameras()&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: #1a5490;&quot;&gt;&lt;b&gt;3. CameraController&lt;/b&gt; &lt;/span&gt;: 카메라를 제어할 수 잇는 컨트롤러이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 첫 번째 파라미터는&lt;b&gt; &quot;사용할 카메라&quot;&lt;/b&gt; 를 입력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 파라미터는&lt;b&gt; &quot;해상도&quot;&lt;/b&gt; 를 설정한다. 그 중 ResolutionPreset.max는 &lt;b&gt;&quot;최대 해상도&lt;/b&gt;&lt;b&gt;&quot;&lt;/b&gt; 를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;227&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca7uEe/btsHVJuGeK3/KsrVOqNIOm82ZqdLSBxKSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca7uEe/btsHVJuGeK3/KsrVOqNIOm82ZqdLSBxKSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca7uEe/btsHVJuGeK3/KsrVOqNIOm82ZqdLSBxKSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca7uEe%2FbtsHVJuGeK3%2FKsrVOqNIOm82ZqdLSBxKSk%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;492&quot; height=&quot;227&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;227&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: #1a5490;&quot;&gt;&lt;b&gt;4. CameraPreview 위젯&lt;/b&gt;&lt;/span&gt; : 카메라를 화면에 보여줄 수 있다. 첫 번째 파라미터에 CameraController를 입력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-2. WebRTC&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상 통화 기능을 구현하려면 -&amp;gt; &lt;b&gt;영상과 음성 정보를 저장, 전송하기 / 클라이언트 간 연결하기 등등&lt;/b&gt; 다양한 기능이 필요하다. &lt;b&gt;웹 브라우저 기반으로 통신하는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&quot;WebRTC&quot;&lt;/span&gt; 라는 API&lt;/b&gt;가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;음성 통화 / 영상 통화 / P2P 파일 공유 기능을 제공&lt;/b&gt;&lt;/u&gt;하므로 다양한 분야에 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebRTC를 사용하려면, 두 클라이언트 말고도 &lt;u&gt;&lt;b&gt;중계용 서버&lt;/b&gt;&lt;/u&gt;가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어로는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;시그널링 서버 (Signalling Server)&lt;/b&gt;&lt;/span&gt;라고 하는데, 직접 구현보다는&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; &quot;아고라&quot; 서비스&lt;/b&gt;&lt;/span&gt;를 이용해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHWaRA/btsHUaHjuhx/q8YF6FOcBdtgmYaKEzKpOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHWaRA/btsHUaHjuhx/q8YF6FOcBdtgmYaKEzKpOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHWaRA/btsHUaHjuhx/q8YF6FOcBdtgmYaKEzKpOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHWaRA%2FbtsHUaHjuhx%2Fq8YF6FOcBdtgmYaKEzKpOK%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;575&quot; height=&quot;401&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;401&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;※ iOS 시뮬레이터는 카메라 기능을 아예 제공하지 않는다.&lt;/b&gt;&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;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-3. 내비게이션 (Navigation)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내비게이션은 &lt;u&gt;&lt;b&gt;플러터에서 화면을 이동할 때 사용하는 클래스&lt;/b&gt;&lt;/u&gt;이다.&lt;b&gt; &lt;/b&gt;&lt;u&gt;&lt;b&gt;스택(Stack) 데이터 구조로 설계&lt;/b&gt;&lt;/u&gt;되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm70jj/btsHUHq4asT/YHwUygkCRvZH4yk1WK9Si1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm70jj/btsHUHq4asT/YHwUygkCRvZH4yk1WK9Si1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm70jj/btsHUHq4asT/YHwUygkCRvZH4yk1WK9Si1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm70jj%2FbtsHUHq4asT%2FYHwUygkCRvZH4yk1WK9Si1%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;564&quot; height=&quot;335&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;335&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;b&gt;Navigator 클래스&lt;/b&gt;에서 제공하는 메서드를 사용해서 내비게이션 스택을 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GpAKQ/btsHVTYaguT/LbQ8OSTpYkKuW5lhECDPYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GpAKQ/btsHVTYaguT/LbQ8OSTpYkKuW5lhECDPYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GpAKQ/btsHVTYaguT/LbQ8OSTpYkKuW5lhECDPYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGpAKQ%2FbtsHVTYaguT%2FLbQ8OSTpYkKuW5lhECDPYk%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;564&quot; height=&quot;331&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;331&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;최상위에 MaterialApp 위젯을 추가해주면, Navigator 클래스의 객체가 자동으로 생성되는데, 이 값을 이용해서 화면을 이동할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1718253357978&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 영상 통화 입장 버튼
 */
class _EntryButton extends StatelessWidget {
  const _EntryButton({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.end,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        ElevatedButton(
          onPressed: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (_) =&amp;gt; CamScreen(),
              ),
            );
          },
          child: Text('입장하기'),
        ),
      ],
    );
  }
}&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;테마를 이용할 때, Theme.of(context)를 사용했던 것처럼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Navigator.of(context)를 실행하면, 위젯 트리의 가장 가까이에 있는 Navigator를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 MaterialApp으로 최상위 위젯을 감싸주면, 앱 어디서든 Navigator를 가져올 수 있다는 의미이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-4. 사전 준비&lt;/b&gt;&lt;/h3&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: #1a5490;&quot;&gt;&lt;b&gt;&quot;permission_handler&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 아고라 API 준비&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WnB8F/btsHUn0H1Cp/WqzhQcKzsf4vUGF7X5Mxw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WnB8F/btsHUn0H1Cp/WqzhQcKzsf4vUGF7X5Mxw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WnB8F/btsHUn0H1Cp/WqzhQcKzsf4vUGF7X5Mxw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWnB8F%2FbtsHUn0H1Cp%2FWqzhQcKzsf4vUGF7X5Mxw0%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;500&quot; height=&quot;634&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;791&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bbrst/btsHVSya65T/j3WEUwj2w4xiEwKknEL730/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bbrst/btsHVSya65T/j3WEUwj2w4xiEwKknEL730/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bbrst/btsHVSya65T/j3WEUwj2w4xiEwKknEL730/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBbrst%2FbtsHVSya65T%2Fj3WEUwj2w4xiEwKknEL730%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;501&quot; height=&quot;701&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;791&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OVu1p/btsHUqXrpM9/bnnWwnv0xEzkKp8eZMw6s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OVu1p/btsHUqXrpM9/bnnWwnv0xEzkKp8eZMw6s1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OVu1p/btsHUqXrpM9/bnnWwnv0xEzkKp8eZMw6s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOVu1p%2FbtsHUqXrpM9%2FbnnWwnv0xEzkKp8eZMw6s1%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;501&quot; height=&quot;425&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;478&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;size18&quot;&gt;&lt;b&gt;➡️ 안드로이드에서 추가할 네이티브 권한&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;READ_PHONE_STATE / ACCESS_NETWORK_STATE&lt;/b&gt; &lt;/span&gt;: 네트워크 상태를 읽는다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;INTERNET&lt;/b&gt;&lt;/span&gt; : 인터넷을 이용해서 영상을 스트리밍해야 하니까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;RECORD_AUDIO / MODIFY_AUDIO_SETTINGS / CAMERA&lt;/b&gt;&lt;/span&gt; : 녹음과 녹화 기능 구현을 위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;BLUETOOTH_CONNECT&lt;/b&gt;&lt;/span&gt; : 블루투스를 이용한 (무선 이어폰같은) 녹음 및 녹화 기능을 사용할 수도 있으니까&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bolaKC/btsHVKUHBqT/OXqR5rFMfaSrsDTOgWJf1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bolaKC/btsHVKUHBqT/OXqR5rFMfaSrsDTOgWJf1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bolaKC/btsHVKUHBqT/OXqR5rFMfaSrsDTOgWJf1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbolaKC%2FbtsHVKUHBqT%2FOXqR5rFMfaSrsDTOgWJf1k%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;562&quot; height=&quot;237&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciOfvT/btsHU8V4pfv/O9HgFVVzygIa7FWJ9knzg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciOfvT/btsHU8V4pfv/O9HgFVVzygIa7FWJ9knzg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciOfvT/btsHU8V4pfv/O9HgFVVzygIa7FWJ9knzg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciOfvT%2FbtsHU8V4pfv%2FO9HgFVVzygIa7FWJ9knzg0%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;559&quot; height=&quot;336&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;336&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;size18&quot;&gt;&lt;b&gt;➡️ iOS에서 추가할 네이티브 권한&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS는 카메라 권한인 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;NSCameraUsageDescription&quot;&lt;/b&gt; &lt;/span&gt;과 마이크 권한인 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;NSMicrophoneUsageDescription&quot;&lt;/b&gt; &lt;/span&gt;만 추가한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb1pyJ/btsHVKUHDbH/BiLUIwnIZdtLOMYxERB5O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb1pyJ/btsHVKUHDbH/BiLUIwnIZdtLOMYxERB5O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb1pyJ/btsHVKUHDbH/BiLUIwnIZdtLOMYxERB5O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb1pyJ%2FbtsHVKUHDbH%2FBiLUIwnIZdtLOMYxERB5O1%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;557&quot; height=&quot;281&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;281&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) permission_handler 패키지를 사용해 플러터에서 권한 관리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;permission_handler 패키지를 이용하면, 안드로이드와 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지의 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;Permission&quot; 클래스&lt;/b&gt;&lt;/span&gt;에 존재하는 권한을 선택한 후 -&amp;gt; &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;request() 함수&lt;/b&gt;&lt;/span&gt;를 실행하면 권한 요청을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값으로는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;PermissionStatus&quot; &lt;/b&gt;&lt;/span&gt;에 해당되는 &lt;b&gt;enum 값&lt;/b&gt;을 받아올 수 있으며, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;PermissionStatus.granted 값&lt;/b&gt;&lt;/span&gt;을 반환받으면 권한이 있다는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGquGW/btsHVSZjaXm/jZl9JCEYaV1MOQW4tXKma1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGquGW/btsHVSZjaXm/jZl9JCEYaV1MOQW4tXKma1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGquGW/btsHVSZjaXm/jZl9JCEYaV1MOQW4tXKma1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGquGW%2FbtsHVSZjaXm%2FjZl9JCEYaV1MOQW4tXKma1%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;564&quot; height=&quot;438&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;438&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;u&gt;&lt;b&gt;필요한 여러 권한을 한 번에 연속적으로 요청&lt;/b&gt;&lt;/u&gt;할 때도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때는 요청하고 싶은 권한을 순서대로&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; &quot;List&quot;&lt;/b&gt;&lt;/span&gt; 에 넣어서, 한번에 request() 함수를 실행해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 권한 요청에 대한 결과는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;Map 형태&quot;&lt;/b&gt;&lt;/span&gt; 로 반환받으며, 확인하고 싶은 권한을 key로 넣어서 가져온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yi2fP/btsHUD98yI3/m38StZJmfjPGzkQ1qTygck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yi2fP/btsHUD98yI3/m38StZJmfjPGzkQ1qTygck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yi2fP/btsHUD98yI3/m38StZJmfjPGzkQ1qTygck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyi2fP%2FbtsHUD98yI3%2Fm38StZJmfjPGzkQ1qTygck%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;510&quot; height=&quot;263&quot; data-origin-width=&quot;510&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-5. 아고라 API 사용법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저, 아고라 API를 활성화시키는 방법은 총 3단계이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) 아고라의 RtcEngine 활성화&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;2) RtcEngine을 통해서, 사용하는 핸드폰의 카메라를 활성화&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;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3) 미리 받아둔 아고라 API 상수값들을 사용해서, testchannel에 참여&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718255673318&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:video_call/const/agora.dart';

class _CamScreenState extends State&amp;lt;CamScreen&amp;gt; {
  RtcEngine? engine; // 아고라 엔진을 저장하는 변수
  int? uid; // 내 ID
  int? otherUid; // 상대방 아이디

  /**
   * 영상 통화 관련 권한 작업을 모두 실행한다
   */
  Future&amp;lt;bool&amp;gt; init() async {
    final resp = await [Permission.camera, Permission.microphone].request();

    final cameraPermission = resp[Permission.camera];
    final micPermission = resp[Permission.microphone];

    if (cameraPermission != PermissionStatus.granted || micPermission != PermissionStatus.granted) {
      throw '카메라 또는 마이크 권한이 없습니다!';
    }

    if (engine == null) {
      // 엔진이 정의되지 않았으면, 새로 생성하기
      engine = createAgoraRtcEngine();

      // 아고라 엔진 초기화
      await engine!.initialize(
        // 초기화할 때 사용하는 설정
        RtcEngineContext(
          // 미리 상수로 저장해둔 APP ID
          appId: APP_ID,

          // 라이브 동영상 송출에 최적화
          channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
        ),
      );

      engine!.registerEventHandler(
        // 아고라 엔진에서 받을 수 있는 이벤트 값들을 등록
        RtcEngineEventHandler(
          onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
            // 채널 접속에 성공했을 때, 실행
            print('채널에 입장했습니다. uid : ${connection.localUid}');
            setState(() {
              this.uid = connection.localUid;
            });
          },
          onLeaveChannel: (RtcConnection connection, RtcStats stats) {
            // 채널의 퇴장했을 때, 실행
            print('채널 퇴장');
            setState(() {
              uid = null;
            });
          },
          onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
            // 다른 사용자가 접속했을 때, 실행
            print('상대가 채널에 입장했습니다! uid : $remoteUid');
            setState(() {
              otherUid = remoteUid;
            });
          },
          onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) {
            // 다른 사용자가 채널을 나갔을 때, 실행
            print('상대가 채널에서 나갔습니다! uid : $otherUid');
            setState(() {
              otherUid = null;
            });
          },
        ),
      );

      // 엔진으로 영상을 송출하겠다고 설정한다
      await engine!.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
      await engine!.enableVideo(); // 동영상 기능을 활성화
      await engine!.startPreview(); // 카메라를 이용해 동영상을 화면에 실행한다

      await engine!.joinChannel( // 채널에 입장하기
        token: TEMP_TOKEN,
        channelId: CHANNEL_NAME,

        // 영상과 관련된 여러가지 설정을 할 수 있으나, 이 프로젝트에서는 안 쓴다
        uid: 0,
        options: ChannelMediaOptions(),
      );
    }

    return true;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ onJoinChannelSuccess&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;RtcConnection connection&lt;/b&gt;&lt;/span&gt; : 영상 통화 정보에 관련된 값. connectionl.localUid로는 내 ID를 가져올 수 있다.&lt;/p&gt;
&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;int elapsed&lt;/b&gt;&lt;/span&gt; : joinChannel을 실행한 후, 콜백이 실행되기까지 걸린 시간&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: #f89009;&quot;&gt; &lt;b&gt;➡️ onUserJoined&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;int remoteUid&lt;/b&gt;&lt;/span&gt; : 통화 상대방의 고유 ID&lt;/p&gt;
&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;int elapsed&lt;/b&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;➡️ onUserOffline&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UserOfflineReasonType reason&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;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ engine!.joinChannel()&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;b&gt;options 파라미터&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;uid 파라미터&lt;/b&gt; : 내 고유 ID를 지정한다. 0은 자동으로 고유 ID가 배정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;b&gt;renderMainView()&lt;/b&gt; 와 &lt;b&gt;renderSubView()&lt;/b&gt; 함수를 작성해서 RtcEngine에서 송수신하는 정보를 화면에 그려주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt; &lt;b&gt;renderSubView()&lt;/b&gt; &lt;/span&gt;: &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;span style=&quot;color: #f89009;&quot;&gt; &lt;b&gt;renderMainView()&lt;/b&gt;&lt;/span&gt; : &quot;상대방의 핸드폰&quot; 이 찍는 화면을 렌더링하는 함수&lt;/p&gt;
&lt;pre id=&quot;code_1718256846645&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  /**
   * 내 핸드폰이 찍는 화면을 렌더링한다
   */
  Widget renderSubView() {
    if (uid != null) {
      // AgoraVideoView 위젯을 사용하면, 동영상을 화면에 보여주는 위젯을 구현할 수 있다
      return AgoraVideoView(
        // VideoViewController를 파라미터로 입력하면,
        // 해당 컨트롤러가 제공하는 동영상 정보를 AgoraVideoView 위젯을 통해 보여줄 수 있다
        controller: VideoViewController(
          rtcEngine: engine!,

          // VideoCanvas에 0을 입력해서 내 영상을 보여준다
          canvas: const VideoCanvas(uid: 0),
        ),
      );
    } else {
      // 아직 내가 채널에 접속하지 않았다면, 로딩 화면을 보여준다
      return CircularProgressIndicator();
    }
  }

  /**
   * 통화 상대 핸드폰이 찍는 화면을 렌더링한다
   */
  Widget renderMainView() {
    if (otherUid != null) {
      // VideoViewController.remote 생성자를 이용하면,
      // 상대방의 동영상을 AgoraVideoView로 그려낼 수 있다
      return AgoraVideoView(
        controller: VideoViewController.remote(
          rtcEngine: engine!,

          // uid에 상대방 ID를 입력한다
          canvas: VideoCanvas(uid: otherUid),
          connection: const RtcConnection(channelId: CHANNEL_NAME),
        ),
      );
    } else {
      // 상대가 아직 채널에 들어오지 않았다면, 대기 메시지를 보여준다
      return Center(
        child: const Text(
          '다른 사용자가 입장할 때까지 대기해주세요!',
          textAlign: TextAlign.center,
        ),
      );
    }
  }&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. [14장] 오늘도 출첵 : 구글 지도, Geolocator 패키지, 다이얼로그&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1. Geolocator 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지리와 관련된 기능&lt;/b&gt;을 쉽게 사용할 수 있는 패키지이다. 크게&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt; 3가지 기능&lt;/b&gt;&lt;/span&gt;이 있다.&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 현재 GPS 위치가 바뀔 때마다, 현재 위치값을 받을 수 있는 기능을 사용한다.&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. 현재 위치와 특정 위치(회사 건물) 간의 거리를 계산한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;이 과정은 2단계로 이루어진다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1-1. 기기의 위치 서비스가 활성화되어 있는지 확인한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o3SjG/btsHWTrVmTo/G8KzXgzhk4KOK42TF30oqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o3SjG/btsHWTrVmTo/G8KzXgzhk4KOK42TF30oqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o3SjG/btsHWTrVmTo/G8KzXgzhk4KOK42TF30oqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo3SjG%2FbtsHWTrVmTo%2FG8KzXgzhk4KOK42TF30oqK%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;559&quot; height=&quot;94&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;94&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: #409d00;&quot;&gt;&lt;b&gt;1-2. 앱에서 위치 서비스 권한을 요청하고 허가 받는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sVzbD/btsHXi5OqaJ/dBhzPAxTkao8mEC0WSaETk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sVzbD/btsHXi5OqaJ/dBhzPAxTkao8mEC0WSaETk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sVzbD/btsHXi5OqaJ/dBhzPAxTkao8mEC0WSaETk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsVzbD%2FbtsHXi5OqaJ%2FdBhzPAxTkao8mEC0WSaETk%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;570&quot; height=&quot;171&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/67eut/btsHWViXyvc/K8R747h2xBB60JPrybZpxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/67eut/btsHWViXyvc/K8R747h2xBB60JPrybZpxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/67eut/btsHWViXyvc/K8R747h2xBB60JPrybZpxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F67eut%2FbtsHWViXyvc%2FK8R747h2xBB60JPrybZpxk%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;286&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;286&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;Geolocator 패키지의 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;getPositionStream()&quot;&lt;/b&gt; &lt;/span&gt;함수를 사용하면, 현재 위치가 변경될 때마다 그 값을 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Position 클래스&lt;/b&gt;&lt;/span&gt; 형태로 주기적으로 반환받을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3AeUC/btsHXmUBSRu/PHWwtVbvqQDWqe7yx3kW70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3AeUC/btsHXmUBSRu/PHWwtVbvqQDWqe7yx3kW70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3AeUC/btsHXmUBSRu/PHWwtVbvqQDWqe7yx3kW70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3AeUC%2FbtsHXmUBSRu%2FPHWwtVbvqQDWqe7yx3kW70%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;338&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;338&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;Geolocator 패키지의 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;distanceBetween()&quot;&lt;/b&gt; &lt;/span&gt;함수를 실행하면, 두 위치간의 거리를 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;미터&quot;&lt;/b&gt;&lt;/span&gt; 단위로 반환받을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XdSkl/btsHXjXXAb9/qlVSJKJGF1IlnySrjPtizK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XdSkl/btsHXjXXAb9/qlVSJKJGF1IlnySrjPtizK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XdSkl/btsHXjXXAb9/qlVSJKJGF1IlnySrjPtizK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXdSkl%2FbtsHXjXXAb9%2FqlVSJKJGF1IlnySrjPtizK%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;308&quot; height=&quot;158&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;158&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-2. 사전 준비&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpcIwT/btsHW4GJkz0/sgfiF8fsZk7YyttaxOcmK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpcIwT/btsHW4GJkz0/sgfiF8fsZk7YyttaxOcmK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpcIwT/btsHW4GJkz0/sgfiF8fsZk7YyttaxOcmK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpcIwT%2FbtsHW4GJkz0%2FsgfiF8fsZk7YyttaxOcmK1%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;569&quot; height=&quot;218&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;218&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;구글 지도를 사용하려면, 안드로이드와 iOS 모두 네이티브 설정이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 구글 클라우드 플랫폼에서 발급받은 API 키를 안드로이드, iOS 네이티브 파일에 등록해야하고,&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ 안드로이드 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VxJ7q/btsHYuxoQR8/wTIkkoakS3ZqDsk25ODgp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VxJ7q/btsHYuxoQR8/wTIkkoakS3ZqDsk25ODgp0/img.png&quot; data-alt=&quot;24년 6월 현재는 compileSdkVersion 34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VxJ7q/btsHYuxoQR8/wTIkkoakS3ZqDsk25ODgp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVxJ7q%2FbtsHYuxoQR8%2FwTIkkoakS3ZqDsk25ODgp0%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;565&quot; height=&quot;547&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;24년 6월 현재는 compileSdkVersion 34&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8qhHR/btsHXFl6IZj/oRV76JS9O4KPhWwWeYBCzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8qhHR/btsHXFl6IZj/oRV76JS9O4KPhWwWeYBCzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8qhHR/btsHXFl6IZj/oRV76JS9O4KPhWwWeYBCzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8qhHR%2FbtsHXFl6IZj%2FoRV76JS9O4KPhWwWeYBCzK%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;558&quot; height=&quot;233&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;233&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ iOS 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vqhjo/btsHXP9RDC6/UfMq0kbnxGBuRJFNvURHCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vqhjo/btsHXP9RDC6/UfMq0kbnxGBuRJFNvURHCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vqhjo/btsHXP9RDC6/UfMq0kbnxGBuRJFNvURHCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvqhjo%2FbtsHXP9RDC6%2FUfMq0kbnxGBuRJFNvURHCk%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;564&quot; height=&quot;472&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lk1A1/btsHW6q5Mrz/APcudoPhiZNSvl0CGTDrRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lk1A1/btsHW6q5Mrz/APcudoPhiZNSvl0CGTDrRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lk1A1/btsHW6q5Mrz/APcudoPhiZNSvl0CGTDrRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLk1A1%2FbtsHW6q5Mrz%2FAPcudoPhiZNSvl0CGTDrRk%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;563&quot; height=&quot;192&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;192&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-3. 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;1) 화면에 구글 지도를 실행하는 방법&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2) 구글 지도에 마커를 생성, 원으로 영역을 표시, 현재 위치를 표시하는 방법&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3) 버튼을 누르면, 현재 위치로 지도를 자동으로 이동하는 방법&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 화면에 구글 지도를 실행하는 방법&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1718259848383&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeScreen extends StatelessWidget {
  static final LatLng companyLatLng = LatLng( // 지도 초기화 위치
    37.5233273, // 위도
    126.921252, // 경도
  );

  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: renderAppBar(),
      body: GoogleMap( // 지도 위치 지정
        initialCameraPosition: CameraPosition(
          target: companyLatLng,
          zoom: 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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;LatLng 클래스&lt;/b&gt;&lt;/span&gt; : google_maps_flutter의 클래스이다. 위도와 경도로 특정 위치를 표현할 수 있다.&lt;/p&gt;
&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: #1a5490;&quot;&gt;&lt;b&gt;GooleMap 위젯&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sweetburble.tistory.com/283#21.%20GoogleMap%20%EC%9C%84%EC%A0%AF%20(goolge_maps_flutter%20%ED%8C%A8%ED%82%A4%EC%A7%80)-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sweetburble.tistory.com/283#21.%20GoogleMap%20%EC%9C%84%EC%A0%AF%20(goolge_maps_flutter%20%ED%8C%A8%ED%82%A4%EC%A7%80)-1&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718260792224&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;24년 6월을 기준으로 작성되었습니다.&amp;nbsp;0. 기본 위젯 알아보기※ Flutter는 RichText 위젯과 Paragraph와 같은 클래스도 제공한다.https://docs.flutter.dev/ui/widgets/text&amp;nbsp;Text widgetsA catalog of Flutter's widgets for display&quot; data-og-host=&quot;sweetburble.tistory.com&quot; data-og-source-url=&quot;https://sweetburble.tistory.com/283#21.%20GoogleMap%20%EC%9C%84%EC%A0%AF%20(goolge_maps_flutter%20%ED%8C%A8%ED%82%A4%EC%A7%80)-1&quot; data-og-url=&quot;https://sweetburble.tistory.com/283&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bW67sk/hyWllM8ubF/vOSMGxLgsBLWYmkJTIQet1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/9gDb1/hyWlk8wMrf/LYg1o0KHrzlNyZkLKwA1g1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/bQVICd/hyWliCSL61/xSuKogbiVDOkaaQSjlrTk1/img.png?width=565&amp;amp;height=794&amp;amp;face=0_0_565_794&quot;&gt;&lt;a href=&quot;https://sweetburble.tistory.com/283#21.%20GoogleMap%20%EC%9C%84%EC%A0%AF%20(goolge_maps_flutter%20%ED%8C%A8%ED%82%A4%EC%A7%80)-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sweetburble.tistory.com/283#21.%20GoogleMap%20%EC%9C%84%EC%A0%AF%20(goolge_maps_flutter%20%ED%8C%A8%ED%82%A4%EC%A7%80)-1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bW67sk/hyWllM8ubF/vOSMGxLgsBLWYmkJTIQet1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/9gDb1/hyWlk8wMrf/LYg1o0KHrzlNyZkLKwA1g1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/bQVICd/hyWliCSL61/xSuKogbiVDOkaaQSjlrTk1/img.png?width=565&amp;amp;height=794&amp;amp;face=0_0_565_794');&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;24년 6월을 기준으로 작성되었습니다.&amp;nbsp;0. 기본 위젯 알아보기※ Flutter는 RichText 위젯과 Paragraph와 같은 클래스도 제공한다.https://docs.flutter.dev/ui/widgets/text&amp;nbsp;Text widgetsA catalog of Flutter's widgets for display&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sweetburble.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;기기 자체의 GPS 사용 권한을 확인하고 -&amp;gt; 앱에서 위치 서비스를 사용할 수 있는지 확인한 후 -&amp;gt; 위치 권한이 없다면, 권한을 재요청하는 로직을 구현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718261229510&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  /**
   * 위치 권한을 관리하는 함수
   */
  Future&amp;lt;String&amp;gt; checkPermission() async {
    // 위치 서비스 활성화 여부 확인
    final isLocationEnabled = await Geolocator.isLocationServiceEnabled();

    if (!isLocationEnabled) { // 기기의 위치 서비스가 비활성화
      return '스마트폰의 위치 서비스를 활성화해주세요!';
    }

    // 앱 내부에서 위치 권한 확인
    LocationPermission checkedPermission = await Geolocator.checkPermission();

    if (checkedPermission == LocationPermission.denied) { // 위치 권한 거절됨
      // 위치 권한 요청
      checkedPermission = await Geolocator.requestPermission();

      if (checkedPermission == LocationPermission.denied) {
        return '위치 권한을 허가해주세요!';
      }

      // 위치 권한 영구거절됨 (앱 내부에서는 재요청 불가)
      if (checkedPermission == LocationPermission.deniedForever) {
        return &quot;앱의 위치 권한을 설정에서 허가해주세요!!&quot;;
      }

      // 위 모든 조건이 통과되면, 위치 권한 허가 완료
      return '위치 권한이 허가되었습니다...';
    }
  }&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;&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;checkPermission() 함수가 Future&amp;lt;String&amp;gt;을 반환하므로, 앞선 13장 처럼 FutureBuilder를 사용해서 UI 로직을 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718261658839&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: renderAppBar(),
      body: FutureBuilder&amp;lt;String&amp;gt;(
        future: checkPermission(),
        builder: (context, snapshot) {
          // 1. 로딩 상태
          if (!snapshot.hasData &amp;amp;&amp;amp; snapshot.connectionState == ConnectionState.waiting) {
            return Center(
              child: CircularProgressIndicator(),
            );
          }

          // 2. 권한 허가된 상태
          if (snapshot.data == '위치 권한이 허가되었습니다.') {
            // 기존 위젯 UI 코드
            ...
          }
          
          // 3. 로딩은 되었으나, 권한이 없는 상태
          return Center(
            child: Text(
              snapshot.data.toString(),
            ),
          );
        },
      ),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;GoogleMap 위젯 참조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;&lt;b&gt;다이얼로그&lt;/b&gt;를 사용해서 구현한다. 상황에 따라 서로 다른 다이얼로그를 렌더링해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; Geolocator.getCurrentPosition()&lt;/b&gt;&lt;/span&gt; : 현재 위치를 반환 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; Geolocator.distanceBetween()&lt;/b&gt; &lt;/span&gt;: 위치 간의 거리를 미터 단위로 구한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718263934740&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ElevatedButton( // [출근하기] 버튼
    onPressed: () async {
      final curPosition = await Geolocator.getCurrentPosition(); // 현재 위치
      final distance = Geolocator.distanceBetween(
        curPosition.latitude, // 현재 위치 위도 
        curPosition.longitude, // 현재 위치 경도
        companyLatLng.latitude, // 목표(회사) 위치 위도
        companyLatLng.longitude, // 목표 위치 경도
      );

      bool canCheck = distance &amp;lt; 100; // 100미터 이내에 있으면, 출근 가능

      showDialog(
        context: context,
        builder: (_) {
          return AlertDialog(
            title: Text('출근하기'),
            // 출근 가능 여부에 따라 다른 메시지 제공
            content: Text(
              canCheck ? '출근 하시겠습니까?' : '출근할 수 없는 위치입니다!',
            ),
            actions: [
              TextButton(
                // 취소를 누르면 false 반환
                onPressed: () {
                  Navigator.of(context).pop(false);
                },
                child: Text('취소'),
              ),
              if (canCheck) // 출근 가능한 상태일때만 [출근하기] 버튼 제공
                TextButton(
                  // [출근하기]를 누르면 true 반환
                  onPressed: () {
                    Navigator.of(context).pop(true);
                  },
                  child: Text('출근하기'),
                ),
            ],
          );
        },
      );
    },
    child: Text('출근하기!'),
  ),&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. [15장] 포토 스티커 : GestureDetector&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-1. 사전 준비&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;334&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEwl1J/btsHYvb80rE/X340nR2VeY92C16BzKL5i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEwl1J/btsHYvb80rE/X340nR2VeY92C16BzKL5i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEwl1J/btsHYvb80rE/X340nR2VeY92C16BzKL5i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEwl1J%2FbtsHYvb80rE%2FX340nR2VeY92C16BzKL5i0%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;334&quot; height=&quot;321&quot; data-origin-width=&quot;334&quot; data-origin-height=&quot;321&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ 안드로이드 권한 설정하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CS8oP/btsHX5dMrff/MWWS5nLtmJ722plAhAiqhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CS8oP/btsHX5dMrff/MWWS5nLtmJ722plAhAiqhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CS8oP/btsHX5dMrff/MWWS5nLtmJ722plAhAiqhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCS8oP%2FbtsHX5dMrff%2FMWWS5nLtmJ722plAhAiqhK%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;566&quot; height=&quot;266&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcKB5J/btsHYUo2Kxn/oLkXhsknvwwUmLIEEVSN5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcKB5J/btsHYUo2Kxn/oLkXhsknvwwUmLIEEVSN5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcKB5J/btsHYUo2Kxn/oLkXhsknvwwUmLIEEVSN5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcKB5J%2FbtsHYUo2Kxn%2FoLkXhsknvwwUmLIEEVSN5k%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;557&quot; height=&quot;104&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;104&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ iOS 권한 설정하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FgtDf/btsHYTcBjms/18kDSUT9NklW0Koj016YSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FgtDf/btsHYTcBjms/18kDSUT9NklW0Koj016YSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FgtDf/btsHYTcBjms/18kDSUT9NklW0Koj016YSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFgtDf%2FbtsHYTcBjms%2F18kDSUT9NklW0Koj016YSK%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;570&quot; height=&quot;313&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;313&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-2. 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12장의 동영상 플레이어에서는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;ImagePicker 패키지&lt;/b&gt;&lt;/span&gt;의 &lt;b&gt;pickVideo() 함수&lt;/b&gt;를 사용했지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; pickImage() 함수&lt;/b&gt;&lt;/span&gt;를 사용해서 이미지를 선택한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718265740317&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; {
  XFile? image; // 선택한 이미지를 저장할 변수

  @override
  Widget build(BuildContext context) {
  }

  void onPickImage() async {
    // 갤러리에서 이미지 선택하기
    final image = await ImagePicker().pickImage(source: ImageSource.gallery);

    setState(() {
      this.image = image;
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ [24년 6월 수정사항]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;TextButton.styleFrom()&lt;/b&gt; &lt;/span&gt;에서 &lt;b&gt;primary 파라미터&lt;/b&gt; 대신 -&amp;gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt; foregroundColor 파라미터&lt;/b&gt;&lt;/span&gt;를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StatefulWidget을 사용할 때, 같은 위젯 여러 개를 리스트로 제공하려면, 그 위젯들을 각각 구분하는 key 파라미터를 지정해야 한다. 이 값이 같으면 같은 위젯으로 인식하고, 다르면 다른 위젯으로 인식한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718372545598&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  child: Stack(
    fit: StackFit.expand, // 자식 위젯들의 크기를 최대로 늘려주기
    children: [
      Image.file(
        File(image!.path),
        // 이미지가 부모 위젯 크기 최대를 차지
        fit: BoxFit.cover,
      ),
      ...stickers.map(
        (sticker) =&amp;gt; Center(
          child: EmoticonSticker(
            key: ObjectKey(sticker.id),
            onTransform: onTransform,
            imgPath: sticker.imgPath,
            isSelected: selectedId == sticker.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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-3. ImageGallerySaver 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;이미지를 갤러리에 저장&lt;/b&gt;&lt;/u&gt;한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718375794265&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  /**
   * 수정한 이미지 저장 함수
   */
  void onSaveImage() async {
    RenderRepaintBoundary boundary = imgKey.currentContext!
        .findRenderObject() as RenderRepaintBoundary;

    ui.Image image = await boundary.toImage(); // 찾은 위젯 바운더리를 이미지로 1차 변환

    // ImageGallerySaver 패키지가 이미지를 저장할 수 있도록 이미지 -&amp;gt; 바이트 데이터로 2차 변환
    ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);

    // 이 바이트 데이터가 8비트 정수형이어야 하기 때문에 바이트 데이터 -&amp;gt; Unit8List 타입으로 3차 변환
    Uint8List pngBytes = byteData!.buffer.asUint8List();

    await ImageGallerySaver.saveImage(pngBytes, quality: 100);

    ScaffoldMessenger.of(context).showSnackBar( // 이미지 저장 후 -&amp;gt; Snackbar 표시
      SnackBar(
        content: Text('저장되었습니다!!'),
      ),
    );
  }&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: #1a5490;&quot;&gt;&lt;b&gt;&quot;8비트 정수형의 바이트 데이터&quot;&lt;/b&gt;&lt;/span&gt; 여야 한다. 그에 맞게 변환 작업이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;RepaintBoundary.toImage()&lt;/b&gt;&lt;/span&gt;가 반환하는 Image 타입은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;material.dart의 Image 위젯&quot;&lt;/b&gt;&lt;/span&gt; 이 아니라 -&amp;gt; &lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;&quot;dart:ui의 Image 클래스&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;&lt;b&gt;※ 더 나아가서 &lt;span style=&quot;color: #f89009;&quot;&gt;&quot;path_provider&quot;&lt;/span&gt; 패키지와 &lt;span style=&quot;color: #f89009;&quot;&gt;&quot;dart:io&quot;&lt;/span&gt;를 사용하면, 앱이 접근할 수 있는 폴더에 파일을 저장할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/cookbook/persistence/reading-writing-files&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/cookbook/persistence/reading-writing-files&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718376064950&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;Read and write files&quot; data-og-description=&quot;How to read from and write to files on disk.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/cookbook/persistence/reading-writing-files&quot; data-og-url=&quot;https://docs.flutter.dev/cookbook/persistence/reading-writing-files&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b9cjFw/hyWleHFiLn/SpdMQ4CoiJLjhXFU8i2IQ1/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/cookbook/persistence/reading-writing-files&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/cookbook/persistence/reading-writing-files&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b9cjFw/hyWleHFiLn/SpdMQ4CoiJLjhXFU8i2IQ1/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461');&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;Read and write files&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How to read from and write to files on disk.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. [16장] 코팩튜브 : REST API, JSON, 유튜브 API&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ObR6p/btsHZJo2fB5/TRXf8wu3uK3dkIAGvWad9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ObR6p/btsHZJo2fB5/TRXf8wu3uK3dkIAGvWad9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ObR6p/btsHZJo2fB5/TRXf8wu3uK3dkIAGvWad9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FObR6p%2FbtsHZJo2fB5%2FTRXf8wu3uK3dkIAGvWad9k%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;65&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;65&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;&lt;b&gt;4-1. REST API&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMncfU/btsH1bxzkXK/75R8lIn7HygPTdPOJ008u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMncfU/btsH1bxzkXK/75R8lIn7HygPTdPOJ008u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMncfU/btsH1bxzkXK/75R8lIn7HygPTdPOJ008u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMncfU%2FbtsH1bxzkXK%2F75R8lIn7HygPTdPOJ008u0%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;572&quot; height=&quot;686&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;686&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;플러터에서 HTTP 요청을 할 때는, 주로 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;http 패키지&quot;&lt;/b&gt; &lt;/span&gt;나 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;dio 패키지&quot;&lt;/b&gt;&lt;/span&gt; 를 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4Tt2d/btsHZvYLBi7/kwYhNHt5tIKRtUVvFfrW21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4Tt2d/btsHZvYLBi7/kwYhNHt5tIKRtUVvFfrW21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4Tt2d/btsHZvYLBi7/kwYhNHt5tIKRtUVvFfrW21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4Tt2d%2FbtsHZvYLBi7%2FkwYhNHt5tIKRtUVvFfrW21%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;564&quot; height=&quot;379&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;379&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-2. 사전 준비&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 클라우드 콘솔 (console.cloud.google.com)에 접속해서, 원하는 프로젝트에 &lt;b&gt;&quot;Youtube Data API V3&quot; 도 활성화&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I7SPy/btsHZhsVUgL/lvJjK4KsmXeyPxQnjvc3xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I7SPy/btsHZhsVUgL/lvJjK4KsmXeyPxQnjvc3xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I7SPy/btsHZhsVUgL/lvJjK4KsmXeyPxQnjvc3xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI7SPy%2FbtsHZhsVUgL%2FlvJjK4KsmXeyPxQnjvc3xK%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;326&quot; height=&quot;181&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;181&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;※ 안드로이드 compileSdkVersion은 34, minSdkVersion은 17로 변경했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-3. 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Youtube Data API V3는 상당히 많은 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;Search:list API&quot;&lt;/b&gt; &lt;/span&gt;를 사용해서 특정 채널의 동영상들을 최신 순서대로 불러오도록 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu8UuJ/btsH0V2YkEw/LALCDZ2yECp55gwqTdXyxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu8UuJ/btsH0V2YkEw/LALCDZ2yECp55gwqTdXyxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu8UuJ/btsH0V2YkEw/LALCDZ2yECp55gwqTdXyxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu8UuJ%2FbtsH0V2YkEw%2FLALCDZ2yECp55gwqTdXyxK%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;567&quot; height=&quot;265&quot; data-origin-width=&quot;567&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 테이블을 기반으로 HTTP 요청을 보내면, 정상 응답일 경우 다음과 같은 구조의 JSON 데이터를 받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQC3Xy/btsH00b0sDW/oAANeTIUnaMgxbS18kWtn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQC3Xy/btsH00b0sDW/oAANeTIUnaMgxbS18kWtn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQC3Xy/btsH00b0sDW/oAANeTIUnaMgxbS18kWtn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQC3Xy%2FbtsH00b0sDW%2FoAANeTIUnaMgxbS18kWtn1%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;499&quot; height=&quot;568&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;609&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;(kind, etag, nextPageToken, pageInfo)&lt;/b&gt; -&amp;gt; 응답에 대한 정보를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트에서는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;items&quot; 키&lt;/b&gt;&lt;/span&gt;에 집중한다. 요청한 동영상의 데이터가 모두 items 키에 리스트로 구현되어 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718427736413&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:cf_tube/const/api.dart';
import 'package:cf_tube/model/video_model.dart';
import 'package:dio/dio.dart';

class YoutubeRepository {
  /**
   * GET 메서드
   */
  static Future&amp;lt;List&amp;lt;VideoModel&amp;gt;&amp;gt; getVideos() async {
    final resp = await Dio().get(
      YOUTUBE_API_BASE_URL, // 요청을 보낸 URL
      queryParameters: { // 요청에 포함할 쿼리 파라미터들
        'channelId': CF_CHANNEL_ID,
        'maxResults': 50,
        'key': API_KEY,
        'part': 'snippet',
        'order': 'date',
      }
    );

    final listWithData = resp.data['items'].where(
        (item) =&amp;gt;
            item?['id']?['videoId'] != null &amp;amp;&amp;amp; item?['snippet']?['title'] != null,
    ); // videoId와 title이 null이 아닌 데이터들만 남긴다

    return listWithData.map&amp;lt;VideoModel&amp;gt;(
        (item) =&amp;gt; VideoModel(
          id: item['id']['videoId'],
          title: item['snippet']['title'],
        ),
    ).toList(); // 필터링된 데이터들을 기반으로 VideoModel 리스트를 생성한다
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-4. ScrollPhysics 클래스 간단 정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;Scrollable&quot;&lt;/b&gt;&lt;/span&gt; 한 위젯의 Physics(물리적 특성)을 결정하는 클래스이다. &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;physics&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;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;➡️ Scollable 한 위젯들&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ListView, PageView, GridView, CustomScrollView, SingleChildScrollView 등등&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: #409d00;&quot;&gt;&lt;b&gt;➡️ 대표적인 ScrollPhysics&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;AlwaysScrollableScrollPhysics&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) NeverScrollableScrollPhysics&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) &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;BouncingScrollPhysics&lt;/b&gt; : 스크린의 Edge 부분에서 바운싱되는 효과가 추가된다.&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: #333333; text-align: start;&quot;&gt;&lt;b&gt;4) &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;PageScrollPhysics&lt;/b&gt; : 구역을 페이지로 나눠서 이동하기 때문에 -&amp;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;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이외 &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;ClampingScrollPhysics, &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;FixedExtentScrollPhysics, &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;RangeMaintainingScrollPhysics, &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;ScrollPhysics 등등...&lt;/span&gt;&lt;/span&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. [17장] 일정 관리 앱 : Table Calendar&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-1. table_calendar 패키지&lt;/b&gt;&lt;/h3&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;p data-ke-size=&quot;size16&quot;&gt;그리고 매우 유연한 디자인 기능을 노출하고 있어서, 이 패키지의 거의 모든 요소를 직접 최적화할 수 잇다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkOLc3/btsHZ6xz1dN/kvzROnsQNxmx42E1EngZGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkOLc3/btsHZ6xz1dN/kvzROnsQNxmx42E1EngZGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkOLc3/btsHZ6xz1dN/kvzROnsQNxmx42E1EngZGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkOLc3%2FbtsHZ6xz1dN%2FkvzROnsQNxmx42E1EngZGk%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;549&quot; height=&quot;453&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m5di6/btsH0j4voLK/5WWb4tcxxDAEq0sBboEnAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m5di6/btsH0j4voLK/5WWb4tcxxDAEq0sBboEnAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m5di6/btsH0j4voLK/5WWb4tcxxDAEq0sBboEnAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm5di6%2FbtsH0j4voLK%2F5WWb4tcxxDAEq0sBboEnAK%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;551&quot; height=&quot;481&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;481&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: #1b711d;&quot;&gt;&lt;b&gt;1. focusedDay&lt;/b&gt;&lt;/span&gt; : 현재 화면에 보이는 날짜(= 현재 포커스된 날짜)를 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 11월 23일로 지정했다면, 화면에 11월 달력이 보인다.&lt;/p&gt;
&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: #1b711d;&quot;&gt;&lt;b&gt;2. firstDay&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: #1b711d;&quot;&gt;&lt;b&gt;3. lastDay&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: #1b711d;&quot;&gt;&lt;b&gt;4. selectedDayPredicate&lt;/b&gt;&lt;/span&gt; : 달력에 표시되는 '선택된 날짜'를, 어떤 날짜로 지정할지 로직을 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;DateTime day 인수&lt;/b&gt;&lt;/span&gt;는 현재 화면에 보이는 날짜를 하나씩 입력하게 되고, &lt;u&gt;&lt;b&gt;하나의 true&lt;/b&gt;&lt;/u&gt;가 반환되면 해당 날짜를 선택하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;5. onDaySelected&lt;/b&gt;&lt;/span&gt; : 날짜가 선택됐을 때 실행되는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;selectedDay&lt;/b&gt; : 선택된 날짜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;focusedDay&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: #1b711d;&quot;&gt;&lt;b&gt;6. onPageChanged&lt;/b&gt; &lt;/span&gt;: 달력의 페이지가 변경될 때마다 실행되는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;focusedDay&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: #1b711d;&quot;&gt;&lt;b&gt;7. rangeSelectionMode&lt;/b&gt;&lt;/span&gt; : 기간 선택 모드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;toggledOn&lt;/b&gt; : 단일 날짜 대신 날짜 기간을 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;toggledOff&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: #1b711d;&quot;&gt;&lt;b&gt;8. onRangeSelected&lt;/b&gt;&lt;/span&gt; : 7번에서&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;rangeSelectionMode.&lt;/span&gt;toggledOn으로 설정됐을 때 실행되는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;start&lt;/b&gt; : 선택한 기간의 시작 날짜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;end&lt;/b&gt; : 선택한 기간의 마지막 날짜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;focusedDay&lt;/b&gt; : 선택이 실행된 순간에 포커스된 날짜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-2. 사전 준비&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pubspec.yaml 파일의 &lt;b&gt;&quot;dependencies&quot;&lt;/b&gt; 에 추가된 패키지들은 앱에 함께 패키징되지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;dev_dependencies&quot;&lt;/b&gt;&lt;/span&gt; 에 추가된 패키지들은 개발할 때만 사용된다. 앱을 실행할 때 필요없는 패키지를 추가할 때 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718430826087&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies:
  flutter:
    sdk: flutter
   
  cupertino_icons: ^1.0.6
  table_calendar: ^3.0.9 # 달력 기능
  intl: ^0.18.1 # 다국어화 기능
  drift: ^2.13.0 # Drift ORM(Object-Relational Mapping) 기능
  sqlite3_flutter_libs: ^0.5.17 # SQLite 데이터베이스
  path_provider: ^2.1.1 # 경로 관련 기능
  path: ^1.8.3 # 경로 관련 기능
  get_it: ^7.6.4 # 프로젝트 전역으로 의존성 주입을 가능하게 한다
  dio: ^5.3.3 # 네트워크 요청을 가능하게 한다
  provider: ^6.0.5 # 전역(글로벌) 상태 관리를 가능하게 한다
  uuid: ^4.1.0 # UUID를 생성 기능

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^3.0.0
  drift_dev: ^2.13.0 # Drift 코드 생성 기능
  build_runner: ^2.4.6 # 플러터에서 코드 생성 기능을 제공한다 (Code Generation을 실행하는 명령어 지원)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-3. 드리프트(Drift) 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클래스를 이용해서 SQLite 데이터베이스를 구현&lt;/b&gt;할 수 있다. &lt;u&gt;&lt;b&gt;직접 SQL 쿼리를 작성하지 않아도, Dart 언어로 데이터베이스 테이블과 쿼리를 구현하면, 드리프트가 자동으로 쿼리를 생성&lt;/b&gt;&lt;/u&gt;해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 자동으로 코드를 작성하는 기능을, 플러터에서는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;코드 생성 (Code generation)&quot;&lt;/b&gt; &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;따라서&lt;b&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt; dev_dependencies에 드리프트 코드 생성 관련 패키지들을 추가&lt;/span&gt;&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-4. 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ [24년 6월 변경사항] 482페이지 main_calendar.dart 에러 수정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CalendarStyle에서 defaultDecoration / weekendDecoration / selectedDecoration 파라미터의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;borderRadius&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt;&amp;nbsp;속성과&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;BoxDecoration&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt;의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;shape&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt; 속성이 충돌하여 발생한 에러이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;BoxDecoration&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt;의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;shape&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt;&amp;nbsp;속성이&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;BoxShape.circle&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt;일 때는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;borderRadius&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt; 속성을 사용할 수 없다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt;따라서, &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;CalendarStyle&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt;에서&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;defaultDecoration&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt;의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;shape&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt;를&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;BoxShape.rectangle&lt;/span&gt;&lt;span style=&quot;background-color: #f9fafb; color: #1f2937; text-align: left;&quot;&gt;으로 변경하면 해결된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718433532610&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  calendarStyle: CalendarStyle(
    isTodayHighlighted: false, // 오늘 하이라이트 제거?
    defaultDecoration: BoxDecoration( // 기본 날짜 스타일
      shape: BoxShape.rectangle,
      borderRadius: BorderRadius.circular(6.0),
      color: LIGHT_GREY_COLOR,
    ),
    weekendDecoration: BoxDecoration( // 주말 날짜 스타일
      shape: BoxShape.rectangle,
      borderRadius: BorderRadius.circular(6.0),
      color: LIGHT_GREY_COLOR,
    ),
    selectedDecoration: BoxDecoration( // 선택된 날짜 스타일
      shape: BoxShape.rectangle,
      borderRadius: BorderRadius.circular(6.0),
      border: Border.all(
        color: PRIMARY_COLOR,
        width: 1.0,
      ),
    ),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dart와 Flutter/도서 내용 정리</category>
      <category>DART</category>
      <category>Flutter</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/284</guid>
      <comments>https://sweetburble.tistory.com/284#entry284comment</comments>
      <pubDate>Thu, 13 Jun 2024 13:05:31 +0900</pubDate>
    </item>
    <item>
      <title>[코드팩토리의 플러터 프로그래밍] - 위젯 정리</title>
      <link>https://sweetburble.tistory.com/283</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 6월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. 기본 위젯 알아보기&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ Flutter는 RichText 위젯과 Paragraph와 같은 클래스도 제공한다.&lt;/b&gt;&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://docs.flutter.dev/ui/widgets/text&quot;&gt;https://docs.flutter.dev/ui/widgets/text&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718088839134&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;Text widgets&quot; data-og-description=&quot;A catalog of Flutter's widgets for displaying and styling text.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/ui/widgets/text&quot; data-og-url=&quot;https://docs.flutter.dev/ui/widgets/text&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bcgJ2y/hyWlllzaJu/2HLkIgadhKrRMkDfcHx36K/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/h5RYH/hyWlfr7DbB/ioK3l0FaGNykPkskWuSJd1/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200,https://scrap.kakaocdn.net/dn/mpXVR/hyWll6Ybst/6PfzajcaUD7zcu6TWNzX0K/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/ui/widgets/text&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/ui/widgets/text&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bcgJ2y/hyWlllzaJu/2HLkIgadhKrRMkDfcHx36K/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/h5RYH/hyWlfr7DbB/ioK3l0FaGNykPkskWuSJd1/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200,https://scrap.kakaocdn.net/dn/mpXVR/hyWll6Ybst/6PfzajcaUD7zcu6TWNzX0K/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200');&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;Text widgets&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A catalog of Flutter's widgets for displaying and styling text.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. IconButton / TextButton 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Flutter에서는 &lt;b&gt;글리프(glyph) 기반&lt;/b&gt;의 아이콘을 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본으로 제공되는 아이콘 목록은 아래 링크에서 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fonts.google.com/icons&quot;&gt;https://fonts.google.com/icons&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718088867394&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;Material Symbols and Icons - Google Fonts&quot; data-og-description=&quot;Material Symbols are our newest icons consolidating over 2,500 glyphs in a single font file with a wide range of design variants.&quot; data-og-host=&quot;fonts.google.com&quot; data-og-source-url=&quot;https://fonts.google.com/icons&quot; data-og-url=&quot;https://fonts.google.com/icons&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jGJ9a/hyWg3GQtyz/URz3973UW8oAnHuFB3aLK0/img.jpg?width=1136&amp;amp;height=648&amp;amp;face=0_0_1136_648,https://scrap.kakaocdn.net/dn/bQlBzJ/hyWgVINFvf/GHYshe3rOo1Oz4Ghi961g1/img.jpg?width=1136&amp;amp;height=648&amp;amp;face=0_0_1136_648&quot;&gt;&lt;a href=&quot;https://fonts.google.com/icons&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://fonts.google.com/icons&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jGJ9a/hyWg3GQtyz/URz3973UW8oAnHuFB3aLK0/img.jpg?width=1136&amp;amp;height=648&amp;amp;face=0_0_1136_648,https://scrap.kakaocdn.net/dn/bQlBzJ/hyWgVINFvf/GHYshe3rOo1Oz4Ghi961g1/img.jpg?width=1136&amp;amp;height=648&amp;amp;face=0_0_1136_648');&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 Symbols and Icons - Google Fonts&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Material Symbols are our newest icons consolidating over 2,500 glyphs in a single font file with a wide range of design variants.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;fonts.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TextButton 위젯&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ [24년 6월 수정사항]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;TextButton.styleFrom()&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;primary 파라미터&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;대신 -&amp;gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;foregroundColor 파라미터&lt;/b&gt;&lt;/span&gt;를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718266425567&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  return Center(
    child: TextButton(
      style: TextButton.styleFrom(
        foregroundColor: Colors.grey,
      ),
      onPressed: onPickImage,
      child: Text('이미지 선택하기'),
    ),
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. GestureDetector 위젯, InkWell 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ GestureDetector 위젯&lt;/b&gt;&lt;/h3&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl1wmc/btsHUQ81Olt/1OSaBRCIhA0sth3cG0Iof0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl1wmc/btsHUQ81Olt/1OSaBRCIhA0sth3cG0Iof0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl1wmc/btsHUQ81Olt/1OSaBRCIhA0sth3cG0Iof0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl1wmc%2FbtsHUQ81Olt%2F1OSaBRCIhA0sth3cG0Iof0%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;503&quot; height=&quot;273&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;227&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfqEU2/btsHVt6yBU2/4ejmNi8JRIjGKCH0Td7WIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfqEU2/btsHVt6yBU2/4ejmNi8JRIjGKCH0Td7WIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfqEU2/btsHVt6yBU2/4ejmNi8JRIjGKCH0Td7WIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfqEU2%2FbtsHVt6yBU2%2F4ejmNi8JRIjGKCH0Td7WIK%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;502&quot; height=&quot;202&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;227&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ onScaleUpdate 파라미터 (제스처)&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;p data-ke-size=&quot;size16&quot;&gt;이 제스처는&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; ScaleUpdateDetails&lt;/b&gt;&lt;/span&gt;를 첫번째 매개변수로 입력받는다. &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;ScaleUpdateDetails는 굉장히 많은 정보를 제공한다.&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: #409d00;&quot;&gt;&lt;b&gt;1) details.scale&lt;/b&gt;&lt;/span&gt; : 확대 배율, 확대/축소 제스처가 &quot;시작된 순간&quot;을 기준으로 몇 배율의 변화가 있는지 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배율은 위젯의 &quot;초기 크기&quot; 기준이 아니기 때문에, onScaleEnd 파라미터가 실행될 때 현재 배율을 꼭 기억해두어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래야 [최근 배율&amp;nbsp; * details.scale] 을 계산해서, 위젯의 초기 크기 기준으로 배율을 계산할 수 있다.&lt;/p&gt;
&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: #409d00;&quot;&gt;&lt;b&gt;2) details.focalPointDelta&lt;/b&gt; &lt;/span&gt;: dy값 및 dx 값을 갖고 있다. 각각 y축 및 x축으로 이동한 수치를 반환받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 수치를 변수로 저장해두고, 확대/축소 제스처에 대한 콜백이 실행될 때마다 변수들을 업데이트하면, 사용자가 의도한 위젯의 변화를 상태관리 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ onScaleEnd 파라미터&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ behavior 파라미터&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1. HitTestBehavior.translucent&lt;/b&gt; &lt;/span&gt;: 위젯의 빈 공간에 제스처를 입력해도, 실행된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ InkWell 위젯&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GestureDetector 보다는 기능이 적지만, 기본적인 애니메이션이 구현되어 있어서 약간의 편리함을 챙긴 위젯이다.&lt;/p&gt;
&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;※ Material 위젯이 상위 위젯에 있어야 한다!&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://api.flutter.dev/flutter/material/InkWell-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/InkWell-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718692531288&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;InkWell class - material library - Dart API&quot; data-og-description=&quot;A rectangular area of a Material that responds to touch. For a variant of this widget that does not clip splashes, see InkResponse. The following diagram shows how an InkWell looks when tapped, when using default values. The InkWell widget must have a Mate&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/InkWell-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/InkWell-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/InkWell-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/InkWell-class.html&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;InkWell class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A rectangular area of a Material that responds to touch. For a variant of this widget that does not clip splashes, see InkResponse. The following diagram shows how an InkWell looks when tapped, when using default values. The InkWell widget must have a Mate&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Row 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csEjid/btsHUaAody7/kesvmuuyz6K4KLSssz1EXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csEjid/btsHUaAody7/kesvmuuyz6K4KLSssz1EXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csEjid/btsHUaAody7/kesvmuuyz6K4KLSssz1EXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsEjid%2FbtsHUaAody7%2Fkesvmuuyz6K4KLSssz1EXK%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;563&quot; height=&quot;265&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;b&gt;주축은 mainAxisAlignment 로, 반대축은 crossAxisAlignment&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;로 배치한다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4r54N/btsHUC4cnOY/o26oaq1BRbO6Xl49blpQm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4r54N/btsHUC4cnOY/o26oaq1BRbO6Xl49blpQm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4r54N/btsHUC4cnOY/o26oaq1BRbO6Xl49blpQm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4r54N%2FbtsHUC4cnOY%2Fo26oaq1BRbO6Xl49blpQm0%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;507&quot; height=&quot;712&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dP4xcY/btsHTGlWxhw/48AN3jcPcUgmNHwz3t8bS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dP4xcY/btsHTGlWxhw/48AN3jcPcUgmNHwz3t8bS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dP4xcY/btsHTGlWxhw/48AN3jcPcUgmNHwz3t8bS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdP4xcY%2FbtsHTGlWxhw%2F48AN3jcPcUgmNHwz3t8bS0%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;504&quot; height=&quot;472&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;527&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Column 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q7wv9/btsHVsGAk9h/4cT0ddSimogF9zXaEx10yK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q7wv9/btsHVsGAk9h/4cT0ddSimogF9zXaEx10yK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q7wv9/btsHVsGAk9h/4cT0ddSimogF9zXaEx10yK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq7wv9%2FbtsHVsGAk9h%2F4cT0ddSimogF9zXaEx10yK%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;503&quot; height=&quot;524&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf0NOb/btsHVrHHL0f/Wxuo4LBhRZNNuQFpX7Co5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf0NOb/btsHVrHHL0f/Wxuo4LBhRZNNuQFpX7Co5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf0NOb/btsHVrHHL0f/Wxuo4LBhRZNNuQFpX7Co5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf0NOb%2FbtsHVrHHL0f%2FWxuo4LBhRZNNuQFpX7Co5K%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;502&quot; height=&quot;651&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pWby0/btsHU4TujFo/RZ8R8fZD4bkdOHBnWYKPGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pWby0/btsHU4TujFo/RZ8R8fZD4bkdOHBnWYKPGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pWby0/btsHU4TujFo/RZ8R8fZD4bkdOHBnWYKPGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpWby0%2FbtsHU4TujFo%2FRZ8R8fZD4bkdOHBnWYKPGk%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;503&quot; height=&quot;117&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;131&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Fle&lt;/b&gt;&lt;b&gt;xible 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Flexible 위젯은 Row나 Column에서 사용하는 위젯이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Flexible에 제공된 child가 Column/Row 안에서 크기를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;최소한&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;으로 차지하게 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로 flex 파라미터를 통해, 각 Flexible 위젯이 얼만큼의 비율로 공간을 차지할지 지정할 수도 있다.&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;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ fit 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FlexFit.loose&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 자식 위젯이 필요한 만큼의 공간만 차지한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FlexFit.fit&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 자식 위젯이 남은 공간을 모두 차지한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDdoD9/btsHUOXFf5P/jDlC0N0tWTUVpEKCm7yzTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDdoD9/btsHUOXFf5P/jDlC0N0tWTUVpEKCm7yzTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDdoD9/btsHUOXFf5P/jDlC0N0tWTUVpEKCm7yzTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDdoD9%2FbtsHUOXFf5P%2FjDlC0N0tWTUVpEKCm7yzTK%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;332&quot; height=&quot;446&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. Expanded 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Flexible 위젯을 상속한 다음, fit 파라미터에 FlexFit.tight를 기본으로 제공해준 위젯이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 Flexible 위젯과 다르게 남는 공간을 최대한으로 차지하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd5Glo/btsHUdqkL5T/j51rkmCV1dwXkFk4wKK7S1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd5Glo/btsHUdqkL5T/j51rkmCV1dwXkFk4wKK7S1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd5Glo/btsHUdqkL5T/j51rkmCV1dwXkFk4wKK7S1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd5Glo%2FbtsHUdqkL5T%2Fj51rkmCV1dwXkFk4wKK7S1%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;500&quot; height=&quot;270&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. Image 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Image 위젯은 다음과 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;5가지 생성자&lt;/b&gt;&lt;/span&gt;가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TxD49/btsHUcdUxbl/c4KByx2V9GEihowVb5jEq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TxD49/btsHUcdUxbl/c4KByx2V9GEihowVb5jEq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TxD49/btsHUcdUxbl/c4KByx2V9GEihowVb5jEq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTxD49%2FbtsHUcdUxbl%2Fc4KByx2V9GEihowVb5jEq1%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;491&quot; height=&quot;131&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;131&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;b&gt;※ 요즘 OS의 개발 정책이 보안을 강조하기 때문에, &lt;span style=&quot;color: #ee2323;&quot;&gt;Image.file 생성자&lt;/span&gt;는 점차 사장되는 추세이다...&lt;/b&gt;&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;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ 이미지 크기 조절&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;width / height&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파라미터&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. 로딩 애니메이션 위젯 2가지&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;LinearProgressIndicator&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 일자 형태&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;CircularProgressIndicator&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;: 동그라미 형태&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ 색상 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Linear / Circular 둘 다 제공한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;backgraoundColor&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 위젯의 배경 색상&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;valueColor&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 실제로 애니메이션으로 움직이는 부분의 색상&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;valueColor는 색상이 애니메이션 되어야 하기 때문에,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;AlwaysStoppedAnimation&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;이라는 클래스에 감싸서 색상을 제공해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718088809187&quot; class=&quot;less&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;  CircularProgressIndicator(
    valueColor: AlwaysStoppedAnimation(
      Colors.white,
    ),
  ),&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;9. Scaffold 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ appBar 파라미터와 AppBar 위젯&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;actions 파라미터&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 이 파라미터(리스트)에 위젯을 입력하면, AppBar의 우측 끝에 순서대로 위젯이 배치된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;10. PageView 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;여러 개의 위젯을 단독 페이지로 생성하고 가로 또는 세로 스와이프로 페이지를 넘길 수 있게 하는 위젯&lt;/b&gt;&lt;/u&gt;이다.&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;PageView는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;PageController&lt;/b&gt;&lt;/span&gt;를 사용해서 조작할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보통 PageController를 State에 선언하고 PageView의 controller 속성에 파라미터로 입력하면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 해당 컨트롤러로 PageView를 조작할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;11. Text 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Text 위젯을 스타일링할 때는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;style 파라미터&lt;/b&gt;&lt;/span&gt;를 사용하면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 Text 위젯의 개별 스타일이 아니라 -&amp;gt; 기본 스타일을 변경하고 싶다면&amp;nbsp;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;테마(Theme)&lt;/b&gt;&lt;/span&gt;를 사용하면 편하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;테마를 사용하면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;13가지 Text 스타일&lt;/b&gt;&lt;/span&gt;을 따로 저장하여, 프로젝트로 불러와서 사용할 수 있다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ textTheme&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ [24년 6월 변경사항]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;책에서 소개한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;TextTheme()&lt;/b&gt;&lt;/span&gt;의 속성 중에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;headline1~6, subtitle1~2, bodyText1~2, caption, button, overline&lt;/b&gt;은 Deprecated 되었다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;headline1~3&lt;b&gt; -&amp;gt; displayLarge / displayMedium / displaySmall&lt;/b&gt;&lt;br /&gt;headline4~5&lt;b&gt; -&amp;gt; headlineMedium / headlineSmall&lt;/b&gt;&lt;br /&gt;headline6 / subtitle1 / subtitle2 &lt;b&gt;-&amp;gt; titleLarge / titleMedium / titleSmall&lt;/b&gt;&lt;br /&gt;bodyText1 / bodyText2 / caption&lt;b&gt; -&amp;gt; bodyLarge / bodyMedium / bodySmall&lt;/b&gt;&lt;br /&gt;button / overline &lt;b&gt;-&amp;gt; labelLarge / labelSmall&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;12. MaterialApp 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ theme 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 파라미터에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;ThemeData 클래스&lt;/b&gt;&lt;/span&gt;를 입력할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ThemeData에서는 플러터가 기본으로 제공하는 대부분의 위젯 기본 스타일을 지정할 수 있다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T5BgZ/btsHTE9AICd/bnMKN7IYW3qFvDQWuYOfW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T5BgZ/btsHTE9AICd/bnMKN7IYW3qFvDQWuYOfW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T5BgZ/btsHTE9AICd/bnMKN7IYW3qFvDQWuYOfW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT5BgZ%2FbtsHTE9AICd%2FbnMKN7IYW3qFvDQWuYOfW0%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;543&quot; height=&quot;278&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;13. Align 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;child 위젯을 어떻게 위치시킬지 정할 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;alignment 파라미터&lt;/b&gt;&lt;/span&gt;에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Alignment&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값을 입력하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tg2e2/btsHU4zbn0Q/AIzPbFgsMwwl1N1jLxwXik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tg2e2/btsHU4zbn0Q/AIzPbFgsMwwl1N1jLxwXik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tg2e2/btsHU4zbn0Q/AIzPbFgsMwwl1N1jLxwXik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftg2e2%2FbtsHU4zbn0Q%2FAIzPbFgsMwwl1N1jLxwXik%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;552&quot; height=&quot;575&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Stack 위젯 내부에서 활용된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;14. TabBar &amp;amp; TabBarView 위젯 그리고 TabController 클래스&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;TabBar와 TabBarView를 사용하기 위해서는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;TabController&quot; &lt;/b&gt;&lt;/span&gt;가 필요하다.&lt;br /&gt;TabController를 초기화하려면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;vsync 기능&lt;/b&gt;&lt;/span&gt;이 필요한데, 이는 State 위젯에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;TickerProviderMixin&lt;/b&gt;&lt;/span&gt;을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;mixin으로 제공&lt;/b&gt;&lt;/u&gt;해줘야 사용할 수 있다.&lt;br /&gt;또한 TabController는 위젯이 생성될 때 단 한 번만 초기화되어야 하니 --&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;initState()에서 초기화&lt;/b&gt;&lt;/u&gt;한다.&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;pre id=&quot;code_1718088809191&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;class _RootScreenState extends State&amp;lt;RootScreen&amp;gt; with TickerProviderStateMixin {
  TabController? controller; // 사용할 TabController 선언

  @override
  void initState() {
    super.initState();

    controller = TabController(length: 2, vsync: this); // 컨트롤러 초기화
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TabBarView( // 탭 화면을 보여줄 위젯
        controller: controller,
        children: renderChildren(),
      ),

      // 아래의 탭 내비게이션을 구현하는 파라미터
      bottomNavigationBar: renderBottomNavigation(),
    );
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;TickerProviderMixin &amp;amp; SingleTickerProviderMixin&lt;/b&gt;&lt;/span&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcVrgH/btsHUcdUxbP/MVic1rKfDvmnvn8vRpCVG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcVrgH/btsHUcdUxbP/MVic1rKfDvmnvn8vRpCVG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcVrgH/btsHUcdUxbP/MVic1rKfDvmnvn8vRpCVG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcVrgH%2FbtsHUcdUxbP%2FMVic1rKfDvmnvn8vRpCVG0%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;567&quot; height=&quot;244&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ DefaultTabController 클래스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TabController를 선언하고 구현하는 것이 귀찮다면, 혹은 간단한 TabBar를 구현할 계획이라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본으로 제공하는&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; &quot;DefaultTabController&quot;&lt;/b&gt; &lt;/span&gt;를 사용하는 것도 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 선언 없이 바로 DefaultTabController 내부에서 TabBar와 TabBarView를 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1721288081696&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  DefaultTabController(
    length: 2,
    child: Column(
      children: [
        TabBar(
          tabs: [
            Tab(text: &quot;제품 상세&quot;,),
            Tab(text: &quot;리뷰&quot;,),
          ],
        ),
        Container(
          height: 500,
          child: TabBarView(
            children: [
              Container(
                child: &quot;제품 상세&quot;.text.make(),
              ),
              Container(
                child: &quot;리뷰&quot;.text.make(),
              ),
            ],
          ),
        ),
      ],
    ),
  ),&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;a href=&quot;https://api.flutter.dev/flutter/material/DefaultTabController-class.html&quot;&gt;https://api.flutter.dev/flutter/material/DefaultTabController-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721288111820&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;DefaultTabController class - material library - Dart API&quot; data-og-description=&quot;The TabController for descendant widgets that don't specify one explicitly. DefaultTabController is an inherited widget that is used to share a TabController with a TabBar or a TabBarView. It's used when sharing an explicitly created TabController isn't co&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/DefaultTabController-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/DefaultTabController-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/DefaultTabController-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/DefaultTabController-class.html&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;DefaultTabController class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The TabController for descendant widgets that don't specify one explicitly. DefaultTabController is an inherited widget that is used to share a TabController with a TabBar or a TabBarView. It's used when sharing an explicitly created TabController isn't co&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;15. BottomNavigationBar -&amp;gt; NavigationBar 클래스&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ BottonNavigationBar는 Material2 클래스로, Material 3 버전은 NavigaitonBar 를 사용한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=DVGYddFaLv0&quot;&gt;https://www.youtube.com/watch?v=DVGYddFaLv0&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=DVGYddFaLv0&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/1BKeS/hyWgX0SAUm/hekTkKHQHKUjFaTn0grh10/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;NavigationBar (Widget of the Week)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/DVGYddFaLv0&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/NavigationBar-class.html&quot;&gt;https://api.flutter.dev/flutter/material/NavigationBar-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718089965623&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;NavigationBar class - material library - Dart API&quot; data-og-description=&quot;Material 3 Navigation Bar component. Navigation bars offer a persistent and convenient way to switch between primary destinations in an app. This widget does not adjust its size with the ThemeData.visualDensity. The MediaQueryData.textScaler does not adjus&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/NavigationBar-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/NavigationBar-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/NavigationBar-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/NavigationBar-class.html&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;NavigationBar class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Material 3 Navigation Bar component. Navigation bars offer a persistent and convenient way to switch between primary destinations in an app. This widget does not adjust its size with the ThemeData.visualDensity. The MediaQueryData.textScaler does not adjus&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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_1718088809192&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;class _RootScreenState extends State&amp;lt;RootScreen&amp;gt; with TickerProviderStateMixin {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TabBarView( // 탭 화면을 보여줄 위젯
        controller: controller,
        children: renderChildren(),
      ),

      // 아래의 탭 내비게이션을 구현하는 파라미터
      bottomNavigationBar: renderBottomNavigation(),
    );
  }

  BottomNavigationBar renderBottomNavigation() {
    // 탭 내비게이션을 구현하는 위젯
    return BottomNavigationBar(
      items: [
        BottomNavigationBarItem(
          icon: Icon(
            Icons.edgesensor_high_outlined,
          ),
          label: '주사위',
        ),
        BottomNavigationBarItem(
          icon: Icon(
            Icons.settings,
          ),
          label: '설정',
        ),
      ],
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;16. Slider 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Slider 위젯은 눌러서 좌우로 움직일 때, 움직인 만큼의 값이 제공되는 콜백 함수가 실행된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 값을 저장하고, 다시 Slider에 넣어주는 것이 중요 포인트이다.&lt;/p&gt;
&lt;pre id=&quot;code_1718088809193&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;Slider(
  min: 0.1, // 최솟값
  max: 10.0, // 최댓값
  divisions: 101, // 최솟값과 최댓값 사이 구간 개수
  value: threshold, // 슬라이더 선택값
  onChanged: onThresholdChange, // 값 변경 시 실행되는 함수
  label: threshold.toStringAsFixed(1), // 표시값
  activeColor: Colors.red, // 슬라이더의 전체적인 색상
),&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/Slider-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/Slider-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718690133569&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;Slider class - material library - Dart API&quot; data-og-description=&quot;A Material Design slider. Used to select from a range of values. The Sliders value is part of the Stateful widget subclass to change the value setState was called. link To create a local project with this code sample, run: flutter create --sample=material.&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/Slider-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/Slider-class.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d2bfhM/hyWlftGkkC/SawECKWRv1AUyyyZMxwv9k/img.png?width=596&amp;amp;height=236&amp;amp;face=0_0_596_236&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/Slider-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/Slider-class.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d2bfhM/hyWlftGkkC/SawECKWRv1AUyyyZMxwv9k/img.png?width=596&amp;amp;height=236&amp;amp;face=0_0_596_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;Slider class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Material Design slider. Used to select from a range of values. The Sliders value is part of the Stateful widget subclass to change the value setState was called. link To create a local project with this code sample, run: flutter create --sample=material.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;17. VideoPlayer 위젯 (video_player 외부 패키지)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;VideoPlayer 위젯&lt;/b&gt;&lt;/span&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;VideoPlayerController&lt;/b&gt;&lt;/span&gt;로 조작할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;initState() 함수&lt;/b&gt;에서, 설정할 VideoPlayerController를 선언한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 예제에서는 파일로부터 VideoPlayerController를 생성하기 때문에,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;VideoPlayerController.file 생성자&lt;/b&gt;&lt;/span&gt;를 이용해서 컨트롤러를 만든다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;컨트롤러를 생성하고 나면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;initialize() 함수를 실행&lt;/b&gt;&lt;/span&gt;해서 동영상을 재생할 수 있는 상태로 준비해줘야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;3. VideoPlayerController를 선언할 수 있는 생성자는 대표적으로 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3가지&lt;/b&gt;&lt;/span&gt;가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qiygB/btsHVu5mk5P/hpNdQMjJTptJOWZrxz2VDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qiygB/btsHVu5mk5P/hpNdQMjJTptJOWZrxz2VDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qiygB/btsHVu5mk5P/hpNdQMjJTptJOWZrxz2VDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqiygB%2FbtsHVu5mk5P%2FhpNdQMjJTptJOWZrxz2VDK%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;482&quot; height=&quot;141&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;141&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1718088809194&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;class CustomVideoPlayer extends StatefulWidget {
  // HomeScreen에서 선택한 동영상을 저장하는 변수
  final XFile video;

  const CustomVideoPlayer({
    required this.video,
    super.key,
  });
  
  @override
  State&amp;lt;CustomVideoPlayer&amp;gt; createState() =&amp;gt; _CustomVideoPlayerState();
}

class _CustomVideoPlayerState extends State&amp;lt;CustomVideoPlayer&amp;gt; {
  // 동영상을 조작하는 컨트롤러
  VideoPlayerController? videoController;

  @override
  void initState() {
    super.initState();
    initializeController(); // 컨트롤러 초기화
  }

  initializeController() async { // 선택한 동영상으로 컨트롤러 초기화
    final videoController = VideoPlayerController.file(
      File(widget.video.path),
    );

    await videoController.initialize();
    
    videoController.addListener(videoControllerListener); // 리스너 등록

    setState(() {
      this.videoController = videoController;
    });
  }
  
   /**
   * 동영상의 재생 상태가 변경될 때마다, setState()를 실행해서 build()를 재실행한다.
   */
   void videoControllerListener() {
     setState(() {});
   }

  @override
  Widget build(BuildContext context) {
    // 동영상 컨트롤러가 준비 중일 때, 로딩 표시
    if (videoController == null) {
      return Center(
        child: CircularProgressIndicator(),
      );
    }

    return AspectRatio(
      aspectRatio: videoController!.value.aspectRatio,
      child: VideoPlayer(
        videoController!,
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;videoController.value.duration&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 동영상의 길이&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;videoController.value.position&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;: 동영상이 현재 재생되고 있는 위치&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;videoController.value.isPlaying&lt;/b&gt;&lt;/span&gt;&amp;nbsp;: 현재 동영상이 재생 중인지 true / false 값으로 반환한다&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;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;videoController.seekTo() 함수&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 동영상의 재생 위치를 변경할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;pause() / play() 함수&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 동영상을 일시 정지 / 재생 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718088809196&quot; class=&quot;less&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;child: Slider( // 동영상 재생 상태를 보여주는 슬라이더
  onChanged: (double val) { // 슬라이더가 이동할 때마다 실행하는 함수
    videoController!.seekTo(
      Duration(seconds: val.toInt()),
    );
  },
  value: videoController!.value.position.inSeconds.toDouble(),
  min: 0,
  max: videoController!.value.duration.inSeconds.toDouble(),
),&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동영상을 이미 선택한 상태에서도 새로운 동영상을 선택할 수 있어야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;동영상의 소스를&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;videoController 변수를 인스턴스화할 때 선언&quot;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;했고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 코드에서&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;videoController 변수는 initState() 함수에서만 선언&quot;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;되기 때문에, 새 동영상이 반영되지 않는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;StatefulWidget 생명주기의 또 하나의 함수인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;didUpdateWidget() 함수&lt;/b&gt;&lt;/span&gt;를 사용해서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;새로운 동영상을 선택하면 새로운 videoController를 생성하도록 코드를 추가해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718088809197&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;class _CustomVideoPlayerState extends State&amp;lt;CustomVideoPlayer&amp;gt; {
  // 동영상을 조작하는 컨트롤러
  VideoPlayerController? videoController;

  /**
   * covariant 키워드는 CustomVideoPlayer 클래스의 상속된 값도 허가해준다.
   * oldWidget : 파라미터의 값이 변경되어서 &quot;폐기&quot;되는 위젯
   */
  @override
  void didUpdateWidget(covariant CustomVideoPlayer oldWidget) {
    super.didUpdateWidget(oldWidget);

    // 새로 선택한 동영상이 같은 동영상인지 확인
    if (oldWidget.video.path != widget.video.path) {
      initializeController();
    }
  }

  initializeController() async { // 선택한 동영상으로 컨트롤러 초기화
    final videoController = VideoPlayerController.file(
      File(widget.video.path),
    );

    await videoController.initialize();

    videoController.addListener(videoControllerListener); // 리스너 등록

    setState(() {
      this.videoController = videoController;
    });
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;17. AspectRatio 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;child 파라미터에 입력되는 위젯의 &quot;비율&quot;&lt;/b&gt;을 정하는 위젯이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비율은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;너비 / 높이&quot;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 입력하면 된다. EX) 16:9 비율 -&amp;gt; 16/9 를 입력&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;18. Stack 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Column 위젯, Row 위젯처럼 children 파라미터에 여러 개의 위젯이 위치할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위젯이 list에 입력되는 순서대로 아래부터 쌓아 올려진다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Stack 위젯은 기본적으로 children 위젯들을 정중앙에 위치시킨다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 특정 위치에 위젯을 옮기고 싶다면 Positioned 위젯을 사용해서 위치를 정해줘야 한다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ fit 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;StackFit.loose :&amp;nbsp;&lt;/b&gt;자식 위젯들이 최소한의 크기만 차지하도록 한다.&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;b&gt;StackFit.expand&lt;/b&gt; : 자식 위젯들이 차지할 수 있는 공간의 최대한의 크기를 차지하도록 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;19. Positioned 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주로 Stack 위젯 내부에서 특정 위치에 위젯을 옮기고 싶다면 -&amp;gt; Positioned 위젯을 사용해서 위치를 정해준다.&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;top / bottom / left / right 파라미터&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 각각 위아래, 왼쪽, 오른쪽에서부터&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;몇 픽셀&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 child 위젯을 위치할지 정한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;20. FutureBuilder 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtube.com/watch?v=zEdw_1B7JHY&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtube.com/watch?v=zEdw_1B7JHY&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=zEdw_1B7JHY&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/d2TnHn/hyWleHdspc/W9gxDaWqdo5iVObc5CbX20/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/0OstC/hyWlnKWH90/EKrHZkosN1rfqNKAUFJhQ1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;FutureBuilder (Widget of the Week)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/zEdw_1B7JHY&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 마디로 말하자면, Future(비동기)를 다루는 Builder? 라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 &lt;u&gt;&lt;b&gt;Future를 반환하는 함수의 결과에 따라 -&amp;gt; 위젯을 렌더링&lt;/b&gt;&lt;/u&gt;할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ future 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future 값을 반환하는 함수를 넣어준다.&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: #f89009;&quot;&gt;&lt;b&gt;➡️ builder 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Future 값에 따라 다르게 렌더링해주고 싶은 로직을 작성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;builder() 함수는 &lt;b&gt;BuildContext와 AsyncSnapshot을 제공&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: #1a5490;&quot;&gt;&lt;b&gt;1. AsyncSnapshot&lt;/b&gt;&lt;/span&gt; : future 파라미터에 입력한 함수의 결괏값 및 에러를 제공하는 역할을 한다. 추가적으로 비동기 함수의 진행 상황도 알 수 있다. AsyncSnapshot에서 제공하는 값이 변경될 때마다 builder() 함수가 재실행된다.&lt;/p&gt;
&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-1. hasError 게터&lt;/b&gt; : 현재 실행한 비동기 함수에서 에러가 있는지 bool 값으로 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러가 있다면, snapshot.error 를 실행해서 에러값을 가져올 수 있다.&lt;/p&gt;
&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-2. hasData 게터&lt;/b&gt; : 현재 실행한 비동기 함수에서 반환받은 데이터가 있는지 bool 값으로 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환받은 데이터가 있다면, snapshot.data를 실행해서 데이터값을 가져올 수 있다.&lt;/p&gt;
&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-3. snapshot.coonectionState&lt;/b&gt; : 비동기 함수의 현재 실행 상태를 가져올 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CPtYB/btsHWQ2NnDf/LBLRJ5kQykx5rJU13BIIeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CPtYB/btsHWQ2NnDf/LBLRJ5kQykx5rJU13BIIeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CPtYB/btsHWQ2NnDf/LBLRJ5kQykx5rJU13BIIeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCPtYB%2FbtsHWQ2NnDf%2FLBLRJ5kQykx5rJU13BIIeK%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;565&quot; height=&quot;257&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1718254384147&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('LIVE'),
      ),
      body: FutureBuilder( // init()의 Future 값을 기반으로 위젯을 렌더링하기 위해서
        future: init(), // Future를 init() 함수로 등록한다
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          if (snapshot.hasError) { // init()이 던진 에러 처리
            return Center(
              child: Text(
                snapshot.error.toString(),
              ),
            );
          }

          if (!snapshot.hasData) { // init() 실행 후, 아직 데이터가 없을 때 (= 로딩 중)
            return Center(
              child: CircularProgressIndicator(),
            );
          }

          return Center(
            child: Text('모든 권한이 있습니다!'),
          );
        }
      ),
    );
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  !snapshot.hasData 대신 snapshot.connectionState == ConnectionState.waiting을 사용하면 안될까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;A. 캐싱(Caching)&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;FutureBuilder는 제공된 비동기 함수의 상태가 변경될 때마다, 상위 builder() 함수가 재실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 이 FutureBuilder는 다른 위젯과 마찬가지로 build() 함수에 영향을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;build()가 다시 실행되면 -&amp;gt; FutureBuilder는 다시 렌더링되고 -&amp;gt; builder() 함수도 다시 실행&lt;/b&gt;&lt;/u&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그럴 때마다 비동기 함수도 매번 다시 실행된다. 그런데 플러터에서는 무수히 많이 build() 함수를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;build()가 실행될때마다 CircularProgressIndicator가 렌더링되어 화면에 깜빡임&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;u&gt;&lt;b&gt;build() 함수에 snapshot.hasData를 이용해서, 기존에 반환받았던 데이터 값을 기억해두어(캐싱), 로딩 상태를 인지하고, 깜빡임을 막는 것&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;21. GoogleMap 위젯 (goolge_maps_flutter 패키지)&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ &lt;span style=&quot;color: #f89009;&quot;&gt;initialCameraPosition 파라미터 (필수!)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;CameraPosition&lt;/b&gt;&lt;/span&gt; 이라는 클래스를 입력해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;▶️ target 파라미터&lt;/b&gt; &lt;/span&gt;: 지도의 중심이 될 위치를 &lt;b&gt;LatLng 형태&lt;/b&gt;의 값으로 입력할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;▶️ zoom 파라미터&lt;/b&gt;&lt;/span&gt; : 확대 정도&lt;/p&gt;
&lt;pre id=&quot;code_1718260186742&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeScreen extends StatelessWidget {
  static final LatLng companyLatLng = LatLng( // 지도 초기화 위치
    37.5233273, // 위도
    126.921252, // 경도
  );

  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: renderAppBar(),
      body: GoogleMap( // 지도 위치 지정
        initialCameraPosition: CameraPosition(
          target: companyLatLng,
          zoom: 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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ markers 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;google_maps_flutter 패키지가 제공하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;Marker&quot; 클래스&lt;/b&gt;&lt;/span&gt;를 사용해서 각 마커별로 ID를 정해주고, 위치를 입력해준다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 다음, GoogleMaps 위젯의 markers 파라미터에 Set 형태로 원하는 만큼 Marker 값들을 넣어주면 된다.&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ 유의할 점&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: markerId에 꼭 유일한 id값을 넣어줘야, Set 안에 넣었을 때 모든 마커가 화면에 표시될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1718262186439&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeScreen extends StatelessWidget {
  static final LatLng companyLatLng = ...;

  static final Marker marker = Marker(
    markerId: MarkerId('company'),
    position: companyLatLng,
  );
  
  
  ~~~
  child: GoogleMap( // 지도 위치 지정
    initialCameraPosition: CameraPosition(
      target: companyLatLng,
      zoom: 16, // 확대 정도 (높을수록 크게 보인다)
    ),
    markers: Set.from([marker]),
  ),
  ~~~&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ circles 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 위치 반경을 표시하고 싶다면, Marker 클래스 대신 -&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;Circle&quot; 클래스&lt;/b&gt;&lt;/span&gt;를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;반경의 색상, 반지름 (원점으로부터 미터 단위의 거리), 테두리 색상, 테두리 두께&lt;/b&gt;&lt;/span&gt;를 정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Circle도 Marker와 마찬가지로 유일한 id값을 제공해줘야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718262674920&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class HomeScreen extends StatelessWidget {
  static final LatLng companyLatLng = ...;

  static final Circle circle = Circle(
    circleId: CircleId('choolCheckCircle'),
    center: companyLatLng, // 원의 중심이 되는 위치
    fillColor: Colors.blue.withOpacity(0.5), // 원의 색상
    radius: 100, // 원의 반지름 (미터 단위)
    strokeColor: Colors.blue, // 원의 테두리 색상
    strokeWidth: 1, // 원의 테두리 두께
  );
  
  
  ~~~
  child: GoogleMap( // 지도 위치 지정
    initialCameraPosition: CameraPosition(
      target: companyLatLng,
      zoom: 16, // 확대 정도 (높을수록 크게 보인다)
    ),
    markers: Set.from([marker]),
    circles: Set.from([circle]),
  ),
  ~~~&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ myLocationEnabled 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;현재 나의 GPS 상의 위치를 지도에 표시&lt;/b&gt;&lt;/u&gt;하는 방법이다. &lt;b&gt;true / false 값&lt;/b&gt;을 제공해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;22. InteractiveViewer 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718265845768&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;InteractiveViewer class - widgets library - Dart API&quot; data-og-description=&quot;A widget that enables pan and zoom interactions with its child. The user can transform the child by dragging to pan or pinching to zoom. By default, InteractiveViewer clips its child using Clip.hardEdge. To prevent this behavior, consider setting clipBehav&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/InteractiveViewer-class.html&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;InteractiveViewer class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that enables pan and zoom interactions with its child. The user can transform the child by dragging to pan or pinching to zoom. By default, InteractiveViewer clips its child using Clip.hardEdge. To prevent this behavior, consider setting clipBehav&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;23. Transform 위젯&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;a href=&quot;https://api.flutter.dev/flutter/widgets/Transform-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/Transform-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718269174782&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;Transform class - widgets library - Dart API&quot; data-og-description=&quot;A widget that applies a transformation before painting its child. Unlike RotatedBox, which applies a rotation prior to layout, this object applies its transformation just prior to painting, which means the transformation is not taken into account when calc&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/Transform-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/Transform-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Transform-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/Transform-class.html&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;Transform class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that applies a transformation before painting its child. Unlike RotatedBox, which applies a rotation prior to layout, this object applies its transformation just prior to painting, which means the transformation is not taken into account when calc&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1718372460726&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@override
  Widget build(BuildContext context) {
    return Transform( // child 위젯을 변형할 수 있는 위젯
      transform: Matrix4.identity()
        ..translate(hTransform, vTransform) // 상,하 움직임 정의
        ..scale(scale, scale), // 확대,축소 정의
      child: Container(),
    );
  }&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;24. RepaintBoundary 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718373119604&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;RepaintBoundary class - widgets library - Dart API&quot; data-og-description=&quot;A widget that creates a separate display list for its child. This widget creates a separate display list for its child, which can improve performance if the subtree repaints at different times than the surrounding parts of the tree. This is useful since Re&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html&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;RepaintBoundary class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that creates a separate display list for its child. This widget creates a separate display list for its child, which can improve performance if the subtree repaints at different times than the surrounding parts of the tree. This is useful since Re&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ key 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자식 위젯을 이미지로 추출하는 기능이 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 기능을 사용하려면 key 파라미터를 입력해주고, 이미지를 추출할 때 이 값을 사용해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718375504371&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; {
  XFile? image; // 선택한 이미지를 저장할 변수
  Set&amp;lt;StickerModel&amp;gt; stickers = {}; // 화면에 추가된 스티커를 저장하는 Set
  String? selectedId; // 현재 선택된 스티커의 ID
  GlobalKey imgKey = GlobalKey(); // 이미지로 전환할 위젯에 입력해줄 키값

  @override
  Widget build(BuildContext context) {}

  /**
   * 이미지 선택 여부에 따라 다른 UI를 렌더링하는 함수
   */
  Widget renderBody() {
    if (image != null) {
      // Stack 위젯의 최대 크기만큼 차지
      return RepaintBoundary( // 스티커로 꾸민 위젯을 다시 이미지로 저장하기 위해 사용
        key: imgKey,
        child: Positioned.fill(
          // 위젯 확대 및 좌우 이동을 가능하게 하는 위젯
          child: InteractiveViewer(
            child: Stack(
              fit: StackFit.expand, // 자식 위젯들의 크기를 최대로 늘려주기
              children: [
                Image.file(
                  File(image!.path),
                  // 이미지가 부모 위젯 크기 최대를 차지
                  fit: BoxFit.cover,
                ),
                ...stickers.map(),
              ],
            ),
          ),
        ),
      );
    } else {
      // 이미지 선택이 안 된 경우, 이미지 선택 버튼 표시
      return Center();
    }
  }

  /**
   * 수정한 이미지 저장 함수
   */
  void onSaveImage() async {
    RenderRepaintBoundary boundary = imgKey.currentContext!
        .findRenderObject() as RenderRepaintBoundary;

    ui.Image image = await boundary.toImage(); // 찾은 위젯 바운더리를 이미지로 1차 변환

    // ImageGallerySaver 패키지가 이미지를 저장할 수 있도록 이미지 -&amp;gt; 바이트 데이터로 2차 변환
    ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);

    // 이 바이트 데이터가 8비트 정수형이어야 하기 때문에 바이트 데이터 -&amp;gt; Unit8List 타입으로 3차 변환
    Uint8List pngBytes = byteData!.buffer.asUint8List();

    await ImageGallerySaver.saveImage(pngBytes, quality: 100);

    ScaffoldMessenger.of(context).showSnackBar( // 이미지 저장 후 -&amp;gt; Snackbar 표시
      SnackBar(
        content: Text('저장되었습니다!!'),
      ),
    );
  }&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;key 파라미터에 GlobalKey를 입력했기 때문에, 어디에서든 이 키값을 이용해서 RepaintBoundary의 기능에 접근할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;imgKey.currentContext.findRenderObject()&lt;/b&gt; &lt;/span&gt;함수는, &lt;b&gt;실제 화면에 렌더링된 RepaintBoundary 위젯을 찾을 수 있다.&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: #f89009;&quot;&gt;&lt;b&gt;toImage()&lt;/b&gt; &lt;/span&gt;함수로 -&amp;gt; RepaintBoundary가 감싸고 있는 영역을 이미지로 변환할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;25. Key 클래스 (ValueKey, ObjectKey, GlobalKey 등등)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;간단하게 설명하자면, &lt;u&gt;&lt;b&gt;위젯 트리 간에 상태를 유지하고 싶을 때&lt;/b&gt;&lt;/u&gt; Key를 사용한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 상황은 List&amp;lt;어떤 위젯&amp;gt; 같이 -&amp;gt; &lt;b&gt;동일한 타입의 위젯 컬렉션을 수정하는 경우&lt;/b&gt;에 자주 발생한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;유지해야 하는 위젯의 트리 최상단에 Key를 추가하고, 위젯에 저장하는 데이터의 타입에 따라 적절한 Key를 선택해야 한다.&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;h4 style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;➡️ ValueKey&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.gif&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsvtil/btsHZ7QzPIF/pLuyGERXFcNq2dNX7OoCI1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsvtil/btsHZ7QzPIF/pLuyGERXFcNq2dNX7OoCI1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsvtil/btsHZ7QzPIF/pLuyGERXFcNq2dNX7OoCI1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bsvtil/btsHZ7QzPIF/pLuyGERXFcNq2dNX7OoCI1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;235&quot; height=&quot;475&quot; data-filename=&quot;1.gif&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;647&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;이 앱에서 각각의 To-do 아이템의 텍스트는 리스트에서 중복되지 않는 고유한 상수라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경우에는 ValueKey가 적절한 후보가 될 수 있으며, To-do 아이템의 텍스트가 Value로 사용된다.&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1718374046793&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return TodoItem(
  key: ValueKey(todo.task),
  todo: todo,
  onDismissed: (direction) =&amp;gt; _removeTodo(context, todo),
);&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;➡️ ObjectKey&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;u&gt;&lt;b&gt;각 사용자의 정보를 리스트로 출력하는 주소록 앱&lt;/b&gt;&lt;/u&gt;이 있다고 가정하자. &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;리스트의 각 Item 위젯들은 &lt;b&gt;FirstName, LastName, BirthDay&lt;/b&gt;를 다루는 &lt;b&gt;AddressBookEntry Object&lt;/b&gt;를 가지고 있다. &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;아마도 BirthDay, FirstName 등의 개별 정보는 리스트의 다른 Item과 중복될 수 있겠지만, FirstName+LastName+BirthDay을 조합하면 리스트에서 고유할 것이다. 이러한 경우에는 ObjectKey가 가장 적절한 선택일 것이다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-origin-height=&quot;1483&quot; data-origin-width=&quot;1642&quot; data-filename=&quot;14.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2qioh/btqCPBcEnU3/8ikHDRTVcyHxUpXYmkHgl1/img.png&quot; width=&quot;446&quot; height=&quot;403&quot; data-origin-height=&quot;1483&quot; data-origin-width=&quot;1642&quot; data-filename=&quot;14.png&quot; /&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;➡️ UniqueKey&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션에서 각 위젯들이 중복된 정보를 가지고 있거나 혹은 각 위젯들에게 유일한 Key를 직접 부여해야하는 경우라면 UniqueKey를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UniqueKey를 사용할 때 주의해야할 점이 있다. 만약&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; UniqueKey를 Build 메서드 안에 위치&lt;/b&gt;&lt;/span&gt;시킨다면, 플러터가 화면을 새롭게 그릴 때마다 Build 메서드가 실행되고 그때마다 새로운 Key가 생성될 것이다. 이 경우에는 Key를 사용하는 이점이 사라진다.&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;에도 문제가 될 수 있다. 플러터가 Build 메서드를 실행할 때마다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 난수가 생성될 수 있고, 따라서 플러터가 화면을 그리는 프레임마다 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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;➡️ PageStorageKey&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PageStorageKey는 사용자의 스크롤 위치를 저장하는데 특화된 키로서, 앱이 스크롤을 기억할 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-origin-height=&quot;629&quot; data-origin-width=&quot;320&quot; data-filename=&quot;15.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L1KVx/btqCTw12fRJ/K9DEkWJ1MbmbbsveOCHBS1/img.gif&quot; width=&quot;258&quot; height=&quot;507&quot; data-origin-height=&quot;629&quot; data-origin-width=&quot;320&quot; data-filename=&quot;15.gif&quot; /&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;➡️ GlobalKey&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GlobalKey는 2가지 사용목적이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1. 위젯이 상태를 잃지 않으면서 부모를 바꿀 수 있도록 한다. &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 서로 다른 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;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;2. 특정 위젯의 정보를 완전히 다른 위젯트리에서 접근 가능하게 한다.&lt;/b&gt;&lt;/span&gt;&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;또한 GlobalKey는 테스트를 진행할 때도 유용하게 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GlobalKey를 사용하여 특정 위젯과 그 상태정보를 추적하고 테스트할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-origin-height=&quot;636&quot; data-origin-width=&quot;320&quot; data-filename=&quot;16.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MmBJB/btqCPAkGb49/ZTYHzaukDaV6rDpDRNthtK/img.gif&quot; width=&quot;256&quot; height=&quot;509&quot; data-origin-height=&quot;636&quot; data-origin-width=&quot;320&quot; data-filename=&quot;16.gif&quot; /&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;항상 그런건 아니지만 GlobalKey는 전역 변수와 유사한 면이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 GlobalKey보다 상태를 공유하고 조회할 수 있는 더 나은 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) &lt;b&gt;InheritedWidget을 사용하거나 또는 Redux나 BLoC 패턴을 이용&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://nsinc.tistory.com/214&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nsinc.tistory.com/214&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718374430560&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;[Flutter] Key란 무엇인가?&quot; data-og-description=&quot;기본적으로 플러터의 위젯은 생성자에서 Key매개변수를 받을 수 있습니다. 하지만 그렇게 많이 사용되지는 않습니다. 위젯이 위젯트리에서 위치를 변경하더라도 Key는 상태정보를 유지합니다. &quot; data-og-host=&quot;nsinc.tistory.com&quot; data-og-source-url=&quot;https://nsinc.tistory.com/214&quot; data-og-url=&quot;https://nsinc.tistory.com/214&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bDB9NV/hyWloQ3GCh/Unzbh1m77UC8VXfrkoRbf1/img.gif?width=320&amp;amp;height=659&amp;amp;face=0_0_320_659,https://scrap.kakaocdn.net/dn/Ippmy/hyWlbc7Ank/wkISDRUHJHWdYs0XcWzwt0/img.gif?width=320&amp;amp;height=659&amp;amp;face=0_0_320_659,https://scrap.kakaocdn.net/dn/dKDKcg/hyWljba2T5/gkcfIbW2sUh9dx8G9TqxjK/img.jpg?width=600&amp;amp;height=519&amp;amp;face=0_0_600_519&quot;&gt;&lt;a href=&quot;https://nsinc.tistory.com/214&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nsinc.tistory.com/214&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bDB9NV/hyWloQ3GCh/Unzbh1m77UC8VXfrkoRbf1/img.gif?width=320&amp;amp;height=659&amp;amp;face=0_0_320_659,https://scrap.kakaocdn.net/dn/Ippmy/hyWlbc7Ank/wkISDRUHJHWdYs0XcWzwt0/img.gif?width=320&amp;amp;height=659&amp;amp;face=0_0_320_659,https://scrap.kakaocdn.net/dn/dKDKcg/hyWljba2T5/gkcfIbW2sUh9dx8G9TqxjK/img.jpg?width=600&amp;amp;height=519&amp;amp;face=0_0_600_519');&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;[Flutter] Key란 무엇인가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로 플러터의 위젯은 생성자에서 Key매개변수를 받을 수 있습니다. 하지만 그렇게 많이 사용되지는 않습니다. 위젯이 위젯트리에서 위치를 변경하더라도 Key는 상태정보를 유지합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nsinc.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;26. ScaffoldMessenger 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Scaffold 위젯의 상위에서 작동한다. 즉, &lt;u&gt;&lt;b&gt;MaterialApp과 Scaffold 사이에 ScaffoldMessenger&lt;/b&gt;&lt;/u&gt;가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/ScaffoldMessenger-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/ScaffoldMessenger-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718375214890&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;ScaffoldMessenger class - material library - Dart API&quot; data-og-description=&quot;Manages SnackBars and MaterialBanners for descendant Scaffolds. This class provides APIs for showing snack bars and material banners at the bottom and top of the screen, respectively. To display one of these notifications, obtain the ScaffoldMessengerState&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/ScaffoldMessenger-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/ScaffoldMessenger-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/ScaffoldMessenger-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/ScaffoldMessenger-class.html&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;ScaffoldMessenger class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Manages SnackBars and MaterialBanners for descendant Scaffolds. This class provides APIs for showing snack bars and material banners at the bottom and top of the screen, respectively. To display one of these notifications, obtain the ScaffoldMessengerState&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1718375360056&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ScaffoldMessenger.of(context).showSnackBar( // 이미지 저장 후 -&amp;gt; Snackbar 표시
  SnackBar(
    content: Text('저장되었습니다!!'),
  ),
);&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;27. SnackBar 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 뿐만 아니라, 아이콘, Row 같은 어떠한 위젯도 스낵바 형식으로 표시할 수 있다.&lt;/p&gt;
&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://api.flutter.dev/flutter/material/SnackBar-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/SnackBar-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718375238773&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;SnackBar class - material library - Dart API&quot; data-og-description=&quot;A lightweight message with an optional action which briefly displays at the bottom of the screen. To display a snack bar, call ScaffoldMessenger.of(context).showSnackBar(), passing an instance of SnackBar that describes the message. To control how long the&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/SnackBar-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/SnackBar-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/SnackBar-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/SnackBar-class.html&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;SnackBar class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A lightweight message with an optional action which briefly displays at the bottom of the screen. To display a snack bar, call ScaffoldMessenger.of(context).showSnackBar(), passing an instance of SnackBar that describes the message. To control how long the&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;28. YoutubePlayer 위젯 (youtube_player_flutter 패키지)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YoutubePlayer 위젯을 조정하려면, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;YoutubePlayerController&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 style=&quot;color: #333333; text-align: start;&quot;&gt;보통 컨트롤러 처럼 initState() 함수에서 초기화하고, dispose() 함수에서 삭제하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718377623397&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _CustomYoutubePlayerState extends State&amp;lt;CustomYoutubePlayer&amp;gt; {
  YoutubePlayerController? controller; // YoutubePlayer 위젯을 조종한다

  @override
  void initState() {
    super.initState();

    controller = YoutubePlayerController(
      initialVideoId: widget.videoModel.id, // 처음 실행할 유튜브 영상의 ID
      flags: YoutubePlayerFlags(
        autoPlay: false, // 자동 재생 사용하지 않기
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch, // 가로로 끝까지 늘린다
      children: [
        YoutubePlayer( // 유튜브 재생기 렌더링
          controller: controller!,
          showVideoProgressIndicator: true,
        ),
        const SizedBox(height: 16.0,),
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 8.0),
          child: Text(
            widget.videoModel.title, // 유튜브 영상 제목
            style: TextStyle(
              color: Colors.white,
              fontSize: 16.0,
              fontWeight: FontWeight.w700,
            ),
          ),
        ),
        const SizedBox(height: 16.0,),
      ],
    );
  }

  @override
  void dispose() {
    super.dispose();
    controller!.dispose(); // State 폐기 시, 컨트롤러 또한 삭제
  }
}&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;29. RefreshIndicator 위젯&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;u&gt;&lt;b&gt;child에는 스크롤이 가능한 위젯이 입력&lt;/b&gt;&lt;/u&gt;되어야 한다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/RefreshIndicator-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/RefreshIndicator-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718428739882&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;RefreshIndicator class - material library - Dart API&quot; data-og-description=&quot;A widget that supports the Material &amp;quot;swipe to refresh&amp;quot; idiom. When the child's Scrollable descendant overscrolls, an animated circular progress indicator is faded into view. When the scroll ends, if the indicator has been dragged far enough for it to becom&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/RefreshIndicator-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/RefreshIndicator-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/RefreshIndicator-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/RefreshIndicator-class.html&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;RefreshIndicator class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that supports the Material &quot;swipe to refresh&quot; idiom. When the child's Scrollable descendant overscrolls, an animated circular progress indicator is faded into view. When the scroll ends, if the indicator has been dragged far enough for it to becom&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1718428976508&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;body: FutureBuilder&amp;lt;List&amp;lt;VideoModel&amp;gt;&amp;gt;(
  future: YoutubeRepository.getVideos(), // 유튜브 영상 &quot;비동기&quot;로 가져오기
  builder: (context, snapshot) {
  	......
    
    return RefreshIndicator( // 새로 고침 기능이 있는 위젯
      onRefresh: () async {
        setState(() {}); // setState()를 실행해서, build() 함수가 재실행되도록 하고, 그러면 FutureBuilder도 다시 렌더링된다
      },
      // List&amp;lt;VideoModel&amp;gt;을 하나씩 CustomYoutubePlayer stateful 위젯으로 매핑
      child: ListView(
        // 아래로 당겨서 스크롤할 때, 튕기는 애니메이션 추가
        physics: BouncingScrollPhysics(),
        children: snapshot.data!
            .map((e) =&amp;gt; CustomYoutubePlayer(videoModel: e))
            .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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;30. TableCalendar 위젯 (table_calendar 패키지)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 매우 유연한 디자인 기능을 노출하고 있어서, 이 패키지의 거의 모든 요소를 직접 최적화할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0u9uN/btsH1EM6Yib/le8DaRAlcA7KmK6AJx6keK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0u9uN/btsH1EM6Yib/le8DaRAlcA7KmK6AJx6keK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0u9uN/btsH1EM6Yib/le8DaRAlcA7KmK6AJx6keK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0u9uN%2FbtsH1EM6Yib%2Fle8DaRAlcA7KmK6AJx6keK%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;549&quot; height=&quot;453&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cy1rLC/btsHZr3q3fv/BPxoWoR18kde2laVKzIjqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cy1rLC/btsHZr3q3fv/BPxoWoR18kde2laVKzIjqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cy1rLC/btsHZr3q3fv/BPxoWoR18kde2laVKzIjqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcy1rLC%2FbtsHZr3q3fv%2FBPxoWoR18kde2laVKzIjqK%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;551&quot; height=&quot;481&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;1. focusedDay&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 현재 화면에 보이는 날짜(= 현재 포커스된 날짜)를 지정할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;EX) 11월 23일로 지정했다면, 화면에 11월 달력이 보인다.&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;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;2. firstDay&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 달력의 가장 첫번째 날짜. 지정된 날짜보다 이전 날짜는 조회 불가능&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;3. lastDay&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 달력의 가장 마지막 날짜. 지정된 날짜보다 이후 날짜는 조회 불가능&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;4. selectedDayPredicate&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 달력에 표시되는 '선택된 날짜'를, 어떤 날짜로 지정할지 로직을 작성한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;DateTime day 인수&lt;/b&gt;&lt;/span&gt;는 현재 화면에 보이는 날짜를 하나씩 입력하게 되고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;하나의 true&lt;/b&gt;&lt;/u&gt;가 반환되면 해당 날짜를 선택하게 된다.&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;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;5. onDaySelected&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 날짜가 선택됐을 때 실행되는 함수&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;selectedDay&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 선택된 날짜&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;focusedDay&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 날짜가 선택된 순간에 포커스되어 있는 날짜&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;6. onPageChanged&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;: 달력의 페이지가 변경될 때마다 실행되는 함수&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;focusedDay&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 달력의 페이지가 변경되면서, 새로 포커스된 날짜&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;7. rangeSelectionMode&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 기간 선택 모드&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;toggledOn&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 단일 날짜 대신 날짜 기간을 선택할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;toggledOff&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 그 반대이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;8. onRangeSelected&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 7번에서&amp;nbsp;rangeSelectionMode.toggledOn으로 설정됐을 때 실행되는 함수이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;start&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 선택한 기간의 시작 날짜&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;end&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 선택한 기간의 마지막 날짜&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;focusedDay&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 선택이 실행된 순간에 포커스된 날짜&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ TableCalendar 위젯의 스타일은 크게 2가지로 나눌 수 있다.&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;1. headerStyle 파라미터&lt;/b&gt;&lt;/span&gt; : 화살표와 년도 및 월이 보이는 최상단을 지정한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) titleCentered&lt;/b&gt; : 연, 월을 보여주는 글자를 가운데 정렬할지&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) formatButtomVisible&lt;/b&gt; : 날짜들을 매월 1주일씩 보여줄지 / 2주일씩 보여줄지 / 전체를 다 보여줄지 선택하는 버튼의 존재 여부&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) titleStyle&lt;/b&gt; : 연, 월을 보여주는 글자의 스타일&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;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;2. calendarStyle 파라미터&lt;/b&gt;&lt;/span&gt; : 날짜들이 나열되어 있는 아랫부분을 지정한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) defaultDecoration / weekend&lt;b&gt;Decoration&lt;/b&gt; / selected&lt;b&gt;Decoration&lt;/b&gt; &lt;/b&gt; : 해당 칸의 BoxDecoration을 설정한다.&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;b&gt;2) defaultTextstyle / weekend&lt;b&gt;Textstyle&lt;/b&gt; / selected&lt;b&gt;Textstyle&lt;/b&gt; &lt;/b&gt; : 해당 칸의 TextStyle을 설정한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ 달력에 기능 부여하기&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;TableCalendar 위젯은 &lt;u&gt;&lt;b&gt;날짜가 선택될 때마다 실행될 콜백 함수를 등록 (&lt;b&gt;onDaySelected 파라미터&lt;/b&gt;)&lt;/b&gt;&lt;/u&gt;하고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로 &lt;u&gt;&lt;b&gt;어떤 날짜가 현재 선택된 상태인지 달력에 표시 (&lt;b&gt;selectedDayPredicate&lt;/b&gt;)&lt;/b&gt;&lt;/u&gt;해주어야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;31. IntrinsicHeight 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내부 위젯들의 높이를 최대 높이로 맞춰준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yTLbM/btsH0kWF9wx/ho6LoQJR4wMmqUrxhMY7B0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yTLbM/btsH0kWF9wx/ho6LoQJR4wMmqUrxhMY7B0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yTLbM/btsH0kWF9wx/ho6LoQJR4wMmqUrxhMY7B0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyTLbM%2FbtsH0kWF9wx%2Fho6LoQJR4wMmqUrxhMY7B0%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;501&quot; height=&quot;538&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;293&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nWKNI/btsHZMGeSXF/DuuNe9ko9hDU3uhGQebZ0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nWKNI/btsHZMGeSXF/DuuNe9ko9hDU3uhGQebZ0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nWKNI/btsHZMGeSXF/DuuNe9ko9hDU3uhGQebZ0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnWKNI%2FbtsHZMGeSXF%2FDuuNe9ko9hDU3uhGQebZ0K%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;259&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;293&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;32. Container 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Container-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/Container-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718677117175&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;Container class - widgets library - Dart API&quot; data-og-description=&quot;A convenience widget that combines common painting, positioning, and sizing widgets. A container first surrounds the child with padding (inflated by any borders present in the decoration) and then applies additional constraints to the padded extent (incorp&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/Container-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/Container-class.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cmnMTw/hyWllACZFk/iGDd2kX4bQitj38FyV3ybK/img.png?width=450&amp;amp;height=250&amp;amp;face=0_0_450_250,https://scrap.kakaocdn.net/dn/cD22iu/hyWoBu4iRf/PxkkfJRJRhKUbO3FXkbH2k/img.png?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Container-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/Container-class.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cmnMTw/hyWllACZFk/iGDd2kX4bQitj38FyV3ybK/img.png?width=450&amp;amp;height=250&amp;amp;face=0_0_450_250,https://scrap.kakaocdn.net/dn/cD22iu/hyWoBu4iRf/PxkkfJRJRhKUbO3FXkbH2k/img.png?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250');&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;Container class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A convenience widget that combines common painting, positioning, and sizing widgets. A container first surrounds the child with padding (inflated by any borders present in the decoration) and then applies additional constraints to the padded extent (incorp&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;➡️ color 파라미터 vs decoration 파라미터&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Container 위젯의 color와 decoration 파라미터는 공존할 수 없다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;decoration 파라미터에 넣는 BoxDecoration 등등에도 color 파라미터가 있고, 똑같이 박스 배경에 색을 넣는 역할을 하기 때문이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;33. Checkbox, Radiobutton, 스위치, 팝업메뉴버튼 위젯들&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) Checkbox 위젯&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1718689427080&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BodyClass extends StatelessWidget {
  const BodyClass({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TestCheckBox(),
      ],
    );
  }
}

class TestCheckBox extends StatefulWidget {
  const TestCheckBox({super.key});

  @override
  State&amp;lt;TestCheckBox&amp;gt; createState() =&amp;gt; _TestCheckBoxState();
}

class _TestCheckBoxState extends State&amp;lt;TestCheckBox&amp;gt; {
  late List&amp;lt;bool&amp;gt; values; // 체크박스들의 체크 여부를 저장하는 리스트

  @override
  void initState() {
    super.initState();
    values = [false, false, false];
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        // 다양한 방법으로 구현할 수 있다
        Checkbox(value: values[0], onChanged: (value) =&amp;gt; changeValue(0, value!),),
        Checkbox(value: values[1], onChanged: (value) =&amp;gt; changeValue(1, value ?? true),),
        Checkbox(value: values[2], onChanged: (value) =&amp;gt; changeValue(2, value ?? false),),
      ],
    );
  }

  void changeValue(int index, bool value) {
    setState(() {
      values[index] = value;
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) RadioButton역할을 하는 Radio 위젯&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1718689503955&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class TestRadioButton extends StatefulWidget {
  const TestRadioButton({super.key});

  @override
  State&amp;lt;TestRadioButton&amp;gt; createState() =&amp;gt; _TestRadioButtonState();
}

enum TestRadioValue {
  test1,
  test2,
  test3,
}

class _TestRadioButtonState extends State&amp;lt;TestRadioButton&amp;gt; {
  TestRadioValue? selectValue;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ListTile(
          leading: Radio&amp;lt;TestRadioValue&amp;gt;(
            value: TestRadioValue.test1,
            groupValue: selectValue,
            onChanged: (value) =&amp;gt; setState(() {
              selectValue = value!;
            }),
          ),
          title: Text(TestRadioValue.test1.name),
          onTap: () =&amp;gt; setState(() {
            if (selectValue != TestRadioValue.test1) {
              selectValue = TestRadioValue.test1;
            }
          }),
        ),
        Radio&amp;lt;TestRadioValue&amp;gt;(
          value: TestRadioValue.test2,
          groupValue: selectValue,
          onChanged: (value) =&amp;gt; setState(() {
            selectValue = value!;
          }),
        ),
        Radio&amp;lt;TestRadioValue&amp;gt;(
          value: TestRadioValue.test3,
          groupValue: selectValue,
          onChanged: (value) =&amp;gt; setState(() {
            selectValue = value!;
          }),
        ),
      ],
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) Switch 위젯과 CupertinoSwitch 위젯&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/Switch-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/Switch-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718690418563&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;Switch class - material library - Dart API&quot; data-og-description=&quot;A Material Design switch. Used to toggle the on/off state of a single setting. The switch itself does not maintain any state. Instead, when the state of the switch changes, the widget calls the onChanged callback. Most widgets that use a switch will listen&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/Switch-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/Switch-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/Switch-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/Switch-class.html&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;Switch class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Material Design switch. Used to toggle the on/off state of a single setting. The switch itself does not maintain any state. Instead, when the state of the switch changes, the widget calls the onChanged callback. Most widgets that use a switch will listen&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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_1718690451468&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class TestSwitch extends StatefulWidget {
  const TestSwitch({super.key});

  @override
  State&amp;lt;TestSwitch&amp;gt; createState() =&amp;gt; _TestSwitchState();
}

class _TestSwitchState extends State&amp;lt;TestSwitch&amp;gt; {
  bool value = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Switch( // 일반적인 스위치
          value: value,
          onChanged: (newValue) =&amp;gt; setState(() {
            value = newValue;
          }),
        ),
        CupertinoSwitch( // iOS 식 스위치
          value: value,
          onChanged: (newValue) =&amp;gt; setState(() {
            value = newValue;
          }),
        ),
      ],
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) PopupmenuButton 위젯&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1718691074744&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum TestEnum {
  test1,
  test2,
  test3,
}

class _TestPopupMenuState extends State&amp;lt;TestPopupMenu&amp;gt; {
  TestEnum selectValue = TestEnum.test1;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(selectValue.name),
        PopupMenuButton(
          itemBuilder: (context) {
            return TestEnum.values
                .map(
                  (value) =&amp;gt; PopupMenuItem(
                    value: value,
                    child: Text(
                      value.name,
                    ),
                  ),
                )
                .toList();
          },
          onSelected: (newValue) =&amp;gt; setState(() {
            selectValue = newValue;
          }),
        ),
      ],
    );
  }
}&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;a href=&quot;https://api.flutter.dev/flutter/material/PopupMenuButton-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/PopupMenuButton-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718691098238&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;PopupMenuButton class - material library - Dart API&quot; data-og-description=&quot;Displays a menu when pressed and calls onSelected when the menu is dismissed because an item was selected. The value passed to onSelected is the value of the selected menu item. One of child or icon may be provided, but not both. If icon is provided, then &quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/PopupMenuButton-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/PopupMenuButton-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/PopupMenuButton-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/PopupMenuButton-class.html&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;PopupMenuButton class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Displays a menu when pressed and calls onSelected when the menu is dismissed because an item was selected. The value passed to onSelected is the value of the selected menu item. One of child or icon may be provided, but not both. If icon is provided, then&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;34. ListTile 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 &lt;span style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: start;&quot;&gt;텍스트와 앞뒤 아이콘을 포함하는 고정 높이의 단일 Row이다.&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;a href=&quot;https://api.flutter.dev/flutter/material/ListTile-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/ListTile-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718689757571&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;ListTile class - material library - Dart API&quot; data-og-description=&quot;A single fixed-height row that typically contains some text as well as a leading or trailing icon. A list tile contains one to three lines of text optionally flanked by icons or other widgets, such as check boxes. The icons (or other widgets) for the tile &quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/ListTile-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/ListTile-class.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/1cTmy/hyWla0e516/0TuJGHamgYF03YXXzdtZZk/img.png?width=400&amp;amp;height=550&amp;amp;face=0_0_400_550&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/ListTile-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/ListTile-class.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/1cTmy/hyWla0e516/0TuJGHamgYF03YXXzdtZZk/img.png?width=400&amp;amp;height=550&amp;amp;face=0_0_400_550');&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;ListTile class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A single fixed-height row that typically contains some text as well as a leading or trailing icon. A list tile contains one to three lines of text optionally flanked by icons or other widgets, such as check boxes. The icons (or other widgets) for the tile&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ SwitchListTile 위젯&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 On/Off 가 가능한 스위치를 메인으로 하는 ListTile이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/SwitchListTile-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/SwitchListTile-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721283648188&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;SwitchListTile class - material library - Dart API&quot; data-og-description=&quot;A ListTile with a Switch. In other words, a switch with a label. The entire list tile is interactive: tapping anywhere in the tile toggles the switch. Tapping and dragging the Switch also triggers the onChanged callback. To ensure that onChanged correctly &quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/SwitchListTile-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/SwitchListTile-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/SwitchListTile-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/SwitchListTile-class.html&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;SwitchListTile class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A ListTile with a Switch. In other words, a switch with a label. The entire list tile is interactive: tapping anywhere in the tile toggles the switch. Tapping and dragging the Switch also triggers the onChanged callback. To ensure that onChanged correctly&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;※ adaptive 생성자는 Android / iOS와 같은 OS에 맞게, 적응형으로 UI를 구현해준다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721283722148&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  SwitchListTile.adaptive(
    value: isSale,
    onChanged: (v) {
      setState(() {
        isSale = v;
      });
    },
    title: &quot;할인 여부&quot;.text.make(),
  ),&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;35. ListView 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/ListView-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718871708843&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;ListView class - widgets library - Dart API&quot; data-og-description=&quot;A scrollable list of widgets arranged linearly. ListView is the most commonly used scrolling widget. It displays its children one after another in the scroll direction. In the cross axis, the children are required to fill the ListView. If non-null, the ite&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kHBGj/hyWoOBG2jS/iot2nqYf2N7e9ItfY6sW4k/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250,https://scrap.kakaocdn.net/dn/RYH6N/hyWoCgWJRT/8kcXq7f6Z7OgLvn4xhSCx0/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250,https://scrap.kakaocdn.net/dn/jRZm5/hyWoADri3J/VQz5pKjFVJBT9bqvWCb7r0/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kHBGj/hyWoOBG2jS/iot2nqYf2N7e9ItfY6sW4k/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250,https://scrap.kakaocdn.net/dn/RYH6N/hyWoCgWJRT/8kcXq7f6Z7OgLvn4xhSCx0/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250,https://scrap.kakaocdn.net/dn/jRZm5/hyWoADri3J/VQz5pKjFVJBT9bqvWCb7r0/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250');&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;ListView class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A scrollable list of widgets arranged linearly. ListView is the most commonly used scrolling widget. It displays its children one after another in the scroll direction. In the cross axis, the children are required to fill the ListView. If non-null, the ite&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;스크롤이 가능한 Column, Row 위젯이라고 생각하면 편하다.&lt;/p&gt;
&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;※ ListView 위젯의 &lt;span style=&quot;color: #ee2323;&quot;&gt;children에 몇 백개의 위젯&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: #006dd7;&quot;&gt;ListView.builder()를 사용&lt;/span&gt;하는 것이 성능상으로 이점도 있고, 안전하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 본문 ListView와 같이 스크롤이 가능한 Scrollable한 위젯들은 반드시 &lt;span style=&quot;color: #ee2323;&quot;&gt;배치되는 수직의 방향의 길이를 명시&lt;/span&gt;해야 한다.&lt;/b&gt;&lt;br /&gt;EX) 세로 스크롤이면 -&amp;gt; 가로 길이, 가로 스크롤이면 -&amp;gt; 세로 길이..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ ListView.separated 생성자&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;children 파라미터 -&amp;gt; itemBuilder로 바뀐다! &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 itemBuilder에&lt;/span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;separatorBuilder&lt;/b&gt;&lt;/span&gt;를 반환해줘야 하는 것이 특징이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; separatorBuilder&lt;/b&gt;&lt;/span&gt; : 커다란 아이템 사이사이에 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;구분선&lt;/b&gt;&lt;/span&gt; 같은 &lt;b&gt;&quot;원하는 위젯&quot;&lt;/b&gt;을 자동으로 넣을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1718953818479&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: 'opensource'.tr().text.make(),
      ),
      body: ListView.separated(
        itemBuilder: (context, index) =&amp;gt; OpensourceItem(packageList[index]),
        itemCount: packageList.length,
        separatorBuilder: (BuildContext context, int index) {
          return const Line().pSymmetric(h:20);
        },
      ),
    );
  }&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;36. &lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;ImplicitlyAnimatedWidget를 상속한 기본 플러터 애니메이션 위젯들&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;ImplicitlyAnimatedWidget&quot;을 상속 받은 클래스들은 당연히 &lt;span style=&quot;color: #f89009;&quot;&gt;Implicit Animation&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ AnimatedContainer 위젯&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html&quot;&gt;https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718960285582&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;AnimatedContainer class - widgets library - Dart API&quot; data-og-description=&quot;Animated version of Container that gradually changes its values over a period of time. The AnimatedContainer will automatically animate between the old and new values of properties when they change using the provided curve and duration. Properties that are&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/AnimatedContainer-class.html&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;AnimatedContainer class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Animated version of Container that gradually changes its values over a period of time. The AnimatedContainer will automatically animate between the old and new values of properties when they change using the provided curve and duration. Properties that are&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;주로 child로 전달 받은 위젯의 크기나, 색 등을 자연스럽게 변경하는 위젯이다.&lt;/p&gt;
&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;u&gt;&lt;b&gt;조건(삼항 연산자)&lt;/b&gt;&lt;/u&gt;을 넣고, &lt;u&gt;&lt;b&gt;속성 값의 시작과 끝&lt;/b&gt;&lt;/u&gt;을 차례로 넣으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718960533337&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  AnimatedContainer(
    duration: 1000.ms,
    height: _tappingCount &amp;gt; 2 ? 60 : 30,
    child: Image.asset(
      '$basePath/icon/toss.png',
    ),
  ),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ AnimatedCrossFade 위젯&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;firstChild &amp;amp; secondChild 파라미터&lt;/span&gt; 2개를 지정해서&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; crossFadeState&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_1718960648126&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;AnimatedCrossFade(
  firstChild: Image.asset(
    '$basePath/icon/toss.png',
    height: 30,
  ),
  secondChild: Image.asset(
    '$basePath/icon/notification.png',
    height: 30,
  ),
  crossFadeState: _tappingCount &amp;gt; 2
      ? CrossFadeState.showFirst
      : CrossFadeState.showSecond,
  duration: 2000.ms,
 ),&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;37. Spacer 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Row나 Column 같은 위젯을 사용할 때, 내부 위젯들의 간격을 조절하는 데 아주 유용하게 사용되는 위젯이다.&lt;/p&gt;
&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://api.flutter.dev/flutter/widgets/Spacer-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/Spacer-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719983397080&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;Spacer class - widgets library - Dart API&quot; data-og-description=&quot;Spacer creates an adjustable, empty spacer that can be used to tune the spacing between widgets in a Flex container, like Row or Column. The Spacer widget will take up any available space, so setting the Flex.mainAxisAlignment on a flex container that cont&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/Spacer-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/Spacer-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Spacer-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/Spacer-class.html&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;Spacer class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spacer creates an adjustable, empty spacer that can be used to tune the spacing between widgets in a Flex container, like Row or Column. The Spacer widget will take up any available space, so setting the Flex.mainAxisAlignment on a flex container that cont&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;38. Dissmissible 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 Column이나, Row, ListView 내부의 여러 자식 위젯이 있다고 한다면,&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://api.flutter.dev/flutter/widgets/Dismissible-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/Dismissible-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719990962513&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;Dismissible class - widgets library - Dart API&quot; data-og-description=&quot;A widget that can be dismissed by dragging in the indicated direction. Dragging or flinging this widget in the DismissDirection causes the child to slide out of view. Following the slide animation, if resizeDuration is non-null, the Dismissible widget anim&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/Dismissible-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/Dismissible-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Dismissible-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/Dismissible-class.html&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;Dismissible class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that can be dismissed by dragging in the indicated direction. Dragging or flinging this widget in the DismissDirection causes the child to slide out of view. Following the slide animation, if resizeDuration is non-null, the Dismissible widget anim&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;➡️ 예제 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1719991546761&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class TodoItem extends StatelessWidget {
  final Todo todo;

  const TodoItem(this.todo, {super.key});

  @override
  Widget build(BuildContext context) {
    return Dismissible(
      key: ValueKey(todo.id),
      onDismissed: (direction) {
        context.holder.removeTodo(todo);
      },
      background: RoundedContainer(
        color: context.appColors.removeTodoBg,
        child: const Row(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            Width(20),
            Icon(
              EvaIcons.trash2Outline,
              color: Colors.white,
            ),
          ],
        ),
      ),
      secondaryBackground: RoundedContainer(
        color: context.appColors.removeTodoBg,
        child: const Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            Icon(
              EvaIcons.trash2Outline,
              color: Colors.white,
            ),
            Width(20),
          ],
        ),
      ),
      child: RoundedContainer(),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ key 파라미터 (필수!)&lt;/b&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ onDissmissed 파라미터&lt;/b&gt;&lt;/span&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;39. AnimatedWidthCollapse 위젯&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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;AnimatedSize 위젯(클래스)&lt;/b&gt;&lt;/span&gt;을 사용해서 구현했다.&lt;/p&gt;
&lt;pre id=&quot;code_1720442991289&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

class Height extends StatelessWidget {
  final double height;

  const Height(
    this.height, {
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: height,
    );
  }
}

class Width extends StatelessWidget {
  final double width;

  const Width(
    this.width, {
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
    );
  }&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;40. IgnorePointer 위젯&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720443860253&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;IgnorePointer class - widgets library - Dart API&quot; data-og-description=&quot;A widget that is invisible during hit testing. When ignoring is true, this widget (and its subtree) is invisible to hit testing. It still consumes space during layout and paints its child as usual. It just cannot be the target of located events, because it&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html&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;IgnorePointer class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that is invisible during hit testing. When ignoring is true, this widget (and its subtree) is invisible to hit testing. It still consumes space during layout and paints its child as usual. It just cannot be the target of located events, because it&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ ignoring 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;boolean 값을 받으며, 그 값이 true일 때는 제스처를 무시하고, false일 때는 제스처를 무시하지 않는다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;41. ClipXXX 관련 위젯들&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ ClipOval 위젯&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 위젯을&lt;span style=&quot;color: #f89009;&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;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/ClipOval-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/ClipOval-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720449587297&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;ClipOval class - widgets library - Dart API&quot; data-og-description=&quot;A widget that clips its child using an oval. By default, inscribes an axis-aligned oval into its layout dimensions and prevents its child from painting outside that oval, but the size and location of the clip oval can be customized using a custom clipper. &quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/ClipOval-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/ClipOval-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/ClipOval-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/ClipOval-class.html&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;ClipOval class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that clips its child using an oval. By default, inscribes an axis-aligned oval into its layout dimensions and prevents its child from painting outside that oval, but the size and location of the clip oval can be customized using a custom clipper.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;42. IndexedStack 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, Scaffold에 NavigationBar를 구현했을 때, Navigation의 변경에 따라 어떤 스크린(위젯)을 보여줄지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현하는 방법은 여러가지가 있다. (본문 14번에 있는 TabBarView 클래스 등...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IndexedStack은 애니메이션 효과 없이도 이동할 수 있으며, 위젯의 &lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;&quot;상태&quot;&lt;/b&gt;&lt;/span&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;a href=&quot;https://api.flutter.dev/flutter/widgets/IndexedStack-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/IndexedStack-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721279961842&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;IndexedStack class - widgets library - Dart API&quot; data-og-description=&quot;A Stack that shows a single child from a list of children. The displayed child is the one with the given index. The stack is always as big as the largest child. If value is null, then nothing is displayed. This example shows a IndexedStack widget being use&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/IndexedStack-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/IndexedStack-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/IndexedStack-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/IndexedStack-class.html&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;IndexedStack class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Stack that shows a single child from a list of children. The displayed child is the one with the given index. The stack is always as big as the largest child. If value is null, then nothing is displayed. This example shows a IndexedStack widget being use&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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_1721280035677&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _HomeScreenState extends State&amp;lt;HomeScreen&amp;gt; {
  int _menuIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _menuIndex,
        children: [
          HomeWidget(),
          Container(
            color: Colors.indigo,
          ),
        ],
      ),
      floatingActionButton: switch (_menuIndex) {
        0 =&amp;gt; FloatingActionButton(
            onPressed: () {},
            child: Icon(Icons.shopping_cart_outlined),
          ),
        1 =&amp;gt; FloatingActionButton(
            onPressed: () {},
            child: Icon(Icons.add),
          ),
        _ =&amp;gt; Container()
      },
      bottomNavigationBar: NavigationBar(
        selectedIndex: _menuIndex,
        onDestinationSelected: (idx) {
          setState(() {
            _menuIndex = idx;
          });
        },
        destinations: [
          NavigationDestination(
            icon: Icon(Icons.store_outlined),
            label: &quot;홈&quot;,
          ),
          NavigationDestination(
            icon: Icon(Icons.storefront),
            label: &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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;43. OverflowBar 위젯 (ButtonBar 위젯 상위호환)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위젯을 행에 배치하고, &lt;u&gt;&lt;b&gt;사용 가능한 수평 공간을 &quot;넘치는&quot; 경우에는 대신 열에 배치하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;위젯&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: start;&quot; 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 style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오버플로우 열은 사용 가능한 너비를 모두 소모한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/OverflowBar-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/OverflowBar-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721282277948&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;OverflowBar class - widgets library - Dart API&quot; data-og-description=&quot;A widget that lays out its children in a row unless they &amp;quot;overflow&amp;quot; the available horizontal space, in which case it lays them out in a column instead. This widget's width will expand to contain its children and the specified spacing until it overflows. Th&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/OverflowBar-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/OverflowBar-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/OverflowBar-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/OverflowBar-class.html&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;OverflowBar class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that lays out its children in a row unless they &quot;overflow&quot; the available horizontal space, in which case it lays them out in a column instead. This widget's width will expand to contain its children and the specified spacing until it overflows. Th&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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_1721282293973&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  OverflowBar(
    alignment: MainAxisAlignment.end,
    children: [
      ElevatedButton(
        onPressed: () {},
        child: &quot;카테고리 일괄등록&quot;.text.make(),
      ),
      ElevatedButton(
        onPressed: () {},
        child: &quot;카테고리 등록&quot;.text.make(),
      ),
    ],
  ),&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;44. DropdownButton 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4a4a4a; text-align: start;&quot;&gt;말 그대로 드롭다운 버튼을 클릭하면 사용자가 여러 항목 중에서 선택할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4a4a4a; 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;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/DropdownButton-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/DropdownButton-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721284381858&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;DropdownButton class - material library - Dart API&quot; data-og-description=&quot;A Material Design button for selecting from a list of items. A dropdown button lets the user select from a number of items. The button shows the currently selected item as well as an arrow that opens a menu for selecting another item. There is a Material 3&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/DropdownButton-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/DropdownButton-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/DropdownButton-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/DropdownButton-class.html&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;DropdownButton class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Material Design button for selecting from a list of items. A dropdown button lets the user select from a number of items. The button shows the currently selected item as well as an arrow that opens a menu for selecting another item. There is a Material 3&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1721284491939&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  DropdownButton(
    isExpanded: true,
    items: [],
    onChanged: (s) {},
  ),&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;size18&quot;&gt;&lt;b&gt;※ 더 디자인이 깔끔하고, 편리한 &quot;dropdown_button2&quot; 라는 패키지가 있는 것 같다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/dropdown_button2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/dropdown_button2&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721284464578&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;dropdown_button2 | Flutter package&quot; data-og-description=&quot;Flutter's core Dropdown Button widget with steady dropdown menu and many options you can customize to your needs.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/dropdown_button2&quot; data-og-url=&quot;https://pub.dev/packages/dropdown_button2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cuvQTt/hyWCLqEmJV/MCmqEgkKJ7KGnkKzJW4ZY1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dPksFA/hyWzssK0v4/GMD9jtkkZDj9uOc0CwRxy0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/dropdown_button2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/dropdown_button2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cuvQTt/hyWCLqEmJV/MCmqEgkKJ7KGnkKzJW4ZY1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dPksFA/hyWzssK0v4/GMD9jtkkZDj9uOc0CwRxy0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;dropdown_button2 | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flutter's core Dropdown Button widget with steady dropdown menu and many options you can customize to your needs.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;45. Wrap 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식들(children)을 &lt;u&gt;&lt;b&gt;줄이나 행으로 배치하다가 공간이 부족해지면 -&amp;gt; 자동으로 줄이나 행을 바꿔준다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723011826204&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Wrap(
  runSpacing: 4,
  children: [
    const _GreyInfo('팩스 : 111-222-3333'),
    SizedBox(
      height: 10,
      child:
          VerticalDivider(color: colorScheme.contentTertiary),
    ),
    const Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        _GreyInfo('이메일 : '),
        _HighlightInfo('fastcampus@fast.com'),
      ],
    ),
  ],
),&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;a href=&quot;https://api.flutter.dev/flutter/widgets/Wrap-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/Wrap-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723011797410&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;Wrap class - widgets library - Dart API&quot; data-og-description=&quot;A widget that displays its children in multiple horizontal or vertical runs. A Wrap lays out each child and attempts to place the child adjacent to the previous child in the main axis, given by direction, leaving spacing space in between. If there is not e&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/Wrap-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/Wrap-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Wrap-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/Wrap-class.html&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;Wrap class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that displays its children in multiple horizontal or vertical runs. A Wrap lays out each child and attempts to place the child adjacent to the previous child in the main axis, given by direction, leaving spacing space in between. If there is not e&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;46. TextFormField 위젯&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/TextFormField-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/TextFormField-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729841072335&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;TextFormField class - material library - Dart API&quot; data-og-description=&quot;A FormField that contains a TextField. This is a convenience widget that wraps a TextField widget in a FormField. A Form ancestor is not required. The Form allows one to save, reset, or validate multiple fields at once. To use without a Form, pass a Global&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/TextFormField-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/TextFormField-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/TextFormField-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/TextFormField-class.html&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;TextFormField class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A FormField that contains a TextField. This is a convenience widget that wraps a TextField widget in a FormField. A Form ancestor is not required. The Form allows one to save, reset, or validate multiple fields at once. To use without a Form, pass a Global&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;&quot;키보드로 어떤 것을 입력할 때&quot;&lt;/b&gt; 자주 사용하는 위젯이다.&lt;/p&gt;
&lt;pre id=&quot;code_1729841150471&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Form(
  key: globalKey,
  child: TextFormField(
    enabled: _isEnabled,
    controller: _emailEditingController,
    keyboardType: TextInputType.emailAddress,
    decoration: InputDecoration(
      border: OutlineInputBorder(),
      labelText: &quot;이메일&quot;,
      prefixIcon: Icon(Icons.email),
      filled: true,
    ),
    validator: (value) {
      // TextFormField의 검증 로직을 직접 작성한다
      // 1. 아무것도 입력하지 않았을 때
      // 2. 공백을 입력했을 때 -&amp;gt; 문자열.isEmpty
      // 3. 이메일 형식이 아닐 때 -&amp;gt; validators 패키지 사용
      if (value == null ||
          value.trim().isEmpty ||
          !isEmail(value.trim())) {
        return &quot;올바른 이메일을 입력해주세요.&quot;;
      }
      return null;
    },
    // TextFormField가 스크린에 하나만 있을 때 유용할 것 같다
    onTapOutside: (_) =&amp;gt; FocusScope.of(context).unfocus(),
  ),
),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ 주요 파라미터 소개&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;enabled&lt;/b&gt;&lt;/span&gt; : 말 그대로 입력 활성화를 true / false인 bool 값으로 결정한다&lt;/p&gt;
&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: #f89009;&quot;&gt;&lt;b&gt;controller&lt;/b&gt;&lt;/span&gt; : 해당 TextFormField에 입력한 값은 controller.text 에 저장되어, 다른 곳에서 사용할 수 있다.&lt;/p&gt;
&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: #f89009;&quot;&gt;&lt;b&gt;keyboardType&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: #f89009;&quot;&gt;&lt;b&gt;decoration&lt;/b&gt;&lt;/span&gt; : 다양한 추가 파라미터를 정의함으로써, TextFormField를 꾸밀 수 있다.&lt;/p&gt;
&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: #f89009;&quot;&gt;&lt;b&gt;validator&lt;/b&gt;&lt;/span&gt; : TextFormField에 입력된 값을 검증하는 로직이 들어간다. 상위 Form 위젯에 전달한 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;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;onTapOutside&lt;/b&gt;&lt;/span&gt; : 해당 TextFormField 바깥을 누르면, 어떤 함수를 실행할 지 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 키보드를 숨기도록 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;FocusScope.of(context).unfocus()&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ 참고 : 키보드가 올라왔을 때, 다시 숨기는 여러가지 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&quot;FocusScope.of(context).unfocus()&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;1) 원하는 영역의 위젯을 &lt;b&gt;GestureDetector 위젯&lt;/b&gt;으로 감싼 다음, onTap 속성에 정의한다.&lt;/p&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 style=&quot;color: #333333; text-align: start;&quot;&gt;TextFormField라면, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;위에도 소개했듯이&lt;span&gt;&lt;b&gt; &quot;onTapOutside&quot;&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 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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dart와 Flutter/Flutter 위젯 정리</category>
      <category>DART</category>
      <category>Flutter</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/283</guid>
      <comments>https://sweetburble.tistory.com/283#entry283comment</comments>
      <pubDate>Tue, 11 Jun 2024 15:56:37 +0900</pubDate>
    </item>
    <item>
      <title>김치 칼국수? - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/282</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240610_김치칼국수_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YBWZz/btsHU8AOAQC/yIso3CjKqRS6jllxfLhxB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YBWZz/btsHU8AOAQC/yIso3CjKqRS6jllxfLhxB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YBWZz/btsHU8AOAQC/yIso3CjKqRS6jllxfLhxB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYBWZz%2FbtsHU8AOAQC%2FyIso3CjKqRS6jllxfLhxB1%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;500&quot; height=&quot;420&quot; data-filename=&quot;edited_20240610_김치칼국수_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/282</guid>
      <comments>https://sweetburble.tistory.com/282#entry282comment</comments>
      <pubDate>Mon, 10 Jun 2024 22:40:45 +0900</pubDate>
    </item>
    <item>
      <title>내용 정리 Part. 1 - [코드팩토리의 플러터 프로그래밍]</title>
      <link>https://sweetburble.tistory.com/281</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 6월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. [07장] 앱을 만들려면 알아야 하는 그 밖의 지식&lt;/b&gt;&lt;/h2&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ UI 프로토타입 도구&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu2Wgz/btsHLFf6V2h/GJxpFcRZZBjJo3XIKcCRjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu2Wgz/btsHLFf6V2h/GJxpFcRZZBjJo3XIKcCRjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu2Wgz/btsHLFf6V2h/GJxpFcRZZBjJo3XIKcCRjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu2Wgz%2FbtsHLFf6V2h%2FGJxpFcRZZBjJo3XIKcCRjK%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;215&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;226&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ 이 책에서 사용한 폴더 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nbevj/btsHL4NiqUR/QLYYdri1YVivOiw2QQRmWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nbevj/btsHL4NiqUR/QLYYdri1YVivOiw2QQRmWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nbevj/btsHL4NiqUR/QLYYdri1YVivOiw2QQRmWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNbevj%2FbtsHL4NiqUR%2FQLYYdri1YVivOiw2QQRmWK%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;530&quot; height=&quot;158&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;169&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ 플러터에서 지원하는 대표적인 주변 장치&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mfsOk/btsHML0LTHN/8PYClyZsRCKdqb7hQ8VnA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mfsOk/btsHML0LTHN/8PYClyZsRCKdqb7hQ8VnA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mfsOk/btsHML0LTHN/8PYClyZsRCKdqb7hQ8VnA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmfsOk%2FbtsHML0LTHN%2F8PYClyZsRCKdqb7hQ8VnA0%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;530&quot; height=&quot;284&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;304&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;※ Flutter에서 Hex 코드 사용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 헥스 코드를 색상으로 사용하려면, &lt;u&gt;&lt;b&gt;Colors 대신 Color 라는 클래스&lt;/b&gt;&lt;/u&gt;를 사용해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HrgPs/btsHNppq3Vq/xDUMG6OFwgKeDrg5jGZNQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HrgPs/btsHNppq3Vq/xDUMG6OFwgKeDrg5jGZNQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HrgPs/btsHNppq3Vq/xDUMG6OFwgKeDrg5jGZNQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHrgPs%2FbtsHNppq3Vq%2FxDUMG6OFwgKeDrg5jGZNQK%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;465&quot; height=&quot;58&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 6자리 헥스 코드의 앞에 + 16진수를 의미하는 0x + 불투명도 100%를 의미하는 FF&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. [08장] 블로그 웹 앱 : 콜백 함수, 웹 뷰, 네이티브 설정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;웹뷰 (WebView)&lt;/b&gt;&lt;/span&gt; : 브라우저를 앱의 네이티브 컴포넌트에 임베딩(embedding)하는 플러터에 내장된 기능&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;span style=&quot;color: #0593d3;&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;u&gt;&lt;b&gt;controller 파라미터에 WebViewController 객체를 입력&lt;/b&gt;&lt;/u&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsUxUx/btsHMLzMnPw/1vHkW9MkgCulIZ0H93i4M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsUxUx/btsHMLzMnPw/1vHkW9MkgCulIZ0H93i4M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsUxUx/btsHMLzMnPw/1vHkW9MkgCulIZ0H93i4M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsUxUx%2FbtsHMLzMnPw%2F1vHkW9MkgCulIZ0H93i4M1%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;559&quot; height=&quot;359&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0Wol5/btsHLyg5DHb/PJerw1nMJCyJ7Locr3kVuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0Wol5/btsHLyg5DHb/PJerw1nMJCyJ7Locr3kVuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0Wol5/btsHLyg5DHb/PJerw1nMJCyJ7Locr3kVuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0Wol5%2FbtsHLyg5DHb%2FPJerw1nMJCyJ7Locr3kVuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;349&quot; height=&quot;147&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;147&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ WebViewController 에는 그외에도 유용한 함수가 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;goBack()&lt;/b&gt;&lt;/span&gt; : 페이지 뒤로 가기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;goForward()&lt;/b&gt; &lt;/span&gt;: 페이지 앞으로 가기&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1. 권한 및 네이티브 설정&lt;/b&gt;&lt;/h3&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;android/app/src/main/AndroidManifest.xml&lt;/b&gt;&lt;/span&gt; 파일에서 권한을 부여할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3XIu3/btsHNWmNk83/oMh9UWUVvLjuYdXtPlqVaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3XIu3/btsHNWmNk83/oMh9UWUVvLjuYdXtPlqVaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3XIu3/btsHNWmNk83/oMh9UWUVvLjuYdXtPlqVaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3XIu3%2FbtsHNWmNk83%2FoMh9UWUVvLjuYdXtPlqVaK%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;202&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZkKUh/btsHLeJTLs6/XjxRHqcolOLhOQaHMglQt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZkKUh/btsHLeJTLs6/XjxRHqcolOLhOQaHMglQt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZkKUh/btsHLeJTLs6/XjxRHqcolOLhOQaHMglQt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZkKUh%2FbtsHLeJTLs6%2FXjxRHqcolOLhOQaHMglQt0%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;566&quot; height=&quot;231&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;231&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: #1a5490;&quot;&gt;&lt;b&gt;android/build.gradle&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: #1a5490;&quot;&gt;&lt;b&gt;android/app/build.gradle&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;※ webview_flutter 플러그인을 사용하려면, 안드로이드 최소 버전(minSdkVersion)을 20 이상으로 설정해야 한다.&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;안드로이드와 iOS에서는 모두 기본적으로 &lt;b&gt;http 웹사이트&lt;/b&gt;를 사용할 수 없도록 설정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정을 해제하려면, AndroidManifest.xml 파일에 아래 코드를 추가해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgXSK4/btsHNBpHHMZ/MIQKye8abEKq1hQ3v5Emuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgXSK4/btsHNBpHHMZ/MIQKye8abEKq1hQ3v5Emuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgXSK4/btsHNBpHHMZ/MIQKye8abEKq1hQ3v5Emuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgXSK4%2FbtsHNBpHHMZ%2FMIQKye8abEKq1hQ3v5Emuk%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;556&quot; height=&quot;98&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/28oo8/btsHLg13hHu/yGjAXrQrKJBkkATkoZxHK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/28oo8/btsHLg13hHu/yGjAXrQrKJBkkATkoZxHK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/28oo8/btsHLg13hHu/yGjAXrQrKJBkkATkoZxHK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F28oo8%2FbtsHLg13hHu%2FyGjAXrQrKJBkkATkoZxHK1%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;125&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;125&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) iOS 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;ios/Runner/Info.plist&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&amp;nbsp;&lt;/span&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;&lt;b&gt;NSAppTransportSecurity&lt;/b&gt;는 http 프로토콜을 허용하는 키-값이지만, 앞으로 Info.plist에 입력할 값들은 대부분 앱에서 이미지, 카메라 등 권한 요청을 할 때 보여줄 메시지를 정의한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ct5B1/btsHN1BFM4C/CyKhzU2KaY2SOMBTtVNfpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ct5B1/btsHN1BFM4C/CyKhzU2KaY2SOMBTtVNfpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ct5B1/btsHN1BFM4C/CyKhzU2KaY2SOMBTtVNfpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCt5B1%2FbtsHN1BFM4C%2FCyKhzU2KaY2SOMBTtVNfpK%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;558&quot; height=&quot;289&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czv3If/btsHLyanQ7J/uHNDT1MfWWLpk8a4icTOQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czv3If/btsHLyanQ7J/uHNDT1MfWWLpk8a4icTOQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czv3If/btsHLyanQ7J/uHNDT1MfWWLpk8a4icTOQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fczv3If%2FbtsHLyanQ7J%2FuHNDT1MfWWLpk8a4icTOQK%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;561&quot; height=&quot;74&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEvhU5/btsHNVnXogH/CGdtcaNuKcU9GfrpW1SrU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEvhU5/btsHNVnXogH/CGdtcaNuKcU9GfrpW1SrU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEvhU5/btsHNVnXogH/CGdtcaNuKcU9GfrpW1SrU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEvhU5%2FbtsHNVnXogH%2FCGdtcaNuKcU9GfrpW1SrU0%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;566&quot; height=&quot;322&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;322&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;a href=&quot;https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717402383265&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 Info.plist Keys and Values&quot; data-og-description=&quot;About Info.plist Keys and Values To provide a better experience for users, iOS and macOS rely on the presence of special metadata in each app or bundle. This metadata is used in many different ways. Some of it is displayed to the user, some of it is used i&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html&quot; data-og-url=&quot;https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html&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;About Info.plist Keys and Values&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;About Info.plist Keys and Values To provide a better experience for users, iOS and macOS rely on the presence of special metadata in each app or bundle. This metadata is used in many different ways. Some of it is displayed to the user, some of it is used i&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-2. WebView를 사용하기 위해 main.dart 수정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 프로젝트를 실행하는 runApp() 함수는 내부적으로 &lt;b&gt;&quot;WidgetsFlutterBinding.ensureInitialized()&quot; (이하 &lt;span style=&quot;color: #f89009;&quot;&gt;ensureInitialized()&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;b&gt;WebViewController를 Property로 직접 인스턴스화&lt;/b&gt;하려면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;main.dart에서 ensureInitialized() 함수를 직접 실행&lt;/b&gt;해줘야 한다. 또는 &lt;b&gt;StatefulWidget에서 initState()를 오버라이딩&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. [09장] 전자액자 : 위젯 생명주기, PageView, Timer&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;위젯 생명주기 (LifeCycle)&lt;/b&gt;&lt;/span&gt; : &lt;b&gt;위젯이 화면에 그려지는 순간부터 -&amp;gt; 삭제되는 순간&lt;/b&gt;까지의 주기를 의미한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;➡️ StatelessWidget&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-origin-width=&quot;237&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnBtIM/btsHM5kQSkk/ktkJE7uKJ3JpXOrdeKIHGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnBtIM/btsHM5kQSkk/ktkJE7uKJ3JpXOrdeKIHGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnBtIM/btsHM5kQSkk/ktkJE7uKJ3JpXOrdeKIHGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnBtIM%2FbtsHM5kQSkk%2FktkJE7uKJ3JpXOrdeKIHGK%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;237&quot; height=&quot;184&quot; data-origin-width=&quot;237&quot; data-origin-height=&quot;184&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;size18&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt; &lt;b&gt;➡️ StatelfulWidget&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StatefulWidget에는 대표적으로 3가지 생명주기가 있다.&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;1) 상태 변경이 없는 생명주기&lt;/b&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b37lKP/btsHNxadlNN/xHkzBxdDYXSZwTQkRTeDa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b37lKP/btsHNxadlNN/xHkzBxdDYXSZwTQkRTeDa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b37lKP/btsHNxadlNN/xHkzBxdDYXSZwTQkRTeDa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb37lKP%2FbtsHNxadlNN%2FxHkzBxdDYXSZwTQkRTeDa1%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;565&quot; height=&quot;518&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0c470/btsHN1hAfBi/YLp9dO5ORVwkKTD1sMLkdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0c470/btsHN1hAfBi/YLp9dO5ORVwkKTD1sMLkdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0c470/btsHN1hAfBi/YLp9dO5ORVwkKTD1sMLkdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0c470%2FbtsHN1hAfBi%2FYLp9dO5ORVwkKTD1sMLkdk%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;564&quot; height=&quot;102&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;102&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;size18&quot;&gt;&lt;b&gt;2) StatefulWidget 생성자의 파라미터가 변경되었을 때, 생명주기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위젯이 생성된 후, 삭제가 되기 전에 파라미터가 변경되면 아래와 같은 생명주기가 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eEC3Zr/btsHNWgj2FD/HJIDJwasChujNTHKwHUh1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eEC3Zr/btsHNWgj2FD/HJIDJwasChujNTHKwHUh1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eEC3Zr/btsHNWgj2FD/HJIDJwasChujNTHKwHUh1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeEC3Zr%2FbtsHNWgj2FD%2FHJIDJwasChujNTHKwHUh1K%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;416&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;416&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;size18&quot;&gt;&lt;b&gt;3) State 자체적으로 build()를 재실행할 때의 생명주기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StatelessWidget은 생성될 때, build() 함수가 한 번 실행되고, 절대로 다시 실행되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 StatefulWidget은 setState() 함수를 실행해서, build() 함수를 자체적으로 재실행할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxKVzS/btsHOffEIIe/ZlyULkVepfLwtinPXXeKik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxKVzS/btsHOffEIIe/ZlyULkVepfLwtinPXXeKik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxKVzS/btsHOffEIIe/ZlyULkVepfLwtinPXXeKik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxKVzS%2FbtsHOffEIIe%2FZlyULkVepfLwtinPXXeKik%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;407&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-1. Timer&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Timer는 특정 시간이 지난 후에, 한번 또는 지속적으로 함수를 실행한다. &lt;b&gt;dart:async&lt;/b&gt; 에 정의되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;Timer.periodic()&lt;/b&gt;&lt;/span&gt;을 사용해서 주기적으로 콜백 함수를 실행한다. Timer.periodic()은 파라미터 2개를 입력받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;263&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBGmnc/btsHMNklZNj/yPHBMadu0lHu3d3VfAKES1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBGmnc/btsHMNklZNj/yPHBMadu0lHu3d3VfAKES1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBGmnc/btsHMNklZNj/yPHBMadu0lHu3d3VfAKES1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBGmnc%2FbtsHMNklZNj%2FyPHBMadu0lHu3d3VfAKES1%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;263&quot; height=&quot;86&quot; data-origin-width=&quot;263&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6YQa8/btsHLFOfe7f/JOkvuwetgZUdKLU5vLv02k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6YQa8/btsHLFOfe7f/JOkvuwetgZUdKLU5vLv02k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6YQa8/btsHLFOfe7f/JOkvuwetgZUdKLU5vLv02k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6YQa8%2FbtsHLFOfe7f%2FJOkvuwetgZUdKLU5vLv02k%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;561&quot; height=&quot;309&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;309&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-2. BoxFit 속성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용되는 위젯 : Image 위젯 등등...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cI9Jwd/btsHLFOfokz/oeZkbtxkgCvK2aQ02QXkUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cI9Jwd/btsHLFOfokz/oeZkbtxkgCvK2aQ02QXkUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cI9Jwd/btsHLFOfokz/oeZkbtxkgCvK2aQ02QXkUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcI9Jwd%2FbtsHLFOfokz%2FoeZkbtxkgCvK2aQ02QXkUK%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;171&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uL5SL/btsHMeQdsG2/2d0hHOl29ZL57AY5xmF31K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uL5SL/btsHMeQdsG2/2d0hHOl29ZL57AY5xmF31K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uL5SL/btsHMeQdsG2/2d0hHOl29ZL57AY5xmF31K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuL5SL%2FbtsHMeQdsG2%2F2d0hHOl29ZL57AY5xmF31K%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;567&quot; height=&quot;820&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ocucI/btsHL3OJCq4/qsIRnK0vy0oXXXnoE4ArVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ocucI/btsHL3OJCq4/qsIRnK0vy0oXXXnoE4ArVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ocucI/btsHL3OJCq4/qsIRnK0vy0oXXXnoE4ArVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FocucI%2FbtsHL3OJCq4%2FqsIRnK0vy0oXXXnoE4ArVK%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;565&quot; height=&quot;175&quot; data-origin-width=&quot;565&quot; data-origin-height=&quot;175&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-3. 상태바 아이콘 색상 변경하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;상태바 (StatusBar)&lt;/b&gt;&lt;/span&gt; : 앱 실행 중에 핸드폰 배터리, 시간, 와이파이 연결 상태 등을 보여주는 영역이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/voMa2/btsHMy10qHs/srOsKY6qPIWjCblkg3Yb6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/voMa2/btsHMy10qHs/srOsKY6qPIWjCblkg3Yb6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/voMa2/btsHMy10qHs/srOsKY6qPIWjCblkg3Yb6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvoMa2%2FbtsHMy10qHs%2FsrOsKY6qPIWjCblkg3Yb6K%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;451&quot; height=&quot;170&quot; data-origin-width=&quot;491&quot; data-origin-height=&quot;185&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;먼저 Flutter 기본 패키지 중에서, Service 패키지를 import한다. 그리고 build() 함수에 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;SystemChrome 클래스&lt;/b&gt;&lt;/span&gt; : 시스템 UI의 그래픽 설정을 변경하는 기능을 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Na8Zj/btsHMBqTZAX/pCnPXEnGKeajT7xkfDHlSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Na8Zj/btsHMBqTZAX/pCnPXEnGKeajT7xkfDHlSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Na8Zj/btsHMBqTZAX/pCnPXEnGKeajT7xkfDHlSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNa8Zj%2FbtsHMBqTZAX%2FpCnPXEnGKeajT7xkfDHlSK%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;602&quot; height=&quot;235&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;235&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_1717419288428&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 상태바 색상을 흰색(light)으로 변경한다
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-4. Curves 클래스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Curves&lt;/b&gt;&lt;/span&gt;는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;애니메이션 작동 방식을 결정한다. Flutter에서 수십 개의 curve 기본 설정이 제공된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/animation/Curves-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/animation/Curves-class.html&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717419986235&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;Curves class - animation library - Dart API&quot; data-og-description=&quot;A collection of common animation curves. See also: Curve, the interface implemented by the constants available from the Curves class. Easing, for the Material animation curves. Constructors Curves() Properties hashCode &amp;rarr; int The hash code for this object&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/animation/Curves-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/animation/Curves-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/animation/Curves-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/animation/Curves-class.html&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;Curves class - animation library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A collection of common animation curves. See also: Curve, the interface implemented by the constants available from the Curves class. Easing, for the Material animation curves. Constructors Curves() Properties hashCode &amp;rarr; int The hash code for this object&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. [10장] 만난 지 며칠 U&amp;amp;I : CupertinoDatePicker, Dialog, DateTime&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ setState()&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setState() 함수는 파라미터로 콜백 함수 하나를 입력 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 콜백 함수에 변경하고 싶은 속성들을 입력해주면, 해당 코드가 반영된 뒤 build() 함수가 재실행된다.&lt;/p&gt;
&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;※ setState()의 콜백 함수는 비동기(async - await)로 작성되면 안된다!!!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ showCupertinoDialog()&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;showCupertinoDialog()는 다이얼로그(대화창)를 실행하는 함수이다. &lt;b&gt;Cupertino&lt;/b&gt;라고 정의된 만큼 iOS 스타일로 다이얼로그가 실행되며, 실행 시 모든 애니메이션과 작동이 iOS 스타일로 적용된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgculp/btsHM6xdBMj/a6gzct7Zo7Y7KQ96RxRKZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgculp/btsHM6xdBMj/a6gzct7Zo7Y7KQ96RxRKZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgculp/btsHM6xdBMj/a6gzct7Zo7Y7KQ96RxRKZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgculp%2FbtsHM6xdBMj%2Fa6gzct7Zo7Y7KQ96RxRKZ1%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;548&quot; height=&quot;395&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;395&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-1 .of 생성자&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blq3Lc/btsHNvjbiq5/QzmBxgmx9yHJ5C7yclPSdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blq3Lc/btsHNvjbiq5/QzmBxgmx9yHJ5C7yclPSdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blq3Lc/btsHNvjbiq5/QzmBxgmx9yHJ5C7yclPSdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblq3Lc%2FbtsHNvjbiq5%2FQzmBxgmx9yHJ5C7yclPSdk%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;518&quot; height=&quot;457&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;457&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-2. GestureTapCallback 클래스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Material 패키지에서 기본으로 제공하는 &lt;b&gt;Typedef&lt;/b&gt;로, &lt;u&gt;&lt;b&gt;버튼의 onPressed() 또는 onTap() 콜백 함수&lt;/b&gt;&lt;/u&gt;들이 이 타입으로 정의되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. [11장] 디지털 주사위 : 가속도계, 자이로스코프, Sensor_Plus&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-1. Sensor_Plus 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sensor_plus 패키지를 사용하면, &lt;u&gt;&lt;b&gt;핸드폰의 가속도계와 자이로스코프 센서를 간단하게 사용&lt;/b&gt;&lt;/u&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 가속도계와 자이로스코프 센서의 데이터는 x, y, z 값을 통합해 전반적인 움직임 수치를 계산해서 -&amp;gt; 핸드폰을 흔든 정도를 수치화하는 &lt;b&gt;정규화(normalization)&lt;/b&gt;가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 단순히 흔들림을 감지하는 기능이 필요하다면, shake 패키지를 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;간단한 sensor_plus 패키지 사용 예제&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crSKgZ/btsHUQAqUeF/oQKqPykjYw8DKPoUkJWSyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crSKgZ/btsHUQAqUeF/oQKqPykjYw8DKPoUkJWSyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crSKgZ/btsHUQAqUeF/oQKqPykjYw8DKPoUkJWSyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrSKgZ%2FbtsHUQAqUeF%2FoQKqPykjYw8DKPoUkJWSyK%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;397&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;397&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-2. shake 패키지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;단순히 흔들림을 감지하는 기능&lt;/b&gt;&lt;/span&gt;이 필요하다면, shake 패키지를 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핸드폰 흔들기를 감지할 때마다 -&amp;gt; 실행할 (콜백) 함수를 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718032398243&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _RootScreenState extends State&amp;lt;RootScreen&amp;gt; with TickerProviderStateMixin {
  int number = 1; // 주사위 숫자
  ShakeDetector? shakeDetector;

  @override
  void initState() {
    super.initState();

    shakeDetector = ShakeDetector.autoStart( // 흔들림 감지를 즉시 시작한다
      shakeSlopTimeMS: 100, // 감지 주기
        shakeThresholdGravity: threshold, // 흔들림 감지 민감도
      onPhoneShake: onPhoneShake, // 감지되면 실행할 함수
    );
  }

  /**
   * 휴대폰 흔들림이 감지되면, 실행할 함수이다
   */
  void onPhoneShake() {
    final rand = new Random();
    
    setState(() {
      number = rand.nextInt(5) + 1;
    });
  }

  @override
  void dispose() {
    shakeDetector!.stopListening(); // 흔들림 감지를 중지한다
    super.dispose();
  }&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;ShakeDetector에는 &lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&quot;waitForStart&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. [12장] 동영상 플레이어 : 화면 회전, 시간 변환, String 패딩&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6-0. 사전 준비&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS에서 동영상을 불러오려면 -&amp;gt; &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;갤러리(사진첩)&quot;&lt;/b&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: #1a5490;&quot;&gt;&lt;b&gt;ImagePicker 패키지&lt;/b&gt;&lt;/span&gt;를 사용하면, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;카메라와 마이크 권한&lt;/b&gt;&lt;/span&gt;까지 필요한 경우가 많으니 3가지 권한을 모두 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;➡️ 갤러리 권한 추가&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;687&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH6xLv/btsHTlPCnh5/pBzTVmZKfpkHAg71MV426K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH6xLv/btsHTlPCnh5/pBzTVmZKfpkHAg71MV426K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH6xLv/btsHTlPCnh5/pBzTVmZKfpkHAg71MV426K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH6xLv%2FbtsHTlPCnh5%2FpBzTVmZKfpkHAg71MV426K%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;564&quot; height=&quot;687&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;687&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6-1. 시간 변환 및 String 패딩&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;video_player 패키지&lt;/b&gt;&lt;/span&gt;를 사용하면, 현재 실행되고 있는 동영상의 위치, 동영상의 총 길이 등을 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Duration 클래스&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;보기 좋은 String 값으로 Duration 클래스를 전환하는 게 매우 중요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAejZZ/btsHTUxr55r/kZga1pVHxA4EJuk4N33ikK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAejZZ/btsHTUxr55r/kZga1pVHxA4EJuk4N33ikK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAejZZ/btsHTUxr55r/kZga1pVHxA4EJuk4N33ikK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAejZZ%2FbtsHTUxr55r%2FkZga1pVHxA4EJuk4N33ikK%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;566&quot; height=&quot;167&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZIpUM/btsHVcp5Bji/wR40ofaScZJXutMwdhJjJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZIpUM/btsHVcp5Bji/wR40ofaScZJXutMwdhJjJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZIpUM/btsHVcp5Bji/wR40ofaScZJXutMwdhJjJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZIpUM%2FbtsHVcp5Bji%2FwR40ofaScZJXutMwdhJjJK%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;563&quot; height=&quot;525&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;525&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6-2. 구현하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;image_picker 패키지&lt;/b&gt;&lt;/span&gt; : 이미지나 동영상을 선택했을 때, &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;XFile&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: #f89009;&quot;&gt;&lt;b&gt;ImagePicker().pickVideo() 함수&lt;/b&gt;&lt;/span&gt;를 실행하면, 동영상을 선택하는 화면을 보여줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수의 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;source 파라미터&lt;/b&gt;&lt;/span&gt;로 -&amp;gt; &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;ImageSource.gallery&lt;/b&gt; &lt;/span&gt;또는 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;ImageSource.camera&lt;/b&gt;&lt;/span&gt;를 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;gallery&lt;/b&gt; : 이미 저장되어 있는 동영상을 갤러리로부터 선택하는 화면을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;camera&lt;/b&gt; : 카메라를 실행한 후, 동영상 촬영을 마치면 해당 영상이 선택된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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.2.1 &lt;span style=&quot;color: #1a5490;&quot;&gt;BoxDecoration 클래스&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BoxDecoration 클래스를 사용하면, Container 위젯의 배경색, 테두리, 모서리 둥근 정도 등등 전반적인 디자인을 변경할 수 있다.&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: #f89009;&quot;&gt;&lt;b&gt;➡️ gradient 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작 부위부터 끝 부위까지 점점 색이 변하는 &lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;LinearGradient&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중앙에서 색깔이 점점 퍼져가는 형태인 &lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;RadialGradient&lt;/b&gt;&lt;/span&gt; 가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1718083714672&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Widget renderEmpty() {
    return Container(
      decoration: getBoxDecoration(),
      child: Column(),
    );
  }

/**
* HomeScreen 배경에 그라데이션 적용
*/
BoxDecoration getBoxDecoration() {
    return BoxDecoration(
      gradient: LinearGradient(
        begin: Alignment.topCenter,
        end: Alignment.bottomCenter,
        colors: [
          Color(0xFF2A3A7C),
          Color(0xFF000118),
        ],
      ),
    );
}&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;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;➡️ boxShadow 파라미터&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 하나의 그림자만 사용하겠지만, 원한다면 List 안에 여러 BoxShadow 클래스를 넣어서 여러 그림자를 적용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;color&lt;/b&gt;&lt;/span&gt; : 그림자로 적용할 색상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;blurRadius&lt;/b&gt;&lt;/span&gt; : 흐림 정도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;spreadRadius&lt;/b&gt;&lt;/span&gt; : 퍼짐 정&lt;/p&gt;
&lt;pre id=&quot;code_1718252261747&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(16.0), // 모서리 둥글게 만들기
          boxShadow: [
            BoxShadow(
              color: Colors.blue[300]!,
              blurRadius: 12.0,
              spreadRadius: 2.0,
            ),
          ],
        ),
      ),
    );
  }&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dart와 Flutter/도서 내용 정리</category>
      <category>DART</category>
      <category>Flutter</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/281</guid>
      <comments>https://sweetburble.tistory.com/281#entry281comment</comments>
      <pubDate>Mon, 3 Jun 2024 14:56:35 +0900</pubDate>
    </item>
    <item>
      <title>콩나물 불고기 - [백종원 식]</title>
      <link>https://sweetburble.tistory.com/280</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 레시피는 4인분 기준이다. 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;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240531_콩나물불고기_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYneuK/btsHJDCStkY/IgTtK42SDqkuqZPjmuwt71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYneuK/btsHJDCStkY/IgTtK42SDqkuqZPjmuwt71/img.png&quot; data-alt=&quot;콩나물 불고기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYneuK/btsHJDCStkY/IgTtK42SDqkuqZPjmuwt71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYneuK%2FbtsHJDCStkY%2FIgTtK42SDqkuqZPjmuwt71%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;500&quot; height=&quot;375&quot; data-filename=&quot;edited_20240531_콩나물불고기_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;콩나물 불고기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=iGhOobaQHso&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=iGhOobaQHso&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=iGhOobaQHso&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/jpU9r/hyWdhLeQGT/1VukNAFiXgdUiwU2ffSvg1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=892_262_1052_436&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;가성비 최고 콩나물불고기&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/iGhOobaQHso&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/280</guid>
      <comments>https://sweetburble.tistory.com/280#entry280comment</comments>
      <pubDate>Fri, 31 May 2024 20:16:19 +0900</pubDate>
    </item>
    <item>
      <title>길거리 토스트 - [백종원 식]</title>
      <link>https://sweetburble.tistory.com/279</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;그 외 변경점 없음!&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: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&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: #333333; text-align: start;&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;/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;edited_20240529_길거리토스트_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0IYRr/btsHHL0hcXE/KctiJqJZHs13UrDVgUm9uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0IYRr/btsHHL0hcXE/KctiJqJZHs13UrDVgUm9uk/img.png&quot; data-alt=&quot;길거리 토스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0IYRr/btsHHL0hcXE/KctiJqJZHs13UrDVgUm9uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0IYRr%2FbtsHHL0hcXE%2FKctiJqJZHs13UrDVgUm9uk%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;500&quot; height=&quot;666&quot; data-filename=&quot;edited_20240529_길거리토스트_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;666&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=z-XcuZL80Lc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=z-XcuZL80Lc&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=z-XcuZL80Lc&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bQFNm3/hyWdnqvu2H/tbcc2nYgyDnih3PfOuG2xk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=1006_178_1146_330&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;햄,치즈,달걀 환상조화 국민 간식 토스트, 신박하게 쉬운 방법~! ㅣ 백종원의 쿠킹로그&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/z-XcuZL80Lc&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/279</guid>
      <comments>https://sweetburble.tistory.com/279#entry279comment</comments>
      <pubDate>Wed, 29 May 2024 19:41:58 +0900</pubDate>
    </item>
    <item>
      <title>프렌치 토스트 - [백종원 식]</title>
      <link>https://sweetburble.tistory.com/278</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240527_프렌치토스트_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfr2TS/btsHDxvt9hm/4v2tULE7XAwQdiPUOyKufk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfr2TS/btsHDxvt9hm/4v2tULE7XAwQdiPUOyKufk/img.png&quot; data-alt=&quot;프렌치 토스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfr2TS/btsHDxvt9hm/4v2tULE7XAwQdiPUOyKufk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfr2TS%2FbtsHDxvt9hm%2F4v2tULE7XAwQdiPUOyKufk%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;500&quot; height=&quot;405&quot; data-filename=&quot;edited_20240527_프렌치토스트_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;405&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=-dBPLgpC-tk&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=-dBPLgpC-tk&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=-dBPLgpC-tk&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/e8CBQ/hyWdeGJAkD/Wt0KOxtNbG9ynZfbr9IQ2K/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=540_360_638_468&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;부드러움 끝판왕 프렌치토스트&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/-dBPLgpC-tk&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/278</guid>
      <comments>https://sweetburble.tistory.com/278#entry278comment</comments>
      <pubDate>Mon, 27 May 2024 20:54:02 +0900</pubDate>
    </item>
    <item>
      <title>치즈 토스트 - [백종원 식]</title>
      <link>https://sweetburble.tistory.com/277</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;나중에는 다른 잼으로도 시도해봐야겠다.&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;edited_20240520_치즈토스트_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRkt3G/btsHvJIECQD/6sAzDNlHkXXnWchz9S62b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRkt3G/btsHvJIECQD/6sAzDNlHkXXnWchz9S62b1/img.png&quot; data-alt=&quot;치즈 토스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRkt3G/btsHvJIECQD/6sAzDNlHkXXnWchz9S62b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRkt3G%2FbtsHvJIECQD%2F6sAzDNlHkXXnWchz9S62b1%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;500&quot; height=&quot;540&quot; data-filename=&quot;edited_20240520_치즈토스트_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;치즈 토스트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240520_치즈토스트+땅콩버터_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TPpEJ/btsHwdP0MwP/nb1qLIjqtDr7qYNU3gAgC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TPpEJ/btsHwdP0MwP/nb1qLIjqtDr7qYNU3gAgC0/img.png&quot; data-alt=&quot;치즈 토스트 + 땅콩버터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TPpEJ/btsHwdP0MwP/nb1qLIjqtDr7qYNU3gAgC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTPpEJ%2FbtsHwdP0MwP%2Fnb1qLIjqtDr7qYNU3gAgC0%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;500&quot; height=&quot;565&quot; data-filename=&quot;edited_20240520_치즈토스트+땅콩버터_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;치즈 토스트 + 땅콩버터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=fD4wD3GTJq4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=fD4wD3GTJq4&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=fD4wD3GTJq4&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/rfcFp/hyV90hkwcA/3KP8iY2APCe2M334huvQZk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=728_120_838_240&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;천국의 맛 '치즈토스트', 그리고 사연 있는 초특급 신메뉴 최초 공개! l 백종원의 쿠킹로그&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/fD4wD3GTJq4&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/277</guid>
      <comments>https://sweetburble.tistory.com/277#entry277comment</comments>
      <pubDate>Mon, 20 May 2024 14:24:44 +0900</pubDate>
    </item>
    <item>
      <title>볶음 라면 - [하루한끼 식]</title>
      <link>https://sweetburble.tistory.com/276</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;size18&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;edited_20240517_볶음라면_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c33cPE/btsHrTzr6Zw/VnJPkjPRY333xwrUj2Jb00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c33cPE/btsHrTzr6Zw/VnJPkjPRY333xwrUj2Jb00/img.png&quot; data-alt=&quot;볶음 라면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c33cPE/btsHrTzr6Zw/VnJPkjPRY333xwrUj2Jb00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc33cPE%2FbtsHrTzr6Zw%2FVnJPkjPRY333xwrUj2Jb00%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;500&quot; height=&quot;375&quot; data-filename=&quot;edited_20240517_볶음라면_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;볶음 라면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=rwVSIYF93KQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=rwVSIYF93KQ&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=rwVSIYF93KQ&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/droBzG/hyV6iXvOCJ/cI69TQFWPhfruKNlUks0oK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;볶음라면 만들기 Stir-fried Noodles&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/rwVSIYF93KQ&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/276</guid>
      <comments>https://sweetburble.tistory.com/276#entry276comment</comments>
      <pubDate>Fri, 17 May 2024 15:40:28 +0900</pubDate>
    </item>
    <item>
      <title>대파 제육볶음 - [백종원 식]</title>
      <link>https://sweetburble.tistory.com/275</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;난이도는 그렇게 어렵지 않다.&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: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240513_대파제육볶음_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVZcoA/btsHl9IUxao/K1aCYyqp7IdqRrpjKQK6a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVZcoA/btsHl9IUxao/K1aCYyqp7IdqRrpjKQK6a1/img.png&quot; data-alt=&quot;대파 제육볶음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVZcoA/btsHl9IUxao/K1aCYyqp7IdqRrpjKQK6a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVZcoA%2FbtsHl9IUxao%2FK1aCYyqp7IdqRrpjKQK6a1%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;500&quot; height=&quot;401&quot; data-filename=&quot;edited_20240513_대파제육볶음_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대파 제육볶음&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=j7s9VRsrm9o&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=j7s9VRsrm9o&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=j7s9VRsrm9o&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/b2NKMN/hyV2uqOWWV/MkPFS4JjlfgNC7pT76Py5k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=980_130_1140_304&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;대파 듬뿍! 삼겹살로 만든 '대파 제육볶음'&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/j7s9VRsrm9o&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/275</guid>
      <comments>https://sweetburble.tistory.com/275#entry275comment</comments>
      <pubDate>Mon, 13 May 2024 11:34:25 +0900</pubDate>
    </item>
    <item>
      <title>소고기 무국수 - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/274</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무는 넣지 않았다. 우삼겹 -&amp;gt; 뼈없는 대패삼겹살을 사용했다. 숙주 -&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;처음이라 쉽지 않았다. 맛은 괜찮았다.&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;edited_20240510_소고기무국수_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5cz8F/btsHlMAaqHD/yTfI19ATwfHxksPOG5ZCRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5cz8F/btsHlMAaqHD/yTfI19ATwfHxksPOG5ZCRk/img.png&quot; data-alt=&quot;소고기 무국수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5cz8F/btsHlMAaqHD/yTfI19ATwfHxksPOG5ZCRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5cz8F%2FbtsHlMAaqHD%2FyTfI19ATwfHxksPOG5ZCRk%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;500&quot; height=&quot;361&quot; data-filename=&quot;edited_20240510_소고기무국수_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;소고기 무국수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=W33-azty2iU&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=W33-azty2iU&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=W33-azty2iU&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bv48r4/hyV2zLMIaq/7rz84l3HJ36gvIHwCcqkq1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;제발 추운날 이렇게 한번만 드셔보세요&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/W33-azty2iU&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/274</guid>
      <comments>https://sweetburble.tistory.com/274#entry274comment</comments>
      <pubDate>Fri, 10 May 2024 23:59:50 +0900</pubDate>
    </item>
    <item>
      <title>간장 콩나물 국수 - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/273</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240506_간장콩나물국수_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7hXRP/btsHb8vLLXY/UzkrcF8kk9sYtHou3l23sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7hXRP/btsHb8vLLXY/UzkrcF8kk9sYtHou3l23sK/img.png&quot; data-alt=&quot;간장 콩나물 국수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7hXRP/btsHb8vLLXY/UzkrcF8kk9sYtHou3l23sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7hXRP%2FbtsHb8vLLXY%2FUzkrcF8kk9sYtHou3l23sK%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;500&quot; height=&quot;369&quot; data-filename=&quot;edited_20240506_간장콩나물국수_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;369&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;간장 콩나물 국수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=GQcV9QJF95o&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=GQcV9QJF95o&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=GQcV9QJF95o&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/0I8a0/hyV2t4QJU4/QSmJlXHmTyrksaUJNunz3k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;제발 자취할 땐 저렴한 '이것'을 넣으세요.&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/GQcV9QJF95o&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/273</guid>
      <comments>https://sweetburble.tistory.com/273#entry273comment</comments>
      <pubDate>Mon, 6 May 2024 14:46:03 +0900</pubDate>
    </item>
    <item>
      <title>대패숙주 볶음밥 - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/272</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240503_대패숙주볶음밥_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSJ63L/btsG9zGsFwM/bKJWr24u2bvoUiqlkFiyQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSJ63L/btsG9zGsFwM/bKJWr24u2bvoUiqlkFiyQk/img.png&quot; data-alt=&quot;대패숙주 볶음밥&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSJ63L/btsG9zGsFwM/bKJWr24u2bvoUiqlkFiyQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSJ63L%2FbtsG9zGsFwM%2FbKJWr24u2bvoUiqlkFiyQk%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;500&quot; height=&quot;410&quot; data-filename=&quot;edited_20240503_대패숙주볶음밥_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대패숙주 볶음밥&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=MwmxZcuXmjo&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=MwmxZcuXmjo&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=MwmxZcuXmjo&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bpWkTw/hyVZnj0srG/tqrLShIYtmvSQE9uTonzh0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/MwmxZcuXmjo&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/272</guid>
      <comments>https://sweetburble.tistory.com/272#entry272comment</comments>
      <pubDate>Fri, 3 May 2024 13:46:46 +0900</pubDate>
    </item>
    <item>
      <title>강의 내용 정리 - [Flutter로 SNS 앱 만들기]</title>
      <link>https://sweetburble.tistory.com/271</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 5월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. [섹션 2. 회원 가입 화면] 가입완료 메시지 표시... 강의 수정 사항&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9분 즈음에 디바이스 자체의 뒤로가기 버튼을 막기 위해&lt;b&gt; WillPopScope&lt;/b&gt;를 사용했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 WillPopScope는 Deprecated 됐으므로,&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; PopScope&lt;/b&gt;&lt;/span&gt;를 대신 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/77529904/willpopscope-is-deprecated-in-flutter&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/77529904/willpopscope-is-deprecated-in-flutter&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1714663006547&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;WillPopScope is deprecated in Flutter&quot; data-og-description=&quot;'WillPopScope' is deprecated and shouldn't be used. Use PopScope instead. This feature was deprecated after v3.12.0-1.0.pre WillPopScope( onWillPop: () async { // your logic return&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/77529904/willpopscope-is-deprecated-in-flutter&quot; data-og-url=&quot;https://stackoverflow.com/questions/77529904/willpopscope-is-deprecated-in-flutter&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bfMcWC/hyVVEHAQQY/HuMs9qOIy2CLNRmnTFYYC1/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/77529904/willpopscope-is-deprecated-in-flutter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/77529904/willpopscope-is-deprecated-in-flutter&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bfMcWC/hyVVEHAQQY/HuMs9qOIy2CLNRmnTFYYC1/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;WillPopScope is deprecated in Flutter&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;'WillPopScope' is deprecated and shouldn't be used. Use PopScope instead. This feature was deprecated after v3.12.0-1.0.pre WillPopScope( onWillPop: () async { // your logic return&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;pre id=&quot;code_1714663019890&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WillPopScope(
  onWillPop: () async =&amp;gt; false,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;[24년 9월 기준 수정 코드]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1714663047348&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// NEW CODE
PopScope(
  canPop: false,
  onPopInvokedWithResult : (_, __) =&amp;gt; false,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Provider에서 context.read() VS context.watch()&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;context.read()&lt;/b&gt;&lt;/span&gt;와 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;context.watch()&lt;/b&gt;&lt;/span&gt;는 상태 관리 라이브러리인 Provider에서 사용되는 메서드이다.&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: #1a5490;&quot;&gt;&lt;b&gt;1. context.read()&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- context.read()는 주어진 Provider에서 데이터를 읽어오는 메서드이다. &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 데이터를 읽어올 때, 해당 Provider의 상태 변화를 구독(감지)하지 않는다. &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 주로 Provider의 값을 일회성으로 읽어올 때 사용된다. &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- EX) 초기화 작업이나 일회성 계산에 사용할 수 있다.&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;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;2. context.watch()&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- context.watch()는 주어진 Provider의 상태 변화를 구독하고, 해당 데이터를 읽어오는 메서드이다. &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- Provider의 상태가 변경될 때마다, 구독한 위젯이 Rebuild된다. &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 주로 UI에 Provider의 데이터를 표시하거나, 데이터 변경에 따라 UI를 업데이트할 때 사용된다. &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- Provider의 상태 변화를 실시간으로 모니터링하고 반영한다.&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;예를 들어, Counter 앱에서 context.read()를 사용하여 초기 값을 읽어올 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;context.watch()를 사용하여 카운터 값의 변화를 UI에 반영할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1714714401766&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = context.watch&amp;lt;CounterProvider&amp;gt;(); // 상태 변화 구독

    return Scaffold(
      appBar: AppBar(
        title: Text('Counter App'),
      ),
      body: Center(
        child: Text(
          '${counter.value}', // Provider 값 표시
          style: TextStyle(fontSize: 32),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () =&amp;gt; context.read&amp;lt;CounterProvider&amp;gt;().increment(), // 일회성 읽기
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Navigator로 스크린(페이지)을 이동할 때의 주의할 점&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Navigator.push() 나 Navigator.pushAndRemoveUntil() 같이 페이지를 이동&lt;/b&gt;할 때 사용하는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt; &lt;b&gt;PageRoute&lt;/b&gt;&lt;/span&gt;는, 주로 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;MaterialPageRoute&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;MaterialPageRoute는 도착한 곳의 위젯의 build() 메서드에서 Scaffold가 선언되어 있어야 한다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. IconKitchen&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드, iOS, 웹 (기타 macOS 등등) 에 맞는 아이콘을 규칙에 맞게 생성할 수 있도록 도와준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;https://icon.kitchen&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. [플러터] 앱의 패키지 이름, 프로젝트 이름 변경하는 법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &lt;span style=&quot;color: #1a5490;&quot;&gt;✅ change_app_package_name 패키지&lt;/span&gt; &lt;/b&gt;&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;a href=&quot;https://dev-nam.tistory.com/46&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev-nam.tistory.com/46&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716557802373&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;[flutter] 앱의 패키지 이름을 변경하는 방법&quot; data-og-description=&quot;플러터에서 앱의 패키지 이름을 변경하는 방법 앱을 출시하려고 빌드하는 과정에서 앱의 패키지 이름을 변경해야 하는 경우가 있습니다. 이때 패키지 이름을 어떻게 변경하는지 알아보겠습니&quot; data-og-host=&quot;dev-nam.tistory.com&quot; data-og-source-url=&quot;https://dev-nam.tistory.com/46&quot; data-og-url=&quot;https://dev-nam.tistory.com/46&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SA2RV/hyV92N7BrL/6ZCvv1em3z0mrQ4WtnAz61/img.png?width=800&amp;amp;height=298&amp;amp;face=0_0_800_298,https://scrap.kakaocdn.net/dn/beQbI3/hyV9OI7XC9/rgFjZtEeznhZTG3MTMHNw0/img.png?width=800&amp;amp;height=298&amp;amp;face=0_0_800_298,https://scrap.kakaocdn.net/dn/buorIh/hyV9Oh06cG/8MZj7EnK9jbP75mN7R7Cwk/img.png?width=1942&amp;amp;height=854&amp;amp;face=0_0_1942_854&quot;&gt;&lt;a href=&quot;https://dev-nam.tistory.com/46&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev-nam.tistory.com/46&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SA2RV/hyV92N7BrL/6ZCvv1em3z0mrQ4WtnAz61/img.png?width=800&amp;amp;height=298&amp;amp;face=0_0_800_298,https://scrap.kakaocdn.net/dn/beQbI3/hyV9OI7XC9/rgFjZtEeznhZTG3MTMHNw0/img.png?width=800&amp;amp;height=298&amp;amp;face=0_0_800_298,https://scrap.kakaocdn.net/dn/buorIh/hyV9Oh06cG/8MZj7EnK9jbP75mN7R7Cwk/img.png?width=1942&amp;amp;height=854&amp;amp;face=0_0_1942_854');&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;[flutter] 앱의 패키지 이름을 변경하는 방법&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;dev-nam.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;패키지 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716557844663&quot; class=&quot;dockerfile&quot; style=&quot;color: #333333; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ flutter pub add change_app_package_name&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;설치가 끝났다면 다음 명령어에 [com.new.package.name] 부분에 원하는 패키지 이름을 넣어서, 실행&lt;/p&gt;
&lt;pre id=&quot;code_1716557844664&quot; class=&quot;python&quot; style=&quot;color: #333333; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;$ flutter pub run change_app_package_name:main {여기에 수정할 패키지 이름}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅ 프로젝트 이름 변경&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blueming-dev.tistory.com/39&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blueming-dev.tistory.com/39&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718956555617&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;flutter app 이름 변경하기&quot; data-og-description=&quot;처음부터 다시 배운다는 마음으로 열심히 flutter에 대해서 공부하고 혹시 다음에도 또 찾을 것 같은 것들을 기록하기로 마음 먹었습니다. 전에도 다 했던 것들인데 다시 열어보니 정말 ??? 물음표&quot; data-og-host=&quot;blueming-dev.tistory.com&quot; data-og-source-url=&quot;https://blueming-dev.tistory.com/39&quot; data-og-url=&quot;https://blueming-dev.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/UXVTH/hyWoCVTADV/byJVYuxIEb0W91UkZZxyjK/img.png?width=394&amp;amp;height=47&amp;amp;face=0_0_394_47,https://scrap.kakaocdn.net/dn/bQpVhn/hyWoIIAfjd/W9bKBicehnxojfXibSwg7K/img.png?width=394&amp;amp;height=47&amp;amp;face=0_0_394_47,https://scrap.kakaocdn.net/dn/bq1uyG/hyWoOWiEgw/ct4psvKyOKFZbDl9eGnHoK/img.png?width=511&amp;amp;height=891&amp;amp;face=0_0_511_891&quot;&gt;&lt;a href=&quot;https://blueming-dev.tistory.com/39&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blueming-dev.tistory.com/39&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/UXVTH/hyWoCVTADV/byJVYuxIEb0W91UkZZxyjK/img.png?width=394&amp;amp;height=47&amp;amp;face=0_0_394_47,https://scrap.kakaocdn.net/dn/bQpVhn/hyWoIIAfjd/W9bKBicehnxojfXibSwg7K/img.png?width=394&amp;amp;height=47&amp;amp;face=0_0_394_47,https://scrap.kakaocdn.net/dn/bq1uyG/hyWoOWiEgw/ct4psvKyOKFZbDl9eGnHoK/img.png?width=511&amp;amp;height=891&amp;amp;face=0_0_511_891');&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;flutter app 이름 변경하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 다시 배운다는 마음으로 열심히 flutter에 대해서 공부하고 혹시 다음에도 또 찾을 것 같은 것들을 기록하기로 마음 먹었습니다. 전에도 다 했던 것들인데 다시 열어보니 정말 ??? 물음표&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blueming-dev.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dart와 Flutter</category>
      <category>Flutter</category>
      <category>안드로이드</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/271</guid>
      <comments>https://sweetburble.tistory.com/271#entry271comment</comments>
      <pubDate>Fri, 3 May 2024 00:37:16 +0900</pubDate>
    </item>
    <item>
      <title>원팬 알리오올리오 - [삼플식당 식]</title>
      <link>https://sweetburble.tistory.com/270</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;페페론치노 대신 인터넷에서 산 크러쉬드레드페퍼를 사용했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파마산 치즈가루는 없어서 넣지 못했다. 그 외 변경점 없음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;이 요리도 한 가지 맛밖에 안나는 것 같아서 처음에는 맛있는데 많이 먹을수록 빠르게 물리는 느낌이 들었다.&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-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240429_알리오올리오_삼플식당.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv6UNM/btsGZUkLCkL/xRu8K8wzwZwKkobNGyl640/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv6UNM/btsGZUkLCkL/xRu8K8wzwZwKkobNGyl640/img.png&quot; data-alt=&quot;원팬 알리오올리오&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv6UNM/btsGZUkLCkL/xRu8K8wzwZwKkobNGyl640/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv6UNM%2FbtsGZUkLCkL%2FxRu8K8wzwZwKkobNGyl640%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;500&quot; height=&quot;375&quot; data-filename=&quot;edited_20240429_알리오올리오_삼플식당.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;원팬 알리오올리오&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=rqKA8lzBHc8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=rqKA8lzBHc8&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=rqKA8lzBHc8&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/basriu/hyVZibasy6/y1soZtE55i6xEpKHGvdEWK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;원팬 파스타 알리오올리오! 생마늘 필요 없음! 초간단 레시피!&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/rqKA8lzBHc8&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/270</guid>
      <comments>https://sweetburble.tistory.com/270#entry270comment</comments>
      <pubDate>Mon, 29 Apr 2024 15:01:18 +0900</pubDate>
    </item>
    <item>
      <title>만두 짜글이 - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/269</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;양이 의외로 많아서 밥이랑 먹으면 1명이서 먹기에는 힘들듯? 그럭저럭 괜찮았다.&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;edited_20240426_만두짜글이_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2gWHv/btsGWNTsYHp/e3P0salxqXnwRba7E403c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2gWHv/btsGWNTsYHp/e3P0salxqXnwRba7E403c1/img.png&quot; data-alt=&quot;만두 짜글이&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2gWHv/btsGWNTsYHp/e3P0salxqXnwRba7E403c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2gWHv%2FbtsGWNTsYHp%2Fe3P0salxqXnwRba7E403c1%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;500&quot; height=&quot;375&quot; data-filename=&quot;edited_20240426_만두짜글이_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;만두 짜글이&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=niMyqoMPttE&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=niMyqoMPttE&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=niMyqoMPttE&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/ksx3W/hyVS0w69MA/PhqZyUfahgo1XkOjiTafLk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;냉동실에 만두가 남았으면 지금 당장 꺼내세요.&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/niMyqoMPttE&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/269</guid>
      <comments>https://sweetburble.tistory.com/269#entry269comment</comments>
      <pubDate>Fri, 26 Apr 2024 14:18:24 +0900</pubDate>
    </item>
    <item>
      <title>고기 국수 - [하루한끼 식]</title>
      <link>https://sweetburble.tistory.com/266</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;딱 비주얼대로 1도 자극적이지 않고, 담백하면서 심심한 그런 맛이다. 먹을만 했다!&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;edited_20240422_고기국수_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AOBwd/btsGRgGMeWn/Tycaclop5xYiLyjFpamHhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AOBwd/btsGRgGMeWn/Tycaclop5xYiLyjFpamHhK/img.png&quot; data-alt=&quot;고기 국수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AOBwd/btsGRgGMeWn/Tycaclop5xYiLyjFpamHhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAOBwd%2FbtsGRgGMeWn%2FTycaclop5xYiLyjFpamHhK%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;500&quot; height=&quot;375&quot; data-filename=&quot;edited_20240422_고기국수_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;고기 국수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=O-7MN2XSYjQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=O-7MN2XSYjQ&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=O-7MN2XSYjQ&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/b9kh66/hyVSWOlJ46/y9GcTAIr7YfXw0w6RGQV30/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;초간단 고기국수&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/O-7MN2XSYjQ&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/266</guid>
      <comments>https://sweetburble.tistory.com/266#entry266comment</comments>
      <pubDate>Mon, 22 Apr 2024 17:46:52 +0900</pubDate>
    </item>
    <item>
      <title>고기 볶음밥 - [이연복 식]</title>
      <link>https://sweetburble.tistory.com/265</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;고기를 50g 정도만 쓰기 때문에 남은 고기가 있으면, 처리하기 좋은 요리인 것 같다!&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;edited_20240419_고기볶음밥_이연복.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzpZ8J/btsGJsoWwhJ/ybtQWD1icgpMLDXNcJgpe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzpZ8J/btsGJsoWwhJ/ybtQWD1icgpMLDXNcJgpe1/img.png&quot; data-alt=&quot;고기 볶음밥&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzpZ8J/btsGJsoWwhJ/ybtQWD1icgpMLDXNcJgpe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzpZ8J%2FbtsGJsoWwhJ%2FybtQWD1icgpMLDXNcJgpe1%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;500&quot; height=&quot;371&quot; data-filename=&quot;edited_20240419_고기볶음밥_이연복.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;고기 볶음밥&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Gp3AqI76Fyk&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=Gp3AqI76Fyk&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Gp3AqI76Fyk&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bV6jvt/hyVPUwQcTX/ZIlkJFCupCClzOR9CNJa60/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=138_278_254_406&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[이연복] 고기 볶음밥&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Gp3AqI76Fyk&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/265</guid>
      <comments>https://sweetburble.tistory.com/265#entry265comment</comments>
      <pubDate>Fri, 19 Apr 2024 13:39:55 +0900</pubDate>
    </item>
    <item>
      <title>대패 볶음짬뽕 - [1분요리 뚝딱이형 식]</title>
      <link>https://sweetburble.tistory.com/264</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240415_대패볶음짬뽕_1분요리뚝딱이형.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tTGA0/btsGE6dHbfs/X8vKvEivh7CsMbqrkCAljK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tTGA0/btsGE6dHbfs/X8vKvEivh7CsMbqrkCAljK/img.png&quot; data-alt=&quot;대패 볶음짬뽕&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tTGA0/btsGE6dHbfs/X8vKvEivh7CsMbqrkCAljK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtTGA0%2FbtsGE6dHbfs%2FX8vKvEivh7CsMbqrkCAljK%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;500&quot; height=&quot;392&quot; data-filename=&quot;edited_20240415_대패볶음짬뽕_1분요리뚝딱이형.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대패 볶음짬뽕&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=AALYq8WdGm4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=AALYq8WdGm4&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=AALYq8WdGm4&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/CbzrN/hyVPX69QWR/efkJu3VZKvwgN2BjEAMreK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;라면요리는 진짜 맛있는 거 아니면 안 가져옵니다.&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/AALYq8WdGm4&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chef-choice.tistory.com/485&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://chef-choice.tistory.com/485&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713175037267&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;&amp;lt; 볶음짬뽕 레시피 &amp;gt; 재료 식용유 : 20ml 대패삼겹살 : 150g 대파 : 40g 배추 : 70g 양파 : 70g 진간장 : 1숟가락 고춧가루 : 1숟가락 해물믹스 : 50g (없다면 생략 가능) 진짬뽕 불닭볶음면 1. 팬에 아래의 재&quot; data-og-host=&quot;chef-choice.tistory.com&quot; data-og-source-url=&quot;https://chef-choice.tistory.com/485&quot; data-og-url=&quot;https://chef-choice.tistory.com/485&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/N7g5P/hyVPW1wdj5/K4QtQnFRWl8IfsUj9GwncK/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/cDtJO2/hyVPJODbnF/MJsLwdOcMmfDIivxcOCfak/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450&quot;&gt;&lt;a href=&quot;https://chef-choice.tistory.com/485&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chef-choice.tistory.com/485&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/N7g5P/hyVPW1wdj5/K4QtQnFRWl8IfsUj9GwncK/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/cDtJO2/hyVPJODbnF/MJsLwdOcMmfDIivxcOCfak/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450');&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;&amp;lt; 볶음짬뽕 레시피 &amp;gt; 재료 식용유 : 20ml 대패삼겹살 : 150g 대파 : 40g 배추 : 70g 양파 : 70g 진간장 : 1숟가락 고춧가루 : 1숟가락 해물믹스 : 50g (없다면 생략 가능) 진짬뽕 불닭볶음면 1. 팬에 아래의 재&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chef-choice.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>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/264</guid>
      <comments>https://sweetburble.tistory.com/264#entry264comment</comments>
      <pubDate>Mon, 15 Apr 2024 18:57:29 +0900</pubDate>
    </item>
    <item>
      <title>참치김치 볶음밥 - [하루한끼 식]</title>
      <link>https://sweetburble.tistory.com/263</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;어쨌든 난이도는 매우 쉬움&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: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;맛있었다.&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;edited_20240410_참치김치볶음밥_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdsaPR/btsGuGUOSB3/G596DY6RxWgi43nKha5mCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdsaPR/btsGuGUOSB3/G596DY6RxWgi43nKha5mCk/img.png&quot; data-alt=&quot;참치김치 볶음밥&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdsaPR/btsGuGUOSB3/G596DY6RxWgi43nKha5mCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdsaPR%2FbtsGuGUOSB3%2FG596DY6RxWgi43nKha5mCk%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;500&quot; height=&quot;375&quot; data-filename=&quot;edited_20240410_참치김치볶음밥_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;참치김치 볶음밥&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=_5DsG7oj4Kw&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=_5DsG7oj4Kw&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=_5DsG7oj4Kw&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bzrZEh/hyVMZ45QMp/KAJL7ZUfZHzPpIrPRgejhK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;보통이 아닌 참치김치 볶음밥&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/_5DsG7oj4Kw&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/263</guid>
      <comments>https://sweetburble.tistory.com/263#entry263comment</comments>
      <pubDate>Wed, 10 Apr 2024 12:39:32 +0900</pubDate>
    </item>
    <item>
      <title>내용 정리 Part. 2 - [SNS 앱을 만들면서 배우는 안드로이드 클라이언트 개발]</title>
      <link>https://sweetburble.tistory.com/262</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 4월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. 타임라인을 나눠서 불러오자&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;페이징(Paging)&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;페이징.png&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;1236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsCJog/btsGwATzn5Z/KvJ40nojpWDOpykciIdRk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsCJog/btsGwATzn5Z/KvJ40nojpWDOpykciIdRk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsCJog/btsGwATzn5Z/KvJ40nojpWDOpykciIdRk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsCJog%2FbtsGwATzn5Z%2FKvJ40nojpWDOpykciIdRk0%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;700&quot; height=&quot;385&quot; data-filename=&quot;페이징.png&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;1236&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는 위 그림과 같이 3가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) 페이지 버튼 UI&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;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: #409d00;&quot;&gt;&lt;b&gt;2) 더 보기 버튼 UI&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일에서는 터치 영역이 작으면 조작이 어렵기 때문에, 목록의 하단에 다음 목록을 불러오는 &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;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3) 무한 스크롤링&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;사용자의 몰입이 끊기는 것을 최소화하기 때문에, SNS의 타임라인 기능에서 주로 사용된다.&lt;/p&gt;
&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;※ 최근 안드로이드 Jetpack의 Paging 라이브러리를 사용하면, 간단히 만들 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이징 기능을 어떤 방법으로 만들 것인가는 &quot;서버의 API 디자인&quot; 에 영향을 받는다. API 디자인은 2가지로 구분할 수 있다.&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) 오프셋 기반 페이지네이션(Pagination)&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;오프셋 기반.webp&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;469&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4V38f/btsGt8jYGgZ/192J3tlVqM4u8i6oWmNd11/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4V38f/btsGt8jYGgZ/192J3tlVqM4u8i6oWmNd11/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4V38f/btsGt8jYGgZ/192J3tlVqM4u8i6oWmNd11/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4V38f%2FbtsGt8jYGgZ%2F192J3tlVqM4u8i6oWmNd11%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;401&quot; data-filename=&quot;오프셋 기반.webp&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;469&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;GET /items?page=2&amp;amp;count=3&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서는&lt;b&gt; &quot;페이지 번호 x 페이지 크기&quot;&lt;/b&gt; 만큼 이동하여 결과를 보내준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 &lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;클라이언트에서 요청한 대로 결과를 보내주는 유연한 구조&lt;/b&gt;&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;그러나,&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;b&gt;오프셋 방식의 API로 &quot;더 보기 버튼 UI&quot; 나 &quot;무한 스크롤링&quot; 을 만들면 문제가 발생하기 쉽다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;기준이 되는 값부터 일정한 개수를 가져오는 방법이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;커서 기반.webp&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLzDXp/btsGvxbSghf/4sDKzfTwgxMjHzauIy0mGK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLzDXp/btsGvxbSghf/4sDKzfTwgxMjHzauIy0mGK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLzDXp/btsGvxbSghf/4sDKzfTwgxMjHzauIy0mGK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLzDXp%2FbtsGvxbSghf%2F4sDKzfTwgxMjHzauIy0mGK%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;600&quot; height=&quot;538&quot; data-filename=&quot;커서 기반.webp&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;GET /itmes?after=5시 55분&amp;amp;count=3&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서는 &lt;span style=&quot;color: #0593d3;&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;바로 다음 페이지만 요청할 수 있기 때문에, 특정 페이지로 바로 이동하는 &quot;페이지 버튼 UI&quot;는 만들 수 없다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;정렬된 필드가 값의 중복을 허용&lt;/b&gt;하는 경우에는(EX : 다른 아이템이 같은 시간을 가진다면), &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;u&gt;&lt;b&gt;&quot;아이디&quot;처럼 값이 중복되지 않는 필드를 키로 사용&lt;/b&gt;&lt;/u&gt;해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8-1. Paging 라이브러리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드에서 무한 스크롤링 기능을 사용하려면, 다음 항목들을 구현해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp; RecyclerView의 위치를 감시하다가, 특정 위치에서 데이터 요청&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp; 불러올 데이터를 가리키는 키 관리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp; 이미 요청한 상태인지 관리하고, 로딩 상태 표시&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp; 새로고침과 요청 실패 시, 재시도&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp; 새로 받은 데이터와 기존의 데이터 중복 확인&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 Paging 라이브러리는 이런 요구사항들이 이미 잘 구현되어 있어서, RecyclerView에서 무한 스크롤링을 사용할 수 있도록 지원하고, &lt;b&gt;Room, LiveData&lt;/b&gt; 등 다른 Jetpack 컴포넌트들과 쉽게 통합될 수 있다.&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;페이징3.png&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JxnnE/btsGwdK2oJM/YVyRj3zdGZmyphaYxgAqm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JxnnE/btsGwdK2oJM/YVyRj3zdGZmyphaYxgAqm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JxnnE/btsGwdK2oJM/YVyRj3zdGZmyphaYxgAqm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJxnnE%2FbtsGwdK2oJM%2FYVyRj3zdGZmyphaYxgAqm1%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;652&quot; height=&quot;333&quot; data-filename=&quot;페이징3.png&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;PagingSource&lt;/b&gt;&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;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;[추가 요소] RemoteMediator&lt;/b&gt;&lt;/span&gt; : API에서 데이터를 받아, 로컬 데이터베이스에 캐시하는 경우에 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PagingSource가 로컬 데이터베이스에 캐시된 데이터를 Pager에 제공하여, 로컬 데이터베이스의 캐시 데이터를 모두&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하면, 다시 RemoteMediator가 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: #409d00;&quot;&gt;&lt;b&gt;PagingConfig&lt;/b&gt;&lt;/span&gt; : 페이지 크기나, Placeholder의 사용 여부 등을 설정할 수 있다.&lt;/p&gt;
&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: #409d00;&quot;&gt;&lt;b&gt;Pager&lt;/b&gt;&lt;/span&gt; : 페이징의 진입점. PagingConfig에서 설정된 값으로, PagingSource를 호출하고 키를 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이징된 데이터를 PagingData로 만들어, PagingDataAdapter에서 사용할 수 있도록 제공한다.&lt;/p&gt;
&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: #409d00;&quot;&gt;&lt;b&gt;PagingDataAdapter&lt;/b&gt;&lt;/span&gt; : RecyclerView.Adapter를 상속해 데이터를 RecyclerView에 표시하고, 데이터의 로딩 상태를 노출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;아이템을 비교하는 DiffUtil.ItemCallback &lt;/b&gt;을 생성자로 받아서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 데이터가 전달될 때 기존의 아이템과 비교하고 변경된 것을 어댑터에게 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;[추가 요소] LoadStateAdapter&lt;/b&gt; &lt;/span&gt;: 로드 상태에 따라 Progress(진행)바나 재시도 버튼을 표시하기 위해 사용한다.&lt;/p&gt;
&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;※ Paging3은 로딩 상태를 표시하기 위해, &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아이템을 제공하는 어댑터와 로딩 상태를 표시하는 어댑터를 연결하는 &quot;ConcatAdapter&quot;를 만드는 방법을 사용한다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;9. 캐시로 HTTP를 효율적으로 사용하자&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9장에서는 상세보기 기능을 만들었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Retrofit(정확히는 OkHttp)에 캐시를 사용&lt;/b&gt;하도록 설정하여, 요청과 응답이 어떻게 달라지는지 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시를 사용하기 위해 작성한 코드는, ApiService의 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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;HTTP 캐시&lt;/b&gt;&lt;/span&gt;는 &lt;span style=&quot;color: #006dd7;&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;GET 요청의 응답만 캐싱&lt;/b&gt;&lt;/span&gt;하고, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;캐싱한 리소스를 사용하려면 HTTP 요청을 통해서만 가져올 수 있다&lt;/b&gt;&lt;/span&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;10. Room으로 오프라인 액세스 지원&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[296 페이지] ViewPager 대체&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문-답 카드를 좌우로 스와이프해서 넘겨보는 UI를 만들 때는, 원래 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;ViewPager&lt;/b&gt;&lt;/span&gt;를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;Paging3의 PagingDataAdapter&lt;/b&gt;는 ViewPager를 지원하지 않기 때문에, RecyclerView를 기반으로 만들어진 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;ViewPager2&lt;/b&gt;&lt;/span&gt;를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10-1. &lt;span style=&quot;color: #1a5490;&quot;&gt;Room Persistence 라이브러리&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Room은 구글에서 만든,&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; ORM(Object Relational Mapping)&lt;/b&gt; &lt;/span&gt;라이브러리이다.&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;b&gt;ORM&lt;/b&gt; : &lt;b&gt;프로그래밍 언어의 객체 &amp;lt;-&amp;gt; 데이터베이스 간의 데이터를 변환해 주는&lt;/b&gt; 기법이다.&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;ORM을 사용하면, &lt;u&gt;&lt;b&gt;쿼리를 코드에서 직접 사용하지 않아도, 프로그래밍 언어의 클래스와 메서드를 통해서 데이터베이스를 사용&lt;/b&gt;&lt;/u&gt;할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나, Room은 일반적인 ORM 라이브러리와는 조금 다르다. -&amp;gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;SQLite만 지원하고, 쿼리를 직접 작성&lt;/b&gt;&lt;/span&gt;해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;캐시, 지연 로딩 같이 많은 ORM 라이브러리가 지원하는 기능이 없다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;순수하게 SQLite의 기능만 지원하면서, ORM의 편의를 취한, 학습 난이도는 낮은 라이브러리&lt;/b&gt;&lt;/span&gt;이다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Room.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3HhMa/btsGyFUJP3s/Sf6cOndVIwZCmhk43JiShk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3HhMa/btsGyFUJP3s/Sf6cOndVIwZCmhk43JiShk/img.png&quot; data-alt=&quot;Room 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3HhMa/btsGyFUJP3s/Sf6cOndVIwZCmhk43JiShk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3HhMa%2FbtsGyFUJP3s%2FSf6cOndVIwZCmhk43JiShk%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;598&quot; height=&quot;347&quot; data-filename=&quot;Room.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Room 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&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;Room은 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Database를 관리하는 RoomDatabase, Entity, DAO(Data Access Object)&lt;/b&gt;&lt;/span&gt;로 구성된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DAO에서 Entity를 사용하고, Database는 DAO와 Entity를 모두 사용하기 때문에, &lt;b&gt;Entity -&amp;gt; DAO -&amp;gt; Database&lt;/b&gt;의 순서대로 만들자.&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;Room 의존성을 추가하기 전에, Room은 어노테이션 프로세서에서 코드를 생성하기 때문에 plugins 블록에 kotlin-kapt 플러그인도 추가한다.&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; 어노테이션 프로세서&lt;/b&gt;&lt;/span&gt; : 컴파일을 할 때, 어노테이션에 따라 코드를 검사하거나 새로운 코드를 작성하는 등의 작업을 할 수 있는 도구&lt;/p&gt;
&lt;pre id=&quot;code_1712822294500&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.jetbrainsKotlinAndroid)
    id(&quot;kotlin-kapt&quot;)
}

dependencies {
    // 306페이지 추가
    implementation(&quot;androidx.room:room-runtime:2.4.3&quot;)
    implementation(&quot;androidx.room:room-ktx:2.4.3&quot;)
    implementation(&quot;androidx.room:room-paging:2.4.3&quot;)
    kapt(&quot;androidx.room:room-compiler:2.4.3&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10-2. SQLite과 Room&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLite에서 INSERT나 UPDATE 구문을 처리하다 보면, &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;b&gt;SQLite에서는&lt;/b&gt; 이런 충돌의 해결 방법을 &lt;b&gt;&quot;ON CONFLICT&quot;&lt;/b&gt; 구분으로 선택하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Room에서는&lt;/b&gt; @Insert와 @Update 어노테이션의 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;onConflict&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;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;OnConflictStrategy.ABORT&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: #409d00;&quot;&gt;&lt;b&gt; OnConflictStrategy.REPLACE&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: #409d00;&quot;&gt;&lt;b&gt; OnConflictStrategy.IGNORE&lt;/b&gt;&lt;/span&gt; : 충돌이 발생하면, 새로운 데이터를 무시하고 트랜잭션을 계속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;@Query 어노테이션&lt;/b&gt;&lt;/span&gt;은 &lt;b&gt;사용자가 정의한 Entity나 Array, List, Cursor 등의 기본 타입뿐만&lt;/b&gt; 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flow, LiveData, Observable&lt;/b&gt;도 지원하고, Paging3 라이브러리와 함께 사용하면&lt;b&gt; PagingSource&lt;/b&gt;도 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Room의 데이터베이스는 &lt;u&gt;&lt;b&gt;RoomDatabase를 상속&lt;/b&gt;&lt;/u&gt;받은 클래스에, &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;@Database 어노테이션&lt;/b&gt;&lt;/span&gt;을 붙여 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에서는 &lt;b&gt;버전, Entity, DAO, 타입 컨버터, 마이그레이션 등&lt;/b&gt; 데이터베이스에 대한 설정을 할 수 있고, SQLite과의 연결을 관리한다.&lt;/p&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의 DAO를 가져오기 위해서는, AppDatabase에 파라미터가 없고, DAO를 반환하는 추상 메서드가 있어야 한다.&lt;/p&gt;
&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;※ 데이터베이스 클래스의 인스턴스를 만드는 과정은 다소 무겁고, 앱이 작동되는 동안 Room을 계속 필요하기 때문에 싱글톤으로 만들어 사용하는 것도 권장한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10-3. [330페이지] 까지 진행했을 때, 주의해야 할&amp;nbsp; 오류&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 내용 그대로 330페이지까지 하고 실행시켜 보았을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 과정에서 UserDao에 문제가 생겨 제대로 실행되지 않는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;Not sure how to convert a Cursor to this method's return type (java.lang.Object)&quot;&lt;/b&gt; &lt;/span&gt;이런 오류일 텐데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 &lt;u&gt;&lt;b&gt;사용하고 있는 코틀린 버전과 Room 라이브러리의 버전이 호환되지 않아서&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://issuetracker.google.com/issues/236612358&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://issuetracker.google.com/issues/236612358&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713249964112&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 Issue Tracker&quot; data-og-description=&quot;&quot; data-og-host=&quot;issuetracker.google.com&quot; data-og-source-url=&quot;https://issuetracker.google.com/issues/236612358&quot; data-og-url=&quot;https://issuetracker.google.com/issues/236612358&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://issuetracker.google.com/issues/236612358&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://issuetracker.google.com/issues/236612358&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;Google Issue Tracker&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;issuetracker.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;p data-ke-size=&quot;size16&quot;&gt;24년 4월 현재를 기준으로 이 프로젝트의 코틀린 버전은 1.9.xx 대이므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 &lt;b&gt;Room 라이브러리의 버전을 2.4.3에서 -&amp;gt; 2.6.1로 변경&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713250072990&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    // 306페이지 추가
    implementation(&quot;androidx.room:room-runtime:2.6.1&quot;)
    implementation(&quot;androidx.room:room-ktx:2.6.1&quot;)
    implementation(&quot;androidx.room:room-paging:2.6.1&quot;)
    kapt(&quot;androidx.room:room-compiler:2.6.1&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10-4. 로컬 데이터베이스를 갱신하는 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 데이터를 로컬 데이터 베이스에 저장하여 캐시로 사용할 땐, 갱신되는 시점이 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, HTTP 캐시에서는 ETag가 변경되거나,&amp;nbsp; max-age가 지나서 만료되는 경우에 갱신을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Daily Q에서는 2가지 방법으로 데이터를 갱신할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) 타임라인 화면을 당겨서 전체 목록을 새로고침&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;Pull-to-refresh&quot;&lt;/b&gt;&lt;/span&gt; 라고 하며, 안드로이드에서는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;SwipeRefreshLayout&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;build.gradle에 의존성을 추가한 다음, RecyclerView을 SwipeRefreshLayout으로 감싸주면 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;11. FCM으로 실시간 알림을 받아보자&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;FCM(Firebase Cloud Messaging)&lt;/b&gt;&lt;/span&gt;은 메시지를 보낼 수 있도록 도와주는 서비스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 전송하는 방식은 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;푸시(Push)와 풀(Pull)&lt;/b&gt;&lt;/span&gt;로 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API를 호출하는 것처럼 클라이언트가 서버에 필요한 정보를 요청해 응답을 받는 것을 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;풀 기법(Pull technology)&lt;/b&gt;&lt;/span&gt;이라고 하고, 반대로 클라이언트가 요청하지 않았지만, 서버에서 능동적으로 클라이언트에게 필요한 메시지를 보내는 것을 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;푸시 기법(Push technology)&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;메일, 메신저, 캘린더, SNS와 같은 서비스는 이벤트가 발생하면 즉시 알 수 있어야 한다.&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;u&gt;&lt;b&gt;모바일에서는 이벤트가 발생했을 때, &quot;서버&quot;가 &quot;앱&quot;에게 알려주는 푸시 기법이 주로 사용&lt;/b&gt;&lt;/u&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;FCM 플로우.png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d82EZm/btsGHi5E2X5/eHPoopcz3hMlhEtFKhUTI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d82EZm/btsGHi5E2X5/eHPoopcz3hMlhEtFKhUTI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d82EZm/btsGHi5E2X5/eHPoopcz3hMlhEtFKhUTI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd82EZm%2FbtsGHi5E2X5%2FeHPoopcz3hMlhEtFKhUTI1%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;650&quot; height=&quot;326&quot; data-filename=&quot;FCM 플로우.png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;702&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;1번째) 로그인한 기기의 토큰을 FCM에 등록하고, &lt;span style=&quot;color: #1a5490;&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;2번째) 발급받은 등록 토큰을 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;i&gt;&lt;b&gt;서버에서 클라이언트에게 보내야 할 정보가 있을 땐,&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번째) API 서버가 보낼 메시지와 메시지를 받을 기기의 &lt;b&gt;&quot;등록 토큰&quot;&lt;/b&gt;을 FCM으로 전달한다.&lt;/p&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번째) FCM이 등록 토큰의 기기에 메시지를 전달한다. 기기에서는 &lt;b&gt;&quot;구글 플레이 서비스&quot;&lt;/b&gt;가 알림을 받아 앱으로 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;11-1. FCM 연동하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1. FCM을 사용하기 위해서, 먼저 파이어베이스 콘솔에서 새 프로젝트를 만든다. &quot;구글 애널리틱스&quot; 사용도 설정한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://console.firebase.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://console.firebase.google.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713253275744&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 계정&quot; data-og-description=&quot;이메일 또는 휴대전화&quot; data-og-host=&quot;accounts.google.com&quot; data-og-source-url=&quot;https://console.firebase.google.com/&quot; data-og-url=&quot;https://accounts.google.com/v3/signin/identifier?continue=https%3A%2F%2Fconsole.firebase.google.com%2F&amp;amp;followup=https%3A%2F%2Fconsole.firebase.google.com%2F&amp;amp;ifkv=ARZ0qKLDDla14SfSFteSt63wW8rfnxSVILahTZadJh3Ve1bwyVrG54gX7kCS7OG6kBWHYGMVaP-Qvw&amp;amp;osid=1&amp;amp;passive=1209600&amp;amp;flowName=WebLiteSignIn&amp;amp;flowEntry=ServiceLogin&amp;amp;dsh=S-788085175%3A1713253274492686&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://console.firebase.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://console.firebase.google.com/&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;로그인 - Google 계정&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;accounts.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;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: #409d00;&quot;&gt;&lt;b&gt;2. 생성된 파이어베이스 프로젝트에서, 안드로이드 앱을 추가한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지 이름은 build.gradle의 applicationId와 동일하게 입력하고, 선택사항은 비워둔다.&lt;/p&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-services.json 파일을 다운로드하여, 프로젝트의 &lt;s&gt;&lt;b&gt;app/src/main 폴더&lt;/b&gt;&lt;/s&gt;로 옮긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ [4월 기준] main 폴더로 넣으면, &quot;google-service.json 파일을 찾을 수 없다.&quot; 고 에러가 발생한다.&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;그 상위폴더인 app/src 폴더에 &lt;span style=&quot;text-align: start;&quot;&gt;google-services.json 파일을 넣어야 한다.&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=tAvP7BPvErg&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=tAvP7BPvErg&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=tAvP7BPvErg&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/IYAjY/hyVS8mbt9o/PE0ZuuxRhN0t5qsYoHI1XK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/tAvP7BPvErg&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3. 안드로이드 클라이언트 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;google-services.json 파일에는 앱에서 파이어베이스 서비스를 사용하기로 한 클라이언트 ID나 API 키 등이 포함되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트의 build.gradle과 앱의 build.gradle에 각각 플러그인을 추가하면, google-services.json에서 필요한 정보가 문자열 리소스로 프로젝트에 추가된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/android/guides/google-services-plugin?hl-ko&amp;amp;hl=ko#processing_the_json_file&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.google.com/android/guides/google-services-plugin?hl-ko&amp;amp;hl=ko#processing_the_json_file&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713253564945&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 서비스 Gradle 플러그인 &amp;nbsp;|&amp;nbsp; Google Play services &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Google 서비스 Gradle 플러그인 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 소개 Android 애플리케이션&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/android/guides/google-services-plugin?hl-ko&amp;amp;hl=ko#processing_the_json_file&quot; data-og-url=&quot;https://developers.google.com/android/guides/google-services-plugin?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bXIS2y/hyVPX0McqW/vm9hdvyZbiwiLVY9DWvnGk/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://developers.google.com/android/guides/google-services-plugin?hl-ko&amp;amp;hl=ko#processing_the_json_file&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/android/guides/google-services-plugin?hl-ko&amp;amp;hl=ko#processing_the_json_file&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bXIS2y/hyVPX0McqW/vm9hdvyZbiwiLVY9DWvnGk/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Google 서비스 Gradle 플러그인 &amp;nbsp;|&amp;nbsp; Google Play services &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Google 서비스 Gradle 플러그인 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 소개 Android 애플리케이션&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.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;pre id=&quot;code_1713253606316&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&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 {
    alias(libs.plugins.androidApplication) apply false
    alias(libs.plugins.jetbrainsKotlinAndroid) apply false
    id(&quot;com.google.gms.google-services&quot;) version &quot;4.3.10&quot; apply false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 build.gradle에는 FCM 의존성과, Play 서비스를 코루틴과 함께 사용하기 위한 의존성도 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 책의 내용처럼 analytics-ktx와 messaging-ktx의 버전을 지정하지 않았더니, 제대로 패키지가 추가되지 않아서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;firebase-bom:26.7.0의 버전과 호환되도록 maven에 나와있는 정보를 참고해서 버전을 직접 지정했다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713255202802&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.jetbrainsKotlinAndroid)
    id(&quot;kotlin-kapt&quot;)
    id(&quot;com.google.gms.google-services&quot;)
}

...

dependencies {
    // 343페이지 추가
    platform(&quot;com.google.firebase:firebase-bom:26.7.0&quot;)
    implementation(&quot;com.google.firebase:firebase-analytics-ktx:18.0.2&quot;)
    implementation(&quot;com.google.firebase:firebase-messaging-ktx:21.0.1&quot;)
    implementation(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.3&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;a href=&quot;https://mvnrepository.com/artifact/com.google.firebase/firebase-bom/26.7.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mvnrepository.com/artifact/com.google.firebase/firebase-bom/26.7.0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ FCM SDK는 앱을 시작할 때, 자동으로 등록 토큰을 생성한다.&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;※ 안드로이드 8(API 26)부터는 미리 알림 채널을 만들어야 하기 때문에, 안드로이드 앱이 시작할 때 채널을 만든다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 그래서 &lt;b&gt;답글과 팔로우를 위한 채널을 만드는 메서드를 각각 만들고, init() 메서드에서 호출하고, App.kt에서 호출&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;size18&quot;&gt;&lt;b&gt;※ FCM에서 전송한 메시지는 FirebaseMessagingService를 확장한 서비스로 전달된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[349페이지]의 [코드 11-8]처럼, &lt;b&gt;FirebaseMessagingService를 상속받는 MessagingService&lt;/b&gt;를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;FCM 백엔드에서 보낸 메시지를 수신하는 onMessageReceived() 메서드&lt;/b&gt;를 재정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onMessageReceived() 메서드로 전달되는 &lt;b&gt;RemoteMessage&lt;/b&gt;에서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;FCM의 2가지 메시지 타입인 &quot;데이터 메시지&quot;와 &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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;1) 알림 메시지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;앱에서 다른 처리 없이 단순하게 서버에서 보내준 메시지를 알림으로 표시할 때 사용&lt;/b&gt;&lt;/u&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 백그라운드에 있을 때는, FCM SDK에서 자동으로 작업 표시줄에 표시하기 때문에 onMessageReceived() 메서드가 호출되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 포그라운드에 있을 때는, FCM SDK가 처리하지 않고 onMessageReceived() 메서드가 호출되며, RemoteMessage.getNotification() 메서드로 가져올 수 있다.&lt;/p&gt;
&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: #1a5490;&quot;&gt;&lt;b&gt;2) 데이터 메시지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;개발자의 필요에 따라 자유롭게 구성할 수 있기 때문에 알림 이외의 용도로도 사용&lt;/b&gt;&lt;/u&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 상태와 상관없이 항상 onMessageReceived() 메서드로 전달된다. 친구 신청 알림을 보낼 때, 보낸 사용자의 정보 메시지에 포함해 앱에서 저장할 수 있게 하거나, 알림을 표시하지 않고 앱의 내부 작동에만 영향을 주는 트리거로 사용할 수 있다.&lt;/p&gt;
&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: #006dd7;&quot;&gt;&lt;b&gt;-&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로&lt;b&gt; AndroidManifest.xml에 MessagingService를 등록&lt;/b&gt;하면, 클라이언트 앱은 실시간 알림을 받을 준비가 끝난다.&lt;/p&gt;
&lt;pre id=&quot;code_1713257081845&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
	&amp;lt;service 
            android:name=&quot;.messaging.MessagingService&quot;
            android:exported=&quot;false&quot;&amp;gt;
            &amp;lt;intent-filter&amp;gt;
                &amp;lt;action android:name=&quot;com.google.firebase.MESSAGING_EVENT&quot; /&amp;gt;
            &amp;lt;/intent-filter&amp;gt;
        &amp;lt;/service&amp;gt;

    &amp;lt;/application&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;11-2. API 서버 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;API 서버가 FCM 백엔드로 메시지 전송을 요청하려면, API 서버에도 인증 정보가 필요하다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;845&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqHEez/btsGG8h2og1/NLcIvLVsWvRYhX3q6s1xWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqHEez/btsGG8h2og1/NLcIvLVsWvRYhX3q6s1xWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqHEez/btsGG8h2og1/NLcIvLVsWvRYhX3q6s1xWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqHEez%2FbtsGG8h2og1%2FNLcIvLVsWvRYhX3q6s1xWk%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;704&quot; height=&quot;378&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;845&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;b&gt;&quot;service_account.json&quot;&lt;/b&gt; 으로 변경하고, &lt;b&gt;DailyQ의 서버 폴더(daily-q-server)로 옮긴다.&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;서버를 시작하면, 인덱스 페이지(http://127.0.0.1:5000)에서 녹색 점과 &quot;Firebase initialized&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;12. 테마 (Theme)&lt;/b&gt;&lt;/h2&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;스타일 (Style)&lt;/b&gt; &lt;/span&gt;: 속성의 묶음. 개별 뷰의 색, 크기 등 뷰의 속성을 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 반복적으로 지정하는 속성들을 하나로 묶어 뷰의 &quot;style&quot; 속성을 재사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일은 일반적으로 values 폴더의 styles.xml 파일이나 themes.xml 파일에 선언한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713502929506&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;style name=&quot;MyButton&quot;&amp;gt;
    &amp;lt;item name=&quot;android:backgroundTint&quot;&amp;gt;@color/green&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;android:textColor&quot;&amp;gt;@color/red&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;app:cornerRadius&quot;&amp;gt;24dp&amp;lt;/item&amp;gt;
&amp;lt;/style&amp;gt;

......

&amp;lt;com.google.android.material.button.MaterialButton
    style=&quot;@style/MyButton&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;12.0.1 스타일 상속&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일은 클래스처럼 다른 스타일을 상속받아 확장할 수 있다. &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;parent 속성&lt;/b&gt;&lt;/span&gt;을 사용하거나 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;점(.) 표기법&lt;/b&gt;&lt;/span&gt;을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713503175186&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;style name=&quot;MySquareButton&quot; parent=&quot;MyButton&quot;&amp;gt;
    ...
&amp;lt;/style&amp;gt;

&amp;lt;style name=&quot;MyButton.Square&quot;&amp;gt;
    ...
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;12-1. &lt;span style=&quot;color: #1a5490;&quot;&gt;테마&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;style&amp;gt; 태그를 사용해 정의한다는 점은 같지만, 용도가 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테마는 뷰가 참조하는 리소스를 정의하기 때문에, &lt;b&gt;&quot;뷰 계층&quot;&lt;/b&gt; 혹은 &lt;b&gt;&quot;앱 전체&quot;&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;AndroidManifest.xml 파일에서 &amp;lt;application&amp;gt; 태그의 theme 속성으로 앱 전체에 적용되도록 하거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;activity&amp;gt; 태그의 theme 속성으로 특정 액티비티의 테마를 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1713503527937&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;style name=&quot;MyTheme&quot; parent=&quot;Theme.MaterialComponents.DayNight&quot;&amp;gt;
    &amp;lt;item name=&quot;materialButtonStyle&quot;&amp;gt;@style/MyButton&amp;lt;/item&amp;gt;
&amp;lt;/style&amp;gt;

&amp;lt;style name=&quot;MyButton&quot; parent=&quot;Widget.MaterialComponents.Button&quot;&amp;gt;
    &amp;lt;item name=&quot;android:backgroundTint&quot;&amp;gt;@color/green&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;android:textColor&quot;&amp;gt;@color/red&amp;lt;/item&amp;gt;
    &amp;lt;item name=&quot;app:cornerRadius&quot;&amp;gt;24dp&amp;lt;/item&amp;gt;
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;12.1.1 테마 속성의 참조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;X&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;ML에서&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;물음표 (?)&lt;/b&gt;&lt;/span&gt;를 사용하면, 테마에 대해 정의된 리소스를 참조할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;?attr/colorPrimary&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;?colorPrimary&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법을 사용하면, 테마에 따라 UI가 자동으로 변경되도록 만들 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1713504162178&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;style name=&quot;Theme.Red&quot; parent=&quot;Theme.DailyQ&quot;&amp;gt;
    &amp;lt;item name=&quot;colorPrimary&quot;&amp;gt;@color/red&amp;lt;/item&amp;gt;
&amp;lt;/style&amp;gt;

&amp;lt;style name=&quot;Theme.Blue&quot; parent=&quot;Theme.DailyQ&quot;&amp;gt;
    &amp;lt;item name=&quot;colorPrimary&quot;&amp;gt;@color/blue&amp;lt;/item&amp;gt;
&amp;lt;/style&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트의 테마가 Theme.Red면 @color/red가 사용되고, Theme.Blue면 @color/blue가 사용될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1713504256543&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;com.google.android.material.button.MaterialButton
    android:backgroundTint=&quot;?colorPrimary&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;wrap_content&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;12.1.2 색과&lt;span style=&quot;color: #1a5490;&quot;&gt; 슬롯(Slot)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Material 테마에서는 색이 사용되는 위치나 종류에 따라 슬롯들로 분류되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;머터리얼 색상 슬롯.png&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cglVl5/btsGMhTMgqD/7kDSeNL9U4n3cqgNb88Zi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cglVl5/btsGMhTMgqD/7kDSeNL9U4n3cqgNb88Zi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cglVl5/btsGMhTMgqD/7kDSeNL9U4n3cqgNb88Zi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcglVl5%2FbtsGMhTMgqD%2F7kDSeNL9U4n3cqgNb88Zi0%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;500&quot; height=&quot;452&quot; data-filename=&quot;머터리얼 색상 슬롯.png&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;962&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: #409d00;&quot;&gt;&lt;b&gt;colorPrimary, colorSecondary&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: #409d00;&quot;&gt;&lt;b&gt;colorPrimaryVariant, colorSecondaryVariant&lt;/b&gt; &lt;/span&gt;: 브랜드 컬러의 변형으로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;앱바와 상태바처럼 브랜드 컬러와 연속되면서 구분&lt;/b&gt;&lt;/u&gt;이 되어야 할 때 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;colorSurface&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: #409d00;&quot;&gt;&lt;b&gt;android:colorBackground&lt;/b&gt;&lt;/span&gt; : 다른 슬롯들과 다르게, Material 테마가 아니라 안드로이드에 정의된 속성이다. 컴포넌트의 배경색으로 사용된다.&lt;/p&gt;
&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: #409d00;&quot;&gt;colorOnPrimary, colorOnSecondary,&lt;/span&gt; &lt;span style=&quot;color: #409d00;&quot;&gt;colorOnSurface, colorOnBackground&lt;/span&gt;&lt;/b&gt; : 이름에서 알 수 있듯이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;colorPrimary 등의 색상등이 사용됐을때, &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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 colors.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;&lt;a href=&quot;https://www.color-blindness.com/color-name-hue/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.color-blindness.com/color-name-hue/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chir.ag/projects/name-that-color/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://chir.ag/projects/name-that-color/&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;12.1.3 &lt;span style=&quot;color: #1a5490;&quot;&gt;타이포그래피 (typography)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림처럼 사용 목적에 따라 분류하고, 모든 화면에서 일관된 스타일을 사용하면 정돈된 느낌을 줄 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lmTvF/btsGMC4CbbN/SJnnvMSaLHkUHimckq9XtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lmTvF/btsGMC4CbbN/SJnnvMSaLHkUHimckq9XtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lmTvF/btsGMC4CbbN/SJnnvMSaLHkUHimckq9XtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlmTvF%2FbtsGMC4CbbN%2FSJnnvMSaLHkUHimckq9XtK%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;602&quot; height=&quot;585&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;739&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;뷰의 &quot;style 속성&quot;&lt;/b&gt;은 크기, 배치, 색 등 다른 종류의 속성 묶음을 변경할 때에도 사용되기 때문에 타이포그래피만 스타일 속성으로 지정해 사용하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;TextView, EditText, Button&lt;/b&gt; 등에는 타이포그래피 관련 속성들의 스타일을 지정하는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&quot;textAppearance&quot; 속성&lt;/b&gt;&lt;/span&gt;이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1713505149107&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;com.google.android.material.textview.MaterialTextView
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    style=&quot;@style/Answer&quot;
    android:textAppearance=&quot;?textAppearanceBody1&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;12-2. 테마 선택 기능&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Primary color (주조색)&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: #1a5490;&quot;&gt;&lt;b&gt;Secondary color (보조색)&lt;/b&gt; &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;테마에 맞춤 속성을 추가하려면, 먼저 속성의 이름과 포맷을 attrs.xml 파일에 정의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 테마의 속성으로 변경할 colors.xml의 리소스 이름들을 속성으로 정의했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;format은 컬러 리소스를 참조하기 위한 &lt;b&gt;&quot;reference&quot;&lt;/b&gt; 와 색의 헥스 코드 문자열을 직접 입력하기 위한 &lt;b&gt;&quot;color&quot;&lt;/b&gt; 이다.&lt;/p&gt;
&lt;pre id=&quot;code_1713510325749&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;resources&amp;gt;
    &amp;lt;attr name=&quot;colorUnderCard&quot; format=&quot;reference|color&quot; /&amp;gt;
    &amp;lt;attr name=&quot;colorFollowButton&quot; format=&quot;reference|color&quot; /&amp;gt;
    &amp;lt;attr name=&quot;colorUnfollowButton&quot; format=&quot;reference|color&quot; /&amp;gt;
&amp;lt;/resources&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;13. 그래들과 배포&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;빌드 (Build)&lt;/b&gt;&lt;/span&gt; : 소스 파일 -&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;빌드의 각 단계를 매번 사람이 수동으로 하는 것은 번거럽고 어려우니, 이 과정들을 자동화한 &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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;그래들(Gradle)&lt;/b&gt;&lt;/span&gt;이다. 유연성과 성능에 초점을 맞춘 오픈소스 빌드 자동화 시스템이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그루비(Groovy)나 코틀린 DSL(Domain-Specific-Language)을 사용해, 빌드 스크립트를 작성해 사용하기 쉽고, 프로젝트에 따라 유연하게 확장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 빌드 중간 단계의 산출물을 재사용하거나, 작업을 병렬로 실행할 수 있어서 빌드 속도가 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.1.1 &lt;span style=&quot;color: #1b711d;&quot;&gt;settings.gradle&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래들은 하나의 프로젝트에 &lt;b&gt;&quot;모바일 앱 모듈&quot;, &quot;Wear 앱 모듈&quot;, &quot;라이브러리 모듈&quot;, &quot;서버 모듈&quot;&lt;/b&gt; 등 여러 서브 프로젝트를 포함하는 멀티 프로젝트 구조를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 스튜디오 범블비(Android Gradle Plugin 7.1.0) 버전부터는 &lt;u&gt;&lt;b&gt;플러그인과 의존성 해결을 위한 저장소 설정&lt;/b&gt;&lt;/u&gt;도 settings.gradle에 기술한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.1.2 &lt;span style=&quot;color: #1b711d;&quot;&gt;프로젝트의 build.gradle&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;plugins 블록에, 사용할 플러그인의 아이디와 버전을 명시하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;settings.gradle의 pluginManagement 의 repositories 블록에 있는 저장소에서 차례대로 찾는다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.1.3 &lt;span style=&quot;color: #1b711d;&quot;&gt;모듈의 build.gradle&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인의 설정과 모듈의 의존성을 기술한다. plugins 블록에 적용할 플러그인을 기술하고, android 블록에서 안드로이드 플러그인을 사용하도록 설정한다. 마지막으로 모듈의 의존성을 dependencies 블록에 기술한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성은 settings.gradle의 dependencyResolutionManagement 블록의 저장소에서 차례대로 찾아서 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.1.4 gradle-wrapper.properties&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래들은 이런 문제를 피하고 동일한 빌드 환경을 만들기 위해, &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;그래들 래퍼(Gradle Wrapper)&lt;/b&gt;&lt;/span&gt;를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래들 래퍼는 프로젝트와 함께 배포되고, gradle-wrapper.properties 파일의 설정에 따라 자동으로 필요한 버전의 그래들을 설치하고, 빌드 스크립트를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.1.5 gradle.properties&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 옵션이나, 병렬 모드 같은 그래들 실행 옵션을 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.1.6 local.properties&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;13-2. 그래들 태스크&lt;/b&gt;&lt;/h3&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;pre id=&quot;code_1713511683466&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task(&quot;clean&quot;) {
    delete(rootProject.buildDir)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ Run anything 창에 &quot;gradle tasks&quot; 라고 입력하면, 간단한 설명과 함께 전체 태스크 목록을 볼 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;13-3. 안드로이드 플러그인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 빌드는 안드로이드 그래들 플러그인에 의해서 처리되고, android 블록에서 빌드 설정을 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1713511885150&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android {
    namespace = &quot;online.dailyq&quot;
    compileSdk = 34

    defaultConfig {
        applicationId = &quot;online.dailyq&quot;
        minSdk = 29
        targetSdk = 34
        versionCode = 1
        versionName = &quot;1.0&quot;

        testInstrumentationRunner = &quot;androidx.test.runner.AndroidJUnitRunner&quot;
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile(&quot;proguard-android-optimize.txt&quot;),
                &quot;proguard-rules.pro&quot;
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = &quot;1.8&quot;
    }
    buildFeatures {
        viewBinding = true
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;compileSdkVersion&lt;/b&gt; : 어떤 버전의 SDK를 사용해 컴파일할지 지정한다. 현재 버전이나 이전 버전에 포함되는 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;buildToolsVersion&lt;/b&gt; : 사용할 빌드 도구의 버전을 지정한다. 생략하면 안드로이드 플러그인의 기본 버전을 사용한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;13.3.1&lt;/span&gt; defaultConfig 블록&lt;/b&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 빌드 변형에 적용되는 값. AndroidManifest.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;1) applicationId&lt;/b&gt; : 고유한 안드로이드 앱 아이디. AndroidManifest.xml의 &amp;lt;manifest&amp;gt; 태그의 package 속성을 덮어쓴다.&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;2) minSdkVersion&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) targetSdkVersion&lt;/b&gt; : 개발자가 만들면서 의도한 안드로이드 버전. 구글 플레이 스토어에서는 주기적으로 특정 버전 이하는 앱 업데이트를 할 수 없게 강제하기 때문에, targetSdkVersion을 최신 버전으로 유지하는 것이 좋다.&lt;/p&gt;
&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) versionCode&lt;/b&gt; : 앱의 버전을 나타내는 &quot;자연수&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5) versionName&lt;/b&gt; : 사용자에게 표시될 버전.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;주(Major) 버전.부(Minor) 버전.수정(Patch) 버전&quot; 같이 3부분으로 나뉘지만 특별하게 정해진 규칙은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;13.3.2&lt;/span&gt; 빌드 타입 (Build Types)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 타입은 buildTypes 블록에서 구성할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAM43o/btsGNsHaZee/Q7FMLA40MI6IVK3aa08OE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAM43o/btsGNsHaZee/Q7FMLA40MI6IVK3aa08OE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAM43o/btsGNsHaZee/Q7FMLA40MI6IVK3aa08OE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAM43o%2FbtsGNsHaZee%2FQ7FMLA40MI6IVK3aa08OE0%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;356&quot; height=&quot;153&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;debug나 release 블록에서는 같은 제품 버전에 대해 디버깅, 난독화, 서명 등의 변형을 구성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본으로 생성된 release 블록에서 isMinifyEnabled 항목을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;난독화(Obfuscation)와 축소(Code Shrinking) 기능&lt;/b&gt;&lt;/span&gt;을 사용할지 여부를 지정하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.3.3 난독화와 코드 축소&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바나 코틀린 코드를 컴파일하면, 가상 머신에서 실행되는 바이트 코드가 만들어진다. 이 바이트 코드는 쉽게 디컴파일(decomplie)되고, 원래의 소스 코드와 유사한 코드를 얻을 수 있어, 보안에 취약할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 코드를 인위적으로 변경하여 읽기 어렵게 만드는 것을 &lt;b&gt;&quot;난독화&quot;&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;&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;이런 규칙을 proguard-rules.pro 파일에 기술하거나, 별도의 파일로 만들어(my-rules.pro) proguardFiles 속성에 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;13.3.4 서명&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※&amp;nbsp; gradle 파일이 groovy 언어에서 kotlin으로 강제되었으므로, 코드도 수정한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713514720182&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val keystorePropertiesFile = rootProject.file(&quot;keystore.properties&quot;)
val keystoreProperties = Properties().apply {
    load(FileInputStream(keystorePropertiesFile))
}

android {
    //...

    signingConfigs {
        register(&quot;release&quot;) {
            storeFile = file(keystoreProperties.getProperty(&quot;storeFile&quot;))
            storePassword = keystoreProperties.getProperty(&quot;storePassword&quot;)
            keyAlias = keystoreProperties.getProperty(&quot;keyAlias&quot;)
            keyPassword = keystoreProperties.getProperty(&quot;keyPassword&quot;)
        }
    }

    //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.3.5 &lt;span style=&quot;color: #1a5490;&quot;&gt;제품 버전 (Product flavors)&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;EX) 무료 버전, 유료 버전, 구글 플레이 스토어 버전, 원스토어 버전, 또는 기기의 아키텍처에 따라(arm 아키텍처) 다른 APK를 만들고 싶을 때 아래 코드 같이 productFlavors 블록에서 제품 변형을 위한 설정을 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1713515071606&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android {
    ...
    
    buildTypes {
       ...
    }

    flavorDimensions(&quot;environment&quot;)

    productFlavors {
        register(&quot;dev&quot;) { // 개발 버전
            dimension = &quot;environment&quot;
            applicationId = &quot;online.dailyq.dev&quot;
            versionNameSuffix = &quot;-dev&quot;
        }
        register(&quot;prov&quot;) { // 출시 버전
            dimension = &quot;environment&quot;
        }
    }

    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.3.6 &lt;span style=&quot;color: #1a5490;&quot;&gt;버전 차원 (Flavor dimension)&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;그래들이 빌드할 때 버전 차원들의 조합으로 최종 빌드 변형을 만들도록 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) tier와 environment 차원을 갖는다면 &lt;b&gt;&quot;빌드 타입 x 버전 차원(tier) x 버전 차원(environment)&quot;&lt;/b&gt;의 빌드 변형을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.3.7 &lt;span style=&quot;color: #1a5490;&quot;&gt;소스 세트 (Source set)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 앱을 만들기 위해서는 코틀린 소스 코드, 이미지나 문자열 등의 리소스, AndroidManifest.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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13.3.5 에서 만든 dev 제품 버전은 applicationId를 &quot;online.dailyq.dev&quot;로 만들었다. 그런데 파이어베이스는 applicationId로 클라이언트를 식별하기 때문에, dev 제품 버전을 빌드하면 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 applicationId를 online.dailyq.dev 로 한 새 파이어베이스 프로젝트를 만들고, 생성한 google-services.json을 dev 폴더를 만들어서 넣으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 dev 버전의 앱과 prod 버전의 앱이 한 기기에 함께 설치했을 때 구분할 수 있도록 dev 소스 세트에 strings.xml 파일을 추가해 앱 이름을 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;13.3.8 &lt;span style=&quot;color: #1a5490;&quot;&gt;매니페스트 플레이스홀더&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 기능의 활성화 여부를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;AndroidManifest.xml의 &amp;lt;meta-data&amp;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 style=&quot;color: #333333; text-align: start;&quot;&gt;AndroidManifest.xml&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: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;build.gradle에서 &lt;b&gt;manifestPlaceholders&lt;/b&gt; 속성으로 변수를 삽입할 수 있다. &quot;키-값&quot; 쌍의 형태로 사용할 변수들을 선언했다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713516630380&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android {
    flavorDimensions(&quot;environment&quot;)

    productFlavors {
        register(&quot;dev&quot;) { // 개발 버전
            dimension = &quot;environment&quot;
            applicationId = &quot;online.dailyq.dev&quot;
            versionNameSuffix = &quot;-dev&quot;
            manifestPlaceholders[&quot;enableFeature&quot;] = &quot;false&quot;
        }
        register(&quot;prov&quot;) { // 출시 버전
            dimension = &quot;environment&quot;
            manifestPlaceholders[&quot;enableFeature&quot;] = &quot;true&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;AndroidManifest.xml의 삽입될 위치에 &lt;b&gt;${키}&lt;/b&gt;의 형태로 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713516820843&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        &amp;lt;meta-data
            android:name=&quot;sdk.specialfeature&quot;
            android:value=&quot;${enableFeature}&quot; /&amp;gt;

    &amp;lt;/application&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;14. 부록 : 파이어베이스로 앱 품질 높이기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;14-1. &lt;span style=&quot;color: #1a5490;&quot;&gt;애널리틱스 (Analytics)&lt;/span&gt;&lt;/b&gt;&lt;/h3&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: #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;b&gt;※ 자동으로 수집되는 이벤트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://support.google.com/analytics/answer/9234069&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://support.google.com/analytics/answer/9234069&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713517174617&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;[GA4] 자동 수집 이벤트 - 애널리틱스 고객센터&quot; data-og-description=&quot;도움이 되었나요? 어떻게 하면 개선할 수 있을까요? 예아니요&quot; data-og-host=&quot;support.google.com&quot; data-og-source-url=&quot;https://support.google.com/analytics/answer/9234069&quot; data-og-url=&quot;https://support.google.com/analytics/answer/9234069&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://support.google.com/analytics/answer/9234069&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://support.google.com/analytics/answer/9234069&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;[GA4] 자동 수집 이벤트 - 애널리틱스 고객센터&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;support.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;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://support.google.com/analytics/answer/9268042&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://support.google.com/analytics/answer/9268042&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713517230928&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;[GA4] 사전 정의된 사용자 측정기준 - 애널리틱스 고객센터&quot; data-og-description=&quot;도움이 되었나요? 어떻게 하면 개선할 수 있을까요? 예아니요&quot; data-og-host=&quot;support.google.com&quot; data-og-source-url=&quot;https://support.google.com/analytics/answer/9268042&quot; data-og-url=&quot;https://support.google.com/analytics/answer/9268042&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://support.google.com/analytics/answer/9268042&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://support.google.com/analytics/answer/9268042&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;[GA4] 사전 정의된 사용자 측정기준 - 애널리틱스 고객센터&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;support.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;14.1.1 &lt;span style=&quot;color: #1a5490;&quot;&gt;이벤트&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;b&gt;FirebaseAnalytics 인스턴스의 logEvent() 메서드&lt;/b&gt;로 이벤트를 기록할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드의 첫 번째 인자에는&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt; &quot;이벤트 이름&quot;&lt;/b&gt;&lt;/span&gt; , 두 번째 인자에는 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;&quot;이벤트 관련 데이터&quot;&lt;/b&gt;&lt;/span&gt; 를 번들로 전달한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713791049061&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val firebaseAnalytics = Firebase.analytics
    
val bundle = Bundle()
bundle.putString(&quot;date&quot;, &quot;2024-04-22&quot;)
bundle.putString(&quot;question_type&quot;, &quot;text&quot;)

firebaseAnalytics.logEvent(&quot;show_question_details&quot;, bundle)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;14.1.2 &lt;span style=&quot;color: #1a5490;&quot;&gt;사용자 속성&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;829&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MeOku/btsGPRgSowU/KmbK6dJnf1ZOTdgX2xu5Mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MeOku/btsGPRgSowU/KmbK6dJnf1ZOTdgX2xu5Mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MeOku/btsGPRgSowU/KmbK6dJnf1ZOTdgX2xu5Mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMeOku%2FbtsGPRgSowU%2FKmbK6dJnf1ZOTdgX2xu5Mk%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;649&quot; height=&quot;398&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;829&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;파이어베이스 콘솔의 &quot;Custom Definitions&quot;&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;setUserProperty() 메서드&lt;/b&gt;로 사용자에게 값을 할당할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 사용 중인 테마를 사용자 속성으로 추가하려면 다음과 같이 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1713791332934&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val firebaseAnalytics = Firebase.analytics

firebaseAnalytics.setUserProperty(&quot;theme&quot;, &quot;dark&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;파이어베이스 프로젝트 설정에서 빅쿼리와 통합하면, 빅쿼리에서 직접 데이터를 조회할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;14-2. &lt;span style=&quot;color: #1a5490;&quot;&gt;크래시리틱스 (Crashlytics)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크래시리틱스는 원격에서 발생한 오류의 정보(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;크래시리틱스를 사용하기 위해서는 SDK와 플러그인을 프로젝트에 추가해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 프로젝트 build.gradle에서 플러그인 아이디와 버전을 추가한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713791599982&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&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(&quot;com.google.firebase.crashlytics&quot;) version &quot;2.8.1&quot; apply false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 모듈 build.gradle에 플러그인을 적용하고, 의존성을 추가한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713791669176&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    ...
    id(&quot;com.google.firebase.crashlytics&quot;)
}

dependencies {
    ...
    // 417페이지 추가
    implementation(&quot;com.google.firebase:firebase-crashlytics-ktx:17.4.0&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1895&quot; data-origin-height=&quot;795&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ugwk1/btsGPRVsSXa/DZf5kxywBrAIHFweqOuAZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ugwk1/btsGPRVsSXa/DZf5kxywBrAIHFweqOuAZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ugwk1/btsGPRVsSXa/DZf5kxywBrAIHFweqOuAZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUgwk1%2FbtsGPRVsSXa%2FDZf5kxywBrAIHFweqOuAZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;703&quot; height=&quot;295&quot; data-origin-width=&quot;1895&quot; data-origin-height=&quot;795&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;비정상 종료를 방지하기 위해 try-catch 구문 같은 방법으로 예외를 처리했지만, 오류에 대한 정보가 필요하다면 &lt;b&gt;&quot;recoredException()&quot;&lt;/b&gt; 메서드로 직접 예외를 전송해 필요한 정보를 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 전송된 오류는 &lt;b&gt;&quot;심각하지 않은 오류&quot;&lt;/b&gt; 로 분류돼, 대시보드에서 구분해서 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;14.2.1 크래시리틱스 비활성화&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;앱의 build.gradle에서 dev 버전에서는 오류 보고 기능을 비활성화하고, prod 버전에서는 오류를 보내도록 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1713792784522&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;productFlavors {
        register(&quot;dev&quot;) { // 개발 버전
            dimension = &quot;environment&quot;
            applicationId = &quot;online.dailyq.dev&quot;
            versionNameSuffix = &quot;-dev&quot;
            ext.enableCrashlytics = false // 추가된 코드
        }
        register(&quot;prov&quot;) { // 출시 버전
            dimension = &quot;environment&quot;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;15. 부록 C. 오픈소스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github에서 안드로이드 프로젝트를 주제별로 선별하고 정리한 유명한 목록 2군데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/JStumpp/awesome-android&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/JStumpp/awesome-android&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713792937955&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 - JStumpp/awesome-android: A curated list of awesome Android packages and resources.&quot; data-og-description=&quot;A curated list of awesome Android packages and resources. - JStumpp/awesome-android&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/JStumpp/awesome-android&quot; data-og-url=&quot;https://github.com/JStumpp/awesome-android&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nxO8w/hyVS3UiWC3/GpNk2NVlnmA8SP8mSMiGBK/img.png?width=1200&amp;amp;height=600&amp;amp;face=956_131_1051_236&quot;&gt;&lt;a href=&quot;https://github.com/JStumpp/awesome-android&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/JStumpp/awesome-android&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nxO8w/hyVS3UiWC3/GpNk2NVlnmA8SP8mSMiGBK/img.png?width=1200&amp;amp;height=600&amp;amp;face=956_131_1051_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;GitHub - JStumpp/awesome-android: A curated list of awesome Android packages and resources.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A curated list of awesome Android packages and resources. - JStumpp/awesome-android&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;a href=&quot;https://github.com/wasabeef/awesome-android-ui&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/wasabeef/awesome-android-ui&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713793005643&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 - wasabeef/awesome-android-ui: A curated list of awesome Android UI/UX libraries&quot; data-og-description=&quot;A curated list of awesome Android UI/UX libraries. Contribute to wasabeef/awesome-android-ui development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/wasabeef/awesome-android-ui&quot; data-og-url=&quot;https://github.com/wasabeef/awesome-android-ui&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dSQEqt/hyVSTxo0Vx/seFnqnz430sEds66LG3vIk/img.png?width=1096&amp;amp;height=570&amp;amp;face=0_0_1096_570&quot;&gt;&lt;a href=&quot;https://github.com/wasabeef/awesome-android-ui&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/wasabeef/awesome-android-ui&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dSQEqt/hyVSTxo0Vx/seFnqnz430sEds66LG3vIk/img.png?width=1096&amp;amp;height=570&amp;amp;face=0_0_1096_570');&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 - wasabeef/awesome-android-ui: A curated list of awesome Android UI/UX libraries&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A curated list of awesome Android UI/UX libraries. Contribute to wasabeef/awesome-android-ui development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드/도서 내용 정리</category>
      <category>안드로이드</category>
      <category>코틀린</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/262</guid>
      <comments>https://sweetburble.tistory.com/262#entry262comment</comments>
      <pubDate>Tue, 9 Apr 2024 23:35:31 +0900</pubDate>
    </item>
    <item>
      <title>기사식당 제육볶음 - [1분요리 뚝딱이형 식]</title>
      <link>https://sweetburble.tistory.com/261</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;레시피가 2~3인분 기준이기 때문에 1인분에 맞게 모든 재료의 양을 절반 정도로 적게 넣었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외 변경점 없음!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;제육볶음 중에서는 가장 어려웠다...&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: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;밥 두 그릇 정도는 거뜬히 해치울 정도&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;edited_20240408_기사식당제육볶음_1분요리뚝딱이형.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LNApi/btsGtnNC5Cl/fC0KzRsl2UWzkiLA0Lh9y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LNApi/btsGtnNC5Cl/fC0KzRsl2UWzkiLA0Lh9y0/img.png&quot; data-alt=&quot;기사식당 제육볶음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LNApi/btsGtnNC5Cl/fC0KzRsl2UWzkiLA0Lh9y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLNApi%2FbtsGtnNC5Cl%2FfC0KzRsl2UWzkiLA0Lh9y0%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;500&quot; height=&quot;377&quot; data-filename=&quot;edited_20240408_기사식당제육볶음_1분요리뚝딱이형.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;377&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=spjIN3vPVVY&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=spjIN3vPVVY&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=spjIN3vPVVY&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bW44Y6/hyVMSdavD7/lsosBPgd2Q9qzVpa1DH9ik/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;최다 요청 메뉴 '제육볶음' 레시피 진한 양념이 매력적인 기사식당 스타일로&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/spjIN3vPVVY&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/261</guid>
      <comments>https://sweetburble.tistory.com/261#entry261comment</comments>
      <pubDate>Mon, 8 Apr 2024 14:55:10 +0900</pubDate>
    </item>
    <item>
      <title>간단한 코틀린 문법</title>
      <link>https://sweetburble.tistory.com/260</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. object와 companion object&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-1. object&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코틀린에서&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;object&quot;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;키워드는, 주로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;싱글톤(Singleton) 객체&lt;/b&gt;&lt;/span&gt;를 생성하는 데 사용된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;생성자는 사용 불가하고, 프로퍼티, 메서드, 초기화 블록은 사용 가능&lt;/b&gt;&lt;/u&gt;하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;다른 클래스나 / 인터페이스를 상속&lt;/b&gt;&lt;/u&gt;받을 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;➡️ object의&amp;nbsp; 3가지 주요 용도&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) 싱글톤 객체 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- object 키워드를 사용하면 싱글톤 객체를 쉽게 만들 수 있다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 해당 object는 전역 변수처럼 사용할 수 있으며, 한 번만 생성된다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;다른&amp;nbsp;클래스에서&amp;nbsp;&lt;b&gt;`ClassName.objectName`&amp;nbsp;&lt;/b&gt;형태로 접근할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1723445796177&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;object Logger {
    fun log(message: String) {
        println(&quot;LOG: $message&quot;)
    }
}

// 다른 클래스에서 사용
Logger.log(&quot;Hello, World!&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;2) 익명 객체 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- object 키워드를 사용하면 익명 객체를 쉽게 만들 수 있다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 인터페이스 구현 시 유용하게 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1723445796178&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;val clickListener = object : View.OnClickListener {
    override fun onClick(v: View) {
        // 클릭 처리 로직
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3) 동반 객체(Companion Object)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 클래스 내부에 &quot;companion object&quot; 블록을 정의하면 클래스 레벨의 메소드와 프로퍼티를 구현할 수 있다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 자바의 정적 메소드 및 필드와 유사한 기능을 제공한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 1장에서 자세히 설명&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;object 키워드를 통해 코틀린 개발자는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;보다 간결하고 명시적인 방식으로 객체 지향 프로그래밍&lt;/b&gt;을 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-2. companion object&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의&lt;span style=&quot;color: #f89009;&quot;&gt; &lt;b&gt;정적 메소드와 필드(static method and fields)&lt;/b&gt;&lt;/span&gt;와 유사한 기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;companion object&lt;/b&gt;&lt;/span&gt;는 클래스에 연관된 메소드와 프로퍼티를 정의할 수 있게 해주는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;companion object는 &lt;u&gt;&lt;b&gt;클래스 당 하나만 생성&lt;/b&gt;&lt;/u&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;➡️ companion object의 4가지 특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1. 클래스 레벨의 메소드와 프로퍼티&lt;/b&gt;&lt;/span&gt; : companion object는 클래스 레벨에서 메소드와 프로퍼티를 정의할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 인스턴스를 생성하지 않고도 클래스 관련 기능을 사용할 수 있다. &lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;2. 싱글톤 패턴&lt;/b&gt;&lt;/span&gt; : companion object는 싱글톤 패턴을 구현하는 데 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'하나의 companion object 인스턴스가 전역적으로 접근 가능하기 때문에 싱글톤 패턴을 구현하기 좋다. &lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3. 이름 지정 가능&lt;/b&gt;&lt;/span&gt; : companion object에는 이름을 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름을 지정하면 &lt;b&gt;&quot;ClassName.companionName.methodName()&quot;&lt;/b&gt; 과 같은 형태로 접근할 수 있다. &lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;4. 확장 함수 사용 가능&lt;/b&gt;&lt;/span&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_1712383164597&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 {
            return MyClass()
        }

        val DEFAULT_VALUE = 42
    }

    fun doSomething() {
        println(&quot;Doing something...&quot;)
    }
}

fun main() {
    // companion object에 정의된 메소드와 프로퍼티 사용
    val instance = MyClass.create()
    instance.doSomething()
    println(MyClass.DEFAULT_VALUE)

    // 확장 함수로 정의된 메소드 사용
    MyClass.myExtensionMethod()
}

fun MyClass.Companion.myExtensionMethod() {
    println(&quot;This is an extension function of the companion object.&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위의 예제에서 MyClass의 companion object에는 &lt;b&gt;&quot;create()&quot;&lt;/b&gt; 메소드와 &lt;b&gt;&quot;DEFAULT_VALUE&quot;&lt;/b&gt; 프로퍼티가 정의되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 MyClass의 인스턴스를 생성하거나, DEFAULT_VALUE에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;&quot;myExtensionMethod()&quot; &lt;/b&gt;는 companion object에 대한 확장 함수로 정의되어 있다. &lt;br /&gt;&lt;br /&gt;이처럼 companion object는 &lt;span style=&quot;color: #006dd7;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. null 처리&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1. !! 연산자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;!! 연산자&lt;/b&gt;&lt;/span&gt;는&lt;b&gt; '강제 언랩' 또는 '안전하지 않은 언랩'&lt;/b&gt;으로 불리며, &lt;u&gt;&lt;b&gt;null이 아닌 값으로 변환&lt;/b&gt;&lt;/u&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;color: #409d00;&quot;&gt;&lt;b&gt;➡️ !! 연산자의&amp;nbsp; 2가지 주요 특징&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. null 가능성이 있는 값 언랩하기 &lt;/b&gt;&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 가능성이 있는 값을 null이 아닌 값으로 변환할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712384843270&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var str: String? = &quot;Hello&quot;
println(str!!.length) // 5
str = null
println(str!!.length) // NullPointerException 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. Nullable 변수를 사용할 때&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수가 null이 아닌 것을 확신한다면 -&amp;gt; !! 연산자를 사용하여, null이 아님을 확실히 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712384890287&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val length = str?.length ?: 0 // str이 null이면 0을 반환
val actualLength = str!!.length // str이 null이면 NullPointerException 발생&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1) !! 연산자를 사용했는데, 값이 null이라면 -&amp;gt; &quot;NullPointerException&quot; 이 발생할 수 있다. &lt;/b&gt;&lt;/span&gt;&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;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;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2-2. 안전 호출 연산자 ?.&lt;/span&gt;&lt;/b&gt;&lt;/h3&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2-3. 엘비스 연산자 ?:&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null인 경우, 대체 값을 반환할 수 있다. &lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #409d00;&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;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ 또는 let, run (객체 초기화), apply 등의 함수 &lt;/span&gt;: null 체크 후, 안전하게 코드를 실행할 수 있다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 코틀린의 Scope function 5가지&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;105&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sitYW/btsI05qXnFV/QC01EtdukARFPOeTPkhkpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sitYW/btsI05qXnFV/QC01EtdukARFPOeTPkhkpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sitYW/btsI05qXnFV/QC01EtdukARFPOeTPkhkpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsitYW%2FbtsI05qXnFV%2FQC01EtdukARFPOeTPkhkpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;435&quot; height=&quot;130&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;105&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;&gt;&lt;b&gt;1) let 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 &lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;let 함수&lt;/span&gt;&lt;/b&gt;는&amp;nbsp;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;it (생략 불가능)&lt;/b&gt;&lt;/span&gt;을 수신 객체로 사용하고, 확장함수로 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &quot;&lt;b&gt;람다 식의 마지막 행&quot;&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;➡️ let의 4가지 주요 특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) null 안전성 높이기&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- let 함수는 null 값인 경우, 코드 블록을 실행하지 않는다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 이를 통해 &lt;b&gt;NullPointerException&lt;/b&gt; 을 방지할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712721714714&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val str: String? = &quot;Hello, World!&quot;
str?.let {
    println(it.length) // 'it'은 str 변수의 값을 가리킴
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&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;&amp;nbsp;&amp;nbsp;- let 함수 내부에서 사용되는 변수는, let 블록 내부로 범위가 제한된다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 이를 통해 불필요한 변수 선언을 줄일 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712721756835&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val length = str?.let {
    val tmpLength = it.length
    println(&quot;Length: $tmpLength&quot;)
    tmpLength
} ?: 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3) 코드 흐름 제어&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- let 함수 내부에서 반환값을 지정하면, 해당 값이 let 함수의 반환값이 된다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 이를 통해 복잡한 코드 흐름을 간단하게 표현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712721840638&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val result = str?.let {
    if (it.length &amp;gt; 5) {
        &quot;Long string&quot;
    } else {
        &quot;Short string&quot;
    }
} ?: &quot;Null string&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;4) 함수형 프로그래밍 스타일 지원&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- let 함수는 함수형 프로그래밍 스타일의 코드 작성을 지원한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 람다식과 함께 사용하면, 가독성 높은 코드를 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712721892279&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val list = listOf(1, 2, 3, 4, 5)
list.filter { it &amp;gt; 2 }
     .map { it * it }
     .let { println(it) } // [9, 16, 25]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) apply 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; apply 함수&lt;/b&gt;&lt;/span&gt;는 수신 객체(receiver) 자신이 반환되기 때문에, &lt;u&gt;&lt;b&gt;객체 초기화와 구성&lt;/b&gt;&lt;/u&gt;에 유용하게 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 수신 객체를 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;this(생략 가능)&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;➡️ apply의 4가지 주요 특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) 객체 구성 및 초기화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;- 객체의 프로퍼티를 설정하는 등의 작업을 수행할 수 있다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 이를 통해 객체 생성 및 초기화 코드를 간단하게 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712722663551&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val person = Person(&quot;John Doe&quot;, 30).apply {
    address = &quot;123 Main St&quot;
    phoneNumber = &quot;555-1234&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&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;&amp;nbsp;&amp;nbsp;- apply 함수는 객체 자신을 반환하므로, 메서드 체이닝(method chaining)을 지원한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 이를 통해 가독성 높은 코드를 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712722702216&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val textView = TextView(context).apply {
    text = &quot;Hello, World!&quot;
    textSize = 16f
    setOnClickListener { /* 클릭 처리 */ }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3) null 안전성 향상&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- apply 함수는 null 값을 허용하므로, null 안전성을 높일 수 있다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- let 함수와 함께 사용하면, null 처리 로직을 간단하게 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712722748766&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val str: String? = &quot;Hello&quot;
str?.let { println(it) }
     ?.apply { println(&quot;Length: $length&quot;) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;4) 구현 로직 숨기기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- apply 함수를 사용하면, 객체 구성 및 초기화 로직을 메서드 호출 내부에 숨길 수 있다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 이를 통해 코드의 가독성을 높일 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712722816424&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun createTextView(context: Context): TextView {
    return TextView(context).apply {
        text = &quot;Hello, World!&quot;
        textSize = 16f
        setOnClickListener { /* 클릭 처리 */ }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) run 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코틀린의&lt;span&gt; &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;run 함수&lt;/span&gt;&lt;/b&gt;는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;this(생략 가능)를&lt;/b&gt;&lt;/span&gt;&amp;nbsp;수신 객체로 사용하고, 확장함수로 호출한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 &quot;&lt;b&gt;람다 식의 마지막 행&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;을 반환한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) also 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코틀린의&lt;span&gt; &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;also 함수&lt;/span&gt;&lt;/b&gt;는&amp;nbsp;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;it (생략 불가능)&lt;/b&gt;&lt;/span&gt;을 수신 객체로 사용하고, 확장함수로 호출한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 &quot;&lt;b&gt;수신객체&quot;&lt;/b&gt;&lt;span&gt; 를&lt;/span&gt;&amp;nbsp;반환한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5) with 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코틀린의&lt;span&gt; &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;with 함수&lt;/span&gt;&lt;/b&gt;는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;this(생략 가능)를&lt;/b&gt;&lt;/span&gt; 수신 객체로 사용하고, with 함수의 인자 형태로 호출한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 &quot;&lt;b&gt;람다 식의 마지막 행&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;을 반환한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 지연 초기화 &lt;b&gt;(Lazy&amp;nbsp;Initialization)&lt;/b&gt; &lt;/b&gt;&lt;/h2&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-1. val과 by lazy&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;val(불변 객체)&lt;/b&gt;&lt;/span&gt; 뒤에&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&amp;nbsp;&quot;by&amp;nbsp;lazy&amp;nbsp;{}&quot;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;구문을 붙이면, &lt;span style=&quot;color: #1a5490;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ val + by lazy의&amp;nbsp; 3가지 주요 특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) 지연 초기화를 통한 메모리 절약&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 변수가 실제로 사용되기 전까지는 초기화를 하지 않음으로써, 불필요한 메모리 사용을 줄일 수 있다. &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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;2) Singleton 패턴 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 지연 초기화를 사용하면, Singleton 패턴을 간단하게 구현할 수 있다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 처음 사용될 때 단 한 번만 객체가 생성되도록 보장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3) 불변성 유지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 지연 초기화를 사용하면, 변수의 불변성을 쉽게 유지할 수 있다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 초기화 시점을 지연시켜, 변수에 대한 접근을 통제할 수 있다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712758140270&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyClass {
    // 지연 초기화된 프로퍼티
    val lazyProperty: String by lazy {
        println(&quot;Initializing lazyProperty&quot;)
        &quot;Hello, World!&quot;
    }

    fun uselazyProperty() {
        println(lazyProperty) // 처음 호출될 때만 &quot;Initializing lazyProperty&quot; 출력
        println(lazyProperty) // 두 번째 호출 시 캐싱된 값 반환
    }
}

fun main() {
    val myClass = MyClass()
    myClass.uselazyProperty() // 처음 호출 시 초기화, 이후 호출 시 캐싱된 값 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;위 예제에서 &lt;b&gt;&quot;lazyProperty&quot;&lt;/b&gt;는 &lt;b&gt;&quot;by lazy { }&quot;&lt;/b&gt; 블록을 통해 지연 초기화된다.&lt;b&gt; &quot;uselazyProperty()&quot;&lt;/b&gt; 함수를 처음 호출하면 &lt;b&gt;&quot;Initializing lazyProperty&quot;&lt;/b&gt; 메시지가 출력되고, 이후에는 캐싱된 값이 반환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-2. var과 lateinit&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 var(변수) 앞에 lateinit을 붙여서 선언해야, 지연 초기화를 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, &lt;u&gt;&lt;b&gt;Primitive(Int 등등) 타입은 lateinit을 사용할 수 없다 -&amp;gt; Integer 같은 래퍼 타입으로 대체&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. [안드로이드 문법] lifecycleScope와 launch&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린과 안드로이드에서,&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; &quot;lifecycleScope&quot;&lt;/b&gt; &lt;/span&gt;와 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;lifecycleScope.launch {}&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) lifecycleScope&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- lifecycleScope는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;안드로이드 컴포넌트(Activity, Fragment 등)의 생명주기&lt;/b&gt;&lt;/span&gt;와 연결된 CoroutineScope이다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 이 CoroutineScope는 컴포넌트의 생명주기와 연결되어, 컴포넌트가 소멸되면 자동으로 해당 CoroutineScope도 취소된다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 따라서 lifecycleScope 를 사용하면, 메모리 누수와 같은 문제를 방지할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712759085726&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_my)

       lifecycleScope.launch {
           // 코루틴 작업 수행
           doSomeWork()
       }
   }

   private suspend fun doSomeWork() {
       // 코루틴 작업 구현
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;2) lifecycleScope.launch {}&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- lifecycleScope.launch {} 는, lifecycleScope의 launch 함수를 사용하여 &lt;u&gt;&lt;b&gt;코루틴을 실행&lt;/b&gt;&lt;/u&gt;하는 것이다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- launch 함수는 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;CoroutineBuilder&lt;/b&gt;&lt;/span&gt;로, 새로운 코루틴을 시작하는 역할을 한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- lifecycleScope.launch {} 를 사용하면, &lt;u&gt;&lt;b&gt;코루틴 작업을 컴포넌트의 생명주기와 연결할 수 있다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712759192735&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyFragment : Fragment() {
   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       super.onViewCreated(view, savedInstanceState)

       lifecycleScope.launch {
           // 코루틴 작업 수행
           fetchData()
       }
   }

   private suspend fun fetchData() {
       // 데이터 가져오기 작업 구현
       val data = getData()
       // UI 업데이트 등의 작업 수행
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;이처럼 lifecycleScope 는 안드로이드 애플리케이션 개발에서 코루틴을 사용할 때 매우 유용한 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트의 생명주기에 맞춰 코루틴 작업을 실행하고 관리할 수 있어, 메모리 누수 등의 문제를 효과적으로 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. vararg&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;vararg&quot;&lt;/b&gt;&lt;/span&gt;는 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;가변 인자 리스트(variable-length argument list)&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;➡️ vararg의&amp;nbsp; 3가지 주요 특징&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) 가변 개수의 인자 받기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- vararg 매개변수를 사용하면, 메소드나 생성자에 가변 개수의 인자를 전달할 수 있다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 인자의 개수는, 0개부터 ~ 임의의 개수까지 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1712822910159&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun sum(vararg numbers: Int): Int {
    return numbers.sum()
}

// 사용 예시
println(sum(1, 2, 3)) // 출력: 6
println(sum()) // 출력: 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&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;&amp;nbsp;&amp;nbsp;- vararg 매개변수에는 개별 인자뿐만 아니라, 배열도 전달할 수 있다. &lt;br /&gt;&amp;nbsp; -&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;배열을 vararg 매개변수에 전달할 때는,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;스프레드 연산자(`*`)&lt;/b&gt;&lt;/span&gt;를 사용해야 한다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 스프레드 연산자는 배열의 각 요소를, 개별 인자로써 전달한다.&lt;/p&gt;
&lt;pre id=&quot;code_1712822971813&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = intArrayOf(1, 2, 3)
println(sum(*numbers)) // 출력: 6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3) 가변 인자 뒤에 다른 인자 사용 불가!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 따라서 vararg 매개변수는, 메소드나 생성자의 마지막 매개변수로 사용해야 한다. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- 그 뒤에 다른 매개변수를 추가할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1712823049795&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun printAll(prefix: String, vararg items: String) {
    for (item in items) {
        println(&quot;$prefix $item&quot;)
    }
}

printAll(&quot;Hello&quot;, &quot;John&quot;, &quot;Jane&quot;, &quot;Tom&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;vararg 키워드를 사용하면, &lt;span style=&quot;color: #006dd7;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘과 코딩 테스트/코틀린 문법</category>
      <category>코틀린</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/260</guid>
      <comments>https://sweetburble.tistory.com/260#entry260comment</comments>
      <pubDate>Sat, 6 Apr 2024 15:02:37 +0900</pubDate>
    </item>
    <item>
      <title>호텔식 스크램블 에그 - [백종원 식]</title>
      <link>https://sweetburble.tistory.com/259</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240405_호텔식스크램블에그_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q6EoT/btsGpK21y51/o03LzkAF446Tk7qYIEFOT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q6EoT/btsGpK21y51/o03LzkAF446Tk7qYIEFOT0/img.png&quot; data-alt=&quot;호텔식 스크램블 에그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q6EoT/btsGpK21y51/o03LzkAF446Tk7qYIEFOT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq6EoT%2FbtsGpK21y51%2Fo03LzkAF446Tk7qYIEFOT0%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;500&quot; height=&quot;342&quot; data-filename=&quot;edited_20240405_호텔식스크램블에그_백종원.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;호텔식 스크램블 에그&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=aazMtxj2UZk&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=aazMtxj2UZk&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=aazMtxj2UZk&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cQBk7q/hyVJ55zc3X/222eZhndFWYzmdknW0Ygok/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=986_130_1224_390&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;실패 없는 스크램블 에그 만들기 꿀팁&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/aazMtxj2UZk&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=eS20JztxOj0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=eS20JztxOj0&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=eS20JztxOj0&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/Ci6ZC/hyVJUpqM6Z/UJo2lByKePDCf0N6wrJRYK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;정말 쉬운 백종원의 생크림 스크램블에그 / freshcream scrambled egg recipe #백종원 계란요리 #scrambled egg&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/eS20JztxOj0&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/259</guid>
      <comments>https://sweetburble.tistory.com/259#entry259comment</comments>
      <pubDate>Fri, 5 Apr 2024 15:18:32 +0900</pubDate>
    </item>
    <item>
      <title>크림 파스타 - [하루한끼 식]</title>
      <link>https://sweetburble.tistory.com/258</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 요리할 때 쓰고 남은 생크림을 사용했기 때문에 250ml 보다는 적게 넣었고,&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;전체적으로 좋았다.&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;edited_20240403_크림파스타_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p4Rar/btsGiBF6fcK/1NMkrkhiqJrKxU7zbnNZBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p4Rar/btsGiBF6fcK/1NMkrkhiqJrKxU7zbnNZBk/img.png&quot; data-alt=&quot;크림 파스타&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p4Rar/btsGiBF6fcK/1NMkrkhiqJrKxU7zbnNZBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp4Rar%2FbtsGiBF6fcK%2F1NMkrkhiqJrKxU7zbnNZBk%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;500&quot; height=&quot;375&quot; data-filename=&quot;edited_20240403_크림파스타_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;크림 파스타&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=V2VHNxwi3kI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/G8dUo/hyVJWzYe3I/r70zoOVDKCPCVaX1ucZFa0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;꼬소한 크림파스타&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/V2VHNxwi3kI&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/258</guid>
      <comments>https://sweetburble.tistory.com/258#entry258comment</comments>
      <pubDate>Wed, 3 Apr 2024 10:32:05 +0900</pubDate>
    </item>
    <item>
      <title>내용 정리 Part. 1 - [SNS 앱을 만들면서 배우는 안드로이드 클라이언트 개발]</title>
      <link>https://sweetburble.tistory.com/257</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 4월 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;스토리보드&lt;/b&gt;&lt;/span&gt; : &lt;span style=&quot;color: #1a5490;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 안드로이드 개발을 준비하자&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구글이 제공하는 웹 &amp;middot; 안드로이드 &amp;middot; iOS 용 아이콘을 받을 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fonts.google.com/icons&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fonts.google.com/icons&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712067966370&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;Material Symbols and Icons - Google Fonts&quot; data-og-description=&quot;Material Symbols are our newest icons consolidating over 2,500 glyphs in a single font file with a wide range of design variants.&quot; data-og-host=&quot;fonts.google.com&quot; data-og-source-url=&quot;https://fonts.google.com/icons&quot; data-og-url=&quot;https://fonts.google.com/icons&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eLhHF/hyVJVAQTjh/dokhVxWee9YbYFkKRfnz3k/img.jpg?width=1136&amp;amp;height=648&amp;amp;face=0_0_1136_648,https://scrap.kakaocdn.net/dn/iWkpy/hyVGD2UWnd/C2k47UoZg15t9YE29xKDQk/img.jpg?width=1136&amp;amp;height=648&amp;amp;face=0_0_1136_648&quot;&gt;&lt;a href=&quot;https://fonts.google.com/icons&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://fonts.google.com/icons&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eLhHF/hyVJVAQTjh/dokhVxWee9YbYFkKRfnz3k/img.jpg?width=1136&amp;amp;height=648&amp;amp;face=0_0_1136_648,https://scrap.kakaocdn.net/dn/iWkpy/hyVGD2UWnd/C2k47UoZg15t9YE29xKDQk/img.jpg?width=1136&amp;amp;height=648&amp;amp;face=0_0_1136_648');&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 Symbols and Icons - Google Fonts&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Material Symbols are our newest icons consolidating over 2,500 glyphs in a single font file with a wide range of design variants.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;fonts.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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 안드로이드 스튜디오에서는 File &amp;gt; New &amp;gt; Vector Asset 에서 프로젝트에 바로 추가할 수 있다.&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: #1a5490;&quot;&gt;&lt;b&gt;mipmap&lt;/b&gt;&lt;/span&gt;도 이미지를 담을 수 있지만, 오직 &lt;u&gt;&lt;b&gt;앱 아이콘&lt;/b&gt;&lt;/u&gt;을 위한 폴더이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mipmap은 drawable과 달리, 실제 화면에 보이는 크기를 고려해서 결정된다.&lt;/p&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) 런처에서 한 줄에 5개의 아이콘이 표시될 때와 3개의 큰 아이콘이 표시될 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 해상도가 아닌 실제 보이는 크기에 맞는 이미지를 선택해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적응형 아이콘 : 안드로이드 8(API 26) 부터 통일성을 위해,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이콘의 전경(foreground)와 배경(background)를 분리해서 아이콘을 구성하도록 지정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 코드에서는 mipmap/ic_launcher.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;※ Material 컴포넌트를 사용하려면, build.gradle에 의존성을 추가해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EX) implementation(&quot;com.google.android.material:material:1.6.1&quot;)&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1. BottomNavigationView 의 속성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) app:backgroundTint&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;2) app:itemIconSize&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) app:itemIconTint&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;4) app:itemTextColor&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;5) app:labelVisibilityMode&lt;/b&gt; : label의 표시 방법을 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - auto : 기본값, 아이템 개수가 3개 이하일 때는, 모든 label을 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - labeled : 모든 아이템의 label을 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - selected : 선택된 아이템의 label 만 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - unlabeled : 모든 label을 표시하지 않는다.&lt;/p&gt;
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 서버와 함께 Hello, World!&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-1. HTTP 메서드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET, POST, PUT, DELETE 와 같은 HTTP 메서드를 설명할 때 &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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;멱등성 (Idempotent)&lt;/b&gt; &lt;/span&gt;: 연산을 여러 번 적용하더라도, 결과값이 달라지지 않는 성질&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 특정 리소스에 대한 DELETE 요청을 한 번 한 것과 열 번 한 것은 서버 입장에서는 같다. -&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;안전성 (Safe)&lt;/b&gt;&lt;/span&gt; : 리소스의 변경 여부로 판단한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) GET 요청은 열 번을 해도 서버의 리소스가 변경되지 않는다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 DELETE는 한 번만 호출해도 리소스가 변경(삭제)되므로 안전한 메서드가 아닌 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-2. 자주 사용하는 HTTP 헤더&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 헤더는 요청 헤더, 응답 헤더, 일반 헤더, Entity 헤더로 나뉜다.&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;1) 요청 헤더&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Referer&lt;/b&gt;&lt;/span&gt; : 요청을 보낸 페이지의 주소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A 페이지에서 링크를 클릭해서 B 페이지로 이동했다면,&amp;nbsp; B 페이지를 요청할 때 A 페이지가 Referer 헤더로 전달된다.&lt;/p&gt;
&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: #1a5490;&quot;&gt;&lt;b&gt;If-Modified-Since&lt;/b&gt;&lt;/span&gt; : 서버의 리소스가 특정 시간 이후로 변경된 경우에만, Entity를 보내달라고 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Entity 가 생략됐을 때, 클라이언트의 캐시를 사용한다.&lt;/p&gt;
&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: #1a5490;&quot;&gt;&lt;b&gt;If-None-Match&lt;/b&gt;&lt;/span&gt; : 서버 리소스의 ETag 가 다른 경우에 요청을 처리한다. 요청에 포함되는 ETag와 서버의 ETag가 다른 경우에 PUT 요청은 리소스를 변경하고, GET 요청은 Entity를 포함한 응답을 내려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 응답 헤더&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Etag&lt;/b&gt;&lt;/span&gt; : 리소스의 특정 버전을 식별할 수 있게 하는 Entity 태그이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 응답으로 받은 ETag 값을 조건부 요청 헤더로 전달해 사용한다.&lt;/p&gt;
&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: #1a5490;&quot;&gt;&lt;b&gt;Last-Modified&lt;/b&gt;&lt;/span&gt; : 리소스의 마지막 수정 시각이다. Etag와 마찬가지로 조건부 요청 헤더로 전달해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) 일반 헤더&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Cache-control&lt;/b&gt; &lt;/span&gt;: 서버, 클라이언트, 프록시 사이의 캐시 정책을 제어한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전송된 리소스가 유효하다고 판단되는 시간을 &quot;max-age&quot; 지시자로 전달해 캐시하도록 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public, private 지시자는 응답이 공유될 수 있는 지 여부를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 구글의 로고 이미지를 요청했다면, public 이겠지만 &amp;lt;=&amp;gt; 내 구글 드라이브 파일 목록을 요청했다면 private 일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-2. HTTP 상태 코드&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RnZeW/btsGodE9HEe/h4qABp7U2TZix2WI2xzKVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RnZeW/btsGodE9HEe/h4qABp7U2TZix2WI2xzKVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RnZeW/btsGodE9HEe/h4qABp7U2TZix2WI2xzKVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRnZeW%2FbtsGodE9HEe%2Fh4qABp7U2TZix2WI2xzKVK%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;401&quot; height=&quot;305&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;373&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;200 OK&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;201 Created&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;202 Accepted&lt;/b&gt; : 요청이 접수되었으나, 처리가 완료되지 않았음. 배치(Batch) 처리 같은 곳에서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 요청 접수 후, 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;b&gt;204 No Content&lt;/b&gt; : 서버가 요청을 성공적으로 수행했지만, 응답 페이로드 본문에 보낼 데이터는 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 웹 문서 편집기에서 Save 버튼 -&amp;gt; save 버튼을 눌러도 같은 화면을 유지해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 대신 결과 내용이 없어도 204 메시지(2xx) 만으로도 성공을 인식할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;423&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwoB1v/btsGqqW5mlA/bupb1mPgBkKiAT2MireGZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwoB1v/btsGqqW5mlA/bupb1mPgBkKiAT2MireGZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwoB1v/btsGqqW5mlA/bupb1mPgBkKiAT2MireGZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwoB1v%2FbtsGqqW5mlA%2Fbupb1mPgBkKiAT2MireGZK%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;451&quot; height=&quot;350&quot; data-origin-width=&quot;545&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;리다이렉션(Redirection) 이란?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 브라우저는 3xx 응답의 결과에 Location 헤더가 있으면, Location이 지정한 위치로 자동 이동(리다이렉트)&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: #409d00;&quot;&gt;&lt;b&gt;1) 영구 리다이렉션&lt;/b&gt;&lt;/span&gt; : 특정 리소스의 URI가 영구적으로 이동, 원래의 URL을 사용 X, 검색 엔진 등에서도 변경 인지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;301 Moved Permanently&lt;/b&gt; : 리다이렉트시 요청 메서드가 GET으로 변하고, 요청 본문이 제거될 수 있다.&lt;/p&gt;
&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;308 Permanent Redirect&lt;/b&gt; : 301과 기능은 같지만, 리다이렉트시 요청 메서드와 요청 본문은 유지된다.&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: #409d00;&quot;&gt;&lt;b&gt;2) 일시적인 리다이렉션&lt;/b&gt;&lt;/span&gt; : 리소스의 URI가 일시적으로 변경, 따라서 검색 엔진 등에서 URL을 변경하면 안됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;302 Found &amp;amp; 303 See other&lt;/b&gt; : 리다이렉트시 요청 메서드가 GET으로 변하고, 요청 본문이 제거될 수 있다.&lt;/p&gt;
&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;307 Temporary Redirect&lt;/b&gt; : 302와 기능은 같지만, 리다이렉트시 요청 메서드와 요청 본문은 유지된다.&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: #1a5490;&quot;&gt;&lt;b&gt;PRG : Post / Redirect / Get&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) POST로 주문 후에 웹 브라우저를 새로고침하면, 중복 주문이 되지 않도록 GET 메서드로 리다이렉트하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실적으로 이미 많은 애플리케이션 라이브러리들이 302를 기본값으로 사용하고 있다.&lt;/p&gt;
&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;300 Multiple Choices&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;304 Not Modified&lt;/b&gt; : 캐시를 목적으로 사용,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에게 리소스가 수정되지 않았음을 알려준다. -&amp;gt; 그러면 클라이언트는 로컬 PC에 저장된 캐시를 재사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HcyJp/btsGon8EJnb/400BK4WxJKD77gFYowSkdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HcyJp/btsGon8EJnb/400BK4WxJKD77gFYowSkdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HcyJp/btsGon8EJnb/400BK4WxJKD77gFYowSkdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHcyJp%2FbtsGon8EJnb%2F400BK4WxJKD77gFYowSkdK%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;201&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;279&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;400 Bad Request&lt;/b&gt; : 클라이언트가 잘못된 요청을 해서, 서버가 요청을 처리할 수 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 요청 파라미터가 잘못되거나, 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;401 Unauthorized&lt;/b&gt; : 클라이언트가 해당 리소스에 대한 인증이 필요함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 인증(Authentication, 본인이 누구인지 확인)되지 않음. 응답에 WWW-Authenticate 헤더와 함께 인증 방법을 설명&lt;/p&gt;
&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: #1a5490;&quot;&gt;&lt;b&gt;※ 인가 (Authorization) : 권한 부여 (ADMIN 권한같이, 특정 리소스에 접근할 수 있는 권한, 인증이 있어야 인가가 있다!)&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;u&gt;&lt;b&gt;그래서 401번은 상태 코드 이름과 내용이 달라서 헷갈릴수도 있다 ㅠㅠ&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;403 Forbidden&lt;/b&gt; : 서버가 요청을 이해했지만 승인을 거부함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) ADMIN 등급이 아닌 사용자가 로그인은 했지만, ADMIN 등급의 리소스에 접근하는 경우&lt;/p&gt;
&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;404 Not Found&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buUJLN/btsGocsH7cr/evRHbPULvJgVboPOKEM6e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buUJLN/btsGocsH7cr/evRHbPULvJgVboPOKEM6e0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buUJLN/btsGocsH7cr/evRHbPULvJgVboPOKEM6e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuUJLN%2FbtsGocsH7cr%2FevRHbPULvJgVboPOKEM6e0%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;594&quot; height=&quot;163&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;201&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;500 Internal Server Error&lt;/b&gt; : 서버 내부 문제로 오류 발생!&amp;nbsp; 애매하면 500 오류를 띄운다.&lt;/p&gt;
&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;503 Service Unavailable&lt;/b&gt; : 서비스 이용 불가. 서버가 일시적인 과부하 또는 예정된 작업으로 잠시 요청을 처리할 수 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retry-After 헤더 필드로 얼마 뒤에 복구되는지 보낼 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-3. 3장의 새로운 레이아웃&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) Material 로 시작하는 뷰&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MaterialCardView, MaterialTextView, MaterialButton 들은 모두 Material 디자인을 위한 기능들이 추가된 뷰이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호환성을 위해서 androidx 패키지의 CardView, AppCompatTextView, AppCompatButton 을 상속받았다.&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;2) LinearLayout의 showDividers&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;하지만 속성 중에 showDividers를 사용하면, LinearLayout의 Child 개수에 따라 자동으로 표시된다.&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;3) TextAppearance&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextView는 글씨 크기, 스타일, 줄 높이 등 여러가지 속성을 갖는다. 이런 속성들을 매번 따로 지정하는 것은 번거롭고 비효율적이기 때문에, 이런 속성들을 묶어 스타일을 만들어 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextView의 경우 모든 뷰에서 사용할 수 있는 style 속성에서는 &quot;위젯의 컬러, 크기&quot; 등을 설정하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;textAppearance 속성에서는 &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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 프래그먼트와 프래그먼트의 뷰는 다른 생애주기를 가지기 때문에, onCreateView에서 생성하고, &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;onDestroyView에서 직접 해제하는 것이 액티비티에서 뷰 바인딩을 사용할 때와 다른점이다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-4. HttpURLConnection으로 API 호출하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드에서 REST API와 연동할 때 사용하는 가장 인기있는 라이브러리는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Retrofit&lt;/b&gt; &lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retrofit 라이브러리를 사용하면, 비동기 요청, 캐싱, 응답 처리, 인증 등 다양한 기능을 쉽게 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 자바의 기본 패키지인 HttpURLConnection을 사용해보자.&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;[코드 3-7] TodayFragment.kt&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 메인 스레드(UI 스레드)에서 API 호출같이 오래 걸리는 IO 작업을 하는 경우, 사용자는 앱이 정지되었다고 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 피하기 위해 안드로이드의 메인 스레드에서 API를 호출하면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;android.os.NetworkOnMainThreadException&quot;&lt;/b&gt; &lt;/span&gt;이 발생한다. &lt;u&gt;&lt;b&gt;따라서 네트워크 작업은 항상 백그라운드 스레드에서 진행해야 한다!&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 메인 스레드에서 네트워크 작업을 할 수 없는 것과 마찬가지로, &lt;u&gt;&lt;b&gt;백그라운드 스레드에서는 UI를 변경할 수 없다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&quot;activity.runOnUiThread&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;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Gson 으로 JSON을 다뤄보자&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-1. Gson 사용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Date는 시간을 담기 위한 객체이다. Date를 원하는 형식의 문자열로 만들기 위해 DateFormat을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DateFormat의 getDateInstance() 메서드는 스타일과 로케일을 인자로 받아 그에 맞는 DateFormat 객체를 생성한다.&lt;/p&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;Locale.KOREA&quot; 일 때, 스타일에 따라 다음의 형식을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FULL : 2024년 4월 1일 수요일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LONG : 2024년 4월 1일 (수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MEDIUM : 2024.4.1.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SHORT : 24.4.1.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1712305634833&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;            val gson = Gson()
            val dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.KOREA)
            val helloWorld = gson.fromJson(body, HelloWorld::class.java)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-2. JSON 속성 네이밍 정책&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gson 객체는 생성 후 변경할 수 없으므로, 생성하기 전에 &quot;GsonBuilder&quot; 를 사용해 원하는 설정을 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 JSON 속성의 이름과 클래스의 속성 이름이 같은 것으로 매핑한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 일반적으로 코틀린이나 자바에서는 카멜 케이스(Camel case)를 사용하고, JSON에서는 스네이크 케이스(Snake case)를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GsonBuilder를 생성하고, &lt;b&gt;setFieldNamingPolicy() 메서드&lt;/b&gt;로 -&amp;gt; 원하는 네이밍 정책을 전달한다.&lt;/p&gt;
&lt;pre id=&quot;code_1712306021050&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val gson = GsonBuilder()
	.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
	.create()&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;IDENTITIY&lt;/b&gt; : 모델과 JSON이 정확히 같은 이름을 사용한다.&lt;/p&gt;
&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;LOWER_CASE_WITH_UNDERSCORES&lt;/b&gt; : JSON 필드를 소문자로 표기하고, 단어의 연결에 언더바(_)를 사용한다.&lt;/p&gt;
&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;UPPER_CAMEL_CASE&lt;/b&gt; : JSON 필드의 첫 글자를 대문자로 표기한다. 단어의 연결은 변경하지 않는다.&lt;/p&gt;
&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;※ 만약 JSON 속성의 이름과 클래스 멤버의 이름이 일정한 규칙이 없다면,&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@SerializedName&amp;nbsp; 어노테이션을 사용해, 명시적으로 지정할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-3. 커스텀 직렬화와 역직렬화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gson에서 JSON을 -&amp;gt; 사용자가 직접 만든 &lt;b&gt;&quot;클래스&quot; 나 &quot;열거형(enum)&quot;&lt;/b&gt;으로 변환하려면, 변환 작업을 할 어댑터를 GsonBuilder에 등록해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;직렬화는 JsonSerializer 인터페이스를, 역직렬화는 JsonDeserializer 인터페이스를 구현&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;size18&quot;&gt;&lt;b&gt;null 값 직렬화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 멤버 변수의 값이 null일 때, JSON 문자열로 변환하면 그 필드는 생략된 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 특별한 이유로 null인 멤버도 JSON 문자열로 직렬화해야 한다면, &lt;b&gt;GsonBuilder의 serializeNulls() 메서드&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;size18&quot;&gt;&lt;b&gt;※ 보기 좋게 출력하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gson은 효율성을 위해서, JSON의 개행이나 공백을 생략한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 읽기에는 불편하므로, 읽기 좋게 만드는 &lt;b&gt;&quot;setPrettyPrinting()&quot; 메서드&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. REST API&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;RESTful (REpresentational State Transfer) 이란?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP의 저자 중 한 명인 로이 필딩의 2000년 논문에 다음의 6가지 제약 조건을 잘 지키는 것이 RESTful 하다고 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;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;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;2) 무상태성 (stateless)&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: #409d00;&quot;&gt;&lt;b&gt;3) 캐시 (Cache)&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: #409d00;&quot;&gt;&lt;b&gt;4) 계층화된 시스템 (Layered System)&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: #409d00;&quot;&gt;&lt;b&gt;5) 주문형 코드 (Code-On-Demand)&lt;/b&gt;&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;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;6) 인터페이스 일관성 (Uniform Interface)&lt;/b&gt;&lt;/span&gt; : REST의 핵심. 아래 4가지 조건을 통해 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;b&gt;&amp;nbsp;6-1) 리소스 식별 (Identification of resources)&lt;/b&gt; : 모든 리소스는 고유한 식별자를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;b&gt;&amp;nbsp;6-2) 표현을 통한 리소스 조작&lt;/b&gt; : 리소스 자체를 전송하는 것이 아니라, 리소스의 표현을 이용해 조작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;b&gt;6-3) 자기 기술적 메시지 (Self-descriptive messages)&lt;/b&gt; : 메시지를 이해하기 위해 필요한 정보가 메시지 안에 전부 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;b&gt;&amp;nbsp;6-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;이 제약 조건들은 분산 하이퍼미디어 시스템을 위한 것으로, 이 조건들을 만족하도록 구현된 대표적인 예가 HTTP+HTML이다.&lt;/p&gt;
&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;RESTful API 와 REST API는 같은 의미로 사용되기도 하고, 다른 의미로 사용되기도 하는 비슷한 용어&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;※ 결국 REST API 스타일의 핵심은 &quot;URI를 리소스의 식별자로 사용&quot; 하는 것, &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &quot;리소스에 대한 행위(CRUD)는 HTTP 메서드를 사용&quot; 해야 한다는 것이다. REST API를 더 많이 사용한다!&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-1. Retrofit 사용법&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1712323281933&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation(&quot;androidx.lifecycle:lifecycle-runtime-ktx:2.4.1&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;lifecycle-runtime-ktx&lt;/b&gt; 는 Activity와 Fragment 의 수명 주기를 따르는 &lt;b&gt;&quot;코루틴 스코프&quot;&lt;/b&gt; 를 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스코프에서 실행된 코루틴은 Lifecycle이 DESTOYED 상태가 되면 자동으로 제거된다.&lt;/p&gt;
&lt;p data-ke-size=&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_1712323770586&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface ApiService {
    
    companion object {
        
        fun create(context: Context): ApiService {
            val gson = GsonBuilder()
                .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                .create()
            
            return Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .baseUrl(&quot;http://10.0.2.2:5000&quot;)
                .build()
                .create(ApiService::class.java)
        }
    }
    
    @GET(&quot;/v1/questions/{qid}&quot;)
    suspend fun getQuestion(@Path(&quot;qid&quot;) qid: String) : Question
}&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;@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;&lt;b&gt;@Header&lt;/b&gt; : 요청의 헤더에 값을 추가한다. EX) @Header(&quot;Accept&quot;) accept: String&lt;/p&gt;
&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;@Query&lt;/b&gt; : 쿼리에 값을 추가한다. GET과 DELETE 요청은 URI의 쿼리로 값을 전달한다.&lt;/p&gt;
&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;@Field&lt;/b&gt; : @FormUrlEncoded와 함께 사용되며 필드로 추가된다.&lt;/p&gt;
&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;@Part&lt;/b&gt; : @Multipart와 함께 사용되며 파트로 추가된다.&lt;/p&gt;
&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;@Body&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;suspend 문법&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;size18&quot;&gt;&lt;b&gt;[코드 5-5] TodayFragment.kt&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 스레드를 코루틴 스코프로 변경했다. 프래그먼트에는, &lt;b&gt;프래그먼트의 수명 주기&lt;/b&gt;에서 사용할 수 있는&lt;b&gt; lifecycle&lt;/b&gt; 스코프와, &lt;b&gt;프래그먼트 뷰&lt;/b&gt;의 수명 주기에서 사용할 수 있는&lt;b&gt; viewLifecycleOwner.lifecycleScope&lt;/b&gt; 가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lifecycleScope는 메인 스레드에 바인딩되어 있기 때문에, runOnUiThread를 제거하고, 직접 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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-2. 컨버터 팩토리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retrofit에는 JSON을 변환하는&lt;b&gt; &quot;GsonConverterFactory&quot;&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) simplexml&lt;/b&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;&lt;b&gt;2) protobuf&lt;/b&gt; : Protocol Buffer의 바이너리를 변환한다.&lt;/p&gt;
&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) scalars&lt;/b&gt; : string, int 등 기본 자료형을 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-3. HTTP 로그 출력하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retrofit에서는 OkHttp를 HTTP 클라이언트로 사용하고 있어서, 코드 몇 줄이면 훌륭한 로깅 기능을 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1712326739276&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 123페이지 추가
    implementation(&quot;com.squareup.okhttp3:logging-interceptor:4.9.0&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;size18&quot;&gt;&lt;b&gt;[ApiService.kt]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712326783724&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    companion object {

        private var INSTANCE : ApiService? = null

        // HTTP 로그 출력하기
        private fun okHttpClient() : OkHttpClient {
            val builder = OkHttpClient.Builder()
            val logging = HttpLoggingInterceptor()
            logging.level = HttpLoggingInterceptor.Level.BODY

            return builder
                .addInterceptor(logging)
                .build()
        }

        private fun create(context: Context): ApiService {
            val gson = GsonBuilder()
                .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                .registerTypeAdapter(LocalDate::class.java, LocalDateAdapter)
                .create()

            return Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addConverterFactory(LocalDateConverterFactory())
                .baseUrl(&quot;http://10.0.2.2:5000&quot;)
                .client(okHttpClient()) // 클라이언트 추가
                .build()
                .create(ApiService::class.java)
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpLoggingInterceptor는 level에 따라 출력하는 로그 내용이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NONE&lt;/b&gt; : 로그를 출력하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BASIC&lt;/b&gt; : 요청 라인과 응답 라인만 풀력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HEADERS&lt;/b&gt; : 요청 라인과 요청 헤더, 응답 라인과 응답 헤더를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BODY&lt;/b&gt; : 요청 라인, 요청 헤더, 응답 라인, 응답 헤더, 본문을 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-4. &lt;span style=&quot;color: #1a5490;&quot;&gt;타임아웃(Timeout)&lt;/span&gt; 설정하기&lt;/b&gt;&lt;/h3&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;OkHttp에서 타임아웃을 설정하는 예제를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1712380244278&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        // HTTP 로그 출력하기
        private fun okHttpClient() : OkHttpClient {
            val builder = OkHttpClient.Builder()
            val logging = HttpLoggingInterceptor()
            logging.level = HttpLoggingInterceptor.Level.BODY
            
            // 로깅 인터셉터 추가 + 타임아웃도 설정한다
            return builder
                .connectTimeout(3, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .addInterceptor(logging)
                .build()
        }&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: #409d00;&quot;&gt;&lt;b&gt;Connect Timeout&lt;/b&gt;&lt;/span&gt; : 정해진 시간 동안 서버와 TCP Handshake 과정을 완료하지 못하면 발생한다.&lt;/p&gt;
&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: #409d00;&quot;&gt;&lt;b&gt;Write Timeout&lt;/b&gt;&lt;/span&gt; : Read Timeout 과 반대로, 서버로 데이터를 보낼 때 정해진 시간을 초과하면 발생한다.&lt;/p&gt;
&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: #409d00;&quot;&gt;&lt;b&gt;Read Timeout&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-5.&amp;nbsp; 질문에 답하는 로직 추가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 질문만 가져왔던 getQuestion() 메서드에서, 답변을 조회, 등록, 수정, 삭제 할 수 있도록 메서드를 추가한다.&lt;/p&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;b&gt;DTO(Date Trasfer Object)인 Answer 데이터 클래스&lt;/b&gt;를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 새로운 메서드의 반환값을&lt;b&gt; 제네릭 클래스인 Response&amp;lt;T&amp;gt;&lt;/b&gt;로 선언하고, 그 안에 Answer를 타입 파라미터로 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 이전과 같이 Question(또는 Answer)를 직접 반환값으로 받으면, HTTP 응답 코드나 헤더와 같은 정보를 사용할 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP의 POST와 PUT 요청을 본문을 갖는데, Retrofit에서 본문을 구성하는 방법은 3가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;@FormUrlEncoded 어노테이션&lt;/b&gt;&lt;/span&gt;은 요청의 Content-Type을 application/x-www-form-urlencoded로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 메서드의 파라미터 중에 @Field 어노테이션이 붙은 것은 본문으로 만든다.&lt;/p&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;multipart/form-data&lt;/b&gt; 로 요청해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Retrofit에서는 메서드에 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;@Multipart 어노테이션&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) JSON으로 요청을 보낼 때 주로 사용하는 방법이다. 요청에 전달할 파라미터를 객체로 만들고, 이 파라미터에&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt; @Body 어노테이션&lt;/b&gt;&lt;/span&gt;을 붙이면 컨버터가 객체를 직렬화하여, 요청의 본문(body)로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-6. 답변 작성, 수정, 삭제 기능&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[코드 5-29] 까지 마치고 나면, 답을 작성할 수는 있지만 바로 작성한 답을 볼 수 없고,&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;왜냐하면 기존에는 TodayFragment의 onViewCreated()에서 답을 가져와서 표시하게 만들었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WriteActivity에서 답을 쓰고 돌아오는 과정에서는 onViewCreated()가 다시 호출되지 않기 때문이다.&lt;/p&gt;
&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; API를 호출해 UI를 갱신하는 코드를&lt;b&gt; &quot;onResume()&quot;&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;onResume()은 답이 변경되지 않았을 때에도 자주 호출 되기 때문에, 복잡한 데이터나 사용자가 많은 서비스의 경우에는 서버에 많은 부하&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;이번에는 답을 쓰거나 변경하면, TodayFragment에 알려줘서 UI를 갱신하도록 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) AndroidX의 Activity와 Fragment에서는 &quot;startActivityForResult()&quot; 사용을 권장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;regisiterForActivityResult&quot;&lt;/b&gt; 로 콜백을 등록하면,&lt;b&gt; &quot;ActivityResultLauncher&quot;&lt;/b&gt; 를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ActivityResultLauncher 의&lt;b&gt; launch() 메서드&lt;/b&gt;로 Intent를 전달해 WriteActivity를 시작하도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; startActivity() 를 &lt;b&gt;&quot;startForResult.launch()&quot;&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;2) WriteActivity에서 완료 버튼을 터치하면 -&amp;gt; &quot;글쓰기 API&quot; 를 호출 -&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 성공 응답을 받으면, MainAcitivity로 결과를 전달하기 위해 &lt;b&gt;&quot;setResult()&quot;&lt;/b&gt; 호출 -&amp;gt; &lt;b&gt;finish() 메서드&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) 그러면 WriteActivity의 결과가, TodayFragment의 &lt;b&gt;&quot;ActivityResultCallback&quot;&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;4) 답을 가져와 레이아웃에 구성하는 코드가 중복되므로, &lt;b&gt;setupAnswer() 메서드&lt;/b&gt;로 추출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1712389251128&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -&amp;gt;
        if (result.resultCode == Activity.RESULT_OK) {
            lifecycleScope.launch {
                setupAnswer()
            }
        }
    }

...

        binding.writeButton.setOnClickListener {
            startForResult.launch(Intent(requireContext(),
                WriteActivity::class.java).apply {
                    putExtra(WriteActivity.EXTRA_QID, question!!.id)
                    putExtra(WriteActivity.EXTRA_MODE, WriteActivity.Mode.WRITE)
            })
        }&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. JWT로 사용자를 인증하자.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth는 여러 환경에서 사용할 수 있도록 다양한 권한 부여 타입이 정의되어 있다.&lt;/p&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;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) 자원 소유자 (Resouce Owner)&lt;/b&gt;&lt;/span&gt;&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;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;2) 자원 서버 (Resource Server)&lt;/b&gt;&lt;/span&gt;&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;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3) 클라이언트 (Client)&lt;/b&gt;&lt;/span&gt;&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;size18&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;4) 권한 부여 서버, 인가 서버 (Authorization Server)&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&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;&lt;b&gt;※ 이미 구글 포토 서버가 있는데, 별도의 인가 서버를 사용하는 이유는 구글의 다른 서비스에 대한 인가도 함께 관리하기 때문이다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth 2.0에는 4가지 권한 부여 방법이 있다. 그 중 2가지만 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;1)&amp;nbsp; Authorization Code Grant&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Authorization-code-grant-flow-for-OAuth.png&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uFwNZ/btsGreJvpI5/SM5BuQbKT9qtdqAcZyap40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uFwNZ/btsGreJvpI5/SM5BuQbKT9qtdqAcZyap40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uFwNZ/btsGreJvpI5/SM5BuQbKT9qtdqAcZyap40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuFwNZ%2FbtsGreJvpI5%2FSM5BuQbKT9qtdqAcZyap40%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;605&quot; height=&quot;389&quot; data-filename=&quot;Authorization-code-grant-flow-for-OAuth.png&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;389&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;Authorization Code Grant는 클라이언트(앱)에 직접 사용자 정보를 입력하는 경우에는 별로 맞지 않다.&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;b&gt;&quot;Resource Owner Password Credentials Grant&quot;&lt;/b&gt; 를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;2) Resource Owner Password Credentials Grant&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;oauth-resource-owner-diagram.png&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kS857/btsGvurzVIu/FIQ7G7TfTStsQyER3RqSyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kS857/btsGvurzVIu/FIQ7G7TfTStsQyER3RqSyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kS857/btsGvurzVIu/FIQ7G7TfTStsQyER3RqSyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkS857%2FbtsGvurzVIu%2FFIQ7G7TfTStsQyER3RqSyK%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;550&quot; height=&quot;393&quot; data-filename=&quot;oauth-resource-owner-diagram.png&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;570&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;액세스 토큰 (Access token)&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: #1a5490;&quot;&gt;&lt;b&gt;리프레시 토큰 (Refresh token)&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;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; 그러나 자원에 접근할 때 사용할 수 없다. 유효 시간이 상대적으로 길다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6-1. 소셜 로그인과 OpenID Connect&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소셜 로그인은 보통 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;OpenID Connect 프로토콜&lt;/b&gt;&lt;/span&gt;을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OpenID Connect&lt;/b&gt; : OAuth 2.0 를 확장해 구현된 인증 프로토콜로, OAuth의 프로토콜에 따라 권한을 요청할 때 스코프에&amp;nbsp; openid를 함께 보내면 액세스 토큰과 함께 JWT(JSON Web Token)으로 만들어진 ID 토큰을 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 OAuth 2.0이 어떤 값들을 주고 받는지 보고 싶다면, &lt;b&gt;구글의 OAuth 2.0 Playground&lt;/b&gt;가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/oauthplayground/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.google.com/oauthplayground/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712578765698&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;OAuth 2.0 Playground&quot; data-og-description=&quot;Request Body Manual entry Enter the data that will be added to the body of the request: File You may choose to send a file as part of the request. When both a file and manual content are provided both will be sent using a multipart request. You may send fi&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/oauthplayground/&quot; data-og-url=&quot;https://developers.google.com/oauthplayground/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developers.google.com/oauthplayground/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/oauthplayground/&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;OAuth 2.0 Playground&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Request Body Manual entry Enter the data that will be added to the body of the request: File You may choose to send a file as part of the request. When both a file and manual content are provided both will be sent using a multipart request. You may send fi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6-2. JWT 란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;JSON 문자열을 토큰으로 사용할 수 있도록, Base64로 인코딩하고, 위조와 변조를 막기 위해 서명을 한 것&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT는 &lt;b&gt;&quot;토큰에 대한 설명을 담고 있는 &lt;span style=&quot;color: #1a5490;&quot;&gt;헤더&lt;/span&gt;&quot;&amp;nbsp; + &quot;전달할 데이터를 담은 &lt;span style=&quot;color: #1a5490;&quot;&gt;페이로드&lt;/span&gt;&quot; + &quot;토큰의 검증을 위한 정보를 담은&lt;span style=&quot;color: #1a5490;&quot;&gt; 서명&lt;/span&gt;&quot;&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;페이로드에는 JWT를 통해 전달하려는 &lt;b&gt;클레임(Claim)&lt;/b&gt;들이 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;클레임&lt;/b&gt;&lt;/span&gt; : 주제에 대한 하나의 정보. &quot;이름-값&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;iss (Issuer)&lt;/b&gt; : JWT 토큰 발급자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sub (Subject)&lt;/b&gt; : 토큰의 제목&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;aud (Audience)&lt;/b&gt; : 토큰 수신자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;exp (Expiration time)&lt;/b&gt; : 토큰 만료 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;nbf (Not before)&lt;/b&gt; : 토큰의 활성화 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;iat (Issued At)&lt;/b&gt; : 토큰이 발급된 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;jti (JWT ID)&lt;/b&gt; : 토큰 식별자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6-3.&lt;span style=&quot;color: #1a5490;&quot;&gt; Base64&lt;/span&gt; 란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;바이너리 데이터를 64개의 문자만 사용해서, 문자열로 변환하는 인코딩 방식&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 안전하게 텍스트로 전달하기 위해서, Base64로 인코딩을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 이미지 같은 바이너리 데이터를 -&amp;gt; CSS나 이메일 같은 텍스트 문서에 포함하려는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드에서는 android.util.Base64에서 Base64 인코딩/디코딩을 지원한다.&lt;/p&gt;
&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;encode() / decode()&lt;/b&gt; : 인코딩/디코딩 결과를&lt;b&gt; &quot;바이트&quot;&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;Base64.encodeToString()&lt;/b&gt; : 인코딩 결과를 &lt;b&gt;&quot;문자열&quot;&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;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;1) DEFAULT&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: #409d00;&quot;&gt;&lt;b&gt;2) NO_WRAP&lt;/b&gt; &lt;/span&gt;: DEFAULT로 인코딩하면, 76자마다 나뉘고, 모든 라인에 개행 문자가 추가된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NO_WRAP은 DEFAULT의 이 기능을 사용하지 않고 -&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;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;3) CRLF&lt;/b&gt; &lt;/span&gt;: 인코딩 시 개행 문자로 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;LF(Line Feed, \n, 0x0A)&lt;/b&gt;&lt;/span&gt; 대신&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;CRLF(Carriage Return + Line Feed, \r\n, 0x0D0A)&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: #409d00;&quot;&gt;&lt;b&gt;4) NO_PADDING&lt;/b&gt; &lt;/span&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;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;5) URL_SAFE&lt;/b&gt;&lt;/span&gt; : 테이블에서 62번, 63번 문자인 + 와 / 는 URL에서 이미 정해진 의미가 있어서, 원래는 인코딩된 문자열을 URL에 사용할 수 없다. 따라서 62번을 - , 63번을 _ 로 사용해서 인코딩과 디코딩에서 모두 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. Retrofit과 Coil&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;비동기와 병렬 처리 / 스토리지와 메모리 캐시 / 플레이스홀더(Placeholder) / 애니메이션 효과 / 포맷 지원 등등&lt;/b&gt; 너무나도 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;구글의 Glide나, 페이스북의 Fresco&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: #1a5490;&quot;&gt;&lt;b&gt;Coil&lt;/b&gt;&lt;/span&gt;을 사용해보자. Coil은 &lt;b&gt;&quot;Coroutine Image Loader&quot;&lt;/b&gt; 의 약자로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코루틴 기반으로 만들어졌고, 코틀린 익스텐션으로 ImageView를 확장하여 편의를 제공&lt;/b&gt;한다.&lt;/p&gt;</description>
      <category>안드로이드/도서 내용 정리</category>
      <category>안드로이드</category>
      <category>코틀린</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/257</guid>
      <comments>https://sweetburble.tistory.com/257#entry257comment</comments>
      <pubDate>Wed, 3 Apr 2024 00:02:17 +0900</pubDate>
    </item>
    <item>
      <title>팽이버섯 튀김 덮밥 - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/256</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;그래도 나쁘지 않은 경험이었던 것 같다.&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;edited_20240401_팽이버섯튀김덮밥_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EXKpD/btsGfzatld8/MGTlPcuE1KyRkgMy37LZ8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EXKpD/btsGfzatld8/MGTlPcuE1KyRkgMy37LZ8K/img.png&quot; data-alt=&quot;팽이버섯 튀김 덮밥&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EXKpD/btsGfzatld8/MGTlPcuE1KyRkgMy37LZ8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEXKpD%2FbtsGfzatld8%2FMGTlPcuE1KyRkgMy37LZ8K%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;500&quot; height=&quot;393&quot; data-filename=&quot;edited_20240401_팽이버섯튀김덮밥_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;393&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;팽이버섯 튀김 덮밥&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=5JD_iTp64vA&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bVUoXc/hyVJ4Lc1wT/0GmJS9xFe53eA2cWC2DEcK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/5JD_iTp64vA&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/256</guid>
      <comments>https://sweetburble.tistory.com/256#entry256comment</comments>
      <pubDate>Mon, 1 Apr 2024 20:06:31 +0900</pubDate>
    </item>
    <item>
      <title>내용 정리 Part.2 - [풀스택 개발이 쉬워지는 다트 &amp;amp; 플러터]</title>
      <link>https://sweetburble.tistory.com/255</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 4월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Flutter 아키텍처 이해하기&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmgyPJ/btsGcrxmwVZ/iAKg5zkwJkFqcsTGUm27d0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmgyPJ/btsGcrxmwVZ/iAKg5zkwJkFqcsTGUm27d0/img.png&quot; data-alt=&quot;플러터 계층 구조 (데스크톱 환경)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmgyPJ/btsGcrxmwVZ/iAKg5zkwJkFqcsTGUm27d0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmgyPJ%2FbtsGcrxmwVZ%2FiAKg5zkwJkFqcsTGUm27d0%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;704&quot; height=&quot;368&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;474&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Flutter Framework&lt;/b&gt; &lt;/span&gt;중 &lt;b&gt;Material과 Cupertino&lt;/b&gt;는,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter로 만든 애플리케이션이 &lt;b&gt;&quot;사용자의 눈에 보여지는 디자인&quot;&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;Material 디자인을 따르는 앱을 만들 때, 여러 위젯을 사용했다. 그래서 바로 밑에 &lt;b&gt;Widgets&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;그리고 이 위젯들은 화면에 출력되기 위해서 Rendering의 지원을 받는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통상 소프트웨어에서 이미지를 인위적으로 만들어내는 작업을 &lt;b&gt;Rendering&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;Animation과 Painting&lt;/b&gt;의 지원을 받아서, 화면에 이미지를 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 사용자의 입력인 터치, 탭, 드래그 등을 지원하는 &lt;b&gt;Gesture&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;Foundation&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;이들을 묶어서 Flutter Framework 라고 부르고, &lt;b&gt;Dart 언어&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;프레임워크가 Dart 언어를 사용해서, 사용자 인터페이스(UI)를 만드는 것을 주 목적으로 한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 목적을 고성능으로 제공하는 기반 기술이 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Flutter Engine&lt;/b&gt; &lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX 1) 화면에 인위적인 이미지를 만들어내는 &lt;b&gt;&quot;컴퓨터 그래픽&quot;&lt;/b&gt; 기능, Flutter는 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;Skia&lt;/b&gt;&lt;/span&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;EX 2) 그리고 &lt;b&gt;파일 처리, 네트워크 송수신&lt;/b&gt; 등 다양한 기술을 Flutter Engine 에서 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter Engine은 성능 상의 이유와 운영체제의 깊은 부분을 접근해야 하는 등의 이유로, &lt;b&gt;C나 C++&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;하드웨어나 운영체제에 무관하게 Flutter Framework와 Engine이 동작하도록 하는 소프트웨어가 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Flutter Embedder&lt;/b&gt;&lt;/span&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Flutter Embedder는 특정 운영체제와 하드웨어에서 최적의 성능을 제공할 수 있도록, &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;Flutter architectural overview&lt;/b&gt; : &lt;a href=&quot;https://docs.flutter.dev/resources/architectural-overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/resources/architectural-overview&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711775096661&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;Flutter architectural overview&quot; data-og-description=&quot;A high-level overview of the architecture of Flutter, including the core principles and concepts that form its design.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/resources/architectural-overview&quot; data-og-url=&quot;https://docs.flutter.dev/resources/architectural-overview&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cUQLkN/hyVGSkHmIi/8fREsaLiCF501fA7pKdJnk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/csErjJ/hyVGJ88VEd/8IwP51wC295eFJfBiPODGk/img.png?width=2913&amp;amp;height=1146&amp;amp;face=0_0_2913_1146,https://scrap.kakaocdn.net/dn/cUULam/hyVGKNHpmQ/xbAcNTcJWIb6KiSp7jJIXk/img.png?width=2013&amp;amp;height=1053&amp;amp;face=0_0_2013_1053&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/resources/architectural-overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/resources/architectural-overview&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cUQLkN/hyVGSkHmIi/8fREsaLiCF501fA7pKdJnk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/csErjJ/hyVGJ88VEd/8IwP51wC295eFJfBiPODGk/img.png?width=2913&amp;amp;height=1146&amp;amp;face=0_0_2913_1146,https://scrap.kakaocdn.net/dn/cUULam/hyVGKNHpmQ/xbAcNTcJWIb6KiSp7jJIXk/img.png?width=2013&amp;amp;height=1053&amp;amp;face=0_0_2013_1053');&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;Flutter architectural overview&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A high-level overview of the architecture of Flutter, including the core principles and concepts that form its design.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Flutter for Web 아키텍처 이해하기&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;따라서 Flutter로 웹 서비스를 개발하는 경우에는 기존과는 다른 내부 아키텍처를 갖는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBIrXa/btsGcPksAmI/Lhnq8TickcmnkfldumE9X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBIrXa/btsGcPksAmI/Lhnq8TickcmnkfldumE9X1/img.png&quot; data-alt=&quot;Flutter for Web 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBIrXa/btsGcPksAmI/Lhnq8TickcmnkfldumE9X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBIrXa%2FbtsGcPksAmI%2FLhnq8TickcmnkfldumE9X1%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;704&quot; height=&quot;271&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Flutter for Web 아키텍처&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;Dart와 Flutter로 만들어진 프로그램은, 기존의 웹 브라우저가 이해하는 JavaScript로 번역된다.&lt;/p&gt;
&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;Dart2js 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://dart.dev/tools/dart2js&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dart.dev/tools/dart2js&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711775339758&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;dart2js: Dart-to-JavaScript compiler&quot; data-og-description=&quot;The dart2js compiler compiles Dart code to deployable JavaScript.&quot; data-og-host=&quot;dart.dev&quot; data-og-source-url=&quot;https://dart.dev/tools/dart2js&quot; data-og-url=&quot;https://dart.dev/tools/dart2js/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jLk4u/hyVGMkvrrI/KNkZIb2BZ3l4xqO7lper1k/img.png?width=2650&amp;amp;height=1488&amp;amp;face=0_0_2650_1488&quot;&gt;&lt;a href=&quot;https://dart.dev/tools/dart2js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dart.dev/tools/dart2js&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jLk4u/hyVGMkvrrI/KNkZIb2BZ3l4xqO7lper1k/img.png?width=2650&amp;amp;height=1488&amp;amp;face=0_0_2650_1488');&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;dart2js: Dart-to-JavaScript compiler&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The dart2js compiler compiles Dart code to deployable JavaScript.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dart.dev&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;이름이 의미하듯이, Dart 언어와 Flutter 프레임워크를 사용해서 만든 프로그램을 JavaScript 언어의 코드로 변환해 준다.&lt;/p&gt;
&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;※ Dart-to-JavaScript 는 최근 dart 명령에 통합되었다. &quot;dart compile js&quot; 라는 명령으로 동작한다.&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;Google Mobile Ads for Flutter : &lt;a href=&quot;https://pub.dev/packages/google_mobile_ads&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/google_mobile_ads&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711775478869&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_mobile_ads | Flutter package&quot; data-og-description=&quot;Flutter plugin for Google Mobile Ads, supporting banner, interstitial (full-screen), rewarded and native ads&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/google_mobile_ads&quot; data-og-url=&quot;https://pub.dev/packages/google_mobile_ads&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NKhvr/hyVGKmEW12/wqLk8kh5qOjRJ7qh1QgHu1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bBIRF2/hyVGTqnD1m/PaUtM2rIhFIx85Mk54lxy1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/google_mobile_ads&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/google_mobile_ads&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NKhvr/hyVGKmEW12/wqLk8kh5qOjRJ7qh1QgHu1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bBIRF2/hyVGTqnD1m/PaUtM2rIhFIx85Mk54lxy1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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_mobile_ads | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flutter plugin for Google Mobile Ads, supporting banner, interstitial (full-screen), rewarded and native ads&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;Google Ads&lt;/b&gt;는 Flutter로 Web 서비스를 개발하는 방법으로 만들어진, 대표적인 상용 수준의 서비스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Dart for Embedded 알아 두기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fluuter로 개발할 수 있는 소프트웨어 실행 환경은 스마트폰, 데스크톱, 웹 서비스 외에 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;임베디드(embedded)&lt;/b&gt;&lt;/span&gt;가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 이야기하면, &lt;b&gt;TV/냉장고/세탁기/에어컨과 같은 가전기기&lt;/b&gt;와 &lt;b&gt;프린터 등의 사무기기&lt;/b&gt;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;광고판/센서/스마트워치 등의 사물인터넷(Internet-of-Things) 장치&lt;/b&gt; 등을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-1. CFEE&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 사용하는 범용 하드웨어와 운영체제가 아닌,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베디드 환경에서 Flutter를 실행하기 위해서&lt;b&gt; 'Custom Flutter Engine Embedders'&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;CFEE 위키 사이트&lt;/b&gt; : &lt;a href=&quot;https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711775795283&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;Custom Flutter Engine Embedders&quot; data-og-description=&quot;Flutter makes it easy and fast to build beautiful apps for mobile and beyond - flutter/flutter&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders&quot; data-og-url=&quot;https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8iLCn/hyVGRzjGOH/6ZsLv4PcurlwHeDTfz7CK0/img.png?width=1386&amp;amp;height=640&amp;amp;face=0_0_1386_640&quot;&gt;&lt;a href=&quot;https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8iLCn/hyVGRzjGOH/6ZsLv4PcurlwHeDTfz7CK0/img.png?width=1386&amp;amp;height=640&amp;amp;face=0_0_1386_640');&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;Custom Flutter Engine Embedders&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flutter makes it easy and fast to build beautiful apps for mobile and beyond - flutter/flutter&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;※ 1. 에서 설명한 플러터의 아키텍처처럼, 하드웨어에 의존적인 부분을 어떻게 수정해야 하는지 기준이 되는 가이드라인이 CFEE라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-2. 소니의 사례&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2020년 말에 자사의 임베디드 환경에 Linux를 구축하고, 이 위에서 Flutter의 Embedder를 개발한 후, Flutter SDK를 수정하였다.&lt;/p&gt;
&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;Sony의 Embedder 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://github.com/sony/flutter-embedded-linux&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/sony/flutter-embedded-linux&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711775954444&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 - sony/flutter-embedded-linux: Embedded Linux embedding for Flutter&quot; data-og-description=&quot;Embedded Linux embedding for Flutter. Contribute to sony/flutter-embedded-linux development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/sony/flutter-embedded-linux&quot; data-og-url=&quot;https://github.com/sony/flutter-embedded-linux&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3d0Jh/hyVGKNHAVd/cqVw7uBrPYv88rNrKZhoB1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/sony/flutter-embedded-linux&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/sony/flutter-embedded-linux&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3d0Jh/hyVGKNHAVd/cqVw7uBrPYv88rNrKZhoB1/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 - sony/flutter-embedded-linux: Embedded Linux embedding for Flutter&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Embedded Linux embedding for Flutter. Contribute to sony/flutter-embedded-linux 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;Sony의 Fluuter 확장 기능 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://github.com/sony/flutter-elinux&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/sony/flutter-elinux&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711775993801&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 - sony/flutter-elinux: Flutter tools for embedded Linux (eLinux)&quot; data-og-description=&quot;Flutter tools for embedded Linux (eLinux). Contribute to sony/flutter-elinux development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/sony/flutter-elinux&quot; data-og-url=&quot;https://github.com/sony/flutter-elinux&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bv9StE/hyVGGxNkwp/qp1LfsLp9T4XKb3SVSbGMk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/sony/flutter-elinux&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/sony/flutter-elinux&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bv9StE/hyVGGxNkwp/qp1LfsLp9T4XKb3SVSbGMk/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 - sony/flutter-elinux: Flutter tools for embedded Linux (eLinux)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flutter tools for embedded Linux (eLinux). Contribute to sony/flutter-elinux 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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-3. 구글의 Fuchsia 운영체제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 관심을 받고 있는 사례는, 구글이 새롭게 만들고 있는 운영체제인 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Fuchsia&lt;/b&gt;&lt;/span&gt;의 Dart/Flutter 도입이다.&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;Fuchsia 운영체제 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://fuchsia.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fuchsia.dev/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711776215520&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;Fuchsia&quot; data-og-description=&quot;Fuchsia is a new open source operating system.&quot; data-og-host=&quot;fuchsia.dev&quot; data-og-source-url=&quot;https://fuchsia.dev/&quot; data-og-url=&quot;https://fuchsia.dev/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://fuchsia.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://fuchsia.dev/&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;Fuchsia&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Fuchsia is a new open source operating system.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;fuchsia.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Flutter 공식 사이트 레퍼런스와 샘플 활용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart와 Flutter는 개발자의 편의를 위해서 표준 라이브러리와 패키지에서 제공하는 기능과 위젯을,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 방법으로 찾아보고 활용할 수 있도록 지원하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;s&gt;&lt;b&gt;4-1. Flutter Gallery&lt;/b&gt;&lt;/s&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 만의 특이하고 독창적인 샘플이다. 구글 플레이 스토어에서 &quot;Flutter Gallery&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;Flutter Gallery 사이트&lt;/b&gt; : &lt;a href=&quot;https://github.com/flutter/gallery#flutter-gallery&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/flutter/gallery#flutter-gallery&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711777136677&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 - flutter/gallery: Flutter Gallery was a resource to help developers evaluate and use Flutter&quot; data-og-description=&quot;Flutter Gallery was a resource to help developers evaluate and use Flutter - flutter/gallery&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/flutter/gallery#flutter-gallery&quot; data-og-url=&quot;https://github.com/flutter/gallery&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nbek1/hyVGGLjsbD/JAyErslAmKpH8YykOuRpk0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://github.com/flutter/gallery#flutter-gallery&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/flutter/gallery#flutter-gallery&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nbek1/hyVGGLjsbD/JAyErslAmKpH8YykOuRpk0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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 - flutter/gallery: Flutter Gallery was a resource to help developers evaluate and use Flutter&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flutter Gallery was a resource to help developers evaluate and use Flutter - flutter/gallery&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;※ 그러나, 2024년 현재는 지원을 중단하고, 뿔뿔이 흩어졌다.&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;a href=&quot;https://flutter-gallery-archive.web.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://flutter-gallery-archive.web.app/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711777674960&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;Flutter Gallery&quot; data-og-description=&quot;Google uses cookies to deliver its services, to personalize ads, and to analyze traffic. You can adjust your privacy controls anytime in your Google settings. Learn more. Okay&quot; data-og-host=&quot;flutter-gallery-archive.web.app&quot; data-og-source-url=&quot;https://flutter-gallery-archive.web.app/&quot; data-og-url=&quot;https://flutter-gallery-archive.web.app/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://flutter-gallery-archive.web.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter-gallery-archive.web.app/&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;Flutter Gallery&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Google uses cookies to deliver its services, to personalize ads, and to analyze traffic. You can adjust your privacy controls anytime in your Google settings. Learn more. Okay&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flutter-gallery-archive.web.app&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-2. &lt;span style=&quot;color: #1a5490;&quot;&gt;Flutter Samples&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter의 각종 위젯 등을 그림으로 확인하고, 필요하면 샘플 코드를 찾아 본인 프로그램에 포함할 수 있도록 도와준다.&lt;/p&gt;
&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;Flutter Samples 사이트&lt;/b&gt; : &lt;a href=&quot;https://flutter.github.io/samples/#&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://flutter.github.io/samples/#&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711777803846&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;Flutter samples&quot; data-og-description=&quot;sample Flutter Maps Firestore A Flutter sample app that shows the end product of the Cloud Nex...&quot; data-og-host=&quot;flutter.github.io&quot; data-og-source-url=&quot;https://flutter.github.io/samples/#&quot; data-og-url=&quot;https://flutter.github.io/samples/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://flutter.github.io/samples/#&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter.github.io/samples/#&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;Flutter samples&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;sample Flutter Maps Firestore A Flutter sample app that shows the end product of the Cloud Nex...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flutter.github.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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-3. &lt;span style=&quot;color: #1a5490;&quot;&gt;Widget Index&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter Samples 처럼 그림으로 위젯들을 보여주는 것은 유사하지만, 대부분의 위젯들에 대해서 기능을 설명하는 동영상이 제공되고 있다.&lt;/p&gt;
&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;Widget Index 사이트&lt;/b&gt; : &lt;a href=&quot;https://docs.flutter.dev/reference/widgets&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/reference/widgets&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711777884396&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;Flutter widget index&quot; data-og-description=&quot;An alphabetical list of Flutter widgets.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/reference/widgets&quot; data-og-url=&quot;https://docs.flutter.dev/reference/widgets&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cdW7ZU/hyVGKfUo2c/4mIrJ2XP2ngLqnbKNkHTvk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/7oWeC/hyVGQApzLc/tOJLFeBDc7NPCx3QEK4RoK/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200,https://scrap.kakaocdn.net/dn/lfFvT/hyVGHwHXlp/A73fUs0KRVxrBySKT6h2K0/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/reference/widgets&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/reference/widgets&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cdW7ZU/hyVGKfUo2c/4mIrJ2XP2ngLqnbKNkHTvk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/7oWeC/hyVGQApzLc/tOJLFeBDc7NPCx3QEK4RoK/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200,https://scrap.kakaocdn.net/dn/lfFvT/hyVGHwHXlp/A73fUs0KRVxrBySKT6h2K0/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200');&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;Flutter widget index&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An alphabetical list of Flutter widgets.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-4. &lt;span style=&quot;color: #1a5490;&quot;&gt;CookBook&lt;/span&gt;&lt;/b&gt;&lt;/h3&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;Flutter CookBook 사이트&lt;/b&gt; : &lt;a href=&quot;https://docs.flutter.dev/cookbook&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/cookbook&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711777972683&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;Cookbook&quot; data-og-description=&quot;The Flutter cookbook provides recipes for many commonly performed tasks.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/cookbook&quot; data-og-url=&quot;https://docs.flutter.dev/cookbook&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/UdlPD/hyVGTqooaa/e7MyOodkNvSkhzK0cA6KUk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/cookbook&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/cookbook&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/UdlPD/hyVGTqooaa/e7MyOodkNvSkhzK0cA6KUk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461');&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;Cookbook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Flutter cookbook provides recipes for many commonly performed tasks.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-5. &lt;span style=&quot;color: #1a5490;&quot;&gt;Codelabs&lt;/span&gt;&lt;/b&gt;&lt;/h3&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;Codelabs 사이트&lt;/b&gt; : &lt;a href=&quot;https://docs.flutter.dev/codelabs&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/codelabs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711778027536&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;Codelabs&quot; data-og-description=&quot;Codelabs to help you quickly get started programming Flutter.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/codelabs&quot; data-og-url=&quot;https://docs.flutter.dev/codelabs&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b0T1k7/hyVGIvCGBn/9vCOnfP2vxIJA5DqQe8Rmk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/codelabs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/codelabs&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b0T1k7/hyVGIvCGBn/9vCOnfP2vxIJA5DqQe8Rmk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461');&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;Codelabs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Codelabs to help you quickly get started programming Flutter.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-6. &lt;span style=&quot;color: #1a5490;&quot;&gt;API Reference&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter가 제공하는 모든 라이브러리와 패키지를 사전처럼 나열한 참조 사이트&lt;/p&gt;
&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;gt; 사용하면 된다.&lt;/p&gt;
&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 Reference 사이트&lt;/b&gt; : &lt;a href=&quot;https://api.flutter.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711778149167&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;Flutter - Dart API docs&quot; data-og-description=&quot;Welcome to the Flutter API reference documentation! Flutter is Google's SDK for crafting beautiful, fast user experiences for mobile, web, and desktop from a single codebase. Flutter works with existing code, is used by developers and organizations around &quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/&quot; data-og-url=&quot;https://api.flutter.dev/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/&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;Flutter - Dart API docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Welcome to the Flutter API reference documentation! Flutter is Google's SDK for crafting beautiful, fast user experiences for mobile, web, and desktop from a single codebase. Flutter works with existing code, is used by developers and organizations around&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Flutter CLI 명령어 이해하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter SDK의 핵심 컴포넌트 중 하나는 CLI 환경에서 동작하는 flutter 명령어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;자주 등장하는 flutter 명령어를 정리하면, 다음과 같다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;388&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z1DPL/btsGct22KZ3/jun2k3cYpKhItXXkJNGTS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z1DPL/btsGct22KZ3/jun2k3cYpKhItXXkJNGTS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z1DPL/btsGct22KZ3/jun2k3cYpKhItXXkJNGTS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz1DPL%2FbtsGct22KZ3%2Fjun2k3cYpKhItXXkJNGTS1%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;388&quot; height=&quot;261&quot; data-origin-width=&quot;388&quot; data-origin-height=&quot;261&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;Flutter 명령어 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://docs.flutter.dev/reference/flutter-cli&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/reference/flutter-cli&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711778314061&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;flutter: The Flutter command-line tool&quot; data-og-description=&quot;The reference page for using 'flutter' in a terminal window.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/reference/flutter-cli&quot; data-og-url=&quot;https://docs.flutter.dev/reference/flutter-cli&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VyViK/hyVGGYQ7lS/sNfsSQkBHdF6EwRK81eZQk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/reference/flutter-cli&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/reference/flutter-cli&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VyViK/hyVGGYQ7lS/sNfsSQkBHdF6EwRK81eZQk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461');&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;flutter: The Flutter command-line tool&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The reference page for using 'flutter' in a terminal window.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-1. &lt;span style=&quot;color: #ef6f53;&quot;&gt;analyze&lt;/span&gt; 명령어 활용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpfUMf/btsGdPj1i1T/4D6EfNSB4LfHwntsI4d8Q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpfUMf/btsGdPj1i1T/4D6EfNSB4LfHwntsI4d8Q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpfUMf/btsGdPj1i1T/4D6EfNSB4LfHwntsI4d8Q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpfUMf%2FbtsGdPj1i1T%2F4D6EfNSB4LfHwntsI4d8Q0%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;310&quot; height=&quot;40&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;40&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;u&gt;&lt;b&gt;프로젝트가 실행되는 디바이스에서의, 프로젝트 실행 상태를 분석해준다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 크롬 브라우저를 사용하는 웹 서비스라면 -&amp;gt; flutter analyze -d chrome 명령으로 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-2. &lt;span style=&quot;color: #ef6f53;&quot;&gt;channel&lt;/span&gt; 명령어 활용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서는 패키지나 라이브러리의 개발 단계 수준에 맞춰서 등급을 정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 &lt;b&gt;stable(안정적) &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;따라서 pub.dev 등에서 패키지를 찾아서 사용하려고 하면, &quot;프로그램의 상태가 안정적이지 않으니 개발 수준 혹은 베타 상태에서 활용하라&quot; 는 설명이 있을 수 있다. 이런 경우에 channel 명령어를 사용한다.&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-origin-width=&quot;181&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daAQ0r/btsGdsijiWz/qKPln1hMDsLhyhondbf4DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daAQ0r/btsGdsijiWz/qKPln1hMDsLhyhondbf4DK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daAQ0r/btsGdsijiWz/qKPln1hMDsLhyhondbf4DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaAQ0r%2FbtsGdsijiWz%2FqKPln1hMDsLhyhondbf4DK%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;181&quot; height=&quot;121&quot; data-origin-width=&quot;181&quot; data-origin-height=&quot;121&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter로 개발하는 소프트웨어의 상태는 stable, beta, master -&amp;gt; 이 3가지 상태로 구분할 수 있다.&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;개발 등급을 변경하고 싶다면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;36&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYKEKL/btsGcNAcSGe/6YOTmGJIZcsL33bdNNSgvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYKEKL/btsGcNAcSGe/6YOTmGJIZcsL33bdNNSgvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYKEKL/btsGcNAcSGe/6YOTmGJIZcsL33bdNNSgvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYKEKL%2FbtsGcNAcSGe%2F6YOTmGJIZcsL33bdNNSgvK%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;211&quot; height=&quot;36&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;36&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5-3. &lt;span style=&quot;color: #ef6f53;&quot;&gt;clean&lt;/span&gt; 명령어 활용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;clean 명령은 build 혹은 run 과정에서 만들어진 많은 작업 파일을 전부 삭제한다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 이외의 다양한 flutter 세부 명령어의 구체적인 정보는, -h(--help) 옵션을 사용하여 확인해 볼 수 있다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. Concurrency 기반 병렬 처리 개발하기&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Isolate(아이솔레이트)&lt;/b&gt; &lt;/span&gt;기술을 사용하면, 내가 만든 프로그램이 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;가장 중요한 차이점은 바로 &lt;b&gt;&quot;서로 정보를 주고 받으면서 작업&quot; &lt;/b&gt;한다는 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Isolate 기술을 사용하면, &lt;b&gt;같은 작업을 하는 프로그램이더라도 더 많은 CPU와 코어가 있는 컴퓨터에서는 더 빠르게 결과를 계산&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_1711781496150&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:io';
import 'dart:convert';
import 'dart:async';
import 'dart:isolate';
import 'package:async/async.dart';

// 비동기 작업을 위한, dart:async 라이브러리
// isolate를 위한 dart:isolate 라이브러리

// _sendAndReceive() 함수를 호출하고, 이 함수가 보내오는 결과들을 비동기적으로 기다리면서, 결과가 올 때마다 화면에 출력한다
void main() async {
  int counter = 5;
  await for (final msg in _sendAndReceive(counter)) {
    print('Received from _sendAndReceive(): &amp;lt; $msg &amp;gt;');
  }
}

/*
_sendAndReceive() 함수는 하나의 값을 리턴하는 간단한 방식이 아니다.

1. 정수 하나를 입력 받아서, 문자열을 꾸준히 리턴하기 위해 반환타입이 Stream&amp;lt;String&amp;gt;이다.

2. 이 함수의 가장 중요한 역할은 동시에 같이 동작할 _doCount() 함수를 isolate 모드로 실행시키는 것이다.
-&amp;gt; 그래서 Isolate.spawn() 함수를 사용한다.

3. Isolate.spawn() 함수의 첫번째 파라미터는, isolate 방식으로 동작할 함수의 이름을 넣어주고,
두번째는 _doCount() 함수가 _sendAndRecevie() 함수에게 정보를 보내는 용도로 사용할 일종의 통신 포트인 p.sendPort이다.

4. 이렇게 ReceivePort()가 p라는 이름으로 만들어지면, _sendAndReceive() 함수는 p로부터 계속 정보가 오기를 기다린다.
-&amp;gt; 따라서 p는 계속 정보를 수신하는 타입이 되어야 하기 때문에, p를 받아서 처리할 수 있는 타입으로 StreamQueue&amp;lt;dynamic&amp;gt;를 사용한다.

5. yield 구문은, 일종의 파이프를 열고 끊임없이 결과를 전달하는 문법이다.

*/
Stream&amp;lt;String&amp;gt; _sendAndReceive(int counter) async* {
  final p = ReceivePort();
  await Isolate.spawn(_doCount, p.sendPort);

  final events = StreamQueue&amp;lt;dynamic&amp;gt;(p);
  SendPort sendPort = await events.next;

  for (var temp = counter; temp &amp;gt;= 0; temp--) {
    sendPort.send(temp);
    String message = await events.next;
    yield message;
  }

  sendPort.send(null);
  await events.cancel();
}

/*
_doCount()와 _sendAndReceive() 함수가 탁구처럼 서로 주고받으면서 동작을 한다.

1. SendPort sendPort = await events.next; &amp;lt;= p.send(commandPort.sendPort);
_doCount() 함수가 _sendAndReceive() 함수에게 최초의 정보를 보냈다.
즉, 서로의 ReceivePort()를 주고받은 것이다.

2. 이제 둘다 for 루프에 진입한다. 반복문은 _sendAndReceive() 함수가 주도한다.
1씩 줄어드는 temp 값을 _doCount() 함수에게 보낸다.
그러면 _doCount() 함수가 commandPort를 통해서 이 값을 받고, 
정수라면 1초 대기 후에, 문자열을 만들어서 다시 _sendAndReceive() 함수에게 보낸다.

3. _sendAndReceive() 함수는 &quot;String message = await events.next;&quot;로 이 문자열을 받아서 화면에 출력한다. 
그리고 반복문이 종료되면, _doCount() 함수에게 null을 보내서 종료를 요청한다.

4. 그러면 _doCount() 함수도 반복문이 종료되게 되고,
Isolate.exit() 함수를 호출해서, isolate 모드로 동작하던 것이 종료된다.
*/
Future&amp;lt;void&amp;gt; _doCount(SendPort p) async {
  print('Isolate _doCount() started.');

  final commandPort = ReceivePort();
  p.send(commandPort.sendPort);

  await for (final message in commandPort) {
    if (message is int) {
      await Future.delayed(Duration(seconds: 1), () =&amp;gt; {});
      final contents = &quot;_doCount(): $message seconds&quot;;
      p.send(contents);
    } else if (message == null) {
      break;
    }
  }

  print('Isolate _doCount() completed.');
  Isolate.exit();
}&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;Concurrency 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://dart.dev/language/concurrency&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dart.dev/language/concurrency&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711781548715&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;Concurrency in Dart&quot; data-og-description=&quot;Use isolates to enable parallel code execution on multiple processor cores.&quot; data-og-host=&quot;dart.dev&quot; data-og-source-url=&quot;https://dart.dev/language/concurrency&quot; data-og-url=&quot;https://dart.dev/language/concurrency/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bgkAqi/hyVGOWVhE2/nhq0URmJFdiDQKxPkgQzH1/img.png?width=1678&amp;amp;height=867&amp;amp;face=0_0_1678_867,https://scrap.kakaocdn.net/dn/coI0AW/hyVGF6JPAE/Znle8jMTo4fDhPmccfeRAK/img.png?width=851&amp;amp;height=764&amp;amp;face=0_0_851_764&quot;&gt;&lt;a href=&quot;https://dart.dev/language/concurrency&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dart.dev/language/concurrency&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bgkAqi/hyVGOWVhE2/nhq0URmJFdiDQKxPkgQzH1/img.png?width=1678&amp;amp;height=867&amp;amp;face=0_0_1678_867,https://scrap.kakaocdn.net/dn/coI0AW/hyVGF6JPAE/Znle8jMTo4fDhPmccfeRAK/img.png?width=851&amp;amp;height=764&amp;amp;face=0_0_851_764');&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;Concurrency in Dart&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Use isolates to enable parallel code execution on multiple processor cores.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dart.dev&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;다만 Concurrency는 Isolate 라는 기술을 어떻게 사용하는지에 관한 설명일 뿐이다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. TensorFlow 활용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TensorFlow는 인공 신경망 기술에서 가장 유명한 소프트웨어 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;사진에서 특정 목표를 추출&quot;&lt;/b&gt; 하거나, &lt;b&gt;&quot;주어진 데이터를 그룹별로 분류&quot;&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;그러나 아쉽게도, Dart 언어는 지원하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고 Dart 언어에서 TensorFlow를 사용할 수 있는 방법이 몇가지 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7-1. pub.dev에 공개되어 있는 라이브러리와 패키지를 사용하는 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 소개했던 tflite 뿐만 아니라, tensorflow 라고 검색하면 여러 패키지들이 검색되니 잘 찾아서 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7-2. C 언어를 이해하고 있다면, FFI를 사용하는 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 챕터에서 설명한 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;FFI (Foreign Function Interface)&lt;/b&gt; &lt;/span&gt;를 사용해서, TensorFlow 중 필요한 부분을 직접 연결해 보는 방법이다. 사실 pub.dev에서 찾을 수 있는 패키지들도 FFI를 기반으로 Dart와 TensorFlow를 연결하는 패키지들이 많다.&lt;/p&gt;
&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;Dart FFI 설명 사이트&lt;/b&gt; : &lt;a href=&quot;https://api.dart.dev/dev/3.4.0-260.0.dev/dart-ffi/dart-ffi-library.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.dart.dev/dev/3.4.0-260.0.dev/dart-ffi/dart-ffi-library.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711781911667&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;dart:ffi library - Dart API&quot; data-og-description=&quot;dart:ffi library Foreign Function Interface for interoperability with the C programming language. For further details, please see: https://dart.dev/server/c-interop. Classes Abi An application binary interface (ABI). AbiSpecificInteger The supertype of all&quot; data-og-host=&quot;api.dart.dev&quot; data-og-source-url=&quot;https://api.dart.dev/dev/3.4.0-260.0.dev/dart-ffi/dart-ffi-library.html&quot; data-og-url=&quot;https://api.dart.dev/dev/3.4.0-260.0.dev/dart-ffi/dart-ffi-library.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.dart.dev/dev/3.4.0-260.0.dev/dart-ffi/dart-ffi-library.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.dart.dev/dev/3.4.0-260.0.dev/dart-ffi/dart-ffi-library.html&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;dart:ffi library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;dart:ffi library Foreign Function Interface for interoperability with the C programming language. For further details, please see: https://dart.dev/server/c-interop. Classes Abi An application binary interface (ABI). AbiSpecificInteger The supertype of all&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.dart.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7-3. C++이나 Python으로 TensorFlow를 사용하는 소프트웨어를 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 소프트웨어에 입력 값을 주고, 결과를 전달받는 프로그램을 Dart와 Flutter로 개발하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;두 소프트웨어 간의 정보 교환은 HTTP의 CRUD 방식 등으로 구현&lt;/b&gt;&lt;/u&gt;하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;마이크로서비스&lt;/b&gt;&lt;/span&gt; 또는 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;서버리스&lt;/b&gt;&lt;/span&gt;라는 용어들이 광범위하게 쓰이는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 설명하면 어차피 기술별로 최적인 언어는 따로 있으니, 각각의 목적과 용도에 맞는 프로그래밍 언어로 독립적으로 개발하고, 입력과 출력만 HTTP 등의 네트워킹 기술로 주고 받으면 된다는 아이디어로 구현된 기술을 말한다.&lt;/p&gt;
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7-4. Firebase 같은 이미 제공되는 서비스를 이용하는 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TensorFlow가 지원하는 기능이 필요하지만, 프로그래밍이 굳이 필요하지 않은 경우 사용할 수 있다.&lt;/p&gt;
&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;Firebase 사이트&lt;/b&gt; : &lt;a href=&quot;https://firebase.google.com/?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebase.google.com/?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711782300139&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;Firebase | Google&amp;rsquo;s Mobile and Web App Development Platform&quot; data-og-description=&quot;개발자가 사용자가 좋아할 만한 앱과 게임을 빌드하도록 지원하는 Google의 모바일 및 웹 앱 개발 플랫폼인 Firebase에 대해 알아보세요.&quot; data-og-host=&quot;firebase.google.com&quot; data-og-source-url=&quot;https://firebase.google.com/?hl=ko&quot; data-og-url=&quot;https://firebase.google.com/?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DWZg6/hyVGObw85V/RckT5mLsyFr9kJmvVWxfYK/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/wWgVF/hyVGPO3uct/N2PbaGEEecprpkRMAtnfik/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/CF5I2/hyVGPIjfp4/KKEQGL7DKyyukLJeWuQJ71/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240&quot;&gt;&lt;a href=&quot;https://firebase.google.com/?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://firebase.google.com/?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DWZg6/hyVGObw85V/RckT5mLsyFr9kJmvVWxfYK/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/wWgVF/hyVGPO3uct/N2PbaGEEecprpkRMAtnfik/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/CF5I2/hyVGPIjfp4/KKEQGL7DKyyukLJeWuQJ71/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240');&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;Firebase | Google&amp;rsquo;s Mobile and Web App Development Platform&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개발자가 사용자가 좋아할 만한 앱과 게임을 빌드하도록 지원하는 Google의 모바일 및 웹 앱 개발 플랫폼인 Firebase에 대해 알아보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;firebase.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;p data-ke-size=&quot;size16&quot;&gt;스마트폰 등의 디바이스에 올라가는 소프트웨어는 직접 개발할 의사가 있으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굳이 서버를 직접 개발하고 운영할 필요가 없는 경우, 구글이 제공하는 Firebase를 사용하여 개발을 간략화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Firebase 에는&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; &quot;ML Kit&quot;&lt;/b&gt;&lt;/span&gt; 가 있기 때문에, 더욱 더 언급하는 이유가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter로 스마트폰 앱을 만들고, Firebase로 서버 기능을 구성하기 위한 노력 속에서 등장한 기술 중 최근 가장 인지도가 높은 기술은&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; FlutterFire&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;FlutterFire 사이트&lt;/b&gt; : &lt;a href=&quot;https://firebaseopensource.com/projects/firebaseextended/flutterfire/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebaseopensource.com/projects/firebaseextended/flutterfire/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711782465536&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;FlutterFire&quot; data-og-description=&quot;Check out this project on firebaseopensource.com!&quot; data-og-host=&quot;firebaseopensource.com&quot; data-og-source-url=&quot;https://firebaseopensource.com/projects/firebaseextended/flutterfire/&quot; data-og-url=&quot;https://firebaseopensource.com/projects/firebaseextended/flutterfire/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4i0eX/hyVGHjc3DZ/GRQyFlKNoEIDKS2T29eg1k/img.png?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256&quot;&gt;&lt;a href=&quot;https://firebaseopensource.com/projects/firebaseextended/flutterfire/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://firebaseopensource.com/projects/firebaseextended/flutterfire/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4i0eX/hyVGHjc3DZ/GRQyFlKNoEIDKS2T29eg1k/img.png?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256');&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;FlutterFire&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Check out this project on firebaseopensource.com!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;firebaseopensource.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;FlutterFire 플러그인 소개 사이트&lt;/b&gt; : &lt;a href=&quot;https://firebase.flutter.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebase.flutter.dev/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711782492501&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;FlutterFire | FlutterFire&quot; data-og-description=&quot;The official Firebase plugins for Flutter&quot; data-og-host=&quot;firebase.flutter.dev&quot; data-og-source-url=&quot;https://firebase.flutter.dev/&quot; data-og-url=&quot;https://firebase.flutter.dev/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baxUZz/hyVGRe3ifG/OOsGt3qucLlZvfrb4GQNo0/img.png?width=222&amp;amp;height=302&amp;amp;face=0_0_222_302,https://scrap.kakaocdn.net/dn/cPLQRl/hyVGSZlKup/p4hPUQbScEy3luRvwNwGd0/img.png?width=222&amp;amp;height=302&amp;amp;face=0_0_222_302,https://scrap.kakaocdn.net/dn/dkVxoC/hyVGKfVx6Q/1PBC9WSAMpOnuxjQr5NcXk/img.png?width=222&amp;amp;height=302&amp;amp;face=0_0_222_302&quot;&gt;&lt;a href=&quot;https://firebase.flutter.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://firebase.flutter.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baxUZz/hyVGRe3ifG/OOsGt3qucLlZvfrb4GQNo0/img.png?width=222&amp;amp;height=302&amp;amp;face=0_0_222_302,https://scrap.kakaocdn.net/dn/cPLQRl/hyVGSZlKup/p4hPUQbScEy3luRvwNwGd0/img.png?width=222&amp;amp;height=302&amp;amp;face=0_0_222_302,https://scrap.kakaocdn.net/dn/dkVxoC/hyVGKfVx6Q/1PBC9WSAMpOnuxjQr5NcXk/img.png?width=222&amp;amp;height=302&amp;amp;face=0_0_222_302');&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;FlutterFire | FlutterFire&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The official Firebase plugins for Flutter&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;firebase.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dart와 Flutter/도서 내용 정리</category>
      <category>DART</category>
      <category>Flutter</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/255</guid>
      <comments>https://sweetburble.tistory.com/255#entry255comment</comments>
      <pubDate>Sat, 30 Mar 2024 14:28:39 +0900</pubDate>
    </item>
    <item>
      <title>토마토 라면 - [하루한끼 식]</title>
      <link>https://sweetburble.tistory.com/254</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;밥을 말아먹기는 뭔가 힘들 것 같다. 먹을만 했다.&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;edited_20240329_토마토라면_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cec3qK/btsGaEiZARG/fuxldx57vgzKmzzRfKNbr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cec3qK/btsGaEiZARG/fuxldx57vgzKmzzRfKNbr0/img.png&quot; data-alt=&quot;토마토 라면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cec3qK/btsGaEiZARG/fuxldx57vgzKmzzRfKNbr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcec3qK%2FbtsGaEiZARG%2Ffuxldx57vgzKmzzRfKNbr0%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;500&quot; height=&quot;375&quot; data-filename=&quot;edited_20240329_토마토라면_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;토마토 라면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=OpSMPDnBDoQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=OpSMPDnBDoQ&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=OpSMPDnBDoQ&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/c7s4k5/hyVGNwCc85/et4VonklXyNU1hNqUo06Z0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;요즘 유행하는 토마토 라면&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/OpSMPDnBDoQ&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/254</guid>
      <comments>https://sweetburble.tistory.com/254#entry254comment</comments>
      <pubDate>Fri, 29 Mar 2024 14:13:21 +0900</pubDate>
    </item>
    <item>
      <title>부추짜박이 - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/253</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&lt;/b&gt;&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;또는 아주 간단한 멸치국물과 소면을 삶아서 만든 멸치국수에 고명으로 2~3스푼 정도 넣으면 맛있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 양도 4~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;edited_20240327_부추짜박이_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UkJ1p/btsF71dOR0d/Qzuxukxp4KELZKF0G1djMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UkJ1p/btsF71dOR0d/Qzuxukxp4KELZKF0G1djMk/img.png&quot; data-alt=&quot;부추짜박이&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UkJ1p/btsF71dOR0d/Qzuxukxp4KELZKF0G1djMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUkJ1p%2FbtsF71dOR0d%2FQzuxukxp4KELZKF0G1djMk%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;500&quot; height=&quot;339&quot; data-filename=&quot;edited_20240327_부추짜박이_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;부추짜박이&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;edited_20240124_124640.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IgNTx/btsGacAbmcY/4RHK8bur8YLeQnvOxPYaVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IgNTx/btsGacAbmcY/4RHK8bur8YLeQnvOxPYaVK/img.png&quot; data-alt=&quot;부추계란밥&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IgNTx/btsGacAbmcY/4RHK8bur8YLeQnvOxPYaVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIgNTx%2FbtsGacAbmcY%2F4RHK8bur8YLeQnvOxPYaVK%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;500&quot; height=&quot;376&quot; data-filename=&quot;edited_20240124_124640.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;376&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=wXWaeaUYtZc&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bHyPBw/hyVDsULS5q/uPaX7meGhZlr428T6Azwnk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/wXWaeaUYtZc&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/253</guid>
      <comments>https://sweetburble.tistory.com/253#entry253comment</comments>
      <pubDate>Wed, 27 Mar 2024 22:29:27 +0900</pubDate>
    </item>
    <item>
      <title>허니버터 갈릭치킨 - [하루한끼 식]</title>
      <link>https://sweetburble.tistory.com/251</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_20240325_허니버터갈릭치킨_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgwYO5/btsF15gFzOP/4xWfSONXmB7urUAfYvXAdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgwYO5/btsF15gFzOP/4xWfSONXmB7urUAfYvXAdk/img.png&quot; data-alt=&quot;허니버터 갈릭치킨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgwYO5/btsF15gFzOP/4xWfSONXmB7urUAfYvXAdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgwYO5%2FbtsF15gFzOP%2F4xWfSONXmB7urUAfYvXAdk%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;500&quot; height=&quot;365&quot; data-filename=&quot;edited_20240325_허니버터갈릭치킨_하루한끼.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;허니버터 갈릭치킨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=-DypmmpDL9M&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/beFJHX/hyVDASpqrs/8eAKnQYF9toIX2ZVmRDWg1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;초간단 굴소스 요리&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/-DypmmpDL9M&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;3분 49초 ~&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/251</guid>
      <comments>https://sweetburble.tistory.com/251#entry251comment</comments>
      <pubDate>Mon, 25 Mar 2024 17:12:37 +0900</pubDate>
    </item>
    <item>
      <title>내용 정리 Part.1 - [풀스택 개발이 쉬워지는 다트 &amp;amp; 플러터]</title>
      <link>https://sweetburble.tistory.com/250</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 4월을 기준으로 작성되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 앞으로 자주 방문하게 될 웹사이트&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) Dart 공식 사이트 : &lt;a href=&quot;https://dart.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dart.dev/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711282491266&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;Dart programming language&quot; data-og-description=&quot;Dart is an approachable, portable, and productive language for high-quality apps on any platform.&quot; data-og-host=&quot;dart.dev&quot; data-og-source-url=&quot;https://dart.dev/&quot; data-og-url=&quot;https://dart.dev/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JNMY9/hyVDwvpwDe/xrIuPI48dw2hmKpDFh5Ht1/img.png?width=2650&amp;amp;height=1488&amp;amp;face=0_0_2650_1488&quot;&gt;&lt;a href=&quot;https://dart.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dart.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JNMY9/hyVDwvpwDe/xrIuPI48dw2hmKpDFh5Ht1/img.png?width=2650&amp;amp;height=1488&amp;amp;face=0_0_2650_1488');&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;Dart programming language&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Dart is an approachable, portable, and productive language for high-quality apps on any platform.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dart.dev&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;Dart 언어의 개요, 샘플 프로그램, 튜토리얼, 표준 확장 기능(라이브러리/패키지)의 설명, 개발 도구, 기술 자료,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그, 웹 기반 프로그램 실행 환경 (DartPad), 개발 환경 설치 프로그램, 커뮤니티 등의 정보를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) Flutter 공식 사이트 : &lt;a href=&quot;https://flutter.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://flutter.dev/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711282638856&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;Flutter - Build apps for any screen&quot; data-og-description=&quot;Flutter transforms the entire app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase.&quot; data-og-host=&quot;flutter.dev&quot; data-og-source-url=&quot;https://flutter.dev/&quot; data-og-url=&quot;https://flutter.dev/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vYA6g/hyVDten6jK/gcnhQCjnqEaH1lgMkVssM0/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/VVW0O/hyVDGkkMnu/Nyr4YGxsy9fY7lVAHpw9q1/img.jpg?width=1088&amp;amp;height=1292&amp;amp;face=0_0_1088_1292,https://scrap.kakaocdn.net/dn/bgfQ6q/hyVDHwVA8K/Wlk7JwjcATkLP96e1eZD21/img.jpg?width=1088&amp;amp;height=1292&amp;amp;face=0_0_1088_1292&quot;&gt;&lt;a href=&quot;https://flutter.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vYA6g/hyVDten6jK/gcnhQCjnqEaH1lgMkVssM0/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/VVW0O/hyVDGkkMnu/Nyr4YGxsy9fY7lVAHpw9q1/img.jpg?width=1088&amp;amp;height=1292&amp;amp;face=0_0_1088_1292,https://scrap.kakaocdn.net/dn/bgfQ6q/hyVDHwVA8K/Wlk7JwjcATkLP96e1eZD21/img.jpg?width=1088&amp;amp;height=1292&amp;amp;face=0_0_1088_1292');&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;Flutter - Build apps for any screen&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flutter transforms the entire app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flutter.dev&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;Flutter의 소개, 기술 설명, 개발 환경 설치 프로그램, 커뮤니티 및 이벤트 등의 정보를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) DartPad : &lt;a href=&quot;https://dartpad.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dartpad.dev/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711282708331&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;DartPad&quot; data-og-description=&quot;&quot; data-og-host=&quot;dartpad.dev&quot; data-og-source-url=&quot;https://dartpad.dev/&quot; data-og-url=&quot;https://dartpad.dev/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dartpad.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dartpad.dev/&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;DartPad&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;dartpad.dev&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;웹 브라우저에서 즉시 Dart와 Flutter 프로그래밍을 시작할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4) Dart Code : &lt;a href=&quot;https://dartcode.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dartcode.org/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711282787025&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;Dart Code - Dart &amp;amp; Flutter support for Visual Studio Code&quot; data-og-description=&quot;&quot; data-og-host=&quot;dartcode.org&quot; data-og-source-url=&quot;https://dartcode.org/&quot; data-og-url=&quot;https://dartcode.org/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dartcode.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dartcode.org/&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;Dart Code - Dart &amp;amp; Flutter support for Visual Studio Code&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;dartcode.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;VSCode 에서 Dart와 Flutter를 사용할 수 있도록 하는 확장 기능을 개발한 곳&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5) pub.dev : &lt;a href=&quot;https://pub.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711282872344&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;The official repository for Dart and Flutter packages.&quot; data-og-description=&quot;Pub is the package manager for the Dart programming language, containing reusable libraries &amp;amp; packages for Flutter and general Dart programs.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/&quot; data-og-url=&quot;https://pub.dev/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bZvHhu/hyVDBwK6PO/yHbNVM3qzM8mBOMPEmEos1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/ebLxEG/hyVDy033Qo/KlLwZXqGxcbKkKdcl0jOuK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bZvHhu/hyVDBwK6PO/yHbNVM3qzM8mBOMPEmEos1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/ebLxEG/hyVDy033Qo/KlLwZXqGxcbKkKdcl0jOuK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;The official repository for Dart and Flutter packages.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Pub is the package manager for the Dart programming language, containing reusable libraries &amp;amp; packages for Flutter and general Dart programs.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&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;Dart와 Flutter로 소프트웨어를 개발하는 프로그래머들이 서로 본인들이 만든 소프트웨어를 공유하는 웹 사이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사이트를 활용할 수 있도록 flutter에서도 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;&quot;flutter pub&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;size18&quot;&gt;&lt;b&gt;pub 명령어 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://dart.dev/tools/pub/cmd&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dart.dev/tools/pub/cmd&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711778758571&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;dart pub&quot; data-og-description=&quot;The command-line interface for pub, a package management tool for Dart.&quot; data-og-host=&quot;dart.dev&quot; data-og-source-url=&quot;https://dart.dev/tools/pub/cmd&quot; data-og-url=&quot;https://dart.dev/tools/pub/cmd/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eNF2h/hyVGGR7Aaf/1qAhD5ywZKi12385claTj0/img.png?width=2650&amp;amp;height=1488&amp;amp;face=0_0_2650_1488&quot;&gt;&lt;a href=&quot;https://dart.dev/tools/pub/cmd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dart.dev/tools/pub/cmd&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eNF2h/hyVGGR7Aaf/1qAhD5ywZKi12385claTj0/img.png?width=2650&amp;amp;height=1488&amp;amp;face=0_0_2650_1488');&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;dart pub&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The command-line interface for pub, a package management tool for Dart.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dart.dev&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;size18&quot;&gt;&lt;b&gt;Pubspec.yaml의 Dependencies 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://dart.dev/tools/pub/dependencies&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dart.dev/tools/pub/dependencies&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711776845948&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;Package dependencies&quot; data-og-description=&quot;Add other packages to your app. Specify package locations, version constraints, and more.&quot; data-og-host=&quot;dart.dev&quot; data-og-source-url=&quot;https://dart.dev/tools/pub/dependencies&quot; data-og-url=&quot;https://dart.dev/tools/pub/dependencies/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dqdLY5/hyVGNReQjT/0tMf9SX1zykuVFysJxSqi1/img.png?width=2650&amp;amp;height=1488&amp;amp;face=0_0_2650_1488&quot;&gt;&lt;a href=&quot;https://dart.dev/tools/pub/dependencies&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dart.dev/tools/pub/dependencies&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dqdLY5/hyVGNReQjT/0tMf9SX1zykuVFysJxSqi1/img.png?width=2650&amp;amp;height=1488&amp;amp;face=0_0_2650_1488');&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;Package dependencies&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Add other packages to your app. Specify package locations, version constraints, and more.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dart.dev&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EsSSD/btsGfJigF1g/kIkoflhYRSLTscf9GTkP30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EsSSD/btsGfJigF1g/kIkoflhYRSLTscf9GTkP30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EsSSD/btsGfJigF1g/kIkoflhYRSLTscf9GTkP30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEsSSD%2FbtsGfJigF1g%2FkIkoflhYRSLTscf9GTkP30%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;650&quot; height=&quot;547&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;670&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;pub.dev 의 메인 화면은 이런 식으로 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;6) Flutter 공식 유튜브 : &lt;a href=&quot;https://www.youtube.com/flutterdev&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/flutterdev&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711282938543&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;Flutter&quot; data-og-description=&quot;Welcome to the official Flutter YouTube channel. Subscribe to stay up to date with best practices about the Flutter SDK. See real code examples, and watch engineers from around the world putting Flutter to work!&quot; data-og-host=&quot;www.youtube.com&quot; data-og-source-url=&quot;https://www.youtube.com/flutterdev&quot; data-og-url=&quot;https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cCEV82/hyVDG5Srnc/k9z0vyJFKzfA1j53Q3mNok/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900,https://scrap.kakaocdn.net/dn/bUGK54/hyVDAEBX0Q/VGyeTztWa8ITkMlhppsec0/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900&quot;&gt;&lt;a href=&quot;https://www.youtube.com/flutterdev&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.youtube.com/flutterdev&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cCEV82/hyVDG5Srnc/k9z0vyJFKzfA1j53Q3mNok/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900,https://scrap.kakaocdn.net/dn/bUGK54/hyVDAEBX0Q/VGyeTztWa8ITkMlhppsec0/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900');&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;Flutter&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Welcome to the official Flutter YouTube channel. Subscribe to stay up to date with best practices about the Flutter SDK. See real code examples, and watch engineers from around the world putting Flutter to work!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.youtube.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;유튜브를 통한 기술 설명을 적극적으로 하기도 하고, Flutter의 기능을 정기적으로 소개하는 콘텐츠도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;7) Medium : &lt;a href=&quot;https://medium.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711283110157&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;Medium &amp;ndash; Where good ideas find you.&quot; data-og-description=&quot;Medium is an open platform where readers find dynamic thinking, and where expert and undiscovered voices can share their writing on any topic.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/&quot; data-og-url=&quot;https://medium.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjmlBK/hyVDwWuwk1/7KJIJNSu7rvRgTcOaZpwpk/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628&quot;&gt;&lt;a href=&quot;https://medium.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjmlBK/hyVDwWuwk1/7KJIJNSu7rvRgTcOaZpwpk/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;Medium &amp;ndash; Where good ideas find you.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Medium is an open platform where readers find dynamic thinking, and where expert and undiscovered voices can share their writing on any topic.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart와 Flutter의 공식 발표(릴리즈)가 있을 때마다, 공식 발표의 주요 설명을 하는 글이 이 사이트에서 공개된다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. mixin&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;u&gt;&lt;b&gt;다른 클래스의 악세서리나 부품처럼 특정 기능만 수행&lt;/b&gt;&lt;/u&gt;하는 게 좋은 상황이면, mixin을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711371467245&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Integer {
  late int _value;

  Integer([int givenValue = 0]) {
    _value = givenValue;
  }

  int get() {
    return _value;
  }

  void set(int givenValue) {
    _value = givenValue;
  }

  // 리턴하는 타입(String)과 함수 이름(asString) 사이에 get 키워드를 사용하여 getter를 정의함
  String get asString =&amp;gt; &quot;$_value&quot;;

  // set이라는 키워드를 사용하여 setter를 정의함
  set value(int givenValue) =&amp;gt; _value = givenValue;

  // 연산자 오버로딩을 위해 + 연산자를 오버로딩함
  Integer operator +(Integer givenValue) {
    return Integer(_value + givenValue.get());
  }
}

/*
사실상 클래스인데, 스스로 객체가 되어 사용되기보다는 다른 클래스의 부속품으로 사용되는 클래스!
*/
mixin ActivationFlag {
  bool _flag = true;

  bool get activated =&amp;gt; _flag;

  set activated(bool givenFlag) =&amp;gt; (_flag = givenFlag);
}

// mixin 을 적용하여 개선
class TimemachineInteger extends Integer with ActivationFlag {
  List&amp;lt;int&amp;gt; _timemachine = [];

  TimemachineInteger([int givenValue = 0]) {
    _value = givenValue;
  }

  // mixin을 추가함으로써, 사용자의 설정에 의해서 바뀌는 값을 저장하거나 &amp;lt;=&amp;gt; 저장하지 않도록 변경
  @override
  void set(int givenValue) {
    if (activated == true) {
      _timemachine.add(_value);
      print(&quot;set($givenValue) called ==&amp;gt; _timemachine is $_timemachine&quot;);
    } else {
      print(&quot;set($givenValue) called ==&amp;gt; current $_value is ignored.&quot;);
    }

    super.set(givenValue);
  }

  List getOld() {
    return _timemachine;
  }

  @override
  String get asString =&amp;gt; &quot;$_value, previously $_timemachine&quot;;
}

void main() {
  // mixin 사용 예제
  var tmpTmInteger = TimemachineInteger(0);
  tmpTmInteger.set(2);
  tmpTmInteger.set(4);
  tmpTmInteger.activated = false;
  tmpTmInteger.set(6);
  tmpTmInteger.activated = true;
  tmpTmInteger.set(8);
  print(&quot;${tmpTmInteger.asString}&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;※ mixin을 아무 클래스나 적용하지 않고, 특정 클래스에만 적용하였으면 좋겠다고 생각할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 이런 경우에는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;'on' 문법&lt;/b&gt;&lt;/span&gt;을 사용한다. (자주 사용하는 문법은 아니지만, 기억해둘만하다)&lt;/p&gt;
&lt;pre id=&quot;code_1711371548178&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mixin ActivationFlag on TimemachineInteger {
}&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;※ 다른 개발자가 개발한 mixin을 사용하는 것도 좋은 방법이다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Dart 언어의 비동기(Asynchronous) 기능&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart 언어로 프로그램을 개발하다 보면, &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Future나 Stream&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;비동기 작업(async)&quot;&lt;/b&gt;&lt;/span&gt;을 지원하기 위한 목적으로 만들어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;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;Future 클래스는 Dart 언어에서 복수의 작업을 동시에 수행하는 비동기 작업을 지원하고 있다.&lt;/p&gt;
&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;※ Stream 클래스는 CPU 보다 속도가 느린 외부 장치를 읽고 쓰는 작업(입출력 작업)과, &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CPU가 계산하는 작업을 동시에 수행하도록 하는 경우에 주로 사용한다.&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_1711373619977&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() async {
  print(&quot;main(): started.&quot;);
  await Future.delayed(Duration(seconds: 3),
      () =&amp;gt; print(&quot;main(): Hello, World! @ 3 seconds later.&quot;));
  print(&quot;main(): completed.&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;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;await&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;async&lt;/b&gt;&lt;/span&gt; : await 문법을 사용하려면, 반드시 await 구문이 있는 함수의 시작 부분에 async를 작성해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711374172557&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void doBackgroundJob(int jobTime) {
  if (jobTime &amp;gt; 0) {
    print(&quot;doBackgroundJob(): $jobTime seconds remained.&quot;);
    Future.delayed(Duration(seconds: 1), () =&amp;gt; doBackgroundJob(jobTime - 1));
  } else {
    print(&quot;doBackgroundJob(): finished.&quot;);
  }
}

void main() async {
  print(&quot;main(): started.&quot;);
  await doBackgroundJob(5);
  print(&quot;main(): completed.&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: #ee2323;&quot;&gt;&lt;b&gt;Q. 그러면, 위의 코드가 의도대로 잘 동작할까?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;A. 아니다, 에러가 발생한다!&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리턴값이 Future 또는 Stream을 리턴하는 함수에 메서드에만 await를 사용할 수 있다! (doBackgroundJob은 void)&lt;/p&gt;
&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;pre id=&quot;code_1711374754501&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
이제 main 함수가 doBackground() 함수의 종료 여부를 알도록 만들어서, doBackground() 함수가 종료된 후에 main 함수가 종료되도록 해보자.
*/

class ActivationFlag {
  late bool _flag;

  ActivationFlag(bool givenFlag) : _flag = givenFlag;
  bool get activated =&amp;gt; _flag;
  set activated(bool givenFlag) =&amp;gt; _flag = givenFlag;
}

void doBackgroundJob(int jobTime, var jobEnd) {
  if (jobTime &amp;gt; 0) {
    print(&quot;doBackgroundJob(): $jobTime seconds remained.&quot;);
    Future.delayed(
        Duration(seconds: 1), () =&amp;gt; doBackgroundJob(jobTime - 1, jobEnd));
  } else {
    print(&quot;doBackgroundJob(): finished.&quot;);
    jobEnd.activated = true;
  }
}

void main() async {
  var bgJobFinished = ActivationFlag(false);

  print(&quot;main(): started.&quot;);
  doBackgroundJob(5, bgJobFinished);
  while (bgJobFinished.activated == false) {
    await Future.delayed(Duration(seconds: 1));
  }
  print(&quot;main(): completed.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-1. 동시에 수행할 작업 추가하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1711375179337&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 방문자의 주문을 접수하는 비동기 작업을 만드는 것
 */

Future&amp;lt;String&amp;gt; serveCustomer() async {
  print(&quot;serveCustomer(): 접수 시작.&quot;);

  var customerOrder = await simulateCustomerOrder();

  print(&quot;serveCustomer(): 주문 접수 완료: $customerOrder.&quot;);

  return customerOrder;
}

Future&amp;lt;String&amp;gt; simulateCustomerOrder() {
  return Future.delayed(Duration(seconds: 2), () =&amp;gt; '아이스 아메리카노');
}

void main() async {
  print(&quot;main(): started.&quot;);

  var customerOrder = await serveCustomer();
  print(&quot;main() : serve '$customerOrder'.&quot;);

  print(&quot;main(): completed.&quot;);
}

/*
main(): started.
serveCustomer(): 접수 시작.
serveCustomer(): 주문 접수 완료: 아이스 아메리카노.
main() : serve '아이스 아메리카노'.
main(): completed.
*/&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;아래와 같은 규칙 때문에 serveCustomer() 함수를 보면, 리턴 값이 Future&amp;lt;String&amp;gt;으로 되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;&quot;async 함수가 리턴 값을 갖는 경우, 리턴 값의 타입은 반드시 Future&amp;lt;&amp;gt;로 정의한다&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;&lt;b&gt;1. 비동기로 동작하는 Future.delayed() 메서드의 리턴 값이 Future 객체이기에,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Future&amp;lt;String&amp;gt;을 리턴하는 simulateCustomerOrder() 함수를 만들었다,&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;2. 이 함수를 호출하면서 결과를 기다려야 하므로, &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;또 다시 await/async 구문을 사용하면서, Future&amp;lt;String&amp;gt;를 비동기로 리턴하는 serveCustomer() 함수를 만들었다.&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. 또 serveCustomer() 함수도 비동기 함수이기에, 결과가 제대로 리턴될 때까지 함수를 호출한 쪽에서 기다려 주어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서 serveCustomer()를 호출하는 main 함수에서도 await/async 구문을 반영한다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 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;Dart 언어는&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; File 클래스&lt;/b&gt;&lt;/span&gt;를 통해서 파일의 내용을 읽고 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Dart 언어는 파일을 다루는 방법을 여러 가지 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-1. Dart 언어의 파일 처리 방법 이해하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 &lt;u&gt;&lt;b&gt;동기 방식과 비동기 방식 중 어떤 방법을 사용할 지 선택&lt;/b&gt;&lt;/u&gt;해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일이 저장되어 있는 저장 장치는 물리적으로 느릴 수 있기에 -&amp;gt; 파일을 다룰 때는 주로 비동기 방식을 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 보면, 파일을 읽고 쓰는 대부분의 함수들은 비동기 함수로서 async 구문을 사용하며, 리턴 값도 Future&amp;lt;&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;두 번째는 &lt;u&gt;&lt;b&gt;파일의 내용을 어떻게 다룰 것인가&lt;/b&gt;&lt;/u&gt;의 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;1. 파일을 하나의 문자열로 다루는 방법 : readAsString()&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: #1b711d;&quot;&gt;2. 스트림 (stream)을 사용하는 방법 : openRead() 또는 openWrite() 메서드&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;3. 랜덤 액세스 (Random access) 방식 : open() 메서드&lt;/b&gt;&lt;/span&gt;를 써서 파일을 연 다음, 원하는 지점으로 바로 이동하면서 읽고 쓴느 작업을 하는 방법이다. 따라서 파일 중 원하는 위치를 찾아가기 위한 &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;※ Random access는 다소 어려운 난이도이므로, 추후 고난이도의 프로그램을 만들어야 하는 경우 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;파일 처리(File structure)&quot; 같은 소프트웨어 분야의 이론 서적을 읽고 적용해보기 바란다.&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_1711462832762&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:io';
import 'dart:convert'; // Dart 언어의 변환 기능을 사용하기 위해

// 파일을 읽는 목적으로 사용할 전용 함수 2개
// 1. readAsString()으로 하나의 파일을 하나의 문자열에 모두 저장한다
Future&amp;lt;String&amp;gt; readFileToString(String fileName) async {
  var file = File(fileName); // 파일을 읽기 위한 File 객체 생성
  String fileContent = await file.readAsString(); // 파일의 내용을 문자열로 읽기
  return fileContent;
}

// 2. Stream 클래스를 이용하는 방법
Future&amp;lt;List&amp;lt;String&amp;gt;&amp;gt; readFileToList(String fileName) async {
  Stream&amp;lt;String&amp;gt; lines = File(fileName)
      .openRead() // 파일을 읽기 위한 Stream 객체 생성
      .transform(utf8.decoder) // 바이트를 UTF-8로 변환
      .transform(LineSplitter()); // 스트림을 개별 라인으로 변환

  // 파일을 사용하는 경우, 더욱 더 에러 처리에 신경을 써야 한다.
  try {
    List&amp;lt;String&amp;gt; sList = [];
    await for (var line in lines) {
      sList.add(line);
    }
    return sList;
  } catch (error) {
    throw (error);
  }
}

void main() async {
  print(&quot;[1] File read scenario #1.&quot;);

  try {
    String fileContent = await readFileToString('src.txt');
    print(fileContent);
  } catch (error) {
    print(error);
  }

  // ======================================================
  print(&quot;[2] File read scenario #2.&quot;);

  try {
    List&amp;lt;String&amp;gt; fileContent = await readFileToList('src.txt');
    for (var fileLine in fileContent) {
      print(fileLine);
    }
  } catch (error) {
    print(error);
  }

  // ======================================================
  print(&quot;[3] File write scenario.&quot;);

  List&amp;lt;String&amp;gt; fileContent = await readFileToList('src.txt');

  var sList = [];
  var iVar1 = 0;
  var iVar2 = 0;
  var count = 0;

  var dstSink = File('dst.txt').openWrite(); // 파일을 쓰기 위한 Stream 객체 생성

  dstSink.write(&quot;:=&amp;gt; FILE ACCESSED ${DateTime.now()}\n&quot;);
  for (var fileLine in fileContent) {
    sList = fileLine.split(',');
    iVar1 = int.parse(sList[0]);
    iVar2 = int.parse(sList[1]);

    print(&quot;$iVar1 x $iVar2 = ${iVar1 * iVar2}&quot;);
    dstSink.write(&quot;$iVar1 x $iVar2 = ${iVar1 * iVar2}\n&quot;);
    count++;
  }

  dstSink.write(&quot;:=&amp;gt; $count ITEMS CALCULATED&quot;);

  dstSink.close(); // 파일 쓰기 종료
}

// File : https://api.dart.dev/stable/dart-io/File-class.html
// Stream : https://api.dart.dev/stable/dart-async/Stream-class.html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Dart의 표준 라이브러리 활용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여태까지 배웠던 &lt;b&gt;Dart 언어 표준 라이브러리(Standard library)&lt;/b&gt;들은 주로&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt; dart:core&lt;/b&gt;&lt;/span&gt;와 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;dart:async&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;File, IOSink, stdin, stdout&lt;/b&gt; 같은 기능들은 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;dart:io&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;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;1. dart:convert&lt;/b&gt;&lt;/span&gt; - JSON, ASCII, UNICODE 등의 데이터 코드 간 변환&lt;/p&gt;
&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: #1b711d;&quot;&gt;&lt;b&gt;2. dart:math&lt;/b&gt;&lt;/span&gt; - 수학의 상수, 함수, 난수(Random number) 생성&lt;/p&gt;
&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: #1b711d;&quot;&gt;&lt;b&gt;3. dart:collection&lt;/b&gt;&lt;/span&gt; - 큐(queue), 연결 리스트(Linked List), 해시 맵(Hash map), 이진 트리(Binary tree)와 같이 컬렉션 타입 라이브러리&lt;/p&gt;
&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: #1b711d;&quot;&gt;&lt;b&gt;4. dart:html&lt;/b&gt; &lt;/span&gt;- 웹 브라우저 및 DOM(Document Object Model)을 다뤄야 하는 웹 기반 응용 프로그램을 위한 HTML 요소 및 기타 리소스를 담당한다.&lt;/p&gt;
&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: #1b711d;&quot;&gt;&lt;b&gt;5. dart:typed_data&lt;/b&gt;&lt;/span&gt; - Unsigned 8바이트 정수와 같은 고정 크기 데이터 및 SIMD(Single Instruction Multiple Data) 숫자타입을 효과적으로 처리하는 데이터 타입&lt;/p&gt;
&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: #1b711d;&quot;&gt;&lt;b&gt;6. dart:ffi&lt;/b&gt; &lt;/span&gt;- C 언어 스타일 인터페이스를 제공하는, 다른 코드와의 상호 운용성을 위한 외부 함수 인터페이스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;7. dart:isolate&lt;/b&gt;&lt;/span&gt; - 격리(isolates)를 사용한 동시(Concurrent) 프로그래밍, 스레드(Thread)와 유사하지만 메모리를 공유하지 않고, 메시지를 통해서만 통신하는 독립 작업 기능&lt;/p&gt;
&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: #1b711d;&quot;&gt;&lt;b&gt;dart:js / dart:js_util / dart:svg / dart:web_audio / dart:web_gl&lt;/b&gt;&lt;/span&gt; 등의 라이브러리가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;※ Dart 표준 라이브러리 개요 : &lt;a href=&quot;https://dart.dev/overview#libraries&quot;&gt;https://dart.dev/overview#libraries&lt;/a&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711463571548&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;Dart overview&quot; data-og-description=&quot;A short introduction to Dart.&quot; data-og-host=&quot;dart.dev&quot; data-og-source-url=&quot;https://dart.dev/overview#libraries&quot; data-og-url=&quot;https://dart.dev/overview/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ceworg/hyVDIwp3P5/ZzYpmfdcNLtILfyh95gccK/img.png?width=2650&amp;amp;height=1488&amp;amp;face=0_0_2650_1488,https://scrap.kakaocdn.net/dn/bgkFtZ/hyVDurj2MO/5r2nMKj5vE5hKefqmKxMV1/img.png?width=1000&amp;amp;height=320&amp;amp;face=0_0_1000_320&quot;&gt;&lt;a href=&quot;https://dart.dev/overview#libraries&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dart.dev/overview#libraries&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ceworg/hyVDIwp3P5/ZzYpmfdcNLtILfyh95gccK/img.png?width=2650&amp;amp;height=1488&amp;amp;face=0_0_2650_1488,https://scrap.kakaocdn.net/dn/bgkFtZ/hyVDurj2MO/5r2nMKj5vE5hKefqmKxMV1/img.png?width=1000&amp;amp;height=320&amp;amp;face=0_0_1000_320');&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;Dart overview&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A short introduction to Dart.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dart.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;※ Dart API Reference:&amp;nbsp;&lt;a href=&quot;https://api.dart.dev/stable/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.dart.dev/stable/index.html&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711463571145&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;Dart - Dart API docs&quot; data-og-description=&quot;Welcome! Welcome to the Dart API reference documentation, covering the Dart core libraries. These include: dart:core: Core functionality such as strings, numbers, collections, errors, dates, and URIs. dart:io: I/O for non-web apps. dart:html: DOM manipulat&quot; data-og-host=&quot;api.dart.dev&quot; data-og-source-url=&quot;https://api.dart.dev/stable/index.html&quot; data-og-url=&quot;https://api.dart.dev/stable/3.3.2/index.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.dart.dev/stable/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.dart.dev/stable/index.html&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;Dart - Dart API docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Welcome! Welcome to the Dart API reference documentation, covering the Dart core libraries. These include: dart:core: Core functionality such as strings, numbers, collections, errors, dates, and URIs. dart:io: I/O for non-web apps. dart:html: DOM manipulat&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.dart.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. Flutter로 모바일 앱 개발하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Material 은 &lt;b&gt;안드로이드 운영체제 전용 그래픽 라이브러리(Material Design 라이브러리)&lt;/b&gt;이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;import 'package:flutter/material.dart';&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 Apple의 Design 방식은 Cupertino 라고 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;import 'package:flutter/cupertino.dart';&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1장. Hello World 프로그램 개발하기&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1711625219829&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

void main() {
  runApp(
    const Center(
      child: Text(
        'Hello, World!',
        textDirection: TextDirection.ltr,
        style: TextStyle(
          fontSize: 32,
          color: Colors.white,
        ),
      ),
    ),
  );
}&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;769&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uHOwJ/btsGcdxaMLp/QXipZ54GLb2K6Mkt7UqLc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uHOwJ/btsGcdxaMLp/QXipZ54GLb2K6Mkt7UqLc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uHOwJ/btsGcdxaMLp/QXipZ54GLb2K6Mkt7UqLc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuHOwJ%2FbtsGcdxaMLp%2FQXipZ54GLb2K6Mkt7UqLc1%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;654&quot; height=&quot;120&quot; data-origin-width=&quot;769&quot; data-origin-height=&quot;141&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;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;TextStyle&lt;/b&gt; &lt;b&gt;클래스&lt;/b&gt; &lt;/span&gt;: Flutter의 표준 클래스. 화면에 글자를 출력해야 하는 경우, 글자의 형태를 어떻게 나타낼 지 정한다.&lt;/p&gt;
&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;※ TextStyle 공식 사이트 : &lt;a href=&quot;https://api.flutter.dev/flutter/painting/TextStyle-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/painting/TextStyle-class.html&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711625349091&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;TextStyle class - painting library - Dart API&quot; data-og-description=&quot;An immutable style describing how to format and paint text. Bold Here, a single line of text in a Text widget is given a specific style override. The style is mixed with the ambient DefaultTextStyle by the Text widget. link content_copy const Text( 'No, we&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/painting/TextStyle-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/painting/TextStyle-class.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/KBRkg/hyVDCJ3x5U/sKsnbk2a2PRwAXxwxJGzhK/img.png?width=1300&amp;amp;height=500&amp;amp;face=0_0_1300_500,https://scrap.kakaocdn.net/dn/Gf9WV/hyVGTi5h9G/edF3r2DI0hWC8tTSyiaNZ1/img.png?width=600&amp;amp;height=801&amp;amp;face=0_0_600_801,https://scrap.kakaocdn.net/dn/ekXEPu/hyVDyt6pku/ozeJIQ9Qi458HraBkxAtbK/img.png?width=800&amp;amp;height=200&amp;amp;face=0_0_800_200&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/painting/TextStyle-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/painting/TextStyle-class.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/KBRkg/hyVDCJ3x5U/sKsnbk2a2PRwAXxwxJGzhK/img.png?width=1300&amp;amp;height=500&amp;amp;face=0_0_1300_500,https://scrap.kakaocdn.net/dn/Gf9WV/hyVGTi5h9G/edF3r2DI0hWC8tTSyiaNZ1/img.png?width=600&amp;amp;height=801&amp;amp;face=0_0_600_801,https://scrap.kakaocdn.net/dn/ekXEPu/hyVDyt6pku/ozeJIQ9Qi458HraBkxAtbK/img.png?width=800&amp;amp;height=200&amp;amp;face=0_0_800_200');&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;TextStyle class - painting library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An immutable style describing how to format and paint text. Bold Here, a single line of text in a Text widget is given a specific style override. The style is mixed with the ambient DefaultTextStyle by the Text widget. link content_copy const Text( 'No, we&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;color: #1a5490;&quot;&gt;Text 클래스&lt;/span&gt; 공식 사이트 : &lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Text-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/Text-class.html&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711625457532&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;Text class - widgets library - Dart API&quot; data-og-description=&quot;A run of text with a single style. The Text widget displays a string of text with single style. The string might break across multiple lines or might all be displayed on the same line depending on the layout constraints. The style argument is optional. Whe&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/Text-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/Text-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Text-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/Text-class.html&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;Text class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A run of text with a single style. The Text widget displays a string of text with single style. The string might break across multiple lines or might all be displayed on the same line depending on the layout constraints. The style argument is optional. Whe&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Center 클래스&lt;/b&gt;&lt;/span&gt; : &quot;child 파라미터에 주어지는 무언가&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;Center 클래스 공식 사이트 : &lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Center-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/Center-class.html&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711625520341&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;Center class - widgets library - Dart API&quot; data-og-description=&quot;A widget that centers its child within itself. This widget will be as big as possible if its dimensions are constrained and widthFactor and heightFactor are null. If a dimension is unconstrained and the corresponding size factor is null then the widget wil&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/Center-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/Center-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/Center-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/Center-class.html&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;Center class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that centers its child within itself. This widget will be as big as possible if its dimensions are constrained and widthFactor and heightFactor are null. If a dimension is unconstrained and the corresponding size factor is null then the widget wil&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;runApp() 함수&lt;/b&gt;&lt;/span&gt; : 주어진 위젯(widget)을 Inflate하여, 화면에 attach한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯은 레이아웃 중에 전체 화면을 채우도록 강제하는 constraints(제약 조건)이 부여된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯을 화면의 원하는 위치에 정렬하려면 Align 위젯을 사용하고, 화면 중앙에 위치하도록 하려면 Center 위젯을 사용해라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart 언어의 main() 함수가 시작 지점인 것처럼, &lt;b&gt;Flutter 프로그램의 시작점은 runApp() 함수&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, main 함수에서는 runApp 함수를 호출하여 Flutter 애플리케이션을 시작하는 기능만 수행한다는 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;main() =&amp;gt; runApp(MyApp());&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2장. Hello World 프로그램 진화시키기 Part.1&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;StatelessWidget&lt;/b&gt;은 &lt;b&gt;StatefulWidget&lt;/b&gt; 과 더불어 Flutter에서 가장 중요하다고 할 수 있는 대표적인 두 개의 위젯이다.&lt;/p&gt;
&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: #1a5490;&quot;&gt;&lt;b&gt;StatefulWidget&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: #1a5490;&quot;&gt;&lt;b&gt;StatelessWidget&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;StatelessWidget 공식 사이트 : &lt;a href=&quot;https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711627163971&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;StatelessWidget class - widgets library - Dart API&quot; data-og-description=&quot;A widget that does not require mutable state. A stateless widget is a widget that describes part of the user interface by building a constellation of other widgets that describe the user interface more concretely. The building process continues recursively&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html&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;StatelessWidget class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that does not require mutable state. A stateless widget is a widget that describes part of the user interface by building a constellation of other widgets that describe the user interface more concretely. The building process continues recursively&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;StatelessWidget 클래스를 상속받으면, 반드시 &lt;b&gt;build() 메서드&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;MaterialApp&lt;/b&gt; &lt;/span&gt;: Material Design을 사용하는 애플리케이션에서 공통적으로 요구되는 다수의 위젯들을 감싸주는 편리한 위젯&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MaterialApp 공식 사이트 :&amp;nbsp;&lt;a href=&quot;https://api.flutter.dev/flutter/material/MaterialApp-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/MaterialApp-class.html&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711627319851&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;MaterialApp class - material library - Dart API&quot; data-og-description=&quot;An application that uses Material Design. A convenience widget that wraps a number of widgets that are commonly required for Material Design applications. It builds upon a WidgetsApp by adding material-design specific functionality, such as AnimatedTheme a&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/MaterialApp-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/MaterialApp-class.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GZouS/hyVDDhS3zO/bkp6e9uZ17kLLFFvgS6Nrk/img.png?width=300&amp;amp;height=534&amp;amp;face=0_0_300_534,https://scrap.kakaocdn.net/dn/kKT8X/hyVGIojrdr/9BCiqLBtcvMTe7NHc11kl1/img.png?width=300&amp;amp;height=534&amp;amp;face=0_0_300_534&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/MaterialApp-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/MaterialApp-class.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GZouS/hyVDDhS3zO/bkp6e9uZ17kLLFFvgS6Nrk/img.png?width=300&amp;amp;height=534&amp;amp;face=0_0_300_534,https://scrap.kakaocdn.net/dn/kKT8X/hyVGIojrdr/9BCiqLBtcvMTe7NHc11kl1/img.png?width=300&amp;amp;height=534&amp;amp;face=0_0_300_534');&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;MaterialApp class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An application that uses Material Design. A convenience widget that wraps a number of widgets that are commonly required for Material Design applications. It builds upon a WidgetsApp by adding material-design specific functionality, such as AnimatedTheme a&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Scaffold&lt;/b&gt; &lt;/span&gt;: Material Design으로 만든 모바일 앱이 화면에 어떻게 나타날지를 표현하는 클래스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;appBar 속성&lt;/b&gt; : scaffold 화면 상단에 보여줄 애플리케이션 바 내용 (또는 타이틀과 메뉴)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;body 속성&lt;/b&gt; : scaffold 화면의 메인 컨텐츠 출력 내용&lt;/p&gt;
&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;Scaffold 클래스 공식 사이트 : &lt;a href=&quot;https://api.flutter.dev/flutter/material/Scaffold-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/Scaffold-class.html&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711627687463&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;Scaffold class - material library - Dart API&quot; data-og-description=&quot;Implements the basic Material Design visual layout structure. This class provides APIs for showing drawers and bottom sheets. To display a persistent bottom sheet, obtain the ScaffoldState for the current BuildContext via Scaffold.of and use the ScaffoldSt&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/Scaffold-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/Scaffold-class.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cC2npr/hyVDDPHHqt/2MK9dEQxquXKV0Bhw3DdSk/img.png?width=300&amp;amp;height=534&amp;amp;face=0_0_300_534,https://scrap.kakaocdn.net/dn/HXp7E/hyVGPnrwzg/fBxvsbwvmO5Dj0CYFeKdV1/img.png?width=300&amp;amp;height=534&amp;amp;face=0_0_300_534&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/Scaffold-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/Scaffold-class.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cC2npr/hyVDDPHHqt/2MK9dEQxquXKV0Bhw3DdSk/img.png?width=300&amp;amp;height=534&amp;amp;face=0_0_300_534,https://scrap.kakaocdn.net/dn/HXp7E/hyVGPnrwzg/fBxvsbwvmO5Dj0CYFeKdV1/img.png?width=300&amp;amp;height=534&amp;amp;face=0_0_300_534');&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;Scaffold class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Implements the basic Material Design visual layout structure. This class provides APIs for showing drawers and bottom sheets. To display a persistent bottom sheet, obtain the ScaffoldState for the current BuildContext via Scaffold.of and use the ScaffoldSt&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;AppBar 클래스 공식 사이트 : &lt;a href=&quot;https://api.flutter.dev/flutter/material/AppBar-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/AppBar-class.html&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711627726957&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;AppBar class - material library - Dart API&quot; data-og-description=&quot;A Material Design app bar. An app bar consists of a toolbar and potentially other widgets, such as a TabBar and a FlexibleSpaceBar. App bars typically expose one or more common actions with IconButtons which are optionally followed by a PopupMenuButton for&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/AppBar-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/AppBar-class.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/orUOw/hyVDEOElpu/0hyA4TNJZYug9M3aEo3vv1/img.png?width=540&amp;amp;height=260&amp;amp;face=0_0_540_260&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/AppBar-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/AppBar-class.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/orUOw/hyVDEOElpu/0hyA4TNJZYug9M3aEo3vv1/img.png?width=540&amp;amp;height=260&amp;amp;face=0_0_540_260');&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;AppBar class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Material Design app bar. An app bar consists of a toolbar and potentially other widgets, such as a TabBar and a FlexibleSpaceBar. App bars typically expose one or more common actions with IconButtons which are optionally followed by a PopupMenuButton for&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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_1711627748490&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

main() =&amp;gt; runApp(MyApp()); 

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'App Title',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('AppBar Title'),
        ),
        body: const Center(
          child: Text(
            'Hello, World!',
            textDirection: TextDirection.ltr,
            style: TextStyle(
              fontSize: 32,
              color: Colors.black,
             ),
          ),
        ),
      ),
    );
  }
}&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;229&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u7Cxx/btsF9OeyWJH/krdkl27kJKdDv5x69Hleek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u7Cxx/btsF9OeyWJH/krdkl27kJKdDv5x69Hleek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u7Cxx/btsF9OeyWJH/krdkl27kJKdDv5x69Hleek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu7Cxx%2FbtsF9OeyWJH%2Fkrdkl27kJKdDv5x69Hleek%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;649&quot; height=&quot;189&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;229&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3장. Hello World 프로그램 진화시키기 Part.1&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;Container 위젯&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5장. Flutter 공식 Counter 프로그램 이해하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DartPad의 Counter 샘플을 분석해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711632842357&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

void main() =&amp;gt; runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;

  const MyHomePage({
    super.key,
    required this.title,
  });

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}&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;debugShowCheckedModeBanner: false&lt;/b&gt; -&amp;gt; 프로그램 실행 화면에 &quot;DEBUG&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;theme&lt;/b&gt;: ThemeData() -&amp;gt; MaterialApp의 기본 테마 설정을 설정한다. theme은 색상, 폰트, 모양 등 앱 외관에 해당하는 설정을 보관한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;primarySwatch&lt;/b&gt;&lt;/span&gt; : Material Design을 따르는 앱의 색상 정보를 저장하는 파라미터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Swatch 설명&lt;/b&gt; : &lt;a href=&quot;https://m2.material.io/design/color/the-color-system.html#color-theme-creation&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://m2.material.io/design/color/the-color-system.html#color-theme-creation&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711633542517&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;m3.material.io&quot; data-og-source-url=&quot;https://m2.material.io/design/color/the-color-system.html#color-theme-creation&quot; data-og-url=&quot;https://material.io&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bMOYMF/hyVDxIKNnX/5BzGDJM1f82jRReefxoaE0/img.jpg?width=1024&amp;amp;height=535&amp;amp;face=0_0_1024_535&quot;&gt;&lt;a href=&quot;https://m2.material.io/design/color/the-color-system.html#color-theme-creation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://m2.material.io/design/color/the-color-system.html#color-theme-creation&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bMOYMF/hyVDxIKNnX/5BzGDJM1f82jRReefxoaE0/img.jpg?width=1024&amp;amp;height=535&amp;amp;face=0_0_1024_535');&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;m3.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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StatefulWidget으로 만드는 프로그램의 구조에는 일종의 공식이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;StatefulWidget은 프로그램의 변경 가능한 '상태(state)'를 가지는 State 위젯&lt;/b&gt;을 만들어서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;state&lt;/span&gt;는 위젯이 프로그램 속에서 존재하는(life-cycle) 동안 변경될 수 있고, &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위젯이 화면 출력 작업을 하는 build() 메서드에 의해서 동기적으로 읽을 수 있는 정보&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;더 쉽게 말하자면, 실제 StatefulWidget 클래스의 객체는 변경할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 실행 동안 바뀌는 값은 StatefulWidget 자체에 저장하는 것이 아니고, StatefulWidget이 만드는 State 위젯에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State 위젯을 생성(create)하는 것이 StatefulWidget의 역할이다. 변경되는 정보 자체는 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;그래서 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;StatefulWidget의 createState() 메서드&lt;/b&gt;&lt;/span&gt;를 오버라이드한 것이다. -&amp;gt; State 위젯 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State 위젯은 변경되는 &lt;b&gt;정보를 저장할 &quot;변수&quot;&lt;/b&gt;를 정의하고, 변수가 화면에 표시될 때의 모습을 구성하는&lt;b&gt; build() 메서드&lt;/b&gt;를 정의한다. -&amp;gt; &lt;u&gt;&lt;b&gt;업데이트 되는 정보는 State 위젯이 가지고 있으니까&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나, 정보가 업데이트되도, 언제 화면을 업데이트할지는 결국 개발자가 &lt;b&gt;setState() 메서드&lt;/b&gt;로 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;607&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQgcTo/btsGaahoaPx/9XVjgerJOfdpXz3kzVkTdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQgcTo/btsGaahoaPx/9XVjgerJOfdpXz3kzVkTdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQgcTo/btsGaahoaPx/9XVjgerJOfdpXz3kzVkTdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQgcTo%2FbtsGaahoaPx%2F9XVjgerJOfdpXz3kzVkTdk%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;510&quot; height=&quot;132&quot; data-origin-width=&quot;607&quot; data-origin-height=&quot;157&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;StatefulWidget을 상속받은 클래스는 3가지 요소를 갖는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 생성자&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711634679375&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const MyHomePage({
    super.key,
    required this.title,
  });&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;Key 타입의 입력 파라미터는 보통 개발자가 직접 부여할 일은 없다.&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;this.x 입력 파라미터는, 생성자를 통해서 받은 입력 파라미터를 활용해서 내부의 값을 설정하는 일반적인 절차이다.&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;2) 상속받은 클래스가 자체적으로 저장하고 싶은 정보&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711634893502&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final String title;&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;예제에서는 AppBar의 title 이름을 저장했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 한 번 말하자면, &lt;b&gt;StatefulWidget을 상속받은 클래스는 '사용자와 상호작용하여 변동 가능한 값' 이 아니다!&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;size18&quot;&gt;&lt;b&gt;3) createState() 메서드의 오버라이드 작업&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 동적으로 변경되고, 화면의 업데이트 작업을 하는 State 위젯을 만들게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 createState()의 반환 값은 -&amp;gt; 자신이 만든 State니까 State&amp;lt;MyHomePage&amp;gt; 같은 형식이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;&lt;b&gt;State 클래스를 상속받은 클래스는 4가지 요소를 갖는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 사용자의 상호작용을 통해서(또는 프로그램 내부 계산 작업을 통해서) 동적으로 변하는 변수 영역&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711635181669&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int _counter = 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4가지 요소 중에서, &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;size18&quot;&gt;&lt;b&gt;2) 이 변수를 실제로 변경하는 영역&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711635231375&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void _incrementCounter() {
    setState(() {
      _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;변수를 변경했으니, 당연히 setState() 메서드를 호출해서, 화면을 업데이트할 필요가 발생했음을 알린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) build() 메서드의 오버라이드 작업&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경된 상태 정보로 인한 화면의 변화를 계산하고, 화면을 다시 그리는 작업을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1711635370795&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@override
  Widget build(BuildContext context) {&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;&lt;b&gt;4) StatefulWidget이 만든 State 위젯이 반대로, StatefulWidget의 정보에 접근하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State&amp;lt;&amp;gt; 클래스가 StatefulWidget을 기반으로 만들어진 클래스니까, 당연히 관련 정보가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;widget&lt;/b&gt;&lt;/span&gt;이라 불리는 프로퍼티에 저장하고, 따라서 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;widget.{StatefulWidget 객체의 상수/final의 이름}&lt;/b&gt; &lt;/span&gt;으로 접근 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1711635784953&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;appBar: AppBar(
        title: Text(widget.title),
      ),&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;StatefulWidget 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711635608877&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;StatefulWidget class - widgets library - Dart API&quot; data-og-description=&quot;A widget that has mutable state. State is information that (1) can be read synchronously when the widget is built and (2) might change during the lifetime of the widget. It is the responsibility of the widget implementer to ensure that the State is promptl&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html&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;StatefulWidget class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A widget that has mutable state. State is information that (1) can be read synchronously when the widget is built and (2) might change during the lifetime of the widget. It is the responsibility of the widget implementer to ensure that the State is promptl&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;State 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://api.flutter.dev/flutter/widgets/State-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/State-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711635631823&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;State class - widgets library - Dart API&quot; data-og-description=&quot;The logic and internal state for a StatefulWidget. State is information that (1) can be read synchronously when the widget is built and (2) might change during the lifetime of the widget. It is the responsibility of the widget implementer to ensure that th&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/State-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/State-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/State-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/State-class.html&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;State class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The logic and internal state for a StatefulWidget. State is information that (1) can be read synchronously when the widget is built and (2) might change during the lifetime of the widget. It is the responsibility of the widget implementer to ensure that th&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6장. Stateless Widget 활용하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flutter on Mobile 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://flutter.dev/multi-platform/mobile&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://flutter.dev/multi-platform/mobile&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711690900685&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;Flutter on Mobile&quot; data-og-description=&quot;Bring your app idea to life to more users from day one by building with Flutter on iOS and Android simultaneously, without sacrificing features, quality, or performance.&quot; data-og-host=&quot;flutter.dev&quot; data-og-source-url=&quot;https://flutter.dev/multi-platform/mobile&quot; data-og-url=&quot;https://flutter.dev/multi-platform/mobile/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rdbeu/hyVGSkoedI/EZvaUpSiUFxKBHdoGLXp21/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/7LMgk/hyVGFrOD11/8Z4if6oPJx6OecYn3WzyZ0/img.png?width=1316&amp;amp;height=1072&amp;amp;face=0_0_1316_1072,https://scrap.kakaocdn.net/dn/DLKMA/hyVGNpQl54/KWaqd9XXyKAFltTz6JIEhK/img.png?width=1088&amp;amp;height=1292&amp;amp;face=0_0_1088_1292&quot;&gt;&lt;a href=&quot;https://flutter.dev/multi-platform/mobile&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter.dev/multi-platform/mobile&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rdbeu/hyVGSkoedI/EZvaUpSiUFxKBHdoGLXp21/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/7LMgk/hyVGFrOD11/8Z4if6oPJx6OecYn3WzyZ0/img.png?width=1316&amp;amp;height=1072&amp;amp;face=0_0_1316_1072,https://scrap.kakaocdn.net/dn/DLKMA/hyVGNpQl54/KWaqd9XXyKAFltTz6JIEhK/img.png?width=1088&amp;amp;height=1292&amp;amp;face=0_0_1088_1292');&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;Flutter on Mobile&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Bring your app idea to life to more users from day one by building with Flutter on iOS and Android simultaneously, without sacrificing features, quality, or performance.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flutter.dev&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;Theme 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://api.flutter.dev/flutter/material/Theme-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/material/Theme-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711691701806&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;Theme class - material library - Dart API&quot; data-og-description=&quot;Applies a theme to descendant widgets. A theme describes the colors and typographic choices of an application. Descendant widgets obtain the current theme's ThemeData object using Theme.of. When a widget uses Theme.of, it is automatically rebuilt if the th&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/material/Theme-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/material/Theme-class.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/Theme-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/material/Theme-class.html&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;Theme class - material library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Applies a theme to descendant widgets. A theme describes the colors and typographic choices of an application. Descendant widgets obtain the current theme's ThemeData object using Theme.of. When a widget uses Theme.of, it is automatically rebuilt if the th&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;Theme.of(context).&lt;/b&gt;&lt;/span&gt; 와 같은 표현은 StatelessWidget 이나 State&amp;lt;&amp;gt; 클래스의 build() 메서드 안에서 종종 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;context는 상위 위젯의 정보가 포함&lt;/b&gt;된다. 따라서 &lt;b&gt;Theme.of(context).primaryColor&lt;/b&gt; 라고 작성하면, 현재의 위젯이 속해 있는 상위 위젯의 &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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ListView 위젯 공식 사이트&lt;/b&gt; : &lt;a href=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.flutter.dev/flutter/widgets/ListView-class.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711691863800&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;ListView class - widgets library - Dart API&quot; data-og-description=&quot;A scrollable list of widgets arranged linearly. ListView is the most commonly used scrolling widget. It displays its children one after another in the scroll direction. In the cross axis, the children are required to fill the ListView. If non-null, the ite&quot; data-og-host=&quot;api.flutter.dev&quot; data-og-source-url=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot; data-og-url=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/OSh7q/hyVGGqIwwk/ZrzzftWXTWt2GTK3EkTBi1/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250,https://scrap.kakaocdn.net/dn/HGZYq/hyVGH4eAm6/RM4Cbg4fcwxkv1GiNI7nw1/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250,https://scrap.kakaocdn.net/dn/SCeiv/hyVGG5jH03/djF1y94T9e4mD1oTMoWYfK/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.flutter.dev/flutter/widgets/ListView-class.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/OSh7q/hyVGGqIwwk/ZrzzftWXTWt2GTK3EkTBi1/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250,https://scrap.kakaocdn.net/dn/HGZYq/hyVGH4eAm6/RM4Cbg4fcwxkv1GiNI7nw1/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250,https://scrap.kakaocdn.net/dn/SCeiv/hyVGG5jH03/djF1y94T9e4mD1oTMoWYfK/img.png?width=400&amp;amp;height=250&amp;amp;face=0_0_400_250');&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;ListView class - widgets library - Dart API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A scrollable list of widgets arranged linearly. ListView is the most commonly used scrolling widget. It displays its children one after another in the scroll direction. In the cross axis, the children are required to fill the ListView. If non-null, the ite&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.flutter.dev&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;Row 위젯처럼 위에서 아래로 항목들을 나열하는 데 사용한다. 차이점으로는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;ListView 위젯&lt;/b&gt;&lt;/span&gt;은 가장 일반적으로 사용되는 &lt;u&gt;&lt;b&gt;스크롤 위젯&lt;/b&gt;&lt;/u&gt;이라는 것이다. 즉, 스크롤 방향으로 포함하고 있는 위젯들을 차례대로 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. Flutter 이해하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter의 기본 철학은, &lt;b&gt;&quot;위젯들을 레고 블록처럼 다루어 프로그램을 만드는 것&quot;&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;Everything is a Widget&quot;&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;size18&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;위젯 공식 사이트 4가지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;1) Widget catalog&lt;/b&gt;&lt;/span&gt; : 그룹별 사전식으로 정리했다. 14개의 항목으로 Flutter의 위젯을 그룹화하여 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/ui/widgets&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/ui/widgets&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711632263992&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;Widget catalog&quot; data-og-description=&quot;A catalog of some of Flutter's rich set of widgets.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/ui/widgets&quot; data-og-url=&quot;https://docs.flutter.dev/ui/widgets&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdY4dn/hyVDtfh8lV/5BO7ZPl44NrnN27jv1cywK/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/ui/widgets&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/ui/widgets&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdY4dn/hyVDtfh8lV/5BO7ZPl44NrnN27jv1cywK/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461');&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;Widget catalog&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A catalog of some of Flutter's rich set of widgets.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;2) Cookbook&lt;/b&gt; &lt;/span&gt;: 해결하고자 하는 문제를 기준으로 정리했다. 비슷한 기존 사례가 있다면, 어떤 위젯이 있는지 알아보고 흉내내기 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) 애니메이션에 관심이 있는 사람이 Animation 그룹을 선택하면, 어떤 방식으로 Animation을 사용할 수 있는지 사례가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/cookbook&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/cookbook&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711632385196&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;Cookbook&quot; data-og-description=&quot;The Flutter cookbook provides recipes for many commonly performed tasks.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/cookbook&quot; data-og-url=&quot;https://docs.flutter.dev/cookbook&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wUgKL/hyVGJU5hBr/rF8f6hIPz1puSgBjynsXJk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/cookbook&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/cookbook&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wUgKL/hyVGJU5hBr/rF8f6hIPz1puSgBjynsXJk/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461');&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;Cookbook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Flutter cookbook provides recipes for many commonly performed tasks.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;3) Sample 프로그램&lt;/b&gt;&lt;/span&gt; : 다양한 종류의 예제들이 설명과 함께 제공되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EX) &quot;Play and pause a video&quot;에서는 동영상을 재생하는 예제 코드와 함께 주요 부분 설명이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://flutter.github.io/samples/#&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://flutter.github.io/samples/#&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711632486237&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;Flutter samples&quot; data-og-description=&quot;sample Flutter Maps Firestore A Flutter sample app that shows the end product of the Cloud Nex...&quot; data-og-host=&quot;flutter.github.io&quot; data-og-source-url=&quot;https://flutter.github.io/samples/#&quot; data-og-url=&quot;https://flutter.github.io/samples/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://flutter.github.io/samples/#&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter.github.io/samples/#&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;Flutter samples&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;sample Flutter Maps Firestore A Flutter sample app that shows the end product of the Cloud Nex...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flutter.github.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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;4) Flutter widget index&lt;/b&gt;&lt;/span&gt; : 사전처럼 모든 위젯을 알파벳 순으로 나열해 놓았다. 이름 + 간단한 1~2줄의 설명 + 간단히 요약한 그림등으로 구성되어 있어서, 알고 싶은 내용을 바로 찾고 싶다면 직관적인 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/reference/widgets&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/reference/widgets&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711632584433&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;Flutter widget index&quot; data-og-description=&quot;An alphabetical list of Flutter widgets.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/reference/widgets&quot; data-og-url=&quot;https://docs.flutter.dev/reference/widgets&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/booERa/hyVGETLsZ2/LIPCxOnfHbs2JzeBObxP1k/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/EqIe9/hyVDGldSfb/FNA8zOY4Ve81AjbZy6IFs0/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200,https://scrap.kakaocdn.net/dn/BrVN6/hyVGLL7qx1/87kbuPf4Jbg3cMW8KvR5t1/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/reference/widgets&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/reference/widgets&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/booERa/hyVGETLsZ2/LIPCxOnfHbs2JzeBObxP1k/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/EqIe9/hyVDGldSfb/FNA8zOY4Ve81AjbZy6IFs0/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200,https://scrap.kakaocdn.net/dn/BrVN6/hyVGLL7qx1/87kbuPf4Jbg3cMW8KvR5t1/img.png?width=300&amp;amp;height=200&amp;amp;face=0_0_300_200');&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;Flutter widget index&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An alphabetical list of Flutter widgets.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. 두고두고 활용할 레퍼런스 프로그램 개발하기&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;8-1. 기본 레퍼런스 프로그램&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1711696185957&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  final _title = 'Flutter SketchApp';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: const MyStatefulWidget(),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State&amp;lt;MyStatefulWidget&amp;gt; createState() =&amp;gt; MyStatefulWidgetState();
}

class MyStatefulWidgetState extends State&amp;lt;MyStatefulWidget&amp;gt; {
  final PageController _pageController = PageController();

  int _selectedIndex = 0;

  static const TextStyle optionStyle = 
      TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
  
  // 첫번째 프로그램은 화면 중앙의 콘텐츠 영역을 간단한 텍스트로 채운다
  static const List&amp;lt;Widget&amp;gt; _widgetOptions = &amp;lt;Widget&amp;gt;[
    Text(
      'Home',
      style: optionStyle,
    ),
    Text(
      'Hello',
      style: optionStyle,
    ),
    Text(
      'Star',
      style: optionStyle,
    ),
  ];

  @override // 배치하다, 배열하다, 처분하다
  void dispose() {
    _pageController.dispose();
    super.dispose();
    // MyStatefulWidgetState의 Base 클래스에서 dispose() 메서드를 호출한다
    // 화면에서 제거되어야 하는 부분이 있을 때 사용하는 용도이다. 
    // 특별한 작업이 없다면, 코드 그대로 사용한다.
  }

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Sketch Application'),
        ),
        body: PageView(
          controller: _pageController,
          children: &amp;lt;Widget&amp;gt;[
            Scaffold(
              body: Center(
                // elementAt()으로 _widgetOptions의 아이템 중 하나를 보여준다
                child: _widgetOptions.elementAt(_selectedIndex),
              ),
              bottomNavigationBar: BottomNavigationBar(
                items: const &amp;lt;BottomNavigationBarItem&amp;gt;[
                  BottomNavigationBarItem(
                    icon: Icon(Icons.home),
                    label: 'Home',
                  ),
                  BottomNavigationBarItem(
                    icon: Icon(Icons.wb_cloudy),
                    label: 'Hello',
                  ),
                  BottomNavigationBarItem(
                    icon: Icon(Icons.star),
                    label: 'Star',
                  ),
                ],
                currentIndex: _selectedIndex,
                selectedItemColor: Colors.white,
                backgroundColor: Colors.blueAccent, // BottomNavigationBar의 기본 색상
                onTap: _onItemTapped,
              ),
            ),
          ],
        ),
      ),
    );
  }
}


/*
 * REFERENCES
 * https://api.flutter.dev/flutter/widgets/PageController-class.html
 * https://api.flutter.dev/flutter/widgets/PageView-class.html
 * https://api.flutter.dev/flutter/widgets/BottomNavigationBarItem-class.html
 */&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: #1a5490;&quot;&gt;&lt;b&gt;PageController 클래스&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;PageView 위젯&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: #1a5490;&quot;&gt;&lt;b&gt;bottomNavigationBar 프로퍼티&lt;/b&gt;&lt;/span&gt; : 말 그대로 아래(bottom)에 있는 메뉴 선택(navigation) 막대기(bar)다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8-2. 통합 프로그램&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;TextButton 위젯&lt;/b&gt;&lt;/span&gt; : 사용자가 &quot;클릭할 수 있는&quot; 글자를 표시한다.&lt;/p&gt;
&lt;pre id=&quot;code_1711697297452&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return Scaffold(
      body: Center(
        // 사용자가 클릭할 수 있는 글자 : TextButton
        child: TextButton(
          style: TextButton.styleFrom(
            primary: Colors.black,
            textStyle: const TextStyle(
              fontSize: 32,
            ),
          ),
          onPressed: () {
            showAlertDialog(context);
          },
          child: const Text('Hello, Press Here!'),
        ),
      ),
    );&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: #1a5490;&quot;&gt;&lt;b&gt;showDialog() 함수&lt;/b&gt;&lt;/span&gt; : Material Design 앱의 화면 상단에, 개발자가 설정한 대로 다이얼로그 박스를 나타낸다.&lt;/p&gt;
&lt;pre id=&quot;code_1711697434858&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void showAlertDialog(BuildContext context) async {
  String result = await showDialog(
    context: context,
    barrierDismissible: false,
    builder: (BuildContext context) {
      return AlertDialog(
        title: const Text('AlertDialog Sample'),
        content: const Text(&quot;Select button you want&quot;),
        actions: &amp;lt;Widget&amp;gt;[
          TextButton(
            child: const Text('OK'),
            onPressed: () {
              Navigator.pop(context, &quot;OK&quot;);
            },
          ),
          TextButton(
            child: const Text('Cancel'),
            onPressed: () {
              Navigator.pop(context, &quot;Cancel&quot;);
            },
          ),
        ],
      );
    },
  );

  print(&quot;showAlertDialog(): $result&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: #1a5490;&quot;&gt;&lt;b&gt;context 파라미터&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: #1a5490;&quot;&gt;&lt;b&gt;barrierDismissible&lt;/b&gt;&lt;/span&gt; : 다이얼로그 박스 밖의 화면을 클릭하면, 다이얼로그 박스를 없앨지 결정하는 파라미터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;there4; false라면, 반드시 다이얼로그의 버튼을 클릭해야 사라진다.&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: #1a5490;&quot;&gt;&lt;b&gt;builder&lt;/b&gt; &lt;/span&gt;: 화면에 다이얼로그 박스를 나타내고, 사용자의 입력에 맞춰 동작하도록 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 간단하게 AlertDialog() 객체를 만들어서 리턴한다.&lt;/p&gt;
&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: #1a5490;&quot;&gt;&lt;b&gt;AlertDialog&lt;/b&gt;&lt;/span&gt; : 제목을 title 에서 정의한다. 사용자에게 안내할 내용은 content에서 정의한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 선택에 맞춰서 반응할 메뉴는 actions에서 정의한다. 예제에서는 TextButton 2개를 담은 &amp;lt;Widget&amp;gt;리스트를 추가했다. 선택한 값은 String result에 저장되므로, 필요한 곳에서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;9. 스마트폰에서 Flutter 프로그램 실행하기&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;9-1. Flutter 공식 사이트의 Get started 완주하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flutter 공식 사이트의 Get started&lt;/b&gt; : &lt;a href=&quot;https://docs.flutter.dev/get-started/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/get-started/install&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711698399002&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;Choose your development platform to get started&quot; data-og-description=&quot;Install Flutter and get started. Downloads available for Windows, macOS, Linux, and ChromeOS operating systems.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/get-started/install&quot; data-og-url=&quot;https://docs.flutter.dev/get-started/install&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b3Z9ri/hyVGMLheny/IZywfzpJOvgzk0clIYZ9n0/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/get-started/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/get-started/install&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b3Z9ri/hyVGMLheny/IZywfzpJOvgzk0clIYZ9n0/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461');&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;Choose your development platform to get started&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Install Flutter and get started. Downloads available for Windows, macOS, Linux, and ChromeOS operating systems.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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;Flutter 한국어 공식 사이트 (적용 안된듯?)&lt;/b&gt; : &lt;a href=&quot;https://flutter-ko.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://flutter-ko.dev/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711698888249&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;Flutter documentation&quot; data-og-description=&quot;Get started with Flutter. Widgets, examples, updates, and API docs to help you write your first Flutter app.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://flutter-ko.dev/&quot; data-og-url=&quot;https://docs.flutter.dev/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cOFDAR/hyVGMkfa4i/qtuIyvATMzhPinQrd8zGKK/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461&quot;&gt;&lt;a href=&quot;https://flutter-ko.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter-ko.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cOFDAR/hyVGMkfa4i/qtuIyvATMzhPinQrd8zGKK/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461');&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;Flutter documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Get started with Flutter. Widgets, examples, updates, and API docs to help you write your first Flutter app.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eOuWcP/btsGefhvUwq/mX2FEipEP9IfkJI22N6M4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eOuWcP/btsGefhvUwq/mX2FEipEP9IfkJI22N6M4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eOuWcP/btsGefhvUwq/mX2FEipEP9IfkJI22N6M4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeOuWcP%2FbtsGefhvUwq%2FmX2FEipEP9IfkJI22N6M4k%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;641&quot; height=&quot;171&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;210&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;</description>
      <category>Dart와 Flutter/도서 내용 정리</category>
      <category>DART</category>
      <category>Flutter</category>
      <category>안드로이드</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/250</guid>
      <comments>https://sweetburble.tistory.com/250#entry250comment</comments>
      <pubDate>Sun, 24 Mar 2024 23:57:06 +0900</pubDate>
    </item>
    <item>
      <title>대패 된장구이 덮밥 - [자취요리신 식]</title>
      <link>https://sweetburble.tistory.com/249</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[레시피 변경점]&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;그 외 변경점 없음!&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: #1a5490;&quot;&gt;&lt;b&gt;[난이도]&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;[맛 평가]&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;간단하게 말하자면 맛있었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;edited_20240322_대패된장구이덮밥_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vF65X/btsF0lCi8Sj/R02KdkkbodzRhsvnbrbAU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vF65X/btsF0lCi8Sj/R02KdkkbodzRhsvnbrbAU1/img.png&quot; data-alt=&quot;대패 된장구이 덮밥&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vF65X/btsF0lCi8Sj/R02KdkkbodzRhsvnbrbAU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvF65X%2FbtsF0lCi8Sj%2FR02KdkkbodzRhsvnbrbAU1%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;500&quot; height=&quot;395&quot; data-filename=&quot;edited_20240322_대패된장구이덮밥_자취요리신.webp&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대패 된장구이 덮밥&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=vLWNlxVMdg8&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/catpvK/hyVDtq9SKD/7OXLw1muUM1GD9DNFX1iKK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;제발 대패삼겹살 이렇게 드셔보세요&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/vLWNlxVMdg8&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>요리</category>
      <category>자취요리</category>
      <author>달거달거</author>
      <guid isPermaLink="true">https://sweetburble.tistory.com/249</guid>
      <comments>https://sweetburble.tistory.com/249#entry249comment</comments>
      <pubDate>Fri, 22 Mar 2024 10:48:07 +0900</pubDate>
    </item>
  </channel>
</rss>