<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Zibro의 개발 낙서장</title>
    <link>https://zibro.tistory.com/</link>
    <description>안드로이드 개발자를 꿈꾸는 곳</description>
    <language>ko</language>
    <pubDate>Fri, 10 Apr 2026 20:50:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Zibro</managingEditor>
    <image>
      <title>Zibro의 개발 낙서장</title>
      <url>https://tistory1.daumcdn.net/tistory/4625469/attach/1dbf5dd901984773a1d06618112564ec</url>
      <link>https://zibro.tistory.com</link>
    </image>
    <item>
      <title>[Flutter] Riverpod으로 상태 관리 하기</title>
      <link>https://zibro.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 애플리케이션을 개발할 때, 프로젝트의 규모가 커지고 복잡해질수록 &lt;b&gt;상태 관리&lt;/b&gt;의 중요성은 더욱 커집니다. 복잡하게 얽힌 상태는 개발 효율성을 떨어뜨리고 유지보수를 어렵게 만드는 주요 원인이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Flutter 개발의 핵심 개념인 &lt;b&gt;상태 관리가 무엇인지&lt;/b&gt; 먼저 알아보고, 그 문제를 가장 우아하고 효율적으로 해결해 주는 &lt;b&gt;Riverpod&lt;/b&gt;에 대해 자세히 소개해 드리겠습니다.&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-5389422917983627&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;  상태 관리란 무엇일까요?&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 관리를 이해하려면 먼저 상태(State)가 무엇인지 알아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 말해, 상태란 &lt;b&gt;시간이 지남에 따라 변할 수 있는 모든 데이터&lt;/b&gt;를 의미합니다. 앱의 '기억'이나 '현재 상황'이라고 생각할 수 있습니다. 예를 들면 다음과 같은 것들이 모두 상태에 해당합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자의 로그인 여부 (로그인 / 로그아웃)&lt;/li&gt;
&lt;li&gt;다크 모드 / 라이트 모드 테마 설정&lt;/li&gt;
&lt;li&gt;쇼핑 앱의 장바구니에 담긴 상품 목록&lt;/li&gt;
&lt;li&gt;화면에 보여줄 로딩 스피너의 표시 여부&lt;/li&gt;
&lt;li&gt;사용자가 입력 중인 텍스트 필드의 내용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 관리란 바로 이러한 &lt;b&gt;앱의 상태들을 체계적으로 관리하고, UI와 데이터를 분리하여 효율적으로 다루는 프로그래밍 패턴 또는 전략&lt;/b&gt;을 말합니다. 좋은 상태 관리는 다음과 같은 목표를 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;관심사의 분리:&lt;/b&gt; UI를 그리는 코드와 앱의 데이터를 다루는 로직을 분리하여 코드를 깔끔하게 유지합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중앙 집중 관리:&lt;/b&gt; 앱의 중요한 상태를 한 곳에서 관리하여 데이터의 일관성을 보장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효율적인 UI 업데이트:&lt;/b&gt; 상태가 변경되었을 때, 전체 화면이 아닌 오직 그 상태를 사용하는 위젯만 정확히 업데이트하여 성능을 최적화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 상태 관리는 복잡한 앱을 더 쉽게 만들고, 테스트하고, 유지보수할 수 있도록 돕는 필수적인 기술입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;zwj;♂️ Riverpod란 무엇이고, 왜 필요한가요?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Riverpod는 기존 Provider 패키지를 만든 개발자가 그 한계점을 개선하여 새롭게 공개한 상태 관리 프레임워크입니다. Provider의 후속 버전이지만, 근본적으로 다른 접근 방식을 채택하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Riverpod가 추구하는 핵심 목표는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컴파일 타임 안전성 확보:&lt;/b&gt; 코드를 실행하기 전인 컴파일 시점에 잠재적인 오류를 감지하여 앱의 안정성을 높여줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;위젯 트리로부터의 독립:&lt;/b&gt; 프로바이더를 BuildContext에 의존하지 않는 전역 객체로 선언하여, 의존성 주입을 훨씬 쉽게 만들어 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유연성과 조합성 증대:&lt;/b&gt; 프로바이더끼리 서로를 참조하는 과정을 단순화하여, 복잡한 상태 로직도 선언적으로 깔끔하게 구성할 수 있도록 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  Riverpod를 사용하면 좋은 점&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;컴파일 타임 안전성 (Compile-time Safety)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;존재하지 않는 프로바이더에 접근하려고 하면 런타임이 아닌 컴파일 시점에 오류를 알려주어, 개발 단계에서부터 버그를 미리 예방할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;위젯 트리로부터의 완벽한 독립&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로바이더를 전역 final 변수로 선언하기 때문에 BuildContext에 얽매이지 않습니다. 덕분에 앱의 어느 계층에서든 상태에 자유롭게 접근할 수 있는 유연성을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효율적인 비동기 처리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FutureProvider와 StreamProvider를 이용하면, FutureBuilder나 StreamBuilder를 복잡하게 사용하지 않고도 비동기 데이터의 로딩, 결과, 오류 상태를 매우 간결하게 관리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쉬워진 테스트 (Testability)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 로직이 UI로부터 분리되어 있어, 각 프로바이더를 독립적으로 단위 테스트하기에 매우 유리한 구조입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;선언적이고 조합 가능한 구조&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 프로바이더가 다른 프로바이더의 상태를 쉽게 참조할 수 있어, 복잡한 의존 관계도 명확하고 선언적으로 표현 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; Riverpod의 핵심 개념 정리&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 프로바이더 (Provider)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로바이더는 상태(데이터)를 감싸서 외부로 제공하는 객체입니다. 목적에 따라 다음과 같이 다양한 종류의 프로바이더를 사용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Provider&lt;/b&gt;: 변경되지 않는 값이나 서비스 객체를 제공할 때 사용합니다. (예: Repository, ApiService 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;StateProvider&lt;/b&gt;: 문자열, 숫자, 불리언처럼 간단하고 외부에서 직접 변경 가능한 상태를 관리할 때 적합합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;StateNotifierProvider&lt;/b&gt;: 복잡한 로직을 가진 불변(immutable) 상태 객체를 관리하기 위해 StateNotifier 클래스와 함께 사용됩니다. Riverpod에서 가장 핵심적인 프로바이더 중 하나입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;FutureProvider&lt;/b&gt;: 비동기 작업(Future)의 결과를 제공하며, 관련 상태를 자동으로 관리해 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;StreamProvider&lt;/b&gt;: Stream으로부터 지속적으로 전달되는 데이터를 실시간으로 상태에 반영할 때 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. ref 객체&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref는 프로바이더와 상호작용하기 위한 '다리' 역할을 하는 참조 객체입니다. 위젯이나 다른 프로바이더 안에서 ref를 통해 원하는 프로바이더의 상태를 읽거나, 메소드를 호출할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 프로바이더 상태를 읽는 방법&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ref.watch(provider)&lt;/b&gt;: 프로바이더의 상태 변화를 계속 지켜봅니다. 상태가 변경되면 해당 위젯을 다시 빌드하여 UI를 즉시 업데이트합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ref.read(provider)&lt;/b&gt;: 프로바이더의 상태를 &lt;b&gt;딱 한 번만&lt;/b&gt; 읽어옵니다. 상태가 바뀌어도 위젯이 다시 빌드되지 않으므로, 주로 버튼을 눌렀을 때처럼 특정 이벤트가 발생했을 때 상태를 변경하는 로직을 호출할 때 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ref.listen(provider, (previous, next) {})&lt;/b&gt;: 상태 변화를 감지하지만 UI를 다시 그리지는 않습니다. 대신 스낵바를 띄우거나 다른 페이지로 이동하는 등 특정 동작을 수행할 때 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 data-ke-size=&quot;size23&quot;&gt;Riverpod 활용 예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;다음은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;StateNotifierProvider&lt;span style=&quot;color: #000000; 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;span style=&quot;color: #000000; text-align: start;&quot;&gt;1. StateNotifier 및 Provider 정의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;caret-color: #000000;&quot;&gt;앱의 상태와 그 상태를 변경하는 로직을 정의합니다. UI와 비즈니스 로직을 분리하는 중요한 부분입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754744919773&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1. 상태 로직을 담고 있는 StateNotifier 클래스
class Counter extends StateNotifier&amp;lt;int&amp;gt; {
  // 초기 상태 값을 super 생성자를 통해 0으로 설정합니다.
  Counter() : super(0);

  // 상태를 1 증가시키는 메소드입니다.
  void increment() {
    state = state + 1;
  }
}

// 2. StateNotifier 인스턴스를 제공하는 전역 프로바이더입니다.
final counterProvider = StateNotifierProvider&amp;lt;Counter, int&amp;gt;((ref) {
  return 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;2. UI 위젯 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI에서 우리가 만든 프로바이더를 사용해 상태를 화면에 표시하고, 버튼 클릭으로 상태를 변경하는 부분을 구현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1754744956017&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter_provider.dart';

void main() {
  // 앱 전체에서 Riverpod를 사용하기 위해 최상단을 ProviderScope로 감싸줍니다.
  runApp(const ProviderScope(child: MyApp()));
}

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

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

// StatelessWidget 대신 ConsumerWidget을 상속받습니다.
class CounterScreen extends ConsumerWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ref.watch로 counterProvider의 상태 변화를 감지해 UI에 반영합니다.
    final int count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod Counter')),
      body: Center(
        child: Text(
          '$count',
          style: Theme.of(context).textTheme.headlineMedium,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // ref.read를 사용해 StateNotifier의 메소드를 호출하여 상태를 변경합니다.
          ref.read(counterProvider.notifier).increment();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이처럼 Riverpod는 &lt;/span&gt;&lt;b&gt;ProviderScope&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;로 상태를 저장할 공간을 마련하고, &lt;/span&gt;&lt;b&gt;StateNotifier&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;로 상태와 로직을 분리하며, UI에서는 &lt;/span&gt;&lt;b&gt;ConsumerWidget&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;과 &lt;b&gt;r&lt;/b&gt;&lt;/span&gt;&lt;b&gt;ef&lt;/b&gt;&lt;span style=&quot;color: #000000; 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, Riverpod는 Flutter 애플리케이션의 생산성과 유지보수성을 크게 향상시켜주는 강력한 상태 관리 솔루션입니다. 컴파일 타임 안전성, 구조적 유연성 등의 장점을 통해 개발자는 더욱 안정적이고 확장 가능한 애플리케이션을 구축할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅이 Riverpod의 핵심 개념을 이해하고 실제 프로젝트에 성공적으로 적용하는 데 도움이 되기를 바랍니다.&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>flutter</category>
      <category>provide</category>
      <category>riverpod</category>
      <category>statemanagement</category>
      <category>State패턴</category>
      <category>상태관리</category>
      <category>상태패턴</category>
      <category>플러터</category>
      <author>Zibro</author>
      <guid isPermaLink="true">https://zibro.tistory.com/49</guid>
      <comments>https://zibro.tistory.com/49#entry49comment</comments>
      <pubDate>Sat, 9 Aug 2025 22:16:37 +0900</pubDate>
    </item>
    <item>
      <title>Android 앱 디컴파일 과정 및 방법 정리</title>
      <link>https://zibro.tistory.com/48</link>
      <description>&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;안드로이드 앱의 APK 파일을 분석하거나 내부 구조를 확인할 때 디컴파일(decompile)이 유용합니다. 이 포스팅에서는 디컴파일의 개념과 사용되는 주요 도구들에 대한 설명을 포함하여, 맥북에서 HomeBrew를 통해 APK 파일을 디컴파일하는 방법을 단계별로 안내하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;디컴파일이란?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;디컴파일(Decompile)이란 실행 가능한 바이너리 파일(예: APK)을 원본 소스 코드와 유사한 형태의 코드로 변환하는 작업입니다. 디컴파일을 통해 앱의 구조, 사용된 기술, 그리고 보안 취약점 등을 분석할 수 있습니다.&lt;/span&gt;&lt;/p&gt;

&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;APK 디컴파일에 사용되는 도구&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;apktool&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;APK 파일에서 리소스(이미지, XML, 레이아웃 등)와 Smali 코드(안드로이드의 중간 코드)를 추출하는 데 사용되는 오픈소스 도구입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;jadx&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Smali 코드를 자바와 비슷한 형태로 변환해주는 도구로, 보다 이해하기 쉬운 소스 코드 탐색이 가능합니다. GUI 형태의 인터페이스를 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Smali 코드란?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Smali는 안드로이드의 Dalvik 바이트 코드를 사람이 읽기 쉬운 형태로 변환한 어셈블리 언어입니다. APK를 디컴파일할 때 처음 얻어지는 코드 형태로, 이를 다시 자바 코드와 유사한 형태로 변환하기 위해 jadx와 같은 추가적인 도구를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;APK 디컴파일 방법&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이제 실제로 맥북에서 APK를 디컴파일하는 방법을 안내하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;1단계: 필요한 도구 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;터미널에서 다음 명령어를 사용하여 apktool과 jadx를 설치합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;brew install apktool jadx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2025-05-22 오후 9.41.33.png&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QCYDQ/btsOagsxdnD/B2kOcbphaRXaKbdcp9Bhx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QCYDQ/btsOagsxdnD/B2kOcbphaRXaKbdcp9Bhx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QCYDQ/btsOagsxdnD/B2kOcbphaRXaKbdcp9Bhx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQCYDQ%2FbtsOagsxdnD%2FB2kOcbphaRXaKbdcp9Bhx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;594&quot; data-filename=&quot;edited_스크린샷 2025-05-22 오후 9.41.33.png&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2단계: APK 파일 준비&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;디컴파일하고 싶은 APK 파일을 접근하기 쉬운 위치(예: 데스크탑 또는 작업 디렉토리)에 준비합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;3단계: APK 디컴파일 (리소스 및 Smali 코드)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;터미널을 열고 APK 파일이 위치한 디렉토리로 이동한 후 다음 명령어를 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;apktool d your_app.apk&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 과정에서 APK의 리소스와 Smali 코드가 추출되어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;your_app&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;디렉터리에 저장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4단계 : APK에서 자바 소스 코드 추출&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;더 이해하기 쉬운 형태의 자바 코드에 가까운 결과물을 얻고 싶다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;jadx&lt;/span&gt;&lt;span&gt;를 사용합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;터미널에서 다음 명령어를 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;GUI로 소스 코드 보기:&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;jadx your_app.apk&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;GUI 없이 명령어로만 결과 얻기:&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;jadx -d output_folder your_app.apk&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이렇게 하면 소스 코드가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;output_folder&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;5단계 : jadx-gui를 통해 디컴파일된 소스 보기&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;jadx-gui를 실행하면 APK 파일의 소스 코드가 탐색 가능한 GUI 환경에서 열립니다. 방법은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;터미널에서 다음 명령어를 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;jadx-gui your_app.apk&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;명령어 실행 후, 별도의 창이 열리며, 왼쪽 패널에 APK 파일의 패키지 구조와 클래스들이 표시됩니다. 원하는 클래스를 클릭하면 우측 패널에 소스 코드가 표시되어 쉽게 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-5389422917983627&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&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;span&gt;마무리&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이제 APK 파일 내부의 리소스, Smali 코드, 그리고 자바 소스 코드까지 성공적으로 디컴파일 및 취약점 분석할 수 있게 되었습니다. APK 분석, 앱 구조 이해, 보안 취약점 점검 등의 다양한 용도로 활용 가능합니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>보안</category>
      <category>apk</category>
      <category>apktool</category>
      <category>decompile</category>
      <category>jadx</category>
      <category>jadx-gui</category>
      <category>smali</category>
      <category>난독화</category>
      <category>디컴파일</category>
      <category>리패키징</category>
      <category>모바일 취약점</category>
      <author>Zibro</author>
      <guid isPermaLink="true">https://zibro.tistory.com/48</guid>
      <comments>https://zibro.tistory.com/48#entry48comment</comments>
      <pubDate>Thu, 22 May 2025 21:52:27 +0900</pubDate>
    </item>
    <item>
      <title>Jetpack Compose 이해하기 (3장) - Compose의 상태 관리와 remember, mutableStateOf</title>
      <link>https://zibro.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Jetpack Compose 시리즈의 이전 장에서는 Composable 함수와 Recomposition의 기본 개념에 대해 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 장에서는 Compose에서의 상태(State) 관리와 핵심이 되는 &lt;b&gt;&lt;code&gt;remember&lt;/code&gt;&lt;/b&gt;, &lt;b&gt;&lt;code&gt;mutableStateOf&lt;/code&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;  Compose에서 상태란?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Compose에서 &lt;b&gt;상태(State)&lt;/b&gt;는 UI가 표시하는 데이터를 의미하며, 상태가 변경되면 Compose는 이를 감지하여 해당 UI를 자동으로 재구성(Recomposition)합니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 최신 상태가 UI에 반영됩니다.&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-5389422917983627&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot;&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  상태 선언 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Compose에서 상태를 선언하는 일반적인 방법은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;remember&lt;/span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;mutableStateOf&lt;/span&gt;를 함께 사용하는 것입니다. 이를 통해 상태를 기억하고 변경 사항을 추적할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1744027659248&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Column {
        Text(text = &quot;Count: $count&quot;)
        Button(onClick = { count++ }) {
            Text(&quot;Increment&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 예제에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;remember&lt;/span&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;mutableStateOf(0)&lt;/span&gt;으로 생성된 상태 객체를 컴포지션에 저장하여, 재구성 시에도 상태가 유지되도록 합니다. 버튼을 클릭하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값이 증가하고, Compose는 이를 감지하여 UI를 자동으로 재구성합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&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;span&gt;&amp;nbsp;&lt;/span&gt;remember와&lt;span&gt;&amp;nbsp;&lt;/span&gt;mutableStateOf의 역할&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;mutableStateOf&lt;/b&gt;&lt;/span&gt;: 관찰 가능한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;MutableState&amp;lt;T&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체를 생성합니다. 이 객체의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;가 변경되면 이를 읽고 있는 Composable 함수의 재구성이 예약됩니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;remember&lt;/b&gt;&lt;/span&gt;: 컴포지션 과정에서 계산된 값을 저장하여, 재구성 시에도 해당 값을 유지합니다. 이를 통해 상태가 재설정되지 않고 유지될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 메커니즘을 통해 Compose는 상태 변화를 효율적으로 감지하고 UI를 업데이트할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;  mutableStateOf 사용하기&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Compose에서 상태를 관리하기 위한 가장 기본적인 방법은&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;b&gt;mutableStateOf&lt;/b&gt;&lt;span&gt;를 사용하는 것입니다&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;mutableStateOf&lt;/span&gt;&lt;/b&gt;&lt;span&gt;를 통해 상태를 선언하고 변경할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt; ️ mutableStateOf 사용 예시&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;@Composable
fun Counter() {
    val count = remember { mutableStateOf(0) }

    Column {
        Text(text = &quot;Count: ${count.value}&quot;)
        Button(onClick = { count.value++ }) {
            Text(&quot;Increment&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 예제에서는 버튼을 클릭할 때마다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;상태 값이 증가하고, Compose가 자동으로 UI를 갱신합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; 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;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;  remember란?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Compose는 Recomposition 시 Composable 함수 내의 상태를 다시 생성하지 않고 유지해야 합니다. 이때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;remember&lt;/span&gt;&lt;span&gt;를 사용하여 상태를 유지할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  remember의 역할&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Composable이 다시 호출될 때 상태를 유지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;상태가 변경될 때만 UI가 갱신되도록 최적화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt; ️ remember 사용 예시&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;code&gt;@Composable
fun RememberExample() {
    val name = remember { mutableStateOf(&quot;Compose&quot;) }

    Column {
        TextField(
            value = name.value,
            onValueChange = { name.value = it }
        )
        Text(text = &quot;Hello, ${name.value}&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 예제에서 입력 필드의 내용이 변경되면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;상태가 업데이트되고, 자동으로 UI가 갱신됩니다&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; 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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  상태 호이스팅(State Hoisting)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;상태 호이스팅은 상태를 해당 상태를 사용하는 Composable 함수의 상위 함수로 이동시키는 패턴입니다. 이를 통해 상태의 소유권을 명확히 하고, 여러 Composable 간에 상태를 공유할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1744028052548&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun CounterApp() {
    var count by remember { mutableStateOf(0) }
    Counter(count = count, onIncrement = { count++ })
}

@Composable
fun Counter(count: Int, onIncrement: () -&amp;gt; Unit) {
    Column {
        Text(text = &quot;Count: $count&quot;)
        Button(onClick = onIncrement) {
            Text(&quot;Increment&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;CounterApp&lt;/span&gt;은 상태를 소유하고 있으며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;Counter&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Composable은 상태와 이벤트 핸들러를 매개변수로 받아 UI를 구성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 상태 관리와 UI 로직을 분리하여 코드의 재사용성과 테스트 용이성을 높일 수 있습니다.&lt;/p&gt;

&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;rememberSaveable&lt;/b&gt;&lt;b&gt;의 활용&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;remember&lt;/span&gt;는 컴포지션 동안 상태를 유지하지만, 화면 회전과 같은 구성 변경 시에는 상태가 유지되지 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;rememberSaveable&lt;/span&gt;을 사용하여 상태를 저장하면 구성 변경에도 상태를 유지할 수 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1744028132158&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun Counter() {
    var count by rememberSaveable { mutableStateOf(0) }

    Column {
        Text(text = &quot;Count: $count&quot;)
        Button(onClick = { count++ }) {
            Text(&quot;Increment&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;rememberSaveable&lt;/span&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;count&lt;/span&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;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;상태 관리의 모범 사례&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;단방향 데이터 흐름(UDF)&lt;/b&gt;&lt;/span&gt;: 상태는 상위에서 하위로 전달되고, 이벤트는 하위에서 상위로 전달되는 패턴을 따릅니다. 이를 통해 상태와 UI 간의 일관성을 유지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;상태 호이스팅&lt;/b&gt;&lt;/span&gt;: 상태를 상위 Composable로 이동시켜 상태의 소유권을 명확히 하고, 재사용성과 테스트 용이성을 높입니다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;구성 변경 대응&lt;/b&gt;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;rememberSaveable&lt;/span&gt;을 사용하여 구성 변경 시에도 상태를 유지하도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 패턴과 도구를 활용하여 Compose에서 효율적이고 안정적인 상태 관리를 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고. &lt;a href=&quot;https://developer.android.com/develop/ui/compose/state?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.android.com/develop/ui/compose/state?hl=ko&lt;/a&gt;&lt;/p&gt;</description>
      <category>안드로이드</category>
      <category>composable</category>
      <category>compose</category>
      <category>jetpack</category>
      <category>mutablestateof</category>
      <category>Recomposition</category>
      <category>Remember</category>
      <category>state</category>
      <author>Zibro</author>
      <guid isPermaLink="true">https://zibro.tistory.com/47</guid>
      <comments>https://zibro.tistory.com/47#entry47comment</comments>
      <pubDate>Mon, 7 Apr 2025 21:17:47 +0900</pubDate>
    </item>
    <item>
      <title>Jetpack Compose 이해하기 (2장) - Jetpack Compose의 기본 개념과 Composable 함수</title>
      <link>https://zibro.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;첫 장에서는 선언형 UI와 명령형 UI의 차이점에 대해 알아보았습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 포스팅에서는 Jetpack Compose의 기본 개념과 핵심이 되는 Composable 함수에 대해 깊이 알아보겠습니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;  Jetpack Compose란?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Jetpack Compose는 안드로이드에서 사용하는 선언형 UI 툴킷으로, 간결하고 직관적인 코드로 UI를 구축할 수 있게 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Compose를 이용하면 UI가 상태 변화에 따라 자동으로 갱신되어 유지보수가 쉽고 생산성이 높아집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Compose는 다음과 같은 특징이 있습니다&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;코드로 UI를 작성하는 방식&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;상태(state) 기반의 자동 UI 갱신&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;p data-ke-size=&quot;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;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;  Composable 함수란?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Compose에서는 UI 요소를&lt;span&gt; 간단하게&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;@Composable&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;어노테이션이 붙은 함수로 정의합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이러한 함수들을 Composable이라고 부르며, Compose의 가장 중요한 개념 중 하나입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt; ️ 간단한 Composable 함수 예시&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1743766034799&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun MyApp() {
    Greeting(name = &quot;Compose&quot;)
}

@Composable
fun Greeting(name: String) {
    Text(text = &quot;Hello, $name!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;결과적으로 화면에는 &quot;Hello, Compose!&quot;라는 텍스트가 표시됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  Composable 함수의 특징&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;함수의 인자를 기반으로 UI를 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;상태 변화 시 자동으로 재구성(recomposition)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;순수 함수(pure function) 형태로 작성되어야 함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;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;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;  Recomposition이란?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Compose에서는 상태가 바뀔 때 UI를 다시 그리는 과정을 &lt;b&gt;재구성(Recomposition)&lt;/b&gt;이라고 합니다. 이는 Compose의 핵심 메커니즘이며, Composable 함수가 다시 호출되어 UI가 갱신됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1743766279250&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Column {
        Text(text = &quot;Count: $count&quot;)
        Button(onClick = { count++ }) {
            Text(&quot;Increment&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 코드에서 버튼을 클릭할 때마다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값이 증가하며, Compose는 상태의 변화를 감지하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Counter&lt;/span&gt;&lt;span&gt;Composable 함수를 다시 호출하여 최신 상태를 반영한 UI를 생성합니다. 즉, 상태 값이 변경될 때 Compose는 자동으로 필요한 Composable만을 재구성(recomposition)하여 UI를 업데이트하는 방식으로 효율적으로 동작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;  Composable의 장점&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&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;상태 변화에 따른 UI 업데이트가 자동화됨&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;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이러한 장점 덕분에 Compose는 현대적인 안드로이드 앱 개발에 필수적인 요소로 자리 잡았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&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;다음 장에서는 &lt;span&gt;&lt;b&gt;Compose의 상태 관리와 remember, mutableStateOf&lt;/b&gt;에 대해서 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>안드로이드</category>
      <category>Android</category>
      <category>Android UI</category>
      <category>composable</category>
      <category>compose</category>
      <category>jetpack</category>
      <category>state</category>
      <category>명령형ui</category>
      <category>선언형UI</category>
      <author>Zibro</author>
      <guid isPermaLink="true">https://zibro.tistory.com/46</guid>
      <comments>https://zibro.tistory.com/46#entry46comment</comments>
      <pubDate>Fri, 4 Apr 2025 20:40:58 +0900</pubDate>
    </item>
    <item>
      <title>Jetpack Compose 이해하기 (1장) - 선언형 UI vs 명령형 UI</title>
      <link>https://zibro.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;안드로이드 앱 개발에서 Jetpack Compose가 등장하면서 기존의 명령형(Imperative) UI 방식에서 선언형(Declarative) UI 방식으로 패러다임이 전환되었습니다. &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;style6&quot; /&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-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;  명령형(Imperative) UI 방식이란?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;명령형 UI는 화면을 구성할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;어떻게(how)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만들어야 하는지를 단계별로 명확하게 지정하는 방식입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;전통적인 안드로이드 개발에서는 XML과 Kotlin 또는 Java 기반의 View 시스템이 이에 해당합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어, 특정 버튼 클릭 시 텍스트의 색상을 바꾸려면 다음과 같이 구현합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743600339139&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val button = findViewById&amp;lt;Button&amp;gt;(R.id.myButton)
button.setOnClickListener {
    textView.setTextColor(Color.RED)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&amp;nbsp;명령형 방식의 단점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;UI 상태가 많아질수록 관리가 복잡해짐&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;p data-ke-size=&quot;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;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;  선언형(Declarative) UI 방식이란?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;선언형 UI는 화면에 &lt;b&gt;무엇(what)&lt;/b&gt;을 표시할 것인지에 초점을 맞춥니다. &lt;/span&gt;&lt;span&gt;&lt;b&gt;상태(state)&lt;/b&gt;를 기반으로 UI를 정의하고, 상태가 변경되면 자동으로 UI가 다시 그려지게 됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Jetpack Compose는 이러한 선언형 UI를 중심으로 설계된 최신 안드로이드 UI 라이브러리입니다. &lt;/span&gt;&lt;span&gt;선언형 방식에서는 &lt;b&gt;상태(state)&lt;/b&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;UI&lt;/b&gt;&lt;/span&gt;&lt;span&gt;가 긴밀하게 연결되어 있습니다. &lt;/span&gt;&lt;span&gt;상태가 변경될 때마다 Compose가 상태 변화를 감지하고, 자동으로 화면을 &lt;b&gt;재구성&lt;/b&gt;(recomposition)합니다. &lt;/span&gt;&lt;span&gt;이를 통해 개발자는 UI가 특정 상태에서 어떻게 보여야 하는지만 명시하면 되고, 상태 관리와 화면 갱신은 Compose가 알아서 처리해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어, 아래 Compose 코드는 상태의 변화에 따라 UI가 자동으로 업데이트되는 방식을 명확하게 보여줍니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743600688077&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Composable
fun ColorChangingText(isRed: Boolean) {
    Text(
        text = &quot;Hello Compose!&quot;,
        color = if (isRed) Color.Red else Color.Black
    )
}&lt;/code&gt;&lt;/pre&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;isRed&lt;/span&gt;&lt;span&gt;가 true에서 false로 변하면 Compose는 자동으로 UI를 갱신하여 색상을 변경합니다.&lt;/span&gt;&lt;/p&gt;

&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  선언형 방식의 장점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&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;상태의 변화에 따라 자동으로 UI를 업데이트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;명확한 상태 관리를 통해 버그 발생 가능성 감소&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;UI가 데이터에 따라 즉시 반응하여 사용자의 인터랙션 처리 효율성 증가&lt;/span&gt;&lt;/li&gt;
&lt;li&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  선언형 방식의 한 가지 공식&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;&lt;b&gt;위젯 또는 함수&amp;rsquo;&lt;/b&gt;의 인자로 상태를 넘겨주면, 해당 상태에 맞는 View를 생성합니다. 함수형 프로그래밍 개념의 순수 함수처럼 같은 상태에 대해서는 항상 똑같은 View를 리턴합니다.&lt;/p&gt;
&lt;p id=&quot;cac7&quot; style=&quot;color: #242424; text-align: start;&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;플러터에서 모든 UI는 위젯으로 구성되어 있지만, Jetpack Compose는 &lt;b&gt;@Composable 함수&lt;/b&gt;로 이루어져 있기 때문에 &amp;lsquo;위젯 또는 함수&amp;rsquo;라고 표현했습니다. 이는 각 프레임워크에서 설계 의도에 따라 달라질 뿐 진리는 변하지 않습니다.&lt;/p&gt;
&lt;p id=&quot;0432&quot; style=&quot;color: #242424; text-align: start;&quot; data-selectable-paragraph=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;이런 특징으로 인해 선언형 UI에서는 XML, HTML&lt;span&gt;과 같은 별도의 마크업 언어를 사용하는 대신 코드로 직접 UI를 작성하는 것이 일반적입니다.&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;선언형 UI로 전환하는 이유?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;명령형 방식은 간단한 앱에서는 효과적일 수 있지만, 복잡한 UI와 다양한 상태가 존재하는 현대 앱에서는 관리하기 어렵습니다. 반면, 선언형 UI는 데이터와 상태가 명확히 구분되어 있어 상태 관리가 효율적입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;구글 역시 Jetpack Compose를 안드로이드 UI의 미래로 제시하고 있으며, 많은 개발자와 커뮤니티가 이 새로운 패러다임으로 빠르게 전환하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음 장에서는 Jetpack Compose의 기본적인 개념과 Composable의 사용 방법을 구체적으로 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; 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;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-5389422917983627&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드</category>
      <category>Android</category>
      <category>composable</category>
      <category>compose</category>
      <category>jetpack</category>
      <category>Recomposition</category>
      <category>xml</category>
      <category>명령형ui</category>
      <category>선언형UI</category>
      <author>Zibro</author>
      <guid isPermaLink="true">https://zibro.tistory.com/45</guid>
      <comments>https://zibro.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 3 Apr 2025 00:11:57 +0900</pubDate>
    </item>
    <item>
      <title>iOS 개발자가 없는 상황에서 Android 개발자가 iOS 앱을 유지보수한 경험</title>
      <link>https://zibro.tistory.com/44</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;1. 배경&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2024년 말, 회사에서 갑작스럽게 iOS 개발자가 퇴사하는 일이 발생했다. 기존 iOS 앱의 유지보수는 물론, 신규 기능 추가까지 필요한 상황이었지만, 팀 내에 iOS 경험이 있는 개발자가 없었다. 결국, Android 개발자인 내가 긴급 투입되어 iOS 앱을 유지보수하게 되었다. 처음에는 막막했지만, 다양한 시행착오를 겪으며 iOS 개발을 경험할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; 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;span&gt;2. iOS 개발 환경 적응하기&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;Xcode와 Swift 익히기&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Android 개발자는 주로 Android Studio와 Kotlin을 사용하지만, iOS는 Xcode와 Swift를 기본으로 한다. 처음에는 UI 배치 방식부터 프로젝트 구조까지 모든 것이 낯설었다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Xcode&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: IDE의 단축키부터 빌드 및 디버깅 방식까지 적응하는 데 시간이 필요했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Swift&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Kotlin과 문법적으로 유사한 부분이 있어 기본적인 코드 이해는 수월했지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Optionals&lt;/span&gt;&lt;span&gt;나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Combine&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 개념은 새로 익혀야 했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;스토리보드 vs 코드 기반 UI&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Android의 XML 레이아웃과 Jetpack Compose처럼 iOS도 스토리보드를 활용하거나 코드로 UI를 작성하는 방식이 있었다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;주요 개념 학습&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;ViewController의 생명주기&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Android의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Activity&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;및&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Fragment&lt;/span&gt;&lt;span&gt;의 생명주기와 유사하지만 미묘한 차이가 있었다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Autolayout&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: ConstraintLayout과 개념이 유사하지만, Xcode의 UI 툴을 이용하는 방식이 달라 적응이 필요했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Dependency Management&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Android의 Gradle과 달리 iOS는 CocoaPods과 Swift Package Manager(SPM)를 활용한다. 프로젝트에 따라 사용 방식이 달라 미리 확인이 필요했다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3. 유지보수 과정에서 겪은 문제와 해결 방법&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;라이브러리 관리 이슈&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존 iOS 프로젝트는 CocoaPods을 사용하고 있었는데,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;pod install&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;실행 시 충돌이 발생했다. 원인은 오래된 의존성 버전과 Xcode 업데이트 불일치였다. 이를 해결하기 위해 다음 단계를 거쳤다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;Podfile.lock&lt;/span&gt;&lt;span&gt;을 삭제하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;pod install --repo-update&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;필요 없는 의존성 제거 및 최신 버전으로 업데이트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Xcode 버전에 맞춰 프로젝트 설정 변경&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;앱 스토어 배포 문제&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Android는 Google Play Console을 통해 쉽게 배포할 수 있지만, iOS는 App Store Connect를 통해 진행해야 한다. Apple의 인증서 및 프로파일 시스템은 처음 접하는 개발자에게는 난관이었다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;프로비저닝 프로파일 및 인증서 설정&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 기존 개발자의 Apple 계정에서 필요한 인증서를 받아야 했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;앱 심사 대응&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Google Play보다 엄격한 심사 기준이 적용되므로, 개인정보 보호 및 UI 가이드라인을 철저히 점검해야 했다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;크래시 분석 및 디버깅&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Android에서는 Firebase Crashlytics를 많이 사용하지만, iOS에서도 동일한 Crashlytics를 사용할 수 있었다. 그러나 iOS 특유의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;dSYM 파일 관리&lt;/b&gt;&lt;/span&gt;&lt;span&gt;가 필요했는데, 이를 제대로 업로드하지 않으면 크래시 리포트에서 스택트레이스를 확인할 수 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;upload-symbols&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;스크립트를 활용하여 dSYM 파일을 자동 업로드하도록 설정&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4. 얻은 교훈과 느낀 점&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;크로스플랫폼 개발의 중요성&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Android와 iOS를 모두 경험해 보니, 크로스플랫폼 개발 프레임워크(Flutter, React Native)의 필요성을 더 깊이 이해하게 되었다. 유지보수 비용을 낮추고 개발 속도를 높이려면 이런 기술도 고려해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;Android와 iOS의 차이를 이해하는 것이 중요하다&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;서로 비슷하면서도 다른 점이 많다. 특히 UI/UX 정책, 권한 시스템, 백그라운드 작업 방식 등에서 차이가 있으므로, 양쪽의 특징을 잘 이해해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;빠르게 적응하는 능력이 중요하다&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;완전히 새로운 환경에서도 빠르게 적응하려면, 공식 문서와 기존 코드 분석을 병행하는 것이 효과적이다. 그리고, ChatGPT 같은 AI 도구를 활용하는 것도 큰 도움이 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;5. 마무리&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;갑작스러운 iOS 개발자의 공백을 메우면서 많은 것을 배울 수 있었다. 물론 Android 개발자로서의 주 업무에 집중해야 하지만, iOS에 대한 기본적인 이해를 갖추면 팀 내에서 더욱 가치 있는 역할을 할 수 있다고 느꼈다. 앞으로도 양 플랫폼의 차이를 이해하며 개발할 수 있도록 지속적으로 학습해야겠다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>cocoapods</category>
      <category>iOS개발</category>
      <category>Swift</category>
      <category>xcode</category>
      <category>회고</category>
      <author>Zibro</author>
      <guid isPermaLink="true">https://zibro.tistory.com/44</guid>
      <comments>https://zibro.tistory.com/44#entry44comment</comments>
      <pubDate>Mon, 31 Mar 2025 22:54:48 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin Flow에서의 Debounce와 Throttle 기법</title>
      <link>https://zibro.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Kotlin에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Flow&lt;span style=&quot;color: #000000; 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;Debounce&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Throttle&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;은 이러한 상황에서 유용하게 사용되는 기법으로, 불필요한 작업을 줄이고 성능을 최적화하는 데 큰 도움이 됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;본 글에서는 이 두 기법의 개념과 이를 Kotlin&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Flow&lt;span style=&quot;color: #000000; 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;h2 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;365&quot; data-start=&quot;331&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Debounce와 Throttle 기법이란?&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;383&quot; data-start=&quot;367&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Debounce&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;531&quot; data-start=&quot;384&quot; data-ke-size=&quot;size16&quot;&gt;Debounce는 연속된 이벤트 중 마지막 이벤트만 실행되도록 처리하는 기법입니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;531&quot; data-start=&quot;384&quot; data-ke-size=&quot;size16&quot;&gt;일정 시간 동안 추가적인 이벤트가 발생하지 않으면, 그때서야 마지막 이벤트를 처리하게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;531&quot; data-start=&quot;384&quot; data-ke-size=&quot;size16&quot;&gt;즉, 빠르게 발생하는 여러 이벤트 중에서 마지막 이벤트를 처리하고자 할 때 유용합니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;550&quot; data-start=&quot;533&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;사전적 의미&lt;b&gt; &lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;631&quot; data-start=&quot;551&quot; data-ke-size=&quot;size16&quot;&gt;Debounce는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;반동을 없애다&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는 &quot;충격을 완화하다&quot;라는 의미를 가지며, 과도한 반복적인 반응을 없애기 위한 방식입니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;650&quot; data-start=&quot;633&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주요 사용 사례 &lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-end=&quot;803&quot; data-start=&quot;651&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;738&quot; data-start=&quot;651&quot;&gt;&lt;b&gt;검색 자동완성&lt;/b&gt;: 사용자가 검색어를 입력할 때마다 서버 요청을 보내는 대신, 입력이 멈춘 후 마지막 검색어만 서버로 보내 불필요한 요청을 방지합니다.&lt;/li&gt;
&lt;li data-end=&quot;803&quot; data-start=&quot;739&quot;&gt;&lt;b&gt;실시간 입력 처리&lt;/b&gt;: 실시간 유효성 검사나 필터링에서, 입력이 끝날 때까지 기다린 후 최종적으로 처리합니다.&lt;/li&gt;
&lt;/ul&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: #000000; text-align: start;&quot; data-end=&quot;826&quot; data-start=&quot;810&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Throttle&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;927&quot; data-start=&quot;827&quot; data-ke-size=&quot;size16&quot;&gt;Throttle은 이벤트가 일정 시간 간격으로만 실행되도록 제한하는 기법입니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;927&quot; data-start=&quot;827&quot; data-ke-size=&quot;size16&quot;&gt;이벤트가 자주 발생하더라도 지정된 시간 간격마다 한 번만 처리되며, 그 사이의 이벤트는 무시됩니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;946&quot; data-start=&quot;929&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;사전적 의미 &lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1016&quot; data-start=&quot;947&quot; data-ke-size=&quot;size16&quot;&gt;Throttle은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;조절하다&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는 &quot;속도를 늦추다&quot;라는 의미를 가지며, 흐름의 속도를 제한하는 방식입니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1035&quot; data-start=&quot;1018&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주요 사용 사례&lt;b&gt; &lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-end=&quot;1166&quot; data-start=&quot;1036&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1098&quot; data-start=&quot;1036&quot;&gt;&lt;b&gt;버튼 클릭 방지&lt;/b&gt;: 버튼을 너무 자주 클릭하는 것을 방지할 때, 일정 시간 간격으로 한 번만 처리합니다.&lt;/li&gt;
&lt;li data-end=&quot;1166&quot; data-start=&quot;1099&quot;&gt;&lt;b&gt;스크롤 최적화&lt;/b&gt;: 스크롤 이벤트가 자주 발생할 수 있지만, 일정 간격으로만 이벤트를 처리하여 성능을 최적화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-5389422917983627&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1220&quot; data-start=&quot;1173&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Kotlin Flow에서 Debounce와 Throttle 구현하기&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1299&quot; data-start=&quot;1222&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin의&lt;span&gt;&amp;nbsp;&lt;/span&gt;Flow에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;debounce와&lt;span&gt;&amp;nbsp;&lt;/span&gt;throttle을 쉽게 구현할 수 있습니다. 우선&lt;span&gt;&amp;nbsp;&lt;/span&gt;debounce부터 살펴보겠습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1328&quot; data-start=&quot;1306&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Debounce 구현 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1490&quot; data-start=&quot;1330&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin의&lt;span&gt;&amp;nbsp;&lt;/span&gt;Flow에서 제공하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;debounce&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는 연속적인 이벤트 중 마지막 이벤트만 처리하는 기능을 제공합니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1490&quot; data-start=&quot;1330&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 검색창에서 사용자가 타이핑을 할 때마다 서버에 요청을 보내는 경우,&lt;span&gt;&amp;nbsp;&lt;/span&gt;debounce를 사용하여 입력이 끝날 때만 서버에 요청을 보낼 수 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1515&quot; data-start=&quot;1492&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Debounce 예시 코드&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1742907866763&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val searchFlow = MutableSharedFlow&amp;lt;String&amp;gt;() // 입력 이벤트 스트림

    // debounce(500): 500ms 동안 추가 입력이 없으면 마지막 입력만 처리
    searchFlow
        .debounce(500) 
        .collect { query -&amp;gt;
            println(&quot;서버 요청: 검색어 = $query&quot;)
        }

    // 사용자가 빠르게 입력하는 시뮬레이션
    launch {
        listOf(&quot;코&quot;, &quot;코틀&quot;, &quot;코틀린&quot;).forEach {
            searchFlow.emit(it)
            delay(200) // 빠르게 입력되는 상황
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;2173&quot; data-start=&quot;2042&quot;&gt;debounce(500): 이 코드에서는 사용자가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;코&quot;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;코틀&quot;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;코틀린&quot;을 빠르게 입력한다고 가정합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;debounce는 입력 후 500ms 동안 추가 입력이 없으면 마지막 입력인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;코틀린&quot;만 처리합니다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;2240&quot; data-start=&quot;2174&quot;&gt;emit:&lt;span&gt;&amp;nbsp;&lt;/span&gt;MutableSharedFlow를 사용해 이벤트를 방출하고, 이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;debounce로 처리합니다.&lt;/li&gt;
&lt;/ul&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: #000000; text-align: start;&quot; data-end=&quot;2307&quot; data-start=&quot;2285&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Throttle 구현 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;2408&quot; data-start=&quot;2309&quot; data-ke-size=&quot;size16&quot;&gt;Throttle은 일정 시간 간격마다 이벤트가 실행되도록 제한합니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;2408&quot; data-start=&quot;2309&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 사용자가 버튼을 빠르게 클릭할 때, 한 번만 처리되도록&lt;span&gt;&amp;nbsp;&lt;/span&gt;Throttle을 적용 할 수 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;2433&quot; data-start=&quot;2410&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Throttle 예시 코드&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1742907923901&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val clickFlow = MutableSharedFlow&amp;lt;Unit&amp;gt;()

    // throttleFirst(1000): 1000ms마다 한 번만 실행
    clickFlow
        .throttleFirst(1000)
        .collect {
            println(&quot;버튼 클릭 이벤트 처리!&quot;)
        }

    // 사용자가 빠르게 클릭하는 시뮬레이션
    launch {
        repeat(5) {
            clickFlow.emit(Unit)
            delay(300) // 사용자가 빠르게 클릭하는 상황
        }
    }
}

// throttleFirst 확장 함수 구현
fun &amp;lt;T&amp;gt; Flow&amp;lt;T&amp;gt;.throttleFirst(windowDuration: Long): Flow&amp;lt;T&amp;gt; = flow {
    var lastTime = 0L
    collect { value -&amp;gt;
        val currentTime = System.currentTimeMillis()
        if (currentTime - lastTime &amp;gt;= windowDuration) {
            lastTime = currentTime
            emit(value)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;3342&quot; data-start=&quot;3235&quot;&gt;throttleFirst(1000): 이 코드에서는 사용자가 300ms 간격으로 버튼을 클릭하지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;throttleFirst(1000)을 적용하여 1000ms마다 한 번만 처리됩니다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;3417&quot; data-start=&quot;3343&quot;&gt;throttleFirst&lt;span&gt;&amp;nbsp;&lt;/span&gt;확장 함수는 이벤트가 발생한 시점에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;windowDuration만큼 간격을 두고 실행하도록 합니다.&lt;/li&gt;
&lt;/ul&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;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;본 글을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Kotlin Flow&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Debounce&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Throttle&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기법을 쉽게 이해하고, 실제 프로젝트에서 어떻게 활용할 수 있는지 알게 되기를 바랍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;caret-color: #000000;&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;</description>
      <category>안드로이드</category>
      <category>Coroutines</category>
      <category>debounce</category>
      <category>flow</category>
      <category>Kotlin</category>
      <category>kotlincoroutines</category>
      <category>kotlinflow</category>
      <category>Throttle</category>
      <author>Zibro</author>
      <guid isPermaLink="true">https://zibro.tistory.com/43</guid>
      <comments>https://zibro.tistory.com/43#entry43comment</comments>
      <pubDate>Tue, 25 Mar 2025 22:25:53 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] invoke 함수</title>
      <link>https://zibro.tistory.com/42</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;Kotlin의 invoke()는 무엇인가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린(Kotlin)에는 이름 없이 호출할 수 있는 특별한 함수, 정확히는 연산자인 &lt;code&gt;invoke()&lt;/code&gt;가 존재합니다. 이 함수는 객체를 마치 함수처럼 사용할 수 있도록 만들어 주는 강력한 기능을 제공합니다. 아래의 예제를 통해 &lt;code&gt;invoke()&lt;/code&gt;의 동작을 살펴보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class Example {
    operator fun invoke(str: String): String {
        return str.toUpperCase()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 &lt;code&gt;Example&lt;/code&gt; 클래스에 &lt;code&gt;invoke()&lt;/code&gt; 연산자를 정의한 예제입니다. 이 &lt;code&gt;invoke()&lt;/code&gt;는 일반 메서드처럼 호출할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;val example = Example()
println(example.invoke(&quot;hello&quot;)) // HELLO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 코틀린에서는 &lt;code&gt;invoke&lt;/code&gt;라는 이름의 함수는 이름 없이 호출될 수 있습니다. 즉, 아래와 같이도 동일한 결과를 얻을 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;println(example(&quot;hello&quot;)) // HELLO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;invoke() 연산자는 객체를 함수처럼 사용할 수 있는 특별한 역할을 수행합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;연산자(operator) 키워드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;invoke()&lt;/code&gt;와 같이 특정 함수에 &lt;code&gt;operator&lt;/code&gt; 키워드를 붙이면, 해당 함수는 코틀린에서 제공하는 연산자로 동작할 수 있습니다. 예를 들어, plus 함수는 &lt;code&gt;+&lt;/code&gt; 연산자로 사용할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;object Sample {
    operator fun plus(str: String): String {
        return this.toString() + str
    }
}

fun main() {
    println(Sample + &quot; Hello~!&quot;) // [Sample의 주소값] Hello~!
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 Sample 객체와 문자열을 더하는 동작을 보여줍니다. plus 함수는 &lt;code&gt;+&lt;/code&gt; 연산자를 통해 호출되며, 이를 통해 더 간결하고 직관적인 코드 작성을 가능하게 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;람다와 invoke&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 람다를 지원하며, 람다는 함수처럼 동작하는 객체입니다. 다음은 람다의 간단한 예제입니다:&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;val toUpperCase = { str: String -&amp;gt; str.toUpperCase() }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다에도 타입이 존재하며, 위의 람다는 &lt;code&gt;(String) -&amp;gt; String&lt;/code&gt; 타입입니다. 더 정확히는, 코틀린 표준 라이브러리에 정의된 &lt;code&gt;Function&amp;lt;P1, R&amp;gt;&lt;/code&gt; 인터페이스 타입입니다. 이 인터페이스는 &lt;code&gt;invoke()&lt;/code&gt; 연산자 하나만 포함하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Function&amp;lt;P1, R&amp;gt;&lt;/code&gt;의 동작을 살펴보면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;val toUpperCase = object : Function1&amp;lt;String, String&amp;gt; {
    override fun invoke(p1: String): String {
        return p1.toUpperCase()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다는 결국 컴파일 시점에 &lt;code&gt;invoke()&lt;/code&gt; 연산자를 가진 객체로 변환됩니다. 따라서 다음 두 코드는 동일하게 동작합니다:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;println(toUpperCase.invoke(&quot;hello&quot;)) // HELLO
println(toUpperCase(&quot;hello&quot;)) // HELLO&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;invoke와 컬렉션 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다를 활용하면 컬렉션 함수에서도 &lt;code&gt;invoke&lt;/code&gt;를 간편하게 사용할 수 있습니다. 예를 들어, 다음 코드는 리스트의 모든 문자열을 대문자로 변환합니다:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val strList = listOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)
    println(strList.map(toUpperCase)) // [A, B, C]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;map&lt;/code&gt; 함수는 리스트의 요소를 순회하며 각 요소에 대해 &lt;code&gt;toUpperCase&lt;/code&gt;를 호출합니다. &lt;code&gt;toUpperCase&lt;/code&gt;는 &lt;code&gt;invoke&lt;/code&gt; 연산자를 가지고 있기 때문에 별도의 메서드 호출 없이 간단히 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 위 코드는 람다를 직접 사용하는 방식으로도 작성할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;fun main() {
    val strList = listOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)
    println(strList.map { str: String -&amp;gt; str.toUpperCase() }) // [A, B, C]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다는 컴파일러에 의해 &lt;code&gt;invoke&lt;/code&gt; 연산자를 가진 객체로 변환되므로, 두 방식 모두 동일한 결과를 제공합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;invoke()&lt;/code&gt; 함수는 코틀린의 강력한 특징 중 하나로, 객체를 함수처럼 호출할 수 있게 만들어줍니다. 이를 통해 코드는 더욱 간결하고 직관적으로 변합니다. 특히 람다와 컬렉션 함수와 함께 사용하면 코드의 가독성과 표현력을 크게 향상시킬 수 있습니다.&lt;/p&gt;</description>
      <category>Kotlin</category>
      <category>Invoke</category>
      <category>Kotlin</category>
      <category>Lamda</category>
      <category>operator</category>
      <category>람다</category>
      <category>연산자</category>
      <category>키워드</category>
      <author>Zibro</author>
      <guid isPermaLink="true">https://zibro.tistory.com/42</guid>
      <comments>https://zibro.tistory.com/42#entry42comment</comments>
      <pubDate>Wed, 15 Jan 2025 22:15:25 +0900</pubDate>
    </item>
    <item>
      <title>Maverick - MVI Framework 톺아보기</title>
      <link>https://zibro.tistory.com/41</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;Mavericks - MVI Framework&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개별적인 상태 속성을 노출하는 대신, 하나의 불변 데이터 클래스를 사용해 뷰모델을 업데이트하고 UI를 렌더링 합니다.&lt;/li&gt;
&lt;li&gt;Airbnb 오픈소스로 만든 MVI 프레임워크로 상태관리를 쉽게 처리하기 위한 솔루션을 제공합니다.&lt;/li&gt;
&lt;li&gt;Mavericks 1.0은&amp;nbsp;RxJava 기반으로 되어 있었는데, 2.0에서 Coroutines으로 새롭게 작성되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Mavericks 2.0에서 추가된 기능&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. Coroutines 사용&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RxJava에서 Coroutines로 전환되면서 더 간결하고 직관적인 비동기 코드 작성 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. execute 개선&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 작업의 상태를 쉽게 관리할 수 있는 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Flow 지원&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mavericks StateFlow와 함께 작동하며 상태 기반 데이터 스트림을 쉽게 관리할 수 있습니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;MVI란?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Model&amp;nbsp;:&amp;nbsp;상태를&amp;nbsp;나타낸다.&amp;nbsp;MVI에서&amp;nbsp;Model은&amp;nbsp;데이터&amp;nbsp;플로우가&amp;nbsp;단방향으로&amp;nbsp;이루어지기&amp;nbsp;위해&amp;nbsp;불변성을&amp;nbsp;보장해야&amp;nbsp;한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;View&amp;nbsp;-&amp;nbsp;View는&amp;nbsp;Activity&amp;nbsp;/&amp;nbsp;Fragment를&amp;nbsp;나타내며,&amp;nbsp;상태를&amp;nbsp;전달받아&amp;nbsp;화면에&amp;nbsp;렌더링&amp;nbsp;한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Intent - 앱 / 사용자가 취하는 행위를 나타내기 위한 의도. View는 Intent를 받고, ViewModel은 intent를 관찰하여 Model을 새로운 상태를 변환합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Mavericks를 구성하는 핵심 개념 3가지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;`MavericksState`, `MavericksViewModel` , `MavericksView`&amp;nbsp;&lt;br /&gt;&lt;br /&gt;MavericksState&lt;br /&gt;State라고 표시해 주는 Interface으로, 상속받은 클래스에서는 화면에 필요한 정보들을 가지고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-size: 1.12em; letter-spacing: 0px;&quot;&gt;Thread Safe&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-size: 1.12em; letter-spacing: 0px;&quot;&gt;가지고 있는 정보를 바탕으로 UI를 그리게 됨(render)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-size: 1.12em; letter-spacing: 0px;&quot;&gt;kotlin data class&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-size: 1.12em; letter-spacing: 0px;&quot;&gt;immutable properties&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-size: 1.12em; letter-spacing: 0px;&quot;&gt;초기 상태를 전달하기 위한 default value를 가지고 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1736857642019&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class UserState(
	val name : String = &quot;사용자&quot;,
	val age : Int = 27,
	val regionName : String = &quot;역삼1동&quot;,
	val profileImageUrl : String? = null
) : MavericksState {
	val introduction : Stirng
		get() = &quot;이름: $name, 나이: $age, 사는 곳: $regionName&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-5389422917983627&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;br /&gt;MavericksViewModel&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면 회전 / LMK 등의 Configuration Changes가 발생할 때, 데이터를 유지하기 위해 `Jetpack ViewMdoel`을 구현하는 형태로 사용하고 있습니다.&lt;/li&gt;
&lt;li&gt;하나의 Immutable Data Class를 가지고 하나의 data class만 업데이트하고 UI를 렌더링&lt;/li&gt;
&lt;li&gt;상태(MavericksSate) 업데이트&lt;/li&gt;
&lt;li&gt;stateFlow를 사용해서 stream으로 상태를 구독할 수 있는 메서드 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1736857730622&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserViewModel(
	initialState : UserState,
	private val userRepository : UserRepository,
	private val regionRepository : RegionRepository
) : MavericksViewModel&amp;lt;UserState&amp;gt;(initialState) {

	companion object : MavericksViewModelFactory&amp;lt;UserViewModel, UserState&amp;gt;. {
		override fun initialState(viewModelContext : ViewModelContext) : MyState {
			return MyState(...)
		}
		override fun create(viewModelContext: ViewModelContext, state: MyState) : MyViewModel {
			return MyViewModle(state, ...)
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MavericksView&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 데이터를 렌더링 하는 곳입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;ViewModel delegates를 통해 MavericksViewModel에 접근할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;invalidate() 메서드를 Override 합니다. 상태 변경 시 UI를 다시 그리는 데 사용됩니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;withState() 메서드를 사용해 state observer를 구현하고, 이를 기반으로 UI를 업데이트합니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;withState()&amp;nbsp;메서드는&amp;nbsp;단일&amp;nbsp;ViewModel에서&amp;nbsp;ViewModel의&amp;nbsp;상태에&amp;nbsp;동기적으로&amp;nbsp;접근하고&amp;nbsp;block의&amp;nbsp;결과를&amp;nbsp;반환합니다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1736858348730&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserFragment : Fragment(), MavericksView {
    private val viewModel: UserViewModel by fragmentViewModel()

    override fun invalidate() = withState(viewModel) { state -&amp;gt;
        binding.nameTextView.text = state.name
        binding.regionTextView.text = state.regionName
        binding.profileImageView.load(state.profileImageUrl)
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드</category>
      <category>AirBnB</category>
      <category>Coroutines</category>
      <category>flow</category>
      <category>Framework</category>
      <category>mavericks</category>
      <category>mvi</category>
      <author>Zibro</author>
      <guid isPermaLink="true">https://zibro.tistory.com/41</guid>
      <comments>https://zibro.tistory.com/41#entry41comment</comments>
      <pubDate>Tue, 14 Jan 2025 21:59:58 +0900</pubDate>
    </item>
    <item>
      <title>[패스트캠퍼스 온라인 강의 후기] 15개 프로젝트로 실무까지 끝내는 Dart &amp;amp; Flutter 앱 개발</title>
      <link>https://zibro.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;FastCampus의 &quot;&lt;b&gt;15개&amp;nbsp;프로젝트로&amp;nbsp;실무까지&amp;nbsp;끝내는&amp;nbsp;Dart&amp;nbsp;&amp;amp;&amp;nbsp;Flutter&amp;nbsp;앱&amp;nbsp;개발&amp;nbsp;완강&lt;/b&gt;&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: #333333; text-align: left;&quot;&gt;먼저 이 강의를 선택하게 된 이유는 Flutter를 공부하고자 강의를 찾어보던중, Flutter 개발자인 지인의 추천을 받아 해당 강의를 결제하게 되었습니다. 강의 커리큘럼 중 각 Native와 통신할 수 있는 부분에 대해서 하나의 챕터를 통해 깊이 배울 수 있어 보여 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;가장 눈에 띄었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-5389422917983627&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;수강 중 현재까지 느낀 점&lt;/b&gt;&lt;/h3&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;아직까지 완강은 못했지만 강의가 없이 인터넷 검색만으로는 얻기 힘든 내용을 전문적인 커리큘럼을 통해 순차적으로 배울 수 있어 믿고 따라갈 수 있었고, 안심이 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_5388.JPG&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKUQxt/btsK4W5HJln/IwuYk9FeKBPiKs7EmPiAW0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKUQxt/btsK4W5HJln/IwuYk9FeKBPiKs7EmPiAW0/img.jpg&quot; data-alt=&quot;flutter animation&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKUQxt/btsK4W5HJln/IwuYk9FeKBPiKs7EmPiAW0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKUQxt%2FbtsK4W5HJln%2FIwuYk9FeKBPiKs7EmPiAW0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4284&quot; height=&quot;5712&quot; data-filename=&quot;IMG_5388.JPG&quot; data-origin-width=&quot;4284&quot; data-origin-height=&quot;5712&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;flutter animation&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 디지털 필기 기능과 여러 강의 자료(개발자 필수 영단어 Notion, Github 주소)를 통해 내가 놓친 부분을 GitHub의 프로젝트를 찾아 확인할 수 있었고, 전혀 들어보지 못했던 개발 용어와 의미를 익힐 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-02 오후 9.00.01.png&quot; data-origin-width=&quot;3390&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYGhNU/btsK42kny9O/sY9X2vxB3ad1NYJLEPuSCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYGhNU/btsK42kny9O/sY9X2vxB3ad1NYJLEPuSCk/img.png&quot; data-alt=&quot;디지털 필기 기능&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYGhNU/btsK42kny9O/sY9X2vxB3ad1NYJLEPuSCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYGhNU%2FbtsK42kny9O%2FsY9X2vxB3ad1NYJLEPuSCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3390&quot; height=&quot;1668&quot; data-filename=&quot;스크린샷 2024-12-02 오후 9.00.01.png&quot; data-origin-width=&quot;3390&quot; data-origin-height=&quot;1668&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;스크린샷 2024-12-02 오후 8.47.19.png&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;1054&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lmtqU/btsK5rqFYro/RrhMRksw6FjpkX0G0jKNY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lmtqU/btsK5rqFYro/RrhMRksw6FjpkX0G0jKNY0/img.png&quot; data-alt=&quot;강의에 포함된 강의 자료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lmtqU/btsK5rqFYro/RrhMRksw6FjpkX0G0jKNY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlmtqU%2FbtsK5rqFYro%2FRrhMRksw6FjpkX0G0jKNY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1096&quot; height=&quot;1054&quot; data-filename=&quot;스크린샷 2024-12-02 오후 8.47.19.png&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;1054&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;h3 data-ke-size=&quot;size23&quot;&gt;앞으로의 목표 및 포부&lt;/h3&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;아직 듣지 못한 챕터의 제목을 살펴보니 직접 접하지 못한 내용과 가장 필요했던 내용들이 나올 예정이라 기대감이 높아지고 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-02 오후 9.11.50.png&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;1822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HmCwp/btsK3PfoIyR/Kl839fHDwB9vbhkcHFWGt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HmCwp/btsK3PfoIyR/Kl839fHDwB9vbhkcHFWGt0/img.png&quot; data-alt=&quot;dart 언어로 가위바위보 프로그램 제작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HmCwp/btsK3PfoIyR/Kl839fHDwB9vbhkcHFWGt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHmCwp%2FbtsK3PfoIyR%2FKl839fHDwB9vbhkcHFWGt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1516&quot; height=&quot;1822&quot; data-filename=&quot;스크린샷 2024-12-02 오후 9.11.50.png&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;1822&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;dart 언어로 가위바위보 프로그램 제작&lt;/figcaption&gt;
&lt;/figure&gt;
&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;/h4&gt;</description>
      <category>강의 후기</category>
      <category>dart</category>
      <category>dart 강의</category>
      <category>fastcampus</category>
      <category>flutter</category>
      <category>flutter 강의</category>
      <category>강의</category>
      <category>패스트캠퍼스</category>
      <author>Zibro</author>
      <guid isPermaLink="true">https://zibro.tistory.com/40</guid>
      <comments>https://zibro.tistory.com/40#entry40comment</comments>
      <pubDate>Mon, 2 Dec 2024 21:13:27 +0900</pubDate>
    </item>
  </channel>
</rss>