<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코딩하는 일용직 노동자</title>
    <link>https://bacassf.tistory.com/</link>
    <description>안드로이드 일용직 노동자.</description>
    <language>ko</language>
    <pubDate>Sat, 25 Jul 2026 22:40:07 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>bacass</managingEditor>
    <image>
      <title>코딩하는 일용직 노동자</title>
      <url>https://tistory1.daumcdn.net/tistory/3884260/attach/11bb5cd7c8fd47efae1a943f9bbf74e1</url>
      <link>https://bacassf.tistory.com</link>
    </image>
    <item>
      <title>터미널 열 때마다 감성 폭발! '네오패치(neofetch)' 설치 및 설정 가이드</title>
      <link>https://bacassf.tistory.com/252</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-12 오전 1.20.30.png&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l2pK1/dJMcacCJPrB/CRtFXawEBaxIN046og6aok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l2pK1/dJMcacCJPrB/CRtFXawEBaxIN046og6aok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l2pK1/dJMcacCJPrB/CRtFXawEBaxIN046og6aok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl2pK1%2FdJMcacCJPrB%2FCRtFXawEBaxIN046og6aok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;483&quot; height=&quot;366&quot; data-filename=&quot;스크린샷 2026-04-12 오전 1.20.30.png&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;터미널을 켤 때마다 내 맥북의 사양과 로고가 멋지게 나타나도록 설정하는 방법을 알려드리겠습니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;1. iTerm2 실행하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;먼저 기본 터미널보다 기능이 다양한 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;5&quot;&gt;iTerm2&lt;/b&gt; 패키지를 열어줍니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6&quot;&gt;2. neofetch 설치하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;맥북의 시스템 정보를 요약해서 보여주는 neofetch를 설치해야 합니다. 터미널에 아래 명령어를 입력해 주세요. (Homebrew가 설치되어 있어야 합니다!)&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiqmLn6n-WTAxUAAAAAHQAAAAAQ8gM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;brew install neofetch
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9&quot;&gt;이미 설치하신 분들은 brew cleanup neofetch로 정리해 주셔도 좋습니다.&lt;/i&gt;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10&quot;&gt;3. zsh 설정 파일 수정하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;터미널을 실행할 때마다 자동으로 neofetch가 실행되도록 설정 파일을 수정해야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;터미널에 vi ~/.zshrc를 입력해 설정 파일을 엽니다.&lt;/li&gt;
&lt;li&gt;파일의 &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;12,1,0&quot;&gt;맨 마지막 줄&lt;/b&gt;로 이동합니다.&lt;/li&gt;
&lt;li&gt;키보드에서 i를 눌러 수정 모드(INSERT)로 진입합니다.&lt;/li&gt;
&lt;li&gt;가장 하단에 neofetch라고 직접 입력해 줍니다.&lt;/li&gt;
&lt;li&gt;입력이 끝났다면 ESC를 누른 뒤, :wq를 입력하고 엔터를 눌러 저장하고 나옵니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-12 오전 1.21.43.png&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnowBU/dJMcacQfRtI/qKouNBpO2fHhrowqUNr4O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnowBU/dJMcacQfRtI/qKouNBpO2fHhrowqUNr4O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnowBU/dJMcacQfRtI/qKouNBpO2fHhrowqUNr4O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnowBU%2FdJMcacQfRtI%2FqKouNBpO2fHhrowqUNr4O1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;271&quot; data-filename=&quot;스크린샷 2026-04-12 오전 1.21.43.png&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13&quot;&gt;4. 설정 적용 및 확인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;이제 iTerm2를 완전히 종료했다가 다시 켜보세요! 짜잔! 화면에 맥북 로고와 함께 상세 정보가 멋지게 출력되는 것을 확인하실 수 있습니다.&lt;/p&gt;</description>
      <category>기타</category>
      <category>iTerm</category>
      <category>neofetch</category>
      <category>terminal</category>
      <category>네오패치</category>
      <category>터미널</category>
      <author>bacass</author>
      <guid isPermaLink="true">https://bacassf.tistory.com/252</guid>
      <comments>https://bacassf.tistory.com/252#entry252comment</comments>
      <pubDate>Sun, 12 Apr 2026 01:23:21 +0900</pubDate>
    </item>
    <item>
      <title>[Android/UI] 나인패치(9-Patch) 이미지, 웹에서 1분 만에 만들기</title>
      <link>https://bacassf.tistory.com/251</link>
      <description>&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 앱을 개발하다 보면 버튼이나 말풍선처럼 &lt;b data-index-in-node=&quot;28&quot; data-path-to-node=&quot;3&quot;&gt;콘텐츠 길이에 따라 크기가 변해야 하는 이미지&lt;/b&gt;를 다룰 때가 많습니다. 이때 사용하는 것이 바로 .9.png, 즉 &lt;b data-index-in-node=&quot;91&quot; data-path-to-node=&quot;3&quot;&gt;나인패치(9-Patch)&lt;/b&gt; 파일입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 스튜디오 내장 에디터도 훌륭하지만, 별도의 툴을 켜지 않고 브라우저에서 직관적으로 작업하고 싶을 때 가장 추천하는 사이트가 바로 **[Android Asset Studio - Nine-Patch Generator]**입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 이 사이트를 활용해 나인패치 이미지를 만드는 방법을 상세히 정리해 보겠습니다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;6&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;1. 사이트 접속 및 이미지 업로드&lt;/h3&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;먼저 아래 링크로 접속합니다.   &lt;a href=&quot;https://romannurik.github.io/AndroidAssetStudio/nine-patches.html&quot; data-ved=&quot;0CAAQ_4QMahgKEwjfmrCfm92TAxUAAAAAHQAAAAAQhAM&quot; data-hveid=&quot;0&quot;&gt;Android Asset Studio - Nine-Patch Generator&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;사이트에 접속하면 상단의 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;9&quot;&gt;'Source'&lt;/b&gt; 버튼을 누르거나, 준비한 .png 파일을 브라우저 화면으로 직접 &lt;b data-index-in-node=&quot;59&quot; data-path-to-node=&quot;9&quot;&gt;드래그 앤 드롭&lt;/b&gt;하여 업로드할 수 있습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&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;스크린샷 2026-04-10 오후 5.55.31.png&quot; data-origin-width=&quot;3008&quot; data-origin-height=&quot;1304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wvZKy/dJMcabw1dwU/93Z7ozkXo7pqNPccfra4J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wvZKy/dJMcabw1dwU/93Z7ozkXo7pqNPccfra4J0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wvZKy/dJMcabw1dwU/93Z7ozkXo7pqNPccfra4J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwvZKy%2FdJMcabw1dwU%2F93Z7ozkXo7pqNPccfra4J0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3008&quot; height=&quot;1304&quot; data-filename=&quot;스크린샷 2026-04-10 오후 5.55.31.png&quot; data-origin-width=&quot;3008&quot; data-origin-height=&quot;1304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;2. 주요 기능 및 옵션 설정&lt;/h3&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;이미지를 올렸다면, 왼쪽 설정 패널에서 다음 항목들을 조정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;Source Density:&lt;/b&gt; 원본 이미지의 밀도(mdpi, hdpi, xhdpi 등)를 선택합니다. 보통 고해상도로 작업했다면 xhdpi나 xxhdpi를 선택하면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;Padding:&lt;/b&gt; 콘텐츠가 들어갈 안쪽 여백을 설정합니다. 나중에 이미지 위의 텍스트가 그림자에 겹치지 않게 조절하는 핵심 단계입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;Optical Bounds:&lt;/b&gt; (필요 시) 이미지의 시각적인 경계를 보정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;3. 스트레치(Stretch) 영역 지정하기 (가장 중요!)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;화면 중앙의 에디터에서 이미지 테두리에 있는 &lt;b data-index-in-node=&quot;25&quot; data-path-to-node=&quot;14&quot;&gt;검은색 선&lt;/b&gt;을 마우스로 드래그하여 조정합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;상단/좌측 선:&lt;/b&gt; 이미지가 늘어날 영역을 지정합니다. (그림자가 있는 모서리는 제외하고 중간의 평평한 부분만 지정해야 깨지지 않습니다.)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;하단/우측 선:&lt;/b&gt; 내부 콘텐츠(텍스트 등)가 위치할 영역(Padding)을 지정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;4. 실시간 미리보기 (Preview)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;이 사이트의 가장 큰 장점은 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;17&quot;&gt;오른쪽 미리보기 화면&lt;/b&gt;입니다. 이미지가 가로로 길어질 때, 세로로 길어질 때, 혹은 둘 다 커질 때 &lt;b data-index-in-node=&quot;71&quot; data-path-to-node=&quot;17&quot;&gt;그림자가 왜곡되지는 않는지&lt;/b&gt; 실시간으로 확인할 수 있습니다. 특히 버튼의 둥근 모서리나 은은한 그림자가 유지되는지 꼭 확인하세요!&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size23&quot;&gt;5. 다운로드 및 프로젝트 적용&lt;/h3&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;설정이 끝났다면 우측상단의 &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;19&quot;&gt;[Download ZIP]&lt;/b&gt; 버튼을 누릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다운로드된 압축 파일을 풀면 res/drawable-xhdpi 등 각 해상도 폴더에 맞춰 최적화된 .9.png 파일들이 들어있습니다.&lt;/li&gt;
&lt;li&gt;이 폴더들을 그대로 안드로이드 프로젝트의 res 폴더에 복사해 넣기만 하면 끝입니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>안드로이드</category>
      <category>9patch</category>
      <category>9패치</category>
      <category>NinePatch</category>
      <category>나인패치</category>
      <author>bacass</author>
      <guid isPermaLink="true">https://bacassf.tistory.com/251</guid>
      <comments>https://bacassf.tistory.com/251#entry251comment</comments>
      <pubDate>Fri, 10 Apr 2026 17:56:48 +0900</pubDate>
    </item>
    <item>
      <title>무료 오픈소스 청소 및 최적화 도구 'Mole'</title>
      <link>https://bacassf.tistory.com/250</link>
      <description>&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-22 오후 10.12.47.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsdt62/dJMcagrjxsk/pOyjmrjRXKkJkraRcnsQBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsdt62/dJMcagrjxsk/pOyjmrjRXKkJkraRcnsQBK/img.png&quot; data-alt=&quot;mole&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsdt62/dJMcagrjxsk/pOyjmrjRXKkJkraRcnsQBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsdt62%2FdJMcagrjxsk%2FpOyjmrjRXKkJkraRcnsQBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;601&quot; height=&quot;344&quot; data-filename=&quot;스크린샷 2026-03-22 오후 10.12.47.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;mole&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. Mole 앱 소개&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Mole은 터미널 기반의 오픈소스 도구로, 유료 앱인 CleanMyMac이나 DaisyDisk의 기능을 무료로 대체할 수 있는 강력한 앱입니다. 광고나 구독료 없이 시스템 청소, 앱 완전 삭제, 디스크 분석, 시스템 모니터링 기능을 모두 제공합니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 설치 방법&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Mole을 설치하기 위해서는 먼저 Mac의 패키지 관리자인 &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;'Homebrew'&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가 설치되어 있어야 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1단계: Homebrew 설치 (이미 설치되어 있다면 건너뛰세요)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;터미널(Terminal) 앱을 열고 아래 명령어를 복사해서 붙여넣은 뒤 엔터를 누릅니다. (암호 입력 필요)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1774185075742&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2단계: Mole 설치&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Homebrew 설치가 완료되었다면, 터미널에 다음 명령어를 입력하여 Mole을 설치합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1774185062807&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install mole&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 실행 방법&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설치가 완료되었다면 실행은 매우 간단합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;터미널을 엽니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;mo 또는 mole 이라고 입력하고 엔터를 누릅니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;화살표 키로 메뉴를 이동하고, 엔터 키로 원하는 기능을 선택하여 실행합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 주요 기능 (Main Features)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Clean Your Mac (청소): 시스템 로그, 캐시, 휴지통 등을 스캔하여 불필요한 파일을 제거하고 기가바이트 단위의 용량을 확보합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Uninstall (앱 삭제): 단순히 앱만 지우는 것이 아니라, 시스템 곳곳에 남은 잔여 파일(Leftover)까지 찾아내어 완벽하게 제거합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Optimize (최적화): DNS 캐시 플러시 등 시스템 성능을 개선하는 다양한 스크립트를 실행하여 Mac을 더 쾌적하게 만듭니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Analyze (디스크 분석): 어떤 폴더나 파일이 용량을 많이 차지하는지 시각적인 그래프로 보여줍니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Status (상태 모니터링): CPU, 메모리, 네트워크, 배터리 상태 등을 실시간으로 확인할 수 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-23 오전 9.24.51.png&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nzhaQ/dJMcadnNt3U/R7fNVMZUrv4LeOqSJmgI91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nzhaQ/dJMcadnNt3U/R7fNVMZUrv4LeOqSJmgI91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nzhaQ/dJMcadnNt3U/R7fNVMZUrv4LeOqSJmgI91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnzhaQ%2FdJMcadnNt3U%2FR7fNVMZUrv4LeOqSJmgI91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;374&quot; data-filename=&quot;스크린샷 2026-03-23 오전 9.24.51.png&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;a href=&quot;https://github.com/tw93/mole&quot;&gt;https://github.com/tw93/mole&lt;/a&gt;&lt;/div&gt;
&lt;figure id=&quot;og_1774185332605&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 - tw93/Mole:   Deep clean and optimize your Mac.&quot; data-og-description=&quot;  Deep clean and optimize your Mac. Contribute to tw93/Mole development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/tw93/mole&quot; data-og-url=&quot;https://github.com/tw93/Mole&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pG2jf/dJMb8QelxqH/dckJLW6KGkEuvQ4injCVZk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/PIkYD/dJMb8SXw6Fr/jjZKvRSv5n8zjzfUkO5ld1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/fPg2S/dJMb8SpHi0F/tEMHFrFIN008qVqd9700m1/img.jpg?width=5090&amp;amp;height=2856&amp;amp;face=0_0_5090_2856&quot;&gt;&lt;a href=&quot;https://github.com/tw93/mole&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/tw93/mole&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pG2jf/dJMb8QelxqH/dckJLW6KGkEuvQ4injCVZk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/PIkYD/dJMb8SXw6Fr/jjZKvRSv5n8zjzfUkO5ld1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/fPg2S/dJMb8SpHi0F/tEMHFrFIN008qVqd9700m1/img.jpg?width=5090&amp;amp;height=2856&amp;amp;face=0_0_5090_2856');&quot;&gt;&amp;nbsp;&lt;/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 - tw93/Mole:   Deep clean and optimize your Mac.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  Deep clean and optimize your Mac. Contribute to tw93/Mole 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;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-22 오후 10.14.28.png&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m7q4I/dJMcac3u0dk/0KVEPoFDg3tvRklRJ1sSvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m7q4I/dJMcac3u0dk/0KVEPoFDg3tvRklRJ1sSvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m7q4I/dJMcac3u0dk/0KVEPoFDg3tvRklRJ1sSvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm7q4I%2FdJMcac3u0dk%2F0KVEPoFDg3tvRklRJ1sSvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;331&quot; data-filename=&quot;스크린샷 2026-03-22 오후 10.14.28.png&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>기타</category>
      <category>APP</category>
      <category>Cleaner</category>
      <category>CleanMyMac</category>
      <category>Mac</category>
      <category>mole</category>
      <category>remove</category>
      <category>몰</category>
      <category>클린마이맥</category>
      <author>bacass</author>
      <guid isPermaLink="true">https://bacassf.tistory.com/250</guid>
      <comments>https://bacassf.tistory.com/250#entry250comment</comments>
      <pubDate>Sun, 22 Mar 2026 22:13:20 +0900</pubDate>
    </item>
    <item>
      <title>[Android] 레이아웃의 유연함을 더해주는 ConstraintLayout 'Barrier'</title>
      <link>https://bacassf.tistory.com/249</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;667&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VyWSR/dJMcad1U2BX/muv2ZS9X3vKxxqgIj2iDpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VyWSR/dJMcad1U2BX/muv2ZS9X3vKxxqgIj2iDpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VyWSR/dJMcad1U2BX/muv2ZS9X3vKxxqgIj2iDpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVyWSR%2FdJMcad1U2BX%2Fmuv2ZS9X3vKxxqgIj2iDpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;449&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;667&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드 앱 UI를 개발하다 보면, 여러 개의 뷰 중에서 **&lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;가장 긴(또는 높은) 뷰&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;**를 기준으로 다른 뷰를 배치해야 하는 상황이 생깁니다. 이때 유용하게 사용할 수 있는 Barrier에 대해 알아보겠습니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. Barrier란 무엇인가?&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Barrier(배리어)는 말 그대로 **&lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;'가상의 벽'&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;**입니다. ConstraintLayout에서 제공하는 Helper 객체로, 화면에 직접 나타나지는 않지만 여러 개의 뷰를 묶어서 그 뷰들의 경계선을 동적으로 만들어 줍니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 왜 사용해야 할까? (Guideline과의 차이)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Guideline: 특정 수치(dp)나 퍼센트(%)로 고정된 선을 만듭니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Barrier: 참조하는 뷰들의 크기에 따라 위치가 가변적입니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어, &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;'이름'&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;과 &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;'전화번호'&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라는 두 개의 TextView가 있을 때, 어떤 것이 더 길어질지 모르는 상황에서 그 오른쪽에 아이콘을 배치하고 싶을 때 사용합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 주요 속성&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;app:barrierDirection: 벽을 세울 방향을 결정합니다. (top, bottom, left, right, start, end)&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;app:constraint_referenced_ids: 이 벽이 참조할 뷰들의 ID를 쉽표(,)로 구분해서 적습니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;app:barrierAllowsGoneWidgets: (선택) 참조하는 뷰가 Gone 상태가 되었을 때 어떻게 처리할지 결정합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 실제 구현 코드&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래 코드는 tv_name과 tv_phone 중 더 긴 쪽의 오른쪽에 btn_next 버튼을 배치하는 예시입니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1770343568796&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;TextView
android:id=&quot;@+id/tv_name&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
android:text=&quot;User Name&quot; /&amp;gt;

&amp;lt;TextView
android:id=&quot;@+id/tv_phone&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
app:layout_constraintTop_toBottomOf=&quot;@id/tv_name&quot;
android:text=&quot;Phone Number&quot; /&amp;gt;

&amp;lt;androidx.constraintlayout.widget.Barrier
android:id=&quot;@+id/barrier_profile&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
app:barrierDirection=&quot;end&quot;
app:constraint_referenced_ids=&quot;tv_name, tv_phone&quot; /&amp;gt;

&amp;lt;Button
android:id=&quot;@+id/btn_next&quot;
android:layout_width=&quot;wrap_content&quot;
android:layout_height=&quot;wrap_content&quot;
app:layout_constraintStart_toEndOf=&quot;@id/barrier_profile&quot;
android:text=&quot;NEXT&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5. 활용 팁 및 마무리&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다국어 대응: 한국어로는 짧은 단어가 영어로나 독일어로는 매우 길어질 수 있습니다. Barrier를 사용하면 레이아웃이 겹치는 현상을 방지할 수 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가상 뷰: Barrier는 뷰 계층 구조에는 존재하지만 실제로 그려지지는 않으므로 성능에 큰 영향을 주지 않습니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리하며: 고정된 레이아웃보다는 데이터의 변화에 유연하게 대응하는 UI가 좋은 UX를 만듭니다. 복잡한 LinearLayout 중첩 대신 ConstraintLayout의 Barrier를 활용해 보세요!&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>안드로이드</category>
      <category>Android</category>
      <category>barrier</category>
      <category>layout</category>
      <category>xml</category>
      <category>레이아웃</category>
      <author>bacass</author>
      <guid isPermaLink="true">https://bacassf.tistory.com/249</guid>
      <comments>https://bacassf.tistory.com/249#entry249comment</comments>
      <pubDate>Fri, 6 Feb 2026 11:07:10 +0900</pubDate>
    </item>
    <item>
      <title>MVI 라이브러리 Orbit</title>
      <link>https://bacassf.tistory.com/248</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;orbitlogo.png&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sp8rC/dJMcabv45sV/K1H7vlkYp1Y11b01vDijtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sp8rC/dJMcabv45sV/K1H7vlkYp1Y11b01vDijtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sp8rC/dJMcabv45sV/K1H7vlkYp1Y11b01vDijtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsp8rC%2FdJMcabv45sV%2FK1H7vlkYp1Y11b01vDijtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;378&quot; data-filename=&quot;orbitlogo.png&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/orbit-mvi/orbit-mvi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/orbit-mvi/orbit-mvi&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767147224636&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 - orbit-mvi/orbit-mvi: A simple MVI framework for Kotlin Multiplatform and Android&quot; data-og-description=&quot;A simple MVI framework for Kotlin Multiplatform and Android - orbit-mvi/orbit-mvi&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/orbit-mvi/orbit-mvi&quot; data-og-url=&quot;https://github.com/orbit-mvi/orbit-mvi&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bsSjqj/hyZQqrsMJC/HCwBamW0JskIKSSQf6QtqK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/JN0qG/hyZQvsKEiu/9VHT5Vxdhl90BFpMTN8J41/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://github.com/orbit-mvi/orbit-mvi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/orbit-mvi/orbit-mvi&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bsSjqj/hyZQqrsMJC/HCwBamW0JskIKSSQf6QtqK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/JN0qG/hyZQvsKEiu/9VHT5Vxdhl90BFpMTN8J41/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 - orbit-mvi/orbit-mvi: A simple MVI framework for Kotlin Multiplatform and Android&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A simple MVI framework for Kotlin Multiplatform and Android - orbit-mvi/orbit-mvi&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;MVI 패턴을 쉽고 안전하게 구현할 수 있도록 도와주는 라이브러리이다.&lt;/p&gt;
&lt;pre id=&quot;code_1767147484985&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class CalculatorState(
    val total: Int = 0
)

sealed class CalculatorSideEffect {
    data class Toast(val text: String) : CalculatorSideEffect()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767147500116&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CalculatorViewModel: ContainerHost&amp;lt;CalculatorState, CalculatorSideEffect&amp;gt;, ViewModel() {

    // Include `orbit-viewmodel` for the factory function
    override val container = container&amp;lt;CalculatorState, CalculatorSideEffect&amp;gt;(CalculatorState())

    fun add(number: Int) = intent {
        postSideEffect(CalculatorSideEffect.Toast(&quot;Adding $number to ${state.total}!&quot;))

        reduce {
            state.copy(total = state.total + number)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767147514915&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CalculatorActivity: AppCompatActivity() {

    // Example of injection using koin, your DI system might differ
    private val viewModel by viewModel&amp;lt;CalculatorViewModel&amp;gt;()

    override fun onCreate(savedState: Bundle?) {
        ...

        addButton.setOnClickListener { viewModel.add(1234) }

        viewModel.observe(state = ::render, sideEffect = ::handleSideEffect)
    }

    private fun render(state: CalculatorState) {
        ...
    }

    private fun handleSideEffect(sideEffect: CalculatorSideEffect) {
        when (sideEffect) {
            is CalculatorSideEffect.Toast -&amp;gt; toast(sideEffect.text)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767147529070&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun CalculatorScreen(viewModel: CalculatorViewModel) {

    val state = viewModel.collectAsState().value

    viewModel.collectSideEffect { handleSideEffect(it) }

    // render UI using data from 'state'
    ...
}

private fun handleSideEffect(sideEffect: CalculatorSideEffect) {
    when (sideEffect) {
        is CalculatorSideEffect.Toast -&amp;gt; toast(sideEffect.text)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드</category>
      <category>Android</category>
      <category>compose</category>
      <category>mvi</category>
      <category>mvi패턴</category>
      <category>안드로이드</category>
      <author>bacass</author>
      <guid isPermaLink="true">https://bacassf.tistory.com/248</guid>
      <comments>https://bacassf.tistory.com/248#entry248comment</comments>
      <pubDate>Wed, 31 Dec 2025 11:15:45 +0900</pubDate>
    </item>
    <item>
      <title>Hilt, 5분 만에 정복하기 (feat. 배달 서비스 비유)</title>
      <link>https://bacassf.tistory.com/247</link>
      <description>&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 개발을 하다 보면 **DI(Dependency Injection, 의존성 주입)**라는 벽에 부딪힙니다. &quot;Dagger2는 너무 어렵다&quot;, &quot;보일러 플레이트 코드가 너무 많다&quot;는 이야기를 들어보셨을 겁니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;Google이 만든 &lt;b&gt;Hilt&lt;/b&gt;는 이 어려운 DI를 아주 쉽게 만들어준 도구입니다. 오늘은 복잡한 이론 대신, **&quot;배달 서비스&quot;**에 비유해서 Hilt의 핵심 사용법을 딱 4단계로 정리해 보겠습니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  Hilt 핵심 개념: &quot;자동 배달 서비스&quot;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;Hilt를 사용하는 것은 &lt;b&gt;요리(객체 생성)를 직접 하지 않고, 배달(주입)시키는 것&lt;/b&gt;과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;나(Activity):&lt;/b&gt; &quot;이거 갖다줘(@Inject)&quot;라고 주문만 하면 됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Hilt:&lt;/b&gt; 주문받은 객체를 문 앞까지 알아서 배달해줌.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;1단계: 배달 앱 켜기 (초기 설정)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;Hilt를 사용하려면 가장 먼저 앱 전체에 &quot;우리 이제 Hilt 배달 서비스 씁니다&quot;라고 알려야 합니다. Application 클래스를 만들고 &lt;b&gt;@HiltAndroidApp&lt;/b&gt; 어노테이션을 붙입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765698968811&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MyApplication.kt
import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp // 필수! Hilt 서비스를 시작하는 버튼
class MyApplication : Application()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의:&lt;/b&gt; AndroidManifest.xml의 &amp;lt;application&amp;gt; 태그 내 android:name=&quot;.MyApplication&quot; 속성 지정도 잊지 마세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;2단계: 메뉴판 만들기 (객체 생성 방법 정의)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;Hilt가 배달을 하려면, 그 객체를 &lt;b&gt;어떻게 만드는지&lt;/b&gt; 알아야 합니다. 특히 Retrofit, Room 같은 라이브러리나 Interface는 Hilt가 만드는 법을 모르기 때문에 **모듈(Module)**을 통해 알려줘야 합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjZtb6HpLyRAxUAAAAAHQAAAAAQmgY&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1765699001778&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// NetworkModule.kt
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class) // 이 메뉴는 앱이 죽을 때까지 제공됩니다 (싱글톤 스코프)
object NetworkModule {

    @Provides // 만드는 방법 제공
    @Singleton // 인스턴스 하나만 만들어서 계속 재사용
    fun provideMyService(): MyService {
        return Retrofit.Builder()
            .baseUrl(&quot;https://api.example.com&quot;)
            .build()
            .create(MyService::class.java)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;3단계: 배달 받을 집 표시 (진입점 설정)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;모든 곳에 배달해 주지는 않습니다. 액티비티나 프래그먼트에서 Hilt를 쓰려면 **&quot;여기는 Hilt 배달 가능 지역입니다&quot;**라고 문패를 달아야 합니다. 바로 **@AndroidEntryPoint**입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765699024877&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MainActivity.kt
import dagger.hilt.android.AndroidEntryPoint
import androidx.appcompat.app.AppCompatActivity

@AndroidEntryPoint // 필수! 여기로 배달해주세요
class MainActivity : AppCompatActivity() {
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size23&quot;&gt;4단계: 주문하기 (의존성 주입)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;이제 준비는 끝났습니다. 변수를 선언하고 **@Inject**만 붙이면 Hilt가 알아서 객체를 채워줍니다. 직접 = MyService() 처럼 초기화할 필요가 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765699045010&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject // 주문! (Hilt가 알아서 채워줌)
    lateinit var myService: MyService 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // myService는 이미 주입되어 있음. 바로 사용 가능!
        myService.doSomething()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;실전: ViewModel에 적용하기&lt;/h3&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 개발의 꽃, ViewModel은 조금 방식이 다릅니다. 필드 주입이 아니라 &lt;b&gt;생성자 주입&lt;/b&gt;을 사용합니다. (이게 Hilt의 표준 방식입니다.)&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. ViewModel 정의 (@HiltViewModel)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765699069008&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MainViewModel.kt
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel // 나 Hilt 뷰모델이야
class MainViewModel @Inject constructor( // 생성자로 주입받음
    private val myService: MyService
) : ViewModel() {
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Activity에서 호출 (by viewModels())&lt;/b&gt; 별도의 Factory를 만들 필요 없이, 아래 한 줄이면 끝납니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765699089542&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    
    // Hilt가 주입한 뷰모델을 가져옴
    private val viewModel: MainViewModel by viewModels() 
}&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;Hilt, 일단 이 4단계 패턴으로 적용해 보면서 더 깊은 기능(Scope, Custom Component 등)을 익혀가시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드</category>
      <category>DAGGER</category>
      <category>DependencyInjection</category>
      <category>DI</category>
      <category>hilt</category>
      <category>의존성주입</category>
      <category>힐트</category>
      <author>bacass</author>
      <guid isPermaLink="true">https://bacassf.tistory.com/247</guid>
      <comments>https://bacassf.tistory.com/247#entry247comment</comments>
      <pubDate>Sun, 14 Dec 2025 17:00:00 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 클린 아키텍처, 쉽게 이해하기.</title>
      <link>https://bacassf.tistory.com/246</link>
      <description>&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드 개발을 하다 보면 한 번쯤 마주치는 거대한 벽, 바로 **&lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;'클린 아키텍처(Clean Architecture)'&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;**입니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;도메인이 어떻고, 의존성이 어떻고...&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 이론으로만 접근하면 너무 어렵게 느껴지는데요. &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오늘은 이 클린 아키텍처를 우리가 흔히 가는 **&lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;'고급 레스토랑'&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;**에 비유해서 아주 쉽게 정리해 보려 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 글을 다 읽고 나면, 머릿속에 아키텍처의 구조가 그림처럼 그려지실 거예요!&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ️ &lt;b&gt;클린 아키텍처란?&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클린 아키텍처는 앱을 **3개의 역할(계층)**로 아주 명확하게 나누는 방법입니다. 서로의 일을 침범하지 않게 해서 수정과 관리를 편하게 만드는 것이 최종 목표죠.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자, 이제 우리 앱을 레스토랑이라고 상상해 봅시다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 레스토랑으로 보는 3가지 계층&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;① 프레젠테이션 계층 (Presentation Layer) =  &amp;zwj;♂️ 웨이터&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하는 일: 손님(사용자)에게 메뉴판(화면)을 보여주고, 주문(터치 입력)을 받습니다. 요리가 완성되면 손님 식탁에 예쁘게 서빙합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특징: 요리하는 법은 전혀 모릅니다. 그저 주방에 &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;주문 들어왔어요!&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라고 소리쳐 알릴 뿐입니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드에서는: Activity, Fragment, ViewModel, XML/Compose&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;② 도메인 계층 (Domain Layer) =  &amp;zwj;  요리사 (핵심!)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하는 일: 주문서(요청)를 보고 레시피(비즈니스 로직)대로 요리를 만듭니다. &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;스테이크는 미디엄으로 굽는다&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;알러지 재료는 뺀다&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 같은 핵심 규칙을 여기서 수행합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특징: 홀에 있는 손님이 남자인지 여자인지, 주문 받은 웨이터가 누구인지 신경 쓰지 않습니다. 오직 &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;'맛있는 요리'&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 만드는 데만 집중합니다. 앱의 심장과 같은 곳입니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드에서는: UseCase, Model(Entity)&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(참고: 이곳에는 안드로이드 관련 코드가 없고, 순수 Java/Kotlin 코드만 존재합니다.)&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;③ 데이터 계층 (Data Layer) =   재료 조달팀&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하는 일: 요리사가 &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;당근 주세요&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 하면 냉장고에서 꺼내오든, 마트에 가서 사 오든(서버 통신, 내부 DB) 어떻게든 재료를 구해와서 요리사에게 건네줍니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특징: 요리사가 이 재료로 무슨 요리를 할지는 관심 없습니다. 오직 신선한 재료를 가져오는 것에만 집중합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드에서는: Repository, DataSource, Retrofit(API), Room(DB)&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 절대 어기면 안 되는 &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;'핵심 규칙'&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; (의존성 규칙)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 구조에서 가장 중요한 규칙은 **&lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;안쪽(요리사)은 바깥쪽(웨이터, 재료팀)을 몰라야 한다&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;**는 것입니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;웨이터(UI) ➡ 요리사(Domain): 웨이터는 요리사를 부를 수 있습니다. 주문을 넣어야 하니까요. (⭕)&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요리사(Domain) ➡ 웨이터(UI): 요리사가 웨이터의 이름이나 오늘 입은 유니폼 색깔을 알 필요가 있을까요? 없습니다. (❌)&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요리사(Domain) ➡ 재료팀(Data): 요리사는 &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;재료 구해줘&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라고 **인터페이스(계약서)**만 던집니다. 실제 재료팀이 이마트를 가는지 홈플러스를 가는지는 모릅니다. (Data 계층이 Domain 계층을 바라보게 구현합니다.)&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  쉽게 말해: 화면(UI) 디자인을 완전히 뜯어고쳐도, 핵심 기능인 요리법(Domain) 코드는 단 한 줄도 수정할 필요가 없어야 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 왜 이렇게 복잡하게 나누나요? (장점)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음엔 파일도 많아지고 복잡해 보이지만, 앱이 커질수록 엄청난 장점을 발휘합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수정이 쉽습니다  ️&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;서버를 바꿀 건데?&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   데이터 계층만 수정하면 됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;화면 디자인 바꿀 건데?&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   프레젠테이션 계층만 바꾸면 됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트가 쉽습니다 ✅&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;화면(UI)이 아직 안 만들어졌어도, 요리사(Domain)의 요리 기능만 따로 테스트해 볼 수 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오래가는 앱을 만듭니다  &lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드 버전이 바뀌거나 새로운 라이브러리가 나와도, 앱의 핵심 로직(Domain)은 안전하게 보호됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  &lt;b&gt;3줄 요약&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Presentation (UI): 화면 보여주기 &amp;amp; 주문 받기&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Domain (Biz Logic): 핵심 기능 수행하기 (가장 중요!)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Data (Repository): 데이터 가져오기&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드</category>
      <category>Android</category>
      <category>architecture</category>
      <category>Clean</category>
      <category>클린아키텍쳐</category>
      <author>bacass</author>
      <guid isPermaLink="true">https://bacassf.tistory.com/246</guid>
      <comments>https://bacassf.tistory.com/246#entry246comment</comments>
      <pubDate>Sun, 14 Dec 2025 15:14:29 +0900</pubDate>
    </item>
    <item>
      <title>Hilt에서 @Provides와 @Binds: 언제, 어떻게 사용해야 할까?</title>
      <link>https://bacassf.tistory.com/245</link>
      <description>&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안드로이드 개발에서 Hilt를 사용하여 의존성 주입(Dependency Injection)을 할 때, 모듈(Module) 내에서 의존성을 제공하는 방법으로 `@Provides`와 `@Binds` 두 가지 애노테이션을 사용합니다. &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;둘 다 의존 객체를 제공하는 역할을 하지만, 사용 목적과 방식, 그리고 성능에서 차이가 있어 상황에 맞게 사용하는 것이 중요합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론부터 말하자면, 인터페이스(interface)와 구현체(implementation)를 묶어주는 단순한 작업에는 `@Binds`를, 복잡한 객체 생성 로직이 필요할 때는 `@Provides`를 사용하는 것이 좋습니다.&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Provides: 객체 생성의 모든 것을 책임진다&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`@Provides`는 Hilt가 직접 생성할 수 없는 타입의 객체를 어떻게 만들어서 제공할지 알려주는 역할을 합니다. &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음과 같은 경우에 주로 사용됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - 외부 라이브러리 객체 생성:** Retrofit, OkHttpClient, Room 데이터베이스처럼 프로젝트 소스 코드에 속하지 않아 생성자에 `@Inject`를 추가할 수 없는 클래스의 인스턴스를 제공해야 할 때 사용합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - 빌더 패턴(Builder Pattern) 사용:** 객체 생성을 위해 빌더 패턴을 사용해야 하는 경우, `@Provides` 함수 본문에 빌더를 통한 생성 로직을 구현할 수 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - 인스턴스 생성 전 조건부 로직 필요:** 특정 조건에 따라 다른 구현체를 제공해야 하는 등 복잡한 로직이 필요한 경우에 유용합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`@Provides` 함수는 반드시 **객체 생성 로직을 포함한 본문(body)을 가지며**, 보통 `object`로 선언된 모듈 안에 위치합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1754805726632&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(&quot;https://api.example.com/&quot;)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Binds: 인터페이스와 구현체를 연결하는 가장 효율적인 방법&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`@Binds`는 **인터페이스에 대한 구현체를 Hilt에게 알려주는, 보다 전문화되고 효율적인 방법**입니다. 특정 인터페이스가 요청될 때 어떤 구현체를 주입해야 할지 지정하는 역할을 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`@Binds`를 사용하기 위한 몇 가지 제약 조건이 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 추상(abstract) 클래스 모듈:** `@Binds` 함수는 반드시 추상 클래스(abstract class)로 선언된 모듈 안에 위치해야 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 추상(abstract) 함수:** 함수 자체도 추상 함수여야 하며, 본문을 가질 수 없습니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - 단일 파라미터:** 함수는 반드시 하나의 파라미터를 가져야 하며, 이 파라미터가 바로 주입될 구현체입니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`@Binds`는 단순히 &lt;/span&gt;&lt;span style=&quot;color: #a31515;&quot;&gt;&quot;이 인터페이스가 필요하면 저 구현체를 사용해&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라고 알려주기만 하므로, Hilt가 더 적은 양의 코드를 생성하게 됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1754805763781&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface UserRepository {
    fun getUser(): User
}

class UserRepositoryImpl @Inject constructor(
    private val remoteDataSource: UserRemoteDataSource
) : UserRepository {
    override fun getUser(): User {
        return remoteDataSource.fetchUser()
    }
}

@Module
@InstallIn(ViewModelComponent::class)
abstract class RepositoryModule {

    @Binds
    abstract fun bindUserRepository(
        userRepositoryImpl: UserRepositoryImpl
    ): UserRepository
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Provides vs. @Binds: 성능 차이는 왜 발생할까?&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 큰 차이점은 **&lt;b&gt;코드 생성량과 런타임 성능&lt;/b&gt;**입니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - @Provides:** Hilt는 `@Provides` 함수를 위해 별도의 팩토리(Factory) 클래스를 생성합니다. &lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 의존성을 주입할 때마다 이 팩토리의 메소드를 호출하여 객체를 생성하므로, 약간의 런타임 오버헤드가 발생할 수 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - @Binds:** `@Binds`는 Hilt에게 단순히 타입 정보만 알려줍니다. &lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Hilt는 이 정보를 바탕으로 별도의 팩토리 클래스 없이 의존성 그래프를 구성합니다. &lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 결과적으로 생성되는 코드가 더 적고 간결하며, 런타임 시 메소드 호출이 줄어들어 더 효율적입니다.&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-10 오후 3.04.39.png&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGPD4C/btsPOJfOgaQ/O1eIqQ5R6vQkYbSy9GUOyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGPD4C/btsPOJfOgaQ/O1eIqQ5R6vQkYbSy9GUOyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGPD4C/btsPOJfOgaQ/O1eIqQ5R6vQkYbSy9GUOyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGPD4C%2FbtsPOJfOgaQ%2FO1eIqQ5R6vQkYbSy9GUOyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1118&quot; height=&quot;450&quot; data-filename=&quot;스크린샷 2025-08-10 오후 3.04.39.png&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 &lt;b&gt;인터페이스와 그 구현체를 1:1로 바인딩하는 경우에는 성능상의 이점을 위해 항상 `@Binds`를 사용하는 것이 권장&lt;/b&gt;됩니다. &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`@Provides`로도 동일한 기능을 구현할 수 있지만, 불필요한 오버헤드가 발생하게 됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>안드로이드</category>
      <category>binds</category>
      <category>DAGGER</category>
      <category>DI</category>
      <category>hilt</category>
      <category>provides</category>
      <category>의존성주입</category>
      <category>힐트</category>
      <author>bacass</author>
      <guid isPermaLink="true">https://bacassf.tistory.com/245</guid>
      <comments>https://bacassf.tistory.com/245#entry245comment</comments>
      <pubDate>Sun, 10 Aug 2025 15:05:16 +0900</pubDate>
    </item>
    <item>
      <title>LaunchedEffect, DisposableEffect, LifecycleEventEffect, LifecycleStartEffect, LifecycleResumeEffect 정리</title>
      <link>https://bacassf.tistory.com/244</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;안드로이드 Jetpack Compose에서 사용하는 주요 Effect Handler들에 대해 각각의 역할과 사용 사례를 중심으로 설명해 드릴게요.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 Effect Handler들은 Composable 함수의 생명주기와는 별개로 실행되어야 하는 작업, 즉 \*\*Side Effect(부수 효과)\*\*를 안전하게 관리하기 위해 사용됩니다. &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어 데이터베이스에 접근하거나, 네트워크 요청을 보내거나, 리스너를 등록하는 등의 작업이 여기에 해당합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. LaunchedEffect&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`LaunchedEffect`는 Composable이 처음으로 화면에 그려질 때(Composition 될 때) **코루틴을 실행**하고 싶을 때 사용합니다. 가장 일반적으로 사용되는 Effect Handler 중 하나입니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * **핵심 개념**: Composable의 생명주기에 맞춰 시작되고, Composable이 화면에서 사라지면 자동으로 취소되는 코루틴 스코프를 제공합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * **주요 사용 사례**:&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * 화면 진입 시 **초기 데이터 로딩** (API 호출)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * **Snackbar나 Toast**를 한 번만 보여주고 싶을 때&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * 특정 조건이 만족되었을 때 \*\*화면 이동(Navigation)\*\*을 하고 싶을 때&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * **동작 방식**:&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * `key`로 지정된 값이 변경될 때마다 기존 코루틴은 **취소**되고, 새로운 코루틴이 시작됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * `key`로 `true`를 사용하면 Composable이 처음 Composition 될 때 **단 한 번만** 실행됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;pre id=&quot;code_1754800906878&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MyScreen(
    scaffoldState: ScaffoldState,
    shouldShowSnackbar: Boolean
) {
    // shouldShowSnackbar 값이 true로 변경될 때마다 스낵바를 보여줍니다.
    if (shouldShowSnackbar) {
        LaunchedEffect(key1 = scaffoldState.snackbarHostState) {
            scaffoldState.snackbarHostState.showSnackbar(
                message = &quot;오류가 발생했습니다.&quot;,
                actionLabel = &quot;확인&quot;
            )
        }
    }
}​&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. DisposableEffect&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`DisposableEffect`는 리소스를 할당하고, Composable이 화면에서 사라질 때 해당 \*\*리소스를 정리(clean-up)\*\*해야 하는 경우에 사용합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * **핵심 개념**: `onDispose` 블록을 통해 정리 로직을 반드시 실행하도록 보장합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * **주요 사용 사례**:&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * `BroadcastReceiver`나 각종 콜백 **리스너를 등록하고 해제**할 때&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * Android `LifecycleObserver`를 추가하고 제거할 때&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * **동작 방식**:&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * `DisposableEffect` 블록 안의 코드는 Composable이 Composition 될 때 실행됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * `onDispose` 블록은 Composable이 Composition에서 벗어나거나, `key`가 변경되어 Effect가 재시작될 때 호출됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1754800853479&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun HomeScreen(
    onStart: () -&amp;gt; Unit,
    onStop: () -&amp;gt; Unit
) {
    val lifecycleOwner = LocalLifecycleOwner.current

    // lifecycleOwner가 변경되지 않는 한 한 번만 실행됩니다.
    DisposableEffect(key1 = lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event -&amp;gt;
            if (event == Lifecycle.Event.ON_START) {
                onStart()
            } else if (event == Lifecycle.Event.ON_STOP) {
                onStop()
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        // Composable이 사라질 때 observer를 정리합니다.
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래의 Effect들은 Android의 `Lifecycle` 이벤트에 좀 더 특화되어 있어 코드를 간결하게 만들어 줍니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. LifecycleEventEffect&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`Lifecycle`의 **모든 이벤트**(`ON_CREATE`, `ON_START`, `ON_RESUME` 등)를 관찰하고, 특정 이벤트가 발생할 때마다 코드를 실행하고 싶을 때 사용합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * **핵심 개념**: `when` 문과 함께 사용하여 각 라이프사이클 이벤트에 맞는 동작을 정의할 수 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1754800793994&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun AnalyticsLogger(user: User) {
    // ON_RESUME 이벤트가 발생할 때마다 로그를 전송합니다.
    LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
        Analytics.logScreenView(&quot;Profile&quot;, user.id)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. LifecycleStartEffect &amp;amp; LifecycleResumeEffect&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이들은 각각 `ON_START` / `ON_STOP` 과 `ON_RESUME` / `ON_PAUSE` **쌍으로 동작하는 로직**을 처리하는 데 특화된 `DisposableEffect`입니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * **핵심 개념**:&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * `LifecycleStartEffect`: `ON_START` 시점에 코드를 실행하고, `ON_STOP` 시점에 `onDispose` 블록을 실행합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * `LifecycleResumeEffect`: `ON_RESUME` 시점에 코드를 실행하고, `ON_PAUSE` 시점에 `onDispose` 블록을 실행합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; * **주요 사용 사례**: `ON_RESUME`일 때 위치 정보 업데이트를 시작하고, `ON_PAUSE`일 때 중단하는 경우처럼 쌍으로 묶이는 작업에 유용합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1754800770079&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun LocationTrackingScreen() {
    // 화면이 사용자에게 보여질 때(ON_RESUME) 위치 추적을 시작하고,
    // 화면이 가려질 때(ON_PAUSE) 추적을 중지합니다.
    LifecycleResumeEffect(Unit) {
        val locationProvider = LocationProvider()
        locationProvider.start()

        onDispose {
        	locationProvider.stop()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-10 오후 1.44.08.png&quot; data-origin-width=&quot;1430&quot; data-origin-height=&quot;898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cui9Fz/btsPO7AL7qu/uZmdrTCzmi1TMrnhrgxylK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cui9Fz/btsPO7AL7qu/uZmdrTCzmi1TMrnhrgxylK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cui9Fz/btsPO7AL7qu/uZmdrTCzmi1TMrnhrgxylK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcui9Fz%2FbtsPO7AL7qu%2FuZmdrTCzmi1TMrnhrgxylK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1430&quot; height=&quot;898&quot; data-filename=&quot;스크린샷 2025-08-10 오후 1.44.08.png&quot; data-origin-width=&quot;1430&quot; data-origin-height=&quot;898&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;br /&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대부분의 일회성 비동기 작업은 `LaunchedEffect`로 충분합니다. &lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;리스너 등록/해제와 같은 명시적인 정리가 필요하면 `DisposableEffect`를, Android 라이프사이클과 더 밀접한 연동이 필요하면&lt;/b&gt; &lt;b&gt;`Lifecycle...Effect` 시리즈를 사용하면 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>안드로이드</category>
      <category>compose</category>
      <category>effect</category>
      <category>컴포즈</category>
      <author>bacass</author>
      <guid isPermaLink="true">https://bacassf.tistory.com/244</guid>
      <comments>https://bacassf.tistory.com/244#entry244comment</comments>
      <pubDate>Sun, 10 Aug 2025 13:43:34 +0900</pubDate>
    </item>
    <item>
      <title>Android 프로젝트에 ktlint + Spotless 적용하기</title>
      <link>https://bacassf.tistory.com/239</link>
      <description>&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-07-27 오후 4.00.49.png&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zGyfw/btsPAnySkJI/hspC8xJiBNroFgclJ998C0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zGyfw/btsPAnySkJI/hspC8xJiBNroFgclJ998C0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zGyfw/btsPAnySkJI/hspC8xJiBNroFgclJ998C0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzGyfw%2FbtsPAnySkJI%2FhspC8xJiBNroFgclJ998C0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;384&quot; data-filename=&quot;스크린샷 2025-07-27 오후 4.00.49.png&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✨ Android 프로젝트에 Ktlint + Spotless로 자동 코드 포맷팅 날개 달기  &lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;안녕하세요! 동료 개발자들과 협업하다 보면 서로 다른 코드 스타일 때문에 골치 아팠던 경험, 다들 한 번쯤 있으시죠?   오늘은 이런 코드 스타일 스트레스를 한 방에 날려버릴 ktlint와 Spotless 조합을 Android 프로젝트에 적용하는 방법을 알아보겠습니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 가이드를 따라오시면, 팀원 모두가 깔끔하고 일관된 코드를 작성하게 되어 코드 리뷰는 즐거워지고, 프로젝트 품질은 쑥쑥 올라갈 거예요!&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  &lt;b&gt;우리의 목표&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 일관된 코드 스타일: 누가 작성하든 항상 같은 모양의 코드를 유지해요.&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 완전 자동화: Ctrl+Alt+L은 이제 그만! 빌드할 때마다 알아서 코드가 예뻐져요.&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 협업 효율 UP: &quot;이 부분은 띄어쓰기가...&quot; 같은 사소한 리뷰는 이제 안녕! 핵심 로직에만 집중해요.&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ️ 1. 준비물 확인하기 (환경 설정)&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;시작하기 전에, 개발 환경이 준비되었는지 확인해 주세요. 최신 버전일수록 좋습니다!&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Android Studio: 최신 버전&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Gradle: 8.0 이상&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kotlin: 1.8 이상&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1753598849765&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;YourProject/
├── app/
│ └── build.gradle.kts #   여기에 설정 추가
├── build.gradle.kts #   여기에 설정 추가
├── gradle/
│ └── libs.versions.toml #   여기에 라이브러리 버전 추가
└── .editorconfig #   여기에 코드 스타일 규칙 추가&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⚙️ 2. Gradle 설정하기&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 본격적으로 Spotless를 우리 프로젝트에 초대해 봅시다. Gradle 설정은 세 단계로 진행됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1단계: 버전 카탈로그에 Spotless 등록 (gradle/libs.versions.toml)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저, 프로젝트에서 사용할 라이브러리 버전을 한곳에서 관리하는 libs.versions.toml 파일에 Spotless와 ktlint를 추가합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1753599059385&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[versions]
spotless = &quot;6.25.0&quot;
ktlint = &quot;1.0.1&quot; # Spotless가 내부적으로 사용할 ktlint 버전

[plugins]
# 'spotless'라는 별명으로 플러그인을 등록합니다.
spotless = { id = &quot;com.diffplug.spotless&quot;, version.ref = &quot;spotless&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2단계: 최상위 build.gradle.kts 파일 수정&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트 전체에서 Spotless 플러그인을 인식할 수 있도록 최상위 build.gradle.kts에 플러그인 별명을 추가해 주세요.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1753599084818&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Top-level build file
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
    alias(libs.plugins.spotless) apply false //   이 줄을 추가해 주세요!
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3단계: 앱 모듈 build.gradle.kts 파일에 Spotless 설정 추가&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 중요한 단계입니다! app 모듈의 build.gradle.kts 파일에 Spotless 플러그인을 적용하고, 상세한 포맷팅 규칙을 설정합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;
&lt;pre id=&quot;code_1753599103051&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.spotless) //   Spotless 플러그인 적용!
    // ... 기타 플러그인
}

// ... 기존 android { ... } 설정 아래에 추가&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;br /&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;// ✨ Spotless 설정 시작 ✨&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1753599220285&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spotless {
    // 1. Kotlin 소스 코드(.kt)에 대한 규칙
    kotlin {
        target(&quot;**/*.kt&quot;) // 모든 .kt 파일을 대상으로 합니다.
        targetExclude(&quot;**/build/**/*.kt&quot;) // build 폴더는 제외해요.

        // ktlint를 포맷터로 사용합니다.
        // libs.versions.ktlint.get()으로 toml에 정의한 버전을 가져와요.
        ktlint(libs.versions.ktlint.get()).editorConfigOverride(
            // .editorconfig 규칙을 무시하고 여기서 직접 규칙을 덮어쓸 수 있어요.
            // 아래 규칙들은 주로 Jetpack Compose 환경에서 편의를 위해 비활성화합니다.
            mapOf(
                &quot;ktlint_function-naming&quot; to &quot;disabled&quot;, // @Composable 함수는 대문자로 시작해야 하니까요!
                &quot;ktlint_standard_no-wildcard-imports&quot; to &quot;disabled&quot;, // 와일드카드 import를 허용해요.
                &quot;ktlint_standard_discouraged-comment-location&quot; to &quot;disabled&quot; // 인자 옆 주석을 허용해요.
                // ... 기타 비활성화 규칙들
            )
        )

        // 기본적인 포맷팅 규칙도 추가해요.
        trimTrailingWhitespace() // 줄 끝의 불필요한 공백 제거
        endWithNewline() // 파일 끝을 개행 문자로 마무리
    }

    // 2. Gradle Kotlin 스크립트(.gradle.kts)에 대한 규칙
    kotlinGradle {
        target(&quot;*.gradle.kts&quot;) // 루트의 .gradle.kts 파일들을 대상으로 합니다.
        ktlint(libs.versions.ktlint.get())
    }
}

//   빌드 전에 자동으로 Spotless를 실행하게 설정!
tasks.preBuild {
dependsOn(tasks.spotlessApply)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  3. .editorconfig로 스타일 규칙 정의하기&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spotless가 포맷팅을 할 때 참고하는 핵심 규칙 파일입니다. 프로젝트 최상위 경로에 .editorconfig 파일을 만들고 아래 내용을 붙여넣으세요. 이 파일 덕분에 Android Studio도 동일한 코드 스타일을 인식하게 됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  잠깐! .editorconfig 파일의 주석은 어떤 규칙이 왜 필요한지 설명해주므로, 팀원들과 규칙을 논의할 때 아주 유용합니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1753599242936&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 이 파일이 프로젝트의 코드 스타일 규칙 최종 보스임을 선언!
root = true

# 모든 파일에 적용될 기본 규칙
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

# Kotlin과 관련된 파일들
[*.{kt,kts}]
# Jetpack Compose 같은 현대적인 환경을 고려해 한 줄 길이를 120자로 넉넉하게 설정
max_line_length = 120
# 와일드카드 import 허용 (e.g., import androidx.compose.material.*)
# Android에서는 여러 클래스를 한 번에 가져오는 경우가 많아 편리해요.
ktlint_standard_no-wildcard-imports = disabled
# @Composable 함수는 대문자로 시작하므로 함수 작명 규칙은 꺼둡니다.
ktlint_function-naming = disabled

# XML 파일 (레이아웃, 매니페스트 등)
[*.xml]
indent_size = 4

# 기타 파일 형식
[*.{yml,yaml}]
indent_size = 2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  4. 이제 사용해볼까요?&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설정은 끝났습니다! 이제 터미널에서 간단한 명령어로 코드 스타일을 관리할 수 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  코드 스타일 검사하고 자동으로 수정하기&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1753599258016&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./gradlew spotlessApply&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  수정은 하지 않고, 스타일 위반 사항만 확인하기&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1753599283751&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./gradlew spotlessCheck&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Android Studio에서도 편하게 실행할 수 있어요!&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오른쪽 Gradle 탭을 열어주세요.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;app &amp;rarr; Tasks &amp;rarr; formatting 폴더로 이동하세요.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;spotlessApply를 더블클릭하면 포맷팅이, spotlessCheck를 더블클릭하면 검사가 실행됩니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;b&gt; 5. 흔한 에러 해결하기 (Troubleshooting)&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;spotlessCheck를 실행했을 때 마주칠 수 있는 대표적인 에러와 해결 방법입니다. 당황하지 마세요!&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;❓ 에러: Wildcard import&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Wildcard import (import a.b.c.*)를 사용하지 말라는 경고가 떠요.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  해결: .editorconfig 파일에 아래 규칙이 있는지 확인하세요.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1753599392799&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[*.{kt,kts}]
ktlint_standard_no-wildcard-imports = disabled&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;❓ 에러: Function name should start with a lowercase letter&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Composable 함수 이름이 대문자라고 에러가 발생해요.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  해결: .editorconfig 파일에 아래 규칙이 있는지 확인하세요.&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1753599410815&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[*.{kt,kts}]
ktlint_function-naming = disabled​&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;❓ 에러: Exceeded max line length (120)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한 줄이 너무 길다는 에러가 발생해요.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  해결: 코드를 여러 줄로 나누는 것이 가장 좋습니다. 하지만 URL처럼 어쩔 수 없는 경우엔 .editorconfig의 max_line_length 값을 늘려주세요.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;꿀팁 대방출 (추가 팁)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;commit 전에 자동으로 검사하기 (Git Hooks)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매번 spotlessApply를 실행하기 귀찮다면, Git이 commit 하기 직전에 자동으로 실행하도록 설정할 수 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트의 .git/hooks/ 폴더로 이동하세요.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;pre-commit이라는 파일을 만들고 아래 내용을 작성하세요. (확장자 없음)&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1753599449484&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/sh
echo &quot;Running spotlessApply before commit...&quot;
./gradlew spotlessApply
git add .​&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파일에 실행 권한을 부여하세요: chmod +x .git/hooks/pre-commit&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 git commit을 할 때마다 코드가 자동으로 포맷팅됩니다! ✨&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CI/CD 파이프라인에 통합하기 (GitHub Actions)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;팀의 코드 품질을 강제하고 싶다면, GitHub Actions를 사용해 Pull Request가 생성될 때마다 코드 스타일을 검사하도록 설정할 수 있습니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1753599479885&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.github/workflows/code-style-check.yml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1753599494852&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Code Style Check

on: [push, pull_request]

jobs:
spotless:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Check code style
run: ./gradlew spotlessCheck # PR에서는 Apply 대신 Check를 사용해 통과/실패만 확인&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  마무리하며&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;축하합니다! 이제 여러분의 Android 프로젝트는 ktlint와 Spotless라는 강력한 코드 스타일 자동화 시스템을 갖추게 되었습니다. 이 시스템이 가져다줄 이점은 명확합니다.&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 높은 가독성: 누구나 쉽게 이해할 수 있는 깔끔한 코드!&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 개발 효율성 증대: 스타일 걱정 없이 핵심 기능 개발에만 집중!&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 즐거운 협업: 코드 스타일에 대한 불필요한 논쟁은 이제 그만!&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 자동화된 품질 관리: CI/CD와 연동하여 언제나 최상의 코드 품질 유지!&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 가이드가 여러분의 즐거운 개발 생활에 도움이 되었기를 바랍니다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>안드로이드</category>
      <category>code format</category>
      <category>ktlint</category>
      <category>spotless</category>
      <category>spotlessApply</category>
      <category>린트</category>
      <category>코드스타일</category>
      <author>bacass</author>
      <guid isPermaLink="true">https://bacassf.tistory.com/239</guid>
      <comments>https://bacassf.tistory.com/239#entry239comment</comments>
      <pubDate>Sun, 27 Jul 2025 15:49:27 +0900</pubDate>
    </item>
  </channel>
</rss>