<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>shin_stealer.log</title>
        <link>https://velog.io/</link>
        <description>I am a Blacksmith.</description>
        <lastBuildDate>Sun, 04 Jan 2026 01:36:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>shin_stealer.log</title>
            <url>https://images.velog.io/images/shin_stealer/profile/ba38ca9e-95b7-488b-8e64-dc1778cd6ef4/tree.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. shin_stealer.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/shin_stealer" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[GetIt 사용 중 궁금증 해소: Factory Function 이란?]]></title>
            <link>https://velog.io/@shin_stealer/Factory-Function%EA%B3%BC-Factory-Pattern-%EC%9D%98-%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@shin_stealer/Factory-Function%EA%B3%BC-Factory-Pattern-%EC%9D%98-%EA%B4%80%EA%B3%84</guid>
            <pubDate>Sun, 04 Jan 2026 01:36:00 GMT</pubDate>
            <description><![CDATA[<p>Factory Function은 Factory Pattern (디자인 패턴 분류 GoF 23가지 중 하나) 을 구현한 방법 중 하나다.</p>
<h3 id="factory-pattern">Factory Pattern</h3>
<ul>
<li>객체 생성 로직을 캡슐화하는 디자인 패턴</li>
<li>Factory function은 Factory pattern을 구현하는 방법 중 하나</li>
</ul>
<h3 id="factory-function">Factory Function</h3>
<ul>
<li>객체를 생성해서 반환하는 함수</li>
<li>생성자를 직접 호출하지 않고 함수로 감싸서 생성</li>
<li>예: <code>() =&gt; ViewModel(useCase: getIt())</code></li>
</ul>
<h2 id="factory-pattern-사용-예시">Factory Pattern 사용 예시</h2>
<h3 id="기본-클래스-정의">기본 클래스 정의</h3>
<pre><code class="language-dart">// UseCase: 비즈니스 로직을 수행하는 클래스
class GetUserDataUseCase {
  final UserRepository repository;

  GetUserDataUseCase({required this.repository});

  Future&lt;User&gt; execute(String userId) async {
    return await repository.getUser(userId);
  }
}

// ViewModel: 화면의 상태와 로직을 관리하는 클래스
class UserProfileViewModel {
  final GetUserDataUseCase useCase;
  User? user;
  bool isLoading = false;

  UserProfileViewModel({required this.useCase}) {
    loadUser(); // 생성자에서 데이터 로드
  }

  void loadUser() async {
    isLoading = true;
    user = await useCase.execute(&#39;123&#39;);
    isLoading = false;
  }
}</code></pre>
<h3 id="factory-pattern-사용-안-할-때">Factory Pattern 사용 안 할 때</h3>
<pre><code class="language-dart">// user_profile_page.dart 파일
final repository = UserRepository();
final useCase = GetUserDataUseCase(repository: repository);
final viewModel1 = UserProfileViewModel(useCase: useCase);

// user_detail_page.dart 파일에서도 ViewModel이 필요하면?
final repository2 = UserRepository();  // 똑같은 코드 반복!
final useCase2 = GetUserDataUseCase(repository: repository2);  // 똑같은 코드 반복!
final viewModel2 = UserProfileViewModel(useCase: useCase2);

// user_settings_page.dart 파일에서도?
final repository3 = UserRepository();  // 또 반복!
final useCase3 = GetUserDataUseCase(repository: repository3);  // 또 반복!
final viewModel3 = UserProfileViewModel(useCase: useCase3);

// 문제점:
// - 생성 로직이 여러 파일에 분산됨 (user_profile_page.dart, user_detail_page.dart 등)
// - UseCase와 ViewModel 생성 코드가 파일마다 중복됨
// - UseCase 생성 방식 변경 시 모든 파일을 수정해야 함
// - 테스트 시 Mock UseCase로 교체하려면 각 파일마다 수정해야 함</code></pre>
<h3 id="factory-pattern-사용할-때">Factory Pattern 사용할 때</h3>
<pre><code class="language-dart">// di_setup.dart 파일 (한 곳에만 작성)
getIt.registerSingleton&lt;UserRepository&gt;(UserRepository());
getIt.registerSingleton&lt;GetUserDataUseCase&gt;(
  GetUserDataUseCase(repository: getIt()),
);

getIt.registerFactory&lt;UserProfileViewModel&gt;(
  () =&gt; UserProfileViewModel(
    useCase: getIt&lt;GetUserDataUseCase&gt;(),  // UseCase는 Singleton에서 가져옴
  ),
);

// user_profile_page.dart 파일
final viewModel1 = getIt&lt;UserProfileViewModel&gt;(); // 간단!

// user_detail_page.dart 파일
final viewModel2 = getIt&lt;UserProfileViewModel&gt;(); // 간단! (새로운 인스턴스)

// user_settings_page.dart 파일
final viewModel3 = getIt&lt;UserProfileViewModel&gt;(); // 간단! (또 다른 새로운 인스턴스)

// 장점:
// - 생성 로직이 di_setup.dart 한 곳에만 있음
// - 각 화면 파일에서는 getIt&lt;Type&gt;()만 호출하면 됨
// - UseCase 생성 방식 변경 시 di_setup.dart만 수정하면 됨
// - 테스트 시 di_setup.dart에서 Mock UseCase로 한 번만 교체하면 모든 화면에 적용
// - 매번 새로운 ViewModel 인스턴스 생성 → 각 화면이 독립적인 상태 유지</code></pre>
<h2 id="getit이란">GetIt이란?</h2>
<p>GetIt은 Flutter/Dart용 의존성 주입(Dependency Injection) 컨테이너 라이브러리다.</p>
<h3 id="주요-기능">주요 기능</h3>
<ul>
<li>객체 생성을 중앙에서 관리</li>
<li>Singleton, Factory 등 다양한 생명주기 지원</li>
<li>의존성을 자동으로 주입</li>
<li>테스트 시 Mock 객체로 쉽게 교체 가능</li>
</ul>
<h3 id="기본-사용법">기본 사용법</h3>
<pre><code class="language-dart">final getIt = GetIt.instance;

// 객체 등록
getIt.registerSingleton&lt;UserRepository&gt;(UserRepositoryImpl());
getIt.registerFactory&lt;UserProfileViewModel&gt;(
  () =&gt; UserProfileViewModel(useCase: getIt()),
);

// 객체 가져오기
final repository = getIt&lt;UserRepository&gt;();
final viewModel = getIt&lt;UserProfileViewModel&gt;();</code></pre>
<h2 id="getit의-registerfactory">GetIt의 registerFactory</h2>
<pre><code class="language-dart">getIt.registerFactory&lt;UserProfileViewModel&gt;(
  () =&gt; UserProfileViewModel(
    useCase: getIt&lt;GetUserDataUseCase&gt;(),
  ),
);</code></pre>
<ul>
<li>Factory function을 등록</li>
<li><code>getIt&lt;UserProfileViewModel&gt;()</code> 호출 시마다 새 인스턴스 생성</li>
<li>Singleton과 달리 매번 새로운 객체 반환</li>
</ul>
<h3 id="왜-viewmodel은-factory를-사용해야-하나">왜 ViewModel은 Factory를 사용해야 하나?</h3>
<pre><code class="language-dart">// 만약 Singleton으로 등록했다면?
getIt.registerSingleton&lt;UserProfileViewModel&gt;(
  UserProfileViewModel(useCase: getIt()),
);

// 화면1에서
final viewModel1 = getIt&lt;UserProfileViewModel&gt;();
viewModel1.user = User(name: &#39;Alice&#39;);  // user 상태 변경

// 화면2에서 (같은 인스턴스!)
final viewModel2 = getIt&lt;UserProfileViewModel&gt;(); 
print(viewModel2.user?.name);  // &#39;Alice&#39; 출력! (화면1의 상태와 공유됨)

// 문제: 화면1의 상태가 화면2에도 영향을 줌 ❌</code></pre>
<p><strong>Factory를 사용하면:</strong></p>
<ul>
<li>각 화면마다 새로운 ViewModel 인스턴스 생성</li>
<li>화면1과 화면2가 서로 독립적인 상태 유지 ✅</li>
<li>생성자에서 <code>loadUser()</code> 호출 → 각 화면이 독립적으로 데이터 로드</li>
</ul>
<h2 id="실제-사용-사례">실제 사용 사례</h2>
<h3 id="1-singleton-pattern">1. Singleton Pattern</h3>
<pre><code class="language-dart">getIt.registerSingleton&lt;UserRepository&gt;(UserRepositoryImpl())
getIt.registerSingleton&lt;GetUserDataUseCase&gt;(
  GetUserDataUseCase(repository: getIt()),
)</code></pre>
<ul>
<li>Repository, UseCase 등 앱 전체에서 하나만 필요한 경우</li>
<li>데이터를 공유하거나 재사용해야 하는 경우</li>
</ul>
<h3 id="2-factory-method-pattern">2. Factory Method Pattern</h3>
<pre><code class="language-dart">getIt.registerFactory&lt;UserProfileViewModel&gt;(
  () =&gt; UserProfileViewModel(useCase: getIt()),
)</code></pre>
<ul>
<li>화면별로 독립적인 ViewModel이 필요한 경우</li>
<li>각 화면이 독립적인 상태를 가져야 하는 경우</li>
</ul>
<h3 id="3-observer-pattern">3. Observer Pattern</h3>
<pre><code class="language-dart">class CartNotifier extends ChangeNotifier {
  void addItem() {
    notifyListeners(); // 상태 변경 알림
  }
}</code></pre>
<ul>
<li>UI 업데이트를 위한 상태 관찰</li>
</ul>
<h3 id="4-dependency-injection">4. Dependency Injection</h3>
<ul>
<li>GetIt으로 의존성 자동 주입</li>
<li>테스트 시 Mock 객체로 교체 용이</li>
</ul>
<h2 id="factory-vs-singleton-in-getit">Factory vs Singleton in GetIt</h2>
<table>
<thead>
<tr>
<th></th>
<th>registerSingleton</th>
<th>registerFactory</th>
</tr>
</thead>
<tbody><tr>
<td>인스턴스</td>
<td>앱 전체 단일 인스턴스</td>
<td>호출마다 새 인스턴스</td>
</tr>
<tr>
<td>등록 방식</td>
<td>직접 인스턴스 또는 Factory function</td>
<td>Factory function만</td>
</tr>
<tr>
<td>사용 예</td>
<td>Repository, UseCase, Service</td>
<td>ViewModel, Cart, Session</td>
</tr>
<tr>
<td>생명주기</td>
<td>앱 시작 시 생성 (또는 지연)</td>
<td>필요 시 생성</td>
</tr>
<tr>
<td>상태 공유</td>
<td>상태가 모든 곳에서 공유됨</td>
<td>각 인스턴스가 독립적인 상태</td>
</tr>
</tbody></table>
<h2 id="핵심-정리">핵심 정리</h2>
<ol>
<li><strong>Factory Function</strong>: 객체 생성 함수</li>
<li><strong>Factory Pattern</strong>: 생성 로직 캡슐화 디자인 패턴</li>
<li><strong>Factory vs Singleton</strong>: 생명주기 차이로 구분</li>
<li><strong>GetIt</strong>: Factory는 지연 생성, Singleton은 즉시 생성 가능</li>
<li><strong>언제 사용?</strong>: 매번 새 인스턴스가 필요한 경우 → Factory 사용</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JSON 직렬화 타입 에러 해결 가이드 (중첩된 객체 이슈)]]></title>
            <link>https://velog.io/@shin_stealer/JSON-%EC%A7%81%EB%A0%AC%ED%99%94-%ED%83%80%EC%9E%85-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-%EA%B0%80%EC%9D%B4%EB%93%9C-%EC%A4%91%EC%B2%A9%EB%90%9C-%EA%B0%9D%EC%B2%B4-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@shin_stealer/JSON-%EC%A7%81%EB%A0%AC%ED%99%94-%ED%83%80%EC%9E%85-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-%EA%B0%80%EC%9D%B4%EB%93%9C-%EC%A4%91%EC%B2%A9%EB%90%9C-%EA%B0%9D%EC%B2%B4-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Sun, 04 Jan 2026 01:00:01 GMT</pubDate>
            <description><![CDATA[<h2 id="에러-내용">에러 내용</h2>
<pre><code>_TypeError (type &#39;Address&#39; is not a subtype of type &#39;Map&lt;String, dynamic&gt;&#39; in type cast)</code></pre><p>JSON 직렬화/역직렬화 과정에서 중첩된 객체가 제대로 변환되지 않을 때 발생함.</p>
<h2 id="문제-원인">문제 원인</h2>
<p><code>@JsonSerializable()</code> 어노테이션에 <code>explicitToJson: true</code> 옵션이 없어서, 중첩된 객체들이 JSON으로 제대로 직렬화되지 않음.</p>
<h2 id="예제-중첩-객체-직렬화-문제">예제: 중첩 객체 직렬화 문제</h2>
<h3 id="예제-모델-구조">예제 모델 구조</h3>
<pre><code class="language-dart">// Address 모델
@JsonSerializable()
class Address {
  final String street;
  final String city;

  Address({required this.street, required this.city});

  factory Address.fromJson(Map&lt;String, dynamic&gt; json) =&gt; 
      _$AddressFromJson(json);
  Map&lt;String, dynamic&gt; toJson() =&gt; _$AddressToJson(this);
}

// User 모델 (Address를 포함)
@JsonSerializable()  // ❌ explicitToJson: true 없음
class User {
  final String name;
  final int age;
  final Address address;  // 중첩 객체

  User({required this.name, required this.age, required this.address});

  factory User.fromJson(Map&lt;String, dynamic&gt; json) =&gt; 
      _$UserFromJson(json);
  Map&lt;String, dynamic&gt; toJson() =&gt; _$UserToJson(this);
}</code></pre>
<h3 id="문제가-있는-생성된-코드">문제가 있는 생성된 코드</h3>
<p><code>explicitToJson: true</code>가 없을 때 생성되는 <code>user.g.dart</code>:</p>
<pre><code class="language-dart">Map&lt;String, dynamic&gt; _$UserToJson(User instance) =&gt; &lt;String, dynamic&gt;{
  &#39;name&#39;: instance.name,
  &#39;age&#39;: instance.age,
  &#39;address&#39;: instance.address,  // ❌ Address 객체가 그대로 저장됨
};</code></pre>
<h3 id="문제-발생-시나리오">문제 발생 시나리오</h3>
<ol>
<li><p><strong>직렬화 (toJson)</strong></p>
<pre><code class="language-dart">final user = User(
  name: &#39;홍길동&#39;,
  age: 30,
  address: Address(street: &#39;강남대로&#39;, city: &#39;서울&#39;),
);

final json = user.toJson();
// 결과: {&#39;name&#39;: &#39;홍길동&#39;, &#39;age&#39;: 30, &#39;address&#39;: Address 객체}
// ❌ address가 Map이 아니라 Address 객체 그대로</code></pre>
</li>
<li><p><strong>저장/전송</strong></p>
<pre><code class="language-dart">// 로컬 스토리지나 API에 저장
await storage.save(json);
// 실제로는 Address 객체가 직렬화되지 않은 상태로 저장됨</code></pre>
</li>
<li><p><strong>역직렬화 (fromJson) - 에러 발생</strong></p>
<pre><code class="language-dart">final savedJson = await storage.load();
final user = User.fromJson(savedJson);
// ❌ 에러 발생
// Address.fromJson()이 Map&lt;String, dynamic&gt;을 기대하지만
// Address 객체가 들어와서 타입 캐스팅 실패</code></pre>
</li>
</ol>
<h3 id="에러-메시지">에러 메시지</h3>
<pre><code>_TypeError (type &#39;Address&#39; is not a subtype of type &#39;Map&lt;String, dynamic&gt;&#39; in type cast)</code></pre><h2 id="해결-방법">해결 방법</h2>
<h3 id="1-explicittojson-true-추가">1. <code>explicitToJson: true</code> 추가</h3>
<p><strong>수정 전</strong>:</p>
<pre><code class="language-dart">@JsonSerializable()  // ❌
class User {
  final Address address;
  // ...
}</code></pre>
<p><strong>수정 후</strong>:</p>
<pre><code class="language-dart">@JsonSerializable(explicitToJson: true)  // ✅
class User {
  final Address address;
  // ...
}</code></pre>
<h3 id="2-수정-후-생성된-코드">2. 수정 후 생성된 코드</h3>
<p><code>explicitToJson: true</code>를 추가한 후 재생성된 <code>user.g.dart</code>:</p>
<pre><code class="language-dart">Map&lt;String, dynamic&gt; _$UserToJson(User instance) =&gt; &lt;String, dynamic&gt;{
  &#39;name&#39;: instance.name,
  &#39;age&#39;: instance.age,
  &#39;address&#39;: instance.address.toJson(),  // ✅ 제대로 직렬화됨
};</code></pre>
<h3 id="3-올바른-동작">3. 올바른 동작</h3>
<ol>
<li><p><strong>직렬화 (toJson)</strong></p>
<pre><code class="language-dart">final user = User(
  name: &#39;홍길동&#39;,
  age: 30,
  address: Address(street: &#39;강남대로&#39;, city: &#39;서울&#39;),
);

final json = user.toJson();
// 결과: {
//   &#39;name&#39;: &#39;홍길동&#39;,
//   &#39;age&#39;: 30,
//   &#39;address&#39;: {&#39;street&#39;: &#39;강남대로&#39;, &#39;city&#39;: &#39;서울&#39;}  // ✅ Map으로 변환됨
// }</code></pre>
</li>
<li><p><strong>역직렬화 (fromJson) - 정상 동작</strong></p>
<pre><code class="language-dart">final savedJson = await storage.load();
final user = User.fromJson(savedJson);
// ✅ 정상 동작
// address가 Map&lt;String, dynamic&gt;이므로 Address.fromJson()이 정상 실행됨</code></pre>
</li>
</ol>
<h2 id="다양한-중첩-구조-예제">다양한 중첩 구조 예제</h2>
<h3 id="예제-1-리스트에-중첩-객체">예제 1: 리스트에 중첩 객체</h3>
<pre><code class="language-dart">@JsonSerializable(explicitToJson: true)  // ✅ 필수
class Order {
  final List&lt;Item&gt; items;  // 리스트 안에 중첩 객체

  Order({required this.items});
  // ...
}</code></pre>
<h3 id="예제-2-다중-중첩">예제 2: 다중 중첩</h3>
<pre><code class="language-dart">@JsonSerializable(explicitToJson: true)  // ✅ 필수
class Company {
  final Address address;  // 1단계 중첩
  // ...
}

@JsonSerializable(explicitToJson: true)  // ✅ 필수
class Employee {
  final Company company;  // 2단계 중첩
  // ...
}</code></pre>
<h3 id="예제-3-옵셔널-중첩-객체">예제 3: 옵셔널 중첩 객체</h3>
<pre><code class="language-dart">@JsonSerializable(explicitToJson: true)  // ✅ 필수
class Profile {
  final Address? address;  // 옵셔널이어도 필요
  // ...
}</code></pre>
<h2 id="적용-규칙">적용 규칙</h2>
<p><strong>중첩 객체가 있는 모든 모델에 <code>explicitToJson: true</code> 추가 필요:</strong></p>
<ul>
<li>✅ 객체를 필드로 가지는 경우</li>
<li>✅ 리스트에 객체가 포함된 경우</li>
<li>✅ 옵셔널 객체를 가지는 경우</li>
<li>✅ 다중 중첩 구조인 경우</li>
</ul>
<h2 id="코드-재생성">코드 재생성</h2>
<p>모델 수정 후 다음 명령어 실행:</p>
<pre><code class="language-bash">flutter pub run build_runner build --delete-conflicting-outputs</code></pre>
<h2 id="주의사항">주의사항</h2>
<ol>
<li><p><strong>기존 데이터</strong>: 이미 잘못된 형식으로 저장된 데이터가 있다면 삭제하거나 마이그레이션 필요.</p>
</li>
<li><p><strong>모든 중첩 레벨</strong>: 중첩이 여러 단계라면 각 단계의 모델 모두에 <code>explicitToJson: true</code> 추가 필요.</p>
</li>
<li><p><strong>빌드 실행</strong>: 모델 수정 후 반드시 <code>build_runner</code> 실행하여 generated 파일 업데이트 필요.</p>
</li>
</ol>
<h2 id="요약">요약</h2>
<ul>
<li><strong>문제</strong>: <code>@JsonSerializable()</code>만 사용하면 중첩 객체가 JSON으로 변환되지 않음</li>
<li><strong>해결</strong>: <code>@JsonSerializable(explicitToJson: true)</code> 추가</li>
<li><strong>결과</strong>: 중첩 객체가 자동으로 <code>toJson()</code>으로 변환되어 올바르게 직렬화됨</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[컬렉션 타입 List, Set, Map]]></title>
            <link>https://velog.io/@shin_stealer/%EC%BB%AC%EB%A0%89%EC%85%98-%ED%83%80%EC%9E%85-List-Set-Map</link>
            <guid>https://velog.io/@shin_stealer/%EC%BB%AC%EB%A0%89%EC%85%98-%ED%83%80%EC%9E%85-List-Set-Map</guid>
            <pubDate>Wed, 31 Dec 2025 04:35:48 GMT</pubDate>
            <description><![CDATA[<h1 id="dart-컬렉션-타입-가이드-list-set-map">Dart 컬렉션 타입 가이드: List, Set, Map</h1>
<h2 id="📋-목차">📋 목차</h2>
<ol>
<li><a href="#list-%EB%A6%AC%EC%8A%A4%ED%8A%B8">List (리스트)</a></li>
<li><a href="#set-%EC%85%8B">Set (셋)</a></li>
<li><a href="#map-%EB%A7%B5">Map (맵)</a></li>
<li><a href="#%EB%B9%84%EA%B5%90%ED%91%9C">비교표</a></li>
<li><a href="#%EC%96%B8%EC%A0%9C-%EB%AC%B4%EC%97%87%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C">언제 무엇을 사용할까?</a></li>
<li><a href="#%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84-big-o">시간 복잡도 (Big O)</a></li>
</ol>
<hr>
<h2 id="list-리스트">List (리스트)</h2>
<h3 id="정의">정의</h3>
<ul>
<li><strong>순서가 있는</strong> 요소들의 컬렉션</li>
<li><strong>중복 요소 허용</strong></li>
<li><strong>인덱스로 접근 가능</strong> (0부터 시작)</li>
</ul>
<h3 id="문법">문법</h3>
<pre><code class="language-dart">// 타입 명시
List&lt;int&gt; numbers = [1, 2, 3, 4, 5];

// 타입 추론
final numbers = &lt;int&gt;[1, 2, 3, 4, 5];

// 빈 리스트
final emptyList = &lt;int&gt;[];</code></pre>
<h3 id="주요-특징">주요 특징</h3>
<ul>
<li>✅ 순서 보장 (삽입 순서 유지)</li>
<li>✅ 중복 허용</li>
<li>✅ 인덱스 접근 가능 (<code>list[0]</code>)</li>
<li>✅ 동적 크기 조절 가능</li>
</ul>
<h3 id="주요-메서드">주요 메서드</h3>
<pre><code class="language-dart">final list = &lt;int&gt;[1, 2, 3];

// 추가
list.add(4);                    // [1, 2, 3, 4]
list.addAll([5, 6]);            // [1, 2, 3, 4, 5, 6]
list.insert(0, 0);              // [0, 1, 2, 3, 4, 5, 6]

// 삭제
list.remove(3);                 // [0, 1, 2, 4, 5, 6]
list.removeAt(0);              // [1, 2, 4, 5, 6]
list.clear();                    // []

// 접근
final first = list[0];          // 첫 번째 요소
final last = list.last;          // 마지막 요소
final length = list.length;      // 길이

// 검색
final contains = list.contains(2);  // true/false
final index = list.indexOf(2);      // 인덱스 반환

// 순회
for (int i = 0; i &lt; list.length; i++) {
  print(list[i]);
}

for (final item in list) {
  print(item);
}</code></pre>
<h3 id="사용-예시">사용 예시</h3>
<pre><code class="language-dart">// 쇼핑 리스트 (순서 중요)
final shoppingList = [&#39;우유&#39;, &#39;빵&#39;, &#39;계란&#39;];

// 점수 목록 (중복 허용)
final scores = [85, 90, 85, 88];

// 메뉴 항목 (인덱스로 접근)
final menuItems = [&#39;홈&#39;, &#39;검색&#39;, &#39;프로필&#39;];
final firstItem = menuItems[0]; // &#39;홈&#39;</code></pre>
<hr>
<h2 id="set-셋">Set (셋)</h2>
<h3 id="정의-1">정의</h3>
<ul>
<li><strong>중복 없는</strong> 요소들의 컬렉션</li>
<li><strong>순서 보장 안 함</strong> (일부 구현체는 순서 유지 가능)</li>
<li><strong>인덱스 접근 불가</strong></li>
</ul>
<h3 id="문법-1">문법</h3>
<pre><code class="language-dart">// 타입 명시
Set&lt;int&gt; ids = {1, 2, 3, 4, 5};

// 타입 추론
final ids = &lt;int&gt;{1, 2, 3, 4, 5};

// 빈 Set
final emptySet = &lt;int&gt;{};</code></pre>
<h3 id="주요-특징-1">주요 특징</h3>
<ul>
<li>✅ 중복 자동 제거</li>
<li>✅ 빠른 검색 (O(1) 평균)</li>
<li>❌ 인덱스 접근 불가</li>
<li>❌ 순서 보장 안 함 (LinkedHashSet은 순서 유지)</li>
</ul>
<h3 id="주요-메서드-1">주요 메서드</h3>
<pre><code class="language-dart">final set = &lt;int&gt;{1, 2, 3};

// 추가
set.add(4);                     // {1, 2, 3, 4}
set.addAll([5, 6]);             // {1, 2, 3, 4, 5, 6}
set.add(3);                     // {1, 2, 3, 4, 5, 6} (중복 무시)

// 삭제
set.remove(3);                  // {1, 2, 4, 5, 6}
set.clear();                     // {}

// 검색
final contains = set.contains(2);  // true/false (O(1) - 매우 빠름!)

// 집합 연산
final set1 = {1, 2, 3};
final set2 = {2, 3, 4};

final intersection = set1.intersection(set2);  // {2, 3} (교집합)
final union = set1.union(set2);                // {1, 2, 3, 4} (합집합)
final difference = set1.difference(set2);      // {1} (차집합)

// 순회
for (final item in set) {
  print(item);
}</code></pre>
<h3 id="사용-예시-1">사용 예시</h3>
<pre><code class="language-dart">// 북마크 ID (중복 방지)
final _ids = &lt;int&gt;{2, 4};

// 고유한 태그 목록
final uniqueTags = {&#39;한식&#39;, &#39;양식&#39;, &#39;중식&#39;, &#39;일식&#39;};

// 저장된 사용자 ID
final savedUserIds = &lt;int&gt;{1, 3, 5, 7};
if (savedUserIds.contains(3)) {  // 빠른 검색!
  print(&#39;이미 저장됨&#39;);
}</code></pre>
<hr>
<h2 id="map-맵">Map (맵)</h2>
<h3 id="정의-2">정의</h3>
<ul>
<li><strong>키-값 쌍(key-value pair)</strong>을 저장하는 컬렉션</li>
<li><strong>키는 중복 불가</strong>, 값은 중복 가능</li>
<li><strong>키로 값을 빠르게 찾을 수 있음</strong></li>
</ul>
<h3 id="문법-2">문법</h3>
<pre><code class="language-dart">// 타입 명시
Map&lt;String, int&gt; scores = {
  &#39;철수&#39;: 85,
  &#39;영희&#39;: 90,
};

// 타입 추론
final scores = &lt;String, int&gt;{
  &#39;철수&#39;: 85,
  &#39;영희&#39;: 90,
};

// 빈 Map
final emptyMap = &lt;String, int&gt;{};</code></pre>
<h3 id="주요-특징-2">주요 특징</h3>
<ul>
<li>✅ 키-값 쌍 저장</li>
<li>✅ 키로 빠른 검색 (O(1) 평균)</li>
<li>✅ 키는 중복 불가, 값은 중복 가능</li>
<li>❌ 인덱스 접근 불가 (키로만 접근)</li>
</ul>
<h3 id="주요-메서드-2">주요 메서드</h3>
<pre><code class="language-dart">final map = &lt;String, int&gt;{
  &#39;철수&#39;: 85,
  &#39;영희&#39;: 90,
};

// 추가/수정
map[&#39;민수&#39;] = 88;                // {&#39;철수&#39;: 85, &#39;영희&#39;: 90, &#39;민수&#39;: 88}
map[&#39;철수&#39;] = 90;                // {&#39;철수&#39;: 90, &#39;영희&#39;: 90, &#39;민수&#39;: 88} (수정)

// 접근
final score = map[&#39;철수&#39;];       // 90
final score2 = map[&#39;없는키&#39;];    // null

// 안전한 접근
final score3 = map[&#39;철수&#39;] ?? 0; // 값이 null이면 0 반환

// 삭제
map.remove(&#39;민수&#39;);              // {&#39;철수&#39;: 90, &#39;영희&#39;: 90}
map.clear();                     // {}

// 검색
final hasKey = map.containsKey(&#39;철수&#39;);  // true
final hasValue = map.containsValue(90);  // true

// 키/값 목록
final keys = map.keys;           // {&#39;철수&#39;, &#39;영희&#39;}
final values = map.values;       // {90, 90}

// 순회
for (final entry in map.entries) {
  print(&#39;${entry.key}: ${entry.value}&#39;);
}

for (final key in map.keys) {
  print(&#39;$key: ${map[key]}&#39;);
}

map.forEach((key, value) {
  print(&#39;$key: $value&#39;);
});</code></pre>
<h3 id="사용-예시-2">사용 예시</h3>
<pre><code class="language-dart">// 사용자 이름-점수
final userScores = &lt;String, int&gt;{
  &#39;철수&#39;: 85,
  &#39;영희&#39;: 90,
  &#39;민수&#39;: 88,
};

// 레시피 ID-북마크 시간
final bookmarkTimes = &lt;int, DateTime&gt;{
  2: DateTime(2024, 1, 15),
  4: DateTime(2024, 1, 20),
};

// 설정값 저장
final settings = &lt;String, dynamic&gt;{
  &#39;theme&#39;: &#39;dark&#39;,
  &#39;language&#39;: &#39;ko&#39;,
  &#39;notifications&#39;: true,
};</code></pre>
<hr>
<h2 id="비교표">비교표</h2>
<table>
<thead>
<tr>
<th>특징</th>
<th>List</th>
<th>Set</th>
<th>Map</th>
</tr>
</thead>
<tbody><tr>
<td><strong>저장하는 것</strong></td>
<td>값만</td>
<td>값만</td>
<td>키-값 쌍</td>
</tr>
<tr>
<td><strong>문법</strong></td>
<td><code>[1, 2, 3]</code></td>
<td><code>{1, 2, 3}</code></td>
<td><code>{&#39;key&#39;: &#39;value&#39;}</code></td>
</tr>
<tr>
<td><strong>순서</strong></td>
<td>✅ 보장</td>
<td>❌ 보장 안 함</td>
<td>❌ 보장 안 함</td>
</tr>
<tr>
<td><strong>중복</strong></td>
<td>✅ 허용</td>
<td>❌ 불가</td>
<td>키 중복 불가, 값 중복 가능</td>
</tr>
<tr>
<td><strong>인덱스 접근</strong></td>
<td>✅ 가능 (<code>list[0]</code>)</td>
<td>❌ 불가</td>
<td>❌ 불가 (키로 접근)</td>
</tr>
<tr>
<td><strong>검색 속도</strong></td>
<td>O(n)</td>
<td>O(1) 평균</td>
<td>O(1) 평균</td>
</tr>
<tr>
<td><strong>주요 용도</strong></td>
<td>순서 있는 목록</td>
<td>고유한 값 집합</td>
<td>키로 값 찾기</td>
</tr>
</tbody></table>
<hr>
<h2 id="언제-무엇을-사용할까">언제 무엇을 사용할까?</h2>
<h3 id="list를-사용하는-경우">List를 사용하는 경우</h3>
<p>✅ <strong>순서가 중요한 경우</strong></p>
<pre><code class="language-dart">final shoppingList = [&#39;우유&#39;, &#39;빵&#39;, &#39;계란&#39;]; // 구매 순서</code></pre>
<p>✅ <strong>중복이 허용되어야 하는 경우</strong></p>
<pre><code class="language-dart">final scores = [85, 90, 85, 88]; // 같은 점수가 여러 번</code></pre>
<p>✅ <strong>인덱스로 접근이 필요한 경우</strong></p>
<pre><code class="language-dart">final menuItems = [&#39;홈&#39;, &#39;검색&#39;, &#39;프로필&#39;];
final firstItem = menuItems[0]; // &#39;홈&#39;</code></pre>
<p>✅ <strong>순차적으로 처리하는 경우</strong></p>
<pre><code class="language-dart">for (int i = 0; i &lt; items.length; i++) {
  print(&#39;${i + 1}. ${items[i]}&#39;);
}</code></pre>
<h3 id="set을-사용하는-경우">Set을 사용하는 경우</h3>
<p>✅ <strong>중복을 방지해야 하는 경우</strong></p>
<pre><code class="language-dart">final _ids = &lt;int&gt;{2, 4}; // 북마크 ID - 같은 ID를 여러 번 저장하면 안 됨</code></pre>
<p>✅ <strong>빠른 검색이 필요한 경우</strong></p>
<pre><code class="language-dart">final savedIds = &lt;int&gt;{1, 3, 5, 7};
if (savedIds.contains(3)) { // O(1) - 매우 빠름!
  print(&#39;이미 저장됨&#39;);
}</code></pre>
<p>✅ <strong>고유한 값들만 관리하는 경우</strong></p>
<pre><code class="language-dart">final uniqueTags = {&#39;한식&#39;, &#39;양식&#39;, &#39;중식&#39;, &#39;일식&#39;}; // 태그는 중복되면 안 됨</code></pre>
<p>✅ <strong>집합 연산이 필요한 경우</strong></p>
<pre><code class="language-dart">final set1 = {1, 2, 3};
final set2 = {2, 3, 4};
final intersection = set1.intersection(set2); // {2, 3}
final union = set1.union(set2); // {1, 2, 3, 4}</code></pre>
<h3 id="map을-사용하는-경우">Map을 사용하는 경우</h3>
<p>✅ <strong>키로 값을 찾아야 하는 경우</strong></p>
<pre><code class="language-dart">final userScores = &lt;String, int&gt;{
  &#39;철수&#39;: 85,
  &#39;영희&#39;: 90,
};
final score = userScores[&#39;철수&#39;]; // 85</code></pre>
<p>✅ <strong>각 항목에 추가 정보가 필요한 경우</strong></p>
<pre><code class="language-dart">final bookmarks = &lt;int, BookmarkInfo&gt;{
  2: BookmarkInfo(time: DateTime.now(), note: &#39;맛있어요&#39;),
  4: BookmarkInfo(time: DateTime.now(), note: &#39;추천!&#39;),
};</code></pre>
<p>✅ <strong>설정값이나 캐시를 저장하는 경우</strong></p>
<pre><code class="language-dart">final settings = &lt;String, dynamic&gt;{
  &#39;theme&#39;: &#39;dark&#39;,
  &#39;language&#39;: &#39;ko&#39;,
  &#39;notifications&#39;: true,
};</code></pre>
<hr>
<h2 id="시간-복잡도-big-o">시간 복잡도 (Big O)</h2>
<h3 id="list">List</h3>
<table>
<thead>
<tr>
<th>연산</th>
<th>시간 복잡도</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>list[index]</code></td>
<td>O(1)</td>
<td>인덱스로 접근</td>
</tr>
<tr>
<td><code>list.add()</code></td>
<td>O(1)</td>
<td>끝에 추가</td>
</tr>
<tr>
<td><code>list.insert()</code></td>
<td>O(n)</td>
<td>중간에 삽입 (요소 이동 필요)</td>
</tr>
<tr>
<td><code>list.contains()</code></td>
<td>O(n)</td>
<td>모든 요소 확인</td>
</tr>
<tr>
<td><code>list.remove()</code></td>
<td>O(n)</td>
<td>요소 찾아서 삭제</td>
</tr>
<tr>
<td><code>list.removeAt()</code></td>
<td>O(n)</td>
<td>인덱스로 삭제 (요소 이동 필요)</td>
</tr>
</tbody></table>
<h3 id="set">Set</h3>
<table>
<thead>
<tr>
<th>연산</th>
<th>시간 복잡도</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>set.add()</code></td>
<td>O(1) 평균</td>
<td>해시 테이블 기반</td>
</tr>
<tr>
<td><code>set.contains()</code></td>
<td>O(1) 평균</td>
<td>매우 빠름!</td>
</tr>
<tr>
<td><code>set.remove()</code></td>
<td>O(1) 평균</td>
<td>빠른 삭제</td>
</tr>
<tr>
<td><code>set.union()</code></td>
<td>O(n)</td>
<td>합집합</td>
</tr>
<tr>
<td><code>set.intersection()</code></td>
<td>O(n)</td>
<td>교집합</td>
</tr>
</tbody></table>
<h3 id="map">Map</h3>
<table>
<thead>
<tr>
<th>연산</th>
<th>시간 복잡도</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>map[key]</code></td>
<td>O(1) 평균</td>
<td>키로 값 찾기</td>
</tr>
<tr>
<td><code>map[key] = value</code></td>
<td>O(1) 평균</td>
<td>추가/수정</td>
</tr>
<tr>
<td><code>map.containsKey()</code></td>
<td>O(1) 평균</td>
<td>키 존재 확인</td>
</tr>
<tr>
<td><code>map.containsValue()</code></td>
<td>O(n)</td>
<td>모든 값 확인</td>
</tr>
<tr>
<td><code>map.remove()</code></td>
<td>O(1) 평균</td>
<td>키로 삭제</td>
</tr>
</tbody></table>
<h3 id="성능-비교-예시">성능 비교 예시</h3>
<p><strong>100만 개의 데이터에서 검색:</strong></p>
<table>
<thead>
<tr>
<th>컬렉션 타입</th>
<th>연산</th>
<th>시간</th>
</tr>
</thead>
<tbody><tr>
<td>List</td>
<td><code>contains()</code></td>
<td>~100ms (O(n))</td>
</tr>
<tr>
<td>Set</td>
<td><code>contains()</code></td>
<td>~0.0001ms (O(1))</td>
</tr>
<tr>
<td>Map</td>
<td><code>containsKey()</code></td>
<td>~0.0001ms (O(1))</td>
</tr>
</tbody></table>
<p><strong>결론:</strong> 자주 검색하는 데이터는 Set이나 Map을 사용하는 것이 훨씬 효율적입니다!</p>
<hr>
<h2 id="요약">요약</h2>
<ul>
<li><strong>List</strong>: 순서 있고 중복 허용, 인덱스 접근 가능</li>
<li><strong>Set</strong>: 중복 없음, 빠른 검색, 고유한 값 집합</li>
<li><strong>Map</strong>: 키-값 쌍, 키로 빠른 검색, 관련 데이터 저장</li>
</ul>
<p>각 컬렉션 타입은 고유한 특성과 용도가 있으므로, 상황에 맞게 선택하는 것이 중요합니다! 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FCM Push Message 초간단 테스팅 방법을 소개합니다. (Android, iOS)]]></title>
            <link>https://velog.io/@shin_stealer/FCM-Push-Message-%EC%B4%88%EA%B0%84%EB%8B%A8-%ED%85%8C%EC%8A%A4%ED%8C%85-%EB%B0%A9%EB%B2%95%EC%9D%84-%EC%86%8C%EA%B0%9C%ED%95%A9%EB%8B%88%EB%8B%A4.-Android-iOS</link>
            <guid>https://velog.io/@shin_stealer/FCM-Push-Message-%EC%B4%88%EA%B0%84%EB%8B%A8-%ED%85%8C%EC%8A%A4%ED%8C%85-%EB%B0%A9%EB%B2%95%EC%9D%84-%EC%86%8C%EA%B0%9C%ED%95%A9%EB%8B%88%EB%8B%A4.-Android-iOS</guid>
            <pubDate>Tue, 16 Dec 2025 07:43:55 GMT</pubDate>
            <description><![CDATA[<h1 id="fcm-푸시-메시지-테스트-서버">FCM 푸시 메시지 테스트 서버</h1>
<p>안녕하세요! 오늘은 제가 사용하려고 개발한 <strong>FCM 푸시 메시지 테스트 서버</strong>를 소개하려고 합니다.</p>
<h2 id="🎯-이런-분들께-추천합니다">🎯 이런 분들께 추천합니다</h2>
<ul>
<li>모바일 앱 개발 중 푸시 알림 기능을 테스트하고 싶은 개발자</li>
<li>FCM 메시지 포맷을 빠르게 실험해보고 싶은 분</li>
<li>대량의 푸시 메시지 발송 테스트가 필요한 분</li>
<li>간단한 웹 인터페이스로 푸시를 보내고 싶은 분</li>
</ul>
<h2 id="💡-왜-만들었나요">💡 왜 만들었나요?</h2>
<p>앱 개발 중 푸시 알림 기능을 테스트할 때마다 매번 코드를 작성하거나 복잡한 툴을 사용해야 하는 번거로움이 있었습니다. 
&quot;그냥 토큰만 입력하면 바로 푸시를 보낼 수 있는 간단한 도구가 있으면 좋겠다&quot;는 생각으로 이 프로젝트를 시작했습니다.</p>
<h2 id="✨-주요-기능">✨ 주요 기능</h2>
<h3 id="1-심플한-웹-인터페이스">1. 심플한 웹 인터페이스</h3>
<p>별도의 클라이언트 설치 없이 브라우저에서 바로 사용할 수 있습니다.</p>
<p><img src="https://raw.githubusercontent.com/stevey-sy/push_test_server/main/screenshot/screenshot_1.png" alt="메인 화면"></p>
<h3 id="2-다중-토큰-지원">2. 다중 토큰 지원</h3>
<p>여러 디바이스에 동시에 푸시를 보낼 수 있습니다. 각 토큰당 원하는 개수만큼 메시지를 발송할 수 있어 부하 테스트에도 유용합니다.</p>
<h3 id="3-메시지-커스터마이징">3. 메시지 커스터마이징</h3>
<p>JSON 형식으로 푸시 메시지를 자유롭게 커스터마이징할 수 있습니다.</p>
<p><img src="https://raw.githubusercontent.com/stevey-sy/push_test_server/main/screenshot/screenshot_2.png" alt="메시지 커스터마이징"></p>
<ul>
<li>Notification (제목, 내용)</li>
<li>Custom Data (key-value 형태의 커스텀 데이터)</li>
<li>Android/iOS 특정 설정</li>
</ul>
<h3 id="4-rest-api-지원">4. REST API 지원</h3>
<p>웹 인터페이스뿐만 아니라 REST API로도 사용할 수 있어 CI/CD 파이프라인이나 자동화 테스트에 통합하기 쉽습니다.</p>
<h2 id="🚀-사용하기">🚀 사용하기</h2>
<p>정말 간단합니다! 필요한 건 딱 두 가지:</p>
<ol>
<li><p><strong>Firebase 서비스 계정 JSON 파일</strong>
Firebase 서비스 계정 키(JSON 파일) 발급 방법
Firebase Console에 접속
프로젝트 선택
프로젝트 설정 &gt; 서비스 계정 탭으로 이동
&quot;새 비공개 키 생성&quot; 클릭하여 JSON 파일 다운로드
JSON 파일을 안전하게 보관</p>
</li>
<li><p><strong>FCM 디바이스 토큰</strong>
모바일 환경에서 Firebase SDK 를 통해 각 Device 별로 부여되는 FCM Token</p>
</li>
</ol>
<pre><code># 설치
npm install

# 환경 변수 설정 (.env 파일)
FIREBASE_SERVICE_ACCOUNT_PATH=./your-firebase-service-account.json

# 실행
npm start
이제 http://localhost:3000에 접속하면 바로 사용할 수 있습니다!</code></pre><h2 id="🌟-특징">🌟 특징</h2>
<p>쉬운 설정: 복잡한 설정 없이 Firebase 서비스 계정 파일만 있으면 됩니다
실시간 결과: 각 토큰별 발송 성공/실패 여부를 실시간으로 확인
대량 테스트: 토큰당 최대 100개까지 메시지를 동시에 발송 가능
사용자 친화적: 직관적인 UI와 JSON 유효성 검사 기능</p>
<h2 id="🔧-기술-스택">🔧 기술 스택</h2>
<p>Backend: Node.js, Express.js
Push Service: Firebase Admin SDK
Frontend: Vanilla JavaScript (의존성 없음!)</p>
<h2 id="📦-github에서-확인하기">📦 GitHub에서 확인하기</h2>
<p>더 자세한 내용은 GitHub 저장소에서 확인하실 수 있습니다: 👉 <a href="https://github.com/stevey-sy/push_test_server">https://github.com/stevey-sy/push_test_server</a></p>
<h2 id="💬-마치며">💬 마치며</h2>
<p>개발하면서 제가 필요했던 기능들을 담아 만든 도구입니다. 푸시 알림 테스트로 시간을 낭비하시는 분들께 조금이나마 도움이 되었으면 좋겠습니다. 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android "Waiting for Debugger" 무한 대기 현상 해결법]]></title>
            <link>https://velog.io/@shin_stealer/Android-Waiting-for-Debugger-%EB%AC%B4%ED%95%9C-%EB%8C%80%EA%B8%B0-%ED%98%84%EC%83%81-%ED%95%B4%EA%B2%B0%EB%B2%95</link>
            <guid>https://velog.io/@shin_stealer/Android-Waiting-for-Debugger-%EB%AC%B4%ED%95%9C-%EB%8C%80%EA%B8%B0-%ED%98%84%EC%83%81-%ED%95%B4%EA%B2%B0%EB%B2%95</guid>
            <pubDate>Thu, 21 Aug 2025 02:20:08 GMT</pubDate>
            <description><![CDATA[<h1 id="android-waiting-for-debugger-무한-대기-현상-해결법">Android &quot;Waiting for Debugger&quot; 무한 대기 현상 해결법</h1>
<h2 id="🚨-문제-상황">🚨 문제 상황</h2>
<p>Android 앱 개발 중 다음과 같은 상황을 경험해보셨나요?</p>
<ul>
<li>디버그 모드가 아닌 일반 실행 상태인데도 &quot;Waiting for Debugger&quot; 메시지가 표시</li>
<li>앱이 무한 대기 상태에 빠져 정상적인 테스트 불가</li>
<li><code>adb kill-server</code> 및 <code>adb start-server</code>로 ADB를 재시작해도 문제가 지속</li>
</ul>
<h2 id="🔍-원인-분석">🔍 원인 분석</h2>
<p>이 현상은 이전에 <code>adb shell am set-debug-app</code> 명령어로 설정된 디버그 앱 설정이 제대로 해제되지 않아 발생합니다. 시스템이 해당 패키지를 여전히 디버그 대상으로 인식하고 있어, 디버거 연결을 기다리는 상태가 지속되는 것입니다.</p>
<h2 id="✅-해결-방법">✅ 해결 방법</h2>
<p>다음 ADB 명령어를 실행하여 문제를 해결할 수 있습니다:</p>
<pre><code class="language-bash">adb shell am clear-debug-app</code></pre>
<h3 id="명령어-설명">명령어 설명</h3>
<ul>
<li><code>clear-debug-app</code>: 이전에 <code>set-debug-app</code>으로 설정된 디버그 앱 설정을 완전히 제거</li>
<li>시스템에서 특정 패키지를 디버그 대상으로 인식하지 않도록 초기화</li>
</ul>
<h2 id="💡-예방-팁">💡 예방 팁</h2>
<ol>
<li><strong>디버그 세션 종료 시</strong>: 디버그 작업 완료 후 명시적으로 <code>clear-debug-app</code> 실행</li>
<li><strong>빌드 변경 시</strong>: Release 빌드로 전환할 때 디버그 설정 확인</li>
<li><strong>디바이스 변경 시</strong>: 새로운 테스트 디바이스 사용 전 디버그 설정 초기화</li>
</ol>
<p>이 간단한 명령어 하나로 개발 생산성을 크게 향상시킬 수 있습니다. 비슷한 문제를 겪고 계시다면 한 번 시도해보세요! 🚀</p>
<h2 id="레퍼런스">레퍼런스</h2>
<p><a href="https://jinryua.tistory.com/8">https://jinryua.tistory.com/8</a>
<a href="https://developer.android.com/tools/adb?hl=ko">https://developer.android.com/tools/adb?hl=ko</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 연습 - 3진법 뒤집기]]></title>
            <link>https://velog.io/@shin_stealer/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%8A%B5-3%EC%A7%84%EB%B2%95-%EB%92%A4%EC%A7%91%EA%B8%B0</link>
            <guid>https://velog.io/@shin_stealer/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%8A%B5-3%EC%A7%84%EB%B2%95-%EB%92%A4%EC%A7%91%EA%B8%B0</guid>
            <pubDate>Thu, 17 Jul 2025 12:38:51 GMT</pubDate>
            <description><![CDATA[<h2 id="📘-프로그래머스-3진법-뒤집기-kotlin-풀이">📘 [프로그래머스] 3진법 뒤집기 (Kotlin 풀이)</h2>
<h4 id="✨-문제-설명">✨ 문제 설명</h4>
<p>자연수 n이 주어졌을 때,</p>
<p>n을 3진법으로 변환하고,</p>
<p>3진법 숫자를 뒤집은 뒤,</p>
<p>그 결과를 다시 10진법으로 변환해서</p>
<p>리턴하는 함수를 작성해야 합니다.</p>
<h4 id="📌-제한사항">📌 제한사항</h4>
<p>n은 1 이상 100,000,000 이하인 자연수입니다.</p>
<h4 id="🧠-문제-접근">🧠 문제 접근</h4>
<p>이 문제는 다음 3단계를 이해하면 쉽게 풀 수 있습니다:</p>
<p>✅ 1. 10진법 → 3진법
Kotlin에서 n.toString(3)을 사용하면 간단하게 3진법 문자열로 바꿀 수 있습니다.</p>
<p>✅ 2. 문자열 뒤집기
reversed()를 사용해서 문자열을 뒤집으면 됩니다.</p>
<p>✅ 3. 3진법 문자열 → 10진법
문자열.toInt(3)으로 3진법 문자열을 다시 10진수로 변환할 수 있습니다.</p>
<p>✏️ 예제 풀이
예제 1)
text
복사
편집
입력: 45</p>
<ol>
<li><p>3진법으로 변환 → 1200</p>
</li>
<li><p>문자열 뒤집기 → 0021</p>
</li>
<li><p>10진법으로 변환 → 7
예제 2)
text
복사
편집
입력: 125</p>
</li>
<li><p>3진법으로 변환 → 11122</p>
</li>
<li><p>문자열 뒤집기 → 22111</p>
</li>
<li><p>10진법으로 변환 → 229
✅ 최종 코드 (Kotlin)
kotlin
복사
편집</p>
<blockquote>
<p>class Solution {
 fun solution(n: Int): Int {</p>
<pre><code> val reversedBase3 = n.toString(3).reversed()
 return reversedBase3.toInt(3)</code></pre><p> }
}</p>
</blockquote>
</li>
</ol>
<p>📚 참고: 진법 변환 요약
목적    Kotlin 코드
10진수 → 3진법 문자열    n.toString(3)
3진법 문자열 → 10진수    &quot;22111&quot;.toInt(3)
문자열 뒤집기    &quot;abc&quot;.reversed()</p>
<p>💬 마무리
이 문제는 Kotlin의 내장 함수만 잘 사용하면 한 줄로도 풀 수 있는 깔끔한 문제입니다.
진법 변환 로직을 직접 구현할 수도 있지만, 코딩테스트에서는 내장함수를 적극 활용하는 것도 전략입니다 💡</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[IntArray.filter(), .sorted() 를 거치면 List<Int> 가 된다.]]></title>
            <link>https://velog.io/@shin_stealer/IntArray.filter-.sorted-%EB%A5%BC-%EA%B1%B0%EC%B9%98%EB%A9%B4-ListInt-%EA%B0%80-%EB%90%9C%EB%8B%A4</link>
            <guid>https://velog.io/@shin_stealer/IntArray.filter-.sorted-%EB%A5%BC-%EA%B1%B0%EC%B9%98%EB%A9%B4-ListInt-%EA%B0%80-%EB%90%9C%EB%8B%A4</guid>
            <pubDate>Sun, 06 Jul 2025 03:53:25 GMT</pubDate>
            <description><![CDATA[<p>IntArray.filter(), .sorted() 를 거치면 List<Int> 가 된다.</p>
<p> <strong>IntArray</strong> -&gt; primitive type, 기본 타입 배열, 즉 int[] 임.</p>
<ul>
<li><p>IntArray 는 Iterable<Int> 가 아님.</p>
</li>
<li><p>하지만 kotlin 에서 filter 는 IntArray 에 대해 List<Int> 를 반환하도록 오버로드 되어있음.</p>
<pre><code>val filtered = arr.filter { it % 5 == 0 } // 타입: List&lt;Int&gt;</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[kotlin 에서 intArray 만들기]]></title>
            <link>https://velog.io/@shin_stealer/kotlin-%EC%97%90%EC%84%9C-intArray-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@shin_stealer/kotlin-%EC%97%90%EC%84%9C-intArray-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 06 Jul 2025 03:41:30 GMT</pubDate>
            <description><![CDATA[<p>val array = [-1] -&gt; 이런 건 없음.</p>
<p>val array = intArrayOf(-1) 이거는 가능함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[.zip() 사용 가능 옵션]]></title>
            <link>https://velog.io/@shin_stealer/.zip-%EC%82%AC%EC%9A%A9-%EA%B0%80%EB%8A%A5-%EC%98%B5%EC%85%98</link>
            <guid>https://velog.io/@shin_stealer/.zip-%EC%82%AC%EC%9A%A9-%EA%B0%80%EB%8A%A5-%EC%98%B5%EC%85%98</guid>
            <pubDate>Sat, 05 Jul 2025 13:24:04 GMT</pubDate>
            <description><![CDATA[<table>
<thead>
<tr>
<th>타입</th>
<th><code>zip()</code> 사용 가능?</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>Array&lt;Int&gt;</code> + <code>Array&lt;Boolean&gt;</code></td>
<td>✅</td>
<td>둘 다 <code>Iterable</code>이니까 가능</td>
</tr>
<tr>
<td><code>IntArray</code> + <code>IntArray</code></td>
<td>✅</td>
<td>Kotlin이 따로 지원함 (특별한 zip 함수 있음)</td>
</tr>
<tr>
<td><code>IntArray</code> + <code>BooleanArray</code></td>
<td>❌</td>
<td>해당 조합은 <code>zip()</code> 오버로드가 없음</td>
</tr>
<tr>
<td><code>IntArray</code> + <code>BooleanArray.toList()</code></td>
<td>✅</td>
<td><code>BooleanArray</code>를 <code>List</code>로 바꾸면 <code>Iterable</code>이므로 zip 가능</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[mutableArrayOf 라는 것은 존재하지 않아.]]></title>
            <link>https://velog.io/@shin_stealer/mutableArrayOf-%EB%9D%BC%EB%8A%94-%EA%B2%83%EC%9D%80-%EC%A1%B4%EC%9E%AC%ED%95%98%EC%A7%80-%EC%95%8A%EC%95%84</link>
            <guid>https://velog.io/@shin_stealer/mutableArrayOf-%EB%9D%BC%EB%8A%94-%EA%B2%83%EC%9D%80-%EC%A1%B4%EC%9E%AC%ED%95%98%EC%A7%80-%EC%95%8A%EC%95%84</guid>
            <pubDate>Sat, 05 Jul 2025 13:09:15 GMT</pubDate>
            <description><![CDATA[<p>❌ mutableArrayOf — 존재하지 않음
✅ mutableListOf — 올바른 함수</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[char ->  int 로 바꿀 때 가능한 옵션]]></title>
            <link>https://velog.io/@shin_stealer/char-int-%EB%A1%9C-%EB%B0%94%EA%BF%80-%EB%95%8C-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%98%B5%EC%85%98</link>
            <guid>https://velog.io/@shin_stealer/char-int-%EB%A1%9C-%EB%B0%94%EA%BF%80-%EB%95%8C-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%98%B5%EC%85%98</guid>
            <pubDate>Sat, 05 Jul 2025 12:47:22 GMT</pubDate>
            <description><![CDATA[<table>
<thead>
<tr>
<th>방법</th>
<th>예시</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td><code>ch - &#39;0&#39;</code></td>
<td><code>&#39;7&#39; - &#39;0&#39;</code> → 7</td>
<td>빠르고 간단</td>
<td>문자 아닌 경우 위험 (예: <code>&#39;A&#39; - &#39;0&#39;</code>)</td>
</tr>
<tr>
<td><code>ch.toString().toInt()</code></td>
<td><code>&#39;7&#39;.toString().toInt()</code> → 7</td>
<td>안전, 직관적</td>
<td>성능 약간 느림</td>
</tr>
<tr>
<td><code>ch.digitToInt()</code></td>
<td><code>&#39;7&#39;.digitToInt()</code> → 7</td>
<td>✅ 추천 (Kotlin 1.5+)</td>
<td>Kotlin 버전 낮으면 불가</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Int x 를 문자열로 바꿔서 자리수 접근 하기]]></title>
            <link>https://velog.io/@shin_stealer/Int-x-%EB%A5%BC-%EB%AC%B8%EC%9E%90%EC%97%B4%EB%A1%9C-%EB%B0%94%EA%BF%94%EC%84%9C-%EC%9E%90%EB%A6%AC%EC%88%98-%EC%A0%91%EA%B7%BC-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@shin_stealer/Int-x-%EB%A5%BC-%EB%AC%B8%EC%9E%90%EC%97%B4%EB%A1%9C-%EB%B0%94%EA%BF%94%EC%84%9C-%EC%9E%90%EB%A6%AC%EC%88%98-%EC%A0%91%EA%B7%BC-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 05 Jul 2025 12:42:20 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>x: Int</p>
</li>
<li><p>x.toString().toCharArray()</p>
</li>
</ul>
<p>여기서 각 자리 수의 합을 구하려면</p>
<ul>
<li><p>x.toString().toCharArray()
  .map{it.toString().toInt()}
  .sum()</p>
</li>
<li><p>Char 에서 toInt() 는 없음.  String 에서 toInt() 는 있음.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[sqrt()]]></title>
            <link>https://velog.io/@shin_stealer/sqrt</link>
            <guid>https://velog.io/@shin_stealer/sqrt</guid>
            <pubDate>Sat, 05 Jul 2025 12:00:54 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>fun sqrt(x: Double): Double </p>
</li>
<li><p>sqrt() 함수는 제곱근을 구할 때 사용한다.</p>
</li>
<li><p>매개변수는 오직 Double 타입이야.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Int.indices는 불가능해]]></title>
            <link>https://velog.io/@shin_stealer/Int.indices%EB%8A%94-%EB%B6%88%EA%B0%80%EB%8A%A5%ED%95%B4</link>
            <guid>https://velog.io/@shin_stealer/Int.indices%EB%8A%94-%EB%B6%88%EA%B0%80%EB%8A%A5%ED%95%B4</guid>
            <pubDate>Sat, 05 Jul 2025 06:35:08 GMT</pubDate>
            <description><![CDATA[<p>Int a = 9</p>
<p>for (i in a.indices) 는 불가능해.</p>
<p>for (i in 1 until n) 은 가능해.</p>
<p>까먹지 말자 쫌!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[📌 Kotlin `IntArray` vs `List<Int>` — 왜 `toList()`가 필요할까?]]></title>
            <link>https://velog.io/@shin_stealer/Kotlin-IntArray-vs-ListInt-%EC%99%9C-toList%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@shin_stealer/Kotlin-IntArray-vs-ListInt-%EC%99%9C-toList%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Wed, 02 Jul 2025 13:12:47 GMT</pubDate>
            <description><![CDATA[<h3 id="✅-상황">✅ 상황</h3>
<pre><code class="language-kotlin">val name = arrayOf(&quot;may&quot;, &quot;kein&quot;)
val yearning = intArrayOf(5, 10)

// 아래 코드 오류 발생
val map = name.zip(yearning) // ❌</code></pre>
<h3 id="✅-이유-intarray는-iterable이-아님">✅ 이유: <code>IntArray</code>는 <code>Iterable</code>이 아님</h3>
<ul>
<li><code>zip()</code> 함수는 <code>Iterable</code> 타입을 요구함</li>
<li><code>IntArray</code>는 <strong>기본형 배열 (primitive)</strong> → <code>Iterable</code> 아님 → 사용 불가</li>
<li><code>List&lt;Int&gt;</code>는 <strong>객체형 컬렉션</strong> → <code>Iterable</code> 구현 → 사용 가능</li>
</ul>
<h3 id="✅-해결-방법">✅ 해결 방법</h3>
<pre><code class="language-kotlin">val map = name.zip(yearning.toList()) // ✅ OK</code></pre>
<ul>
<li><code>IntArray</code>를 <code>toList()</code>로 변환하여 <code>zip()</code> 가능</li>
</ul>
<h3 id="✅-비교-정리">✅ 비교 정리</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th><code>IntArray</code></th>
<th><code>List&lt;Int&gt;</code></th>
</tr>
</thead>
<tbody><tr>
<td>타입</td>
<td>기본형 배열</td>
<td>객체형 컬렉션</td>
</tr>
<tr>
<td>Java 대응</td>
<td><code>int[]</code></td>
<td><code>List&lt;Integer&gt;</code></td>
</tr>
<tr>
<td><code>Iterable</code> 여부</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td><code>zip()</code>, <code>map()</code> 사용</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>메모리 효율</td>
<td>좋음</td>
<td>낮음 (객체로 인해 오버헤드)</td>
</tr>
</tbody></table>
<h3 id="✅-결론">✅ 결론</h3>
<ul>
<li><code>zip()</code>, <code>map()</code> 등 <strong>고차 함수 사용 시</strong> → <code>IntArray.toList()</code> 필요</li>
<li>실수 줄이려면 <code>List&lt;Int&gt;</code> 중심으로 사용하는 습관도 👍</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jetpack Compose 에서 Shared element transitions 적용기]]></title>
            <link>https://velog.io/@shin_stealer/Jetpack-Compose-%EC%97%90%EC%84%9C-Shared-element-transitions-%EC%A0%81%EC%9A%A9%EA%B8%B0</link>
            <guid>https://velog.io/@shin_stealer/Jetpack-Compose-%EC%97%90%EC%84%9C-Shared-element-transitions-%EC%A0%81%EC%9A%A9%EA%B8%B0</guid>
            <pubDate>Sun, 25 May 2025 08:31:24 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 Jetpack Compose에서 SharedTransitionLayout과 sharedElement()를 이용해 Shared Element Transition을 구현한 경험을 공유합니다. 공식 사이트에 예제가 잘 올라와 있지만, 막상 적용하는데 시행착오가 있었습니다. 간단한 구현에 필요한 엑기스들만 요약해서 올려봅니다.</p>
<p>먼저 예시에 사용된 프로젝트는 Google 공식 샘플 앱인 nowinandroid 의 아키텍처를 채용하고 있는 점 참고부탁드립니다. </p>
<ul>
<li>MVVM 아키텍쳐</li>
<li>Multi Modules</li>
</ul>
<h2 id="🎯-구현-목표">🎯 구현 목표</h2>
<p>책 리스트 화면 -&gt; 상세 화면으로 전환 시, 
책 표지 이미지가 자연스럽게 전환되도록 Shared Element Transition 구현.</p>
  <img src="https://velog.velcdn.com/images/shin_stealer/post/29f834ab-4135-4bda-a6ab-57053516e5cc/image.gif" width="40%" />


<h2 id="📝-구현-포인트-딱-이것만-기억합시다">📝 구현 포인트 (딱 이것만 기억합시다.)</h2>
<h4 id="✅-1-전체-ui를-sharedtransitionlayout으로-감싸기">✅ 1. 전체 UI를 SharedTransitionLayout으로 감싸기</h4>
<h4 id="✅-2-공유할-요소에-sharedtransitionscope-animatedvisibilityscope-전달">✅ 2. 공유할 요소에 sharedTransitionScope, animatedVisibilityScope 전달</h4>
<h4 id="✅-3-요소마다-고유-key와-애니메이션-효과-설정">✅ 3. 요소마다 고유 key와 애니메이션 효과 설정</h4>
<h1 id="📝-구현-과정">📝 구현 과정</h1>
<p><img src="https://velog.velcdn.com/images/shin_stealer/post/432e8a2e-447b-4e3e-92f3-d6b61f36b19b/image.png" alt=""></p>
<blockquote>
<h4 id="1-애니메이션-적용할-전체-부분을-sharedtransitionlayout-로-씌우기">1. 애니메이션 적용할 전체 부분을 SharedTransitionLayout 로 씌우기.</h4>
</blockquote>
<p>본 예시의 경우, 두 개의 화면이 존재합니다.</p>
<ul>
<li>리스트 화면(SearchBookScreen), </li>
<li>상세 화면(SearchBookDetailScreen).</li>
</ul>
<p>그리고 화면 이동 시 공유될 element 가 있습니다.</p>
<ul>
<li>리스트 화면 item의 이미지 (element A)</li>
<li>상세 화면의 이미지 (element B)</li>
</ul>
<p>리스트 화면의 item 을 클릭했을 때, 상세 화면으로 이동하며 
가 공유됩니다. </p>
<p>먼저 애니메이션 적용할 전체 부분은 Navigation Graph 에 적용했습니다.</p>
<pre><code>// MainActivity
 SharedTransitionLayout {
   NavigationGraph(navController = navController, sharedTransitionScope = this)
 }
</code></pre><blockquote>
<h4 id="2-애니메이션을-주고자-하는-각-element-에-sharedtransitionscope-animatedvisibilityscope-전달하기">2. 애니메이션을 주고자 하는 각 element 에 sharedTransitionScope, animatedVisibilityScope 전달하기.</h4>
</blockquote>
<p>각 Screen -&gt; element 까지 sharedTransitionScope을 전달합니다.
sharedTransitionScope은 SharedTransitionLayout 에서 받아올 수 있습니다.</p>
<pre><code>// MainActivity
@SuppressLint(&quot;UnusedSharedTransitionModifierParameter&quot;)
@OptIn(ExperimentalAnimationApi::class, ExperimentalSharedTransitionApi::class)
@Composable
fun NavigationGraph(navController: NavHostController, sharedTransitionScope: SharedTransitionScope) {
    NavHost(
        navController = navController,
        startDestination = MY_LIBRARY_ROUTE
    ) {
        myLibraryScreen(
            onNavigateToSearch = { navController.navigateToSearch() }
        )

        searchBookScreen(
            sharedTransitionScope = sharedTransitionScope,
            onNavigateBack = { navController.popBackStack()},
            onNavigateToDetail = { isbn, cover -&gt;
                navController.navigateToSearchBookDetail(isbn, cover)
            }
        )

        searchBookDetailScreen(
            sharedTransitionScope = sharedTransitionScope,
            onNavigateBack = { navController.popBackStack() }
        )
    }
}</code></pre><pre><code>// 
@OptIn(ExperimentalSharedTransitionApi::class)
fun NavGraphBuilder.searchBookScreen(
    sharedTransitionScope: SharedTransitionScope,
    onNavigateBack: () -&gt; Unit,
    onNavigateToDetail: (isbn: String, cover: String) -&gt; Unit,
) {
    composable(SEARCH_ROUTE) {
        SearchBookScreen(
            sharedTransitionScope = sharedTransitionScope,
            animatedVisibilityScope = this,
            onNavigateBack = onNavigateBack,
            onNavigateToDetail = onNavigateToDetail,
        )
    }
}</code></pre><p>이번에는 animatedVisibilityScope 를 전달합니다. animatedVisibilityScope 은 composable로 부터 받아올 수 있습니다.</p>
<p>SearchScreen 의 매개변수로 sharedTransitionScope, animatedVisibilityScope 을 넘겨주었습니다.</p>
<blockquote>
<h4 id="3-각-element-에-key-제공-애니메이션-효과-적용하기">3. 각 element 에 key 제공, 애니메이션 효과 적용하기</h4>
</blockquote>
<p>이제 넘겨받은 sharedTransitionScope, animatedVisibilityScope 변수를 원하는 element 에 연결해줍니다.</p>
<pre><code>### SEARCH BOOK SCREEN
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class, ExperimentalSharedTransitionApi::class)
@Composable
fun SearchBookScreen(
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope,
    onNavigateBack: () -&gt; Unit,
    onNavigateToDetail: (isbn: String, cover: String) -&gt; Unit,
    viewModel: SearchViewModel = hiltViewModel(),
)
... 화면 구성..</code></pre><pre><code>### SEARCH BOOK SCREEN
        with(sharedTransitionScope) {
            SubcomposeAsyncImage(
                model = book.cover,
                contentDescription = null,
                modifier = Modifier
                    .sharedElement(
                        rememberSharedContentState(key = &quot;image/${book.cover}&quot;),
                        animatedVisibilityScope = animatedVisibilityScope,
                        boundsTransform = {initial, taget -&gt; tween(durationMillis = 1000)}
                    )
                    .size(width = 90.dp, height = 140.dp)
                    .padding(start= 10.dp, bottom= 10.dp)</code></pre><p>마찬가지로 SearchBookDetailScreen 의 매개변수로 sharedTransitionScope, animatedVisibilityScope 을 넘겨주었습니다.</p>
<pre><code>### SEARCH BOOK DETAIL SCREEN
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
@Composable
fun SearchBookDetailScreen(
    isbn: String,
    cover: String,
    onNavigateBack: () -&gt; Unit,
    viewModel: SearchDetailViewModel = hiltViewModel(),
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope
) {
... 화면 구성 소스</code></pre><pre><code>### SEARCH BOOK DETAIL SCREEN
 with(sharedTransitionScope) {
         SubcomposeAsyncImage(
            model = cover,
            contentDescription = &quot;책 표지&quot;,
            modifier = Modifier
            .sharedElement(
                rememberSharedContentState(key = &quot;image/$cover&quot;),
                animatedVisibilityScope = animatedVisibilityScope,
                boundsTransform = {initial, taget -&gt; tween(durationMillis = 1000)}
            )
            ... </code></pre><p>저의 경우에는 애니메이션을 적용할 sharedElements는 SubcomposeAsyncImage 였습니다. </p>
<p><strong>애니메이션이 공유될 두 가지의 elements 에 
with(sharedTransitionScope) {} 블럭으로 SubcomposeAsyncImage 을 감쌉니다.</strong></p>
<p>그러면 modifier.sharedElement() 속성을 사용할 수 있게됩니다.</p>
<p>.sharedElement() 안에는 이제 <strong>unique 한 key 값</strong>을 넣어야 합니다.
그리고 <strong>animatedVisibilityScope 속성에는 매개변수로 받아온 변수를 세팅</strong>합니다.</p>
<p>마지막으로 원하는 애니메이션 효과를 적용합니다. 이 부분은 생략해도 기본 boundsTransform 애니메이션이 적용되었습니다.</p>
<blockquote>
<p>더 자세한 소스는 아래 링크에서 확인 가능합니다.
<a href="https://github.com/stevey-sy/odok-compose">https://github.com/stevey-sy/odok-compose</a></p>
</blockquote>
<blockquote>
<p>더 자세한 동작 원리와 효과들은 공식 가이드에서 확인 가능합니다.
<a href="https://developer.android.com/develop/ui/compose/animation/shared-elements">https://developer.android.com/develop/ui/compose/animation/shared-elements</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[왜 LiveData 의 Observe 말고 StateFlow를 사용할까?]]></title>
            <link>https://velog.io/@shin_stealer/%EC%99%9C-LiveData-%EC%9D%98-Observe-%EB%A7%90%EA%B3%A0-StateFlow%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@shin_stealer/%EC%99%9C-LiveData-%EC%9D%98-Observe-%EB%A7%90%EA%B3%A0-StateFlow%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Mon, 24 Mar 2025 15:32:53 GMT</pubDate>
            <description><![CDATA[<h3 id="stateflow란">StateFlow란?</h3>
<p>상태를 담는 상자: StateFlow는 &quot;항상 최신 값을 담고 있는 상자&quot;라고 생각하면 돼요. 예를 들어, 학교 게시판에 최신 공지사항이 항상 붙어 있는 것처럼, StateFlow는 항상 최신 상태(값)를 기억해요.</p>
<p>바뀔 때마다 알려줌: 이 상자의 내용이 바뀌면, 그 변화를 보고 싶어하는 사람(예: 앱의 화면)이 바로 업데이트를 받을 수 있어요.</p>
<p>StateFlow를 사용하지 않으면 어떤 점이 불편할까?
직접 갱신해야 함: 만약 단순한 변수를 사용한다면, 그 값이 바뀌었을 때 매번 직접 찾아서(또는 알려줘야) 업데이트해야 해요. 예를 들어, 게임의 점수를 화면에 보여주려면, 점수가 바뀔 때마다 누군가가 &quot;이제 점수가 이렇게 바뀌었어!&quot;라고 알려줘야 해요.</p>
<p>업데이트 누락 위험: 만약 값이 바뀌었는데 그 변화를 제대로 전달하지 못하면, 화면에 오래된 정보가 남아 있을 수 있어요. 예를 들어, 여러분이 점수를 기록하는데, 점수가 바뀌어도 화면에 그 변화가 바로 나타나지 않는다면 게임이 제대로 진행되지 않겠죠?</p>
<p>관리 복잡도 증가: 여러 곳에서 데이터를 직접 관리하고 업데이트해야 하므로, 코드가 복잡해지고 실수가 생길 확률도 높아져요.</p>
<h3 id="정리하면">정리하면</h3>
<p>StateFlow는 <strong>&quot;항상 최신 상태를 자동으로 관리해주는 도구&quot;</strong>예요.</p>
<p>사용하면: 데이터가 바뀔 때마다 자동으로 최신 정보를 화면에 보여줄 수 있어서 편리하고 안전해요.</p>
<p>사용하지 않으면: 최신 정보를 직접 관리해야 하므로, 업데이트가 늦어지거나 빠뜨리는 문제가 생길 수 있어요.</p>
<p>이런 이유로 StateFlow는 앱에서 데이터의 최신 상태를 유지하고, 그 변화를 자동으로 반영하는 데 큰 도움이 됩니다.</p>
<p>LiveData 방식도 충분히 잘 동작하고 널리 사용되는 패턴입니다. 다만, StateFlow를 사용하면 다음과 같은 몇 가지 장점을 얻을 수 있어요:</p>
<h3 id="코루틴과의-자연스러운-통합">코루틴과의 자연스러운 통합</h3>
<p>LiveData는 Android Lifecycle에 맞게 설계되어 있지만, StateFlow는 코루틴 기반이기 때문에 suspend 함수나 다른 Flow 연산자들과 함께 사용하기가 더 쉽습니다. 그래서 비동기 처리를 할 때 코드가 더 간결해질 수 있어요.</p>
<h3 id="항상-최신-값-유지">항상 최신 값 유지</h3>
<p>StateFlow는 항상 최신 상태를 보관하기 때문에, 구독자가 새롭게 연결되었을 때 즉시 최신 값을 받을 수 있어요. LiveData도 최신 값을 유지하지만, StateFlow는 코루틴 환경에서 더 예측 가능한 방식으로 동작합니다.</p>
<h3 id="테스트-용이성">테스트 용이성</h3>
<p>코루틴과 Flow를 기반으로 하기 때문에, 단위 테스트나 통합 테스트를 할 때 상태 흐름을 제어하고 검증하기가 더 쉽습니다.</p>
<h3 id="불필요한-observeforever-방지">불필요한 observeForever 방지</h3>
<p>LiveData를 observeForever로 관찰하면 Lifecycle의 도움을 받지 못해 메모리 누수 위험이 있습니다. 반면, StateFlow는 LifecycleScope나 repeatOnLifecycle 같은 코루틴 기반 함수를 사용하여 안전하게 구독할 수 있어요.</p>
<p>즉, 기존의 LiveData 방식도 충분히 잘 동작하지만, 코루틴을 적극적으로 사용하고자 할 때는 StateFlow가 더 깔끔하고 관리하기 쉬운 선택이 될 수 있습니다. 어떤 방식을 선택할지는 프로젝트의 요구사항과 팀의 선호도에 따라 달라질 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[왜 suspend 함수를 사용해야할까?]]></title>
            <link>https://velog.io/@shin_stealer/%EC%99%9C-suspend-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@shin_stealer/%EC%99%9C-suspend-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Fri, 06 Dec 2024 05:12:38 GMT</pubDate>
            <description><![CDATA[<p>기존 비동기 작업(콜백 기반)의 단점과 코루틴으로 개선된 예시</p>
<h3 id="1-기존-비동기-작업-콜백-기반">1. 기존 비동기 작업: 콜백 기반</h3>
<p>콜백(callback) 방식의 구조
콜백 방식은 비동기 작업이 끝난 후 실행될 코드를 함수로 전달하는 방식입니다. 하지만 작업이 많아질수록 <strong>콜백 지옥(Callback Hell)</strong>이라고 불리는 문제가 발생합니다.
코드가 중첩되면서 읽기 어렵고, 디버깅도 힘들어집니다.</p>
<p>예제: 네트워크 요청 후 데이터 처리
아래는 콜백 기반으로 두 개의 네트워크 요청을 순차적으로 실행하는 예입니다.</p>
<p>kotlin</p>
<pre><code>fun fetchData(callback: (String) -&gt; Unit) {
    // 네트워크 요청 A
    simulateNetworkRequest(&quot;Request A&quot;) { resultA -&gt;
        // 네트워크 요청 B (A의 결과를 이용)
        simulateNetworkRequest(&quot;Request B based on $resultA&quot;) { resultB -&gt;
            // 최종 데이터 처리
            callback(resultB)
        }
    }
}

fun simulateNetworkRequest(data: String, callback: (String) -&gt; Unit) {
    Thread {
        Thread.sleep(2000) // 네트워크 요청 시뮬레이션 (2초 대기)
        callback(&quot;$data - Response&quot;)
    }.start()
}

fun main() {
    fetchData { finalResult -&gt;
        println(&quot;최종 결과: $finalResult&quot;)
    }
}</code></pre><p>문제점</p>
<p>콜백 안에 콜백을 정의하다 보면 코드가 오른쪽으로 점점 밀리면서 복잡해집니다.
요청이 3개, 4개로 늘어나면 가독성이 더 떨어집니다.</p>
<p>에러 처리 어려움.
에러가 발생했을 때, 어떤 요청에서 발생했는지 파악하기 어렵습니다.
에러 처리를 하려면 각각의 콜백마다 에러를 처리하는 코드를 추가해야 합니다.</p>
<h3 id="2-코루틴으로-개선된-비동기-작업">2. 코루틴으로 개선된 비동기 작업</h3>
<p>코루틴은 비동기 작업을 동기식 코드처럼 작성할 수 있도록 도와줍니다. suspend 함수를 사용하면 중첩된 콜백 없이 작업의 흐름을 순차적으로 작성할 수 있습니다.</p>
<p>코드 예제: 동일한 작업을 코루틴으로 구현
kotlin</p>
<pre><code>import kotlinx.coroutines.*

suspend fun fetchData(): String {
    val resultA = simulateNetworkRequest(&quot;Request A&quot;)
    val resultB = simulateNetworkRequest(&quot;Request B based on $resultA&quot;)
    return resultB
}

suspend fun simulateNetworkRequest(data: String): String {
    delay(2000) // 네트워크 요청 시뮬레이션 (2초 대기)
    return &quot;$data - Response&quot;
}

fun main() = runBlocking {
    val finalResult = fetchData()
    println(&quot;최종 결과: $finalResult&quot;)
}</code></pre><p>개선된 점
코드 가독성:</p>
<p>작업 순서를 동기식 코드처럼 직관적으로 작성할 수 있습니다.
네트워크 요청 A → B → 결과 처리의 흐름이 명확합니다.
에러 처리 간편화:</p>
<p>try-catch를 사용하여 에러를 처리할 수 있습니다.
어떤 작업에서 에러가 발생했는지 쉽게 파악 가능합니다.</p>
<h3 id="3-에러-처리-예제">3. 에러 처리 예제</h3>
<p>콜백 기반의 에러 처리
에러 처리를 추가하려면 각 콜백마다 별도의 에러 처리 로직을 추가해야 합니다.</p>
<p>kotlin</p>
<pre><code>fun fetchData(callback: (String?, Throwable?) -&gt; Unit) {
    simulateNetworkRequest(&quot;Request A&quot;) { resultA, errorA -&gt;
        if (errorA != null) {
            callback(null, errorA)
            return@simulateNetworkRequest
        }
        simulateNetworkRequest(&quot;Request B based on $resultA&quot;) { resultB, errorB -&gt;
            if (errorB != null) {
                callback(null, errorB)
                return@simulateNetworkRequest
            }
            callback(resultB, null)
        }
    }
}

fun simulateNetworkRequest(
    data: String,
    callback: (String?, Throwable?) -&gt; Unit
) {
    Thread {
        try {
            Thread.sleep(2000)
            callback(&quot;$data - Response&quot;, null)
        } catch (e: Exception) {
            callback(null, e)
        }
    }.start()
}</code></pre><p>코루틴 기반의 에러 처리
코루틴에서는 try-catch로 간단하게 처리할 수 있습니다.</p>
<p>kotlin</p>
<pre><code>suspend fun fetchData(): String {
    return try {
        val resultA = simulateNetworkRequest(&quot;Request A&quot;)
        val resultB = simulateNetworkRequest(&quot;Request B based on $resultA&quot;)
        resultB
    } catch (e: Exception) {
        &quot;에러 발생: ${e.message}&quot;
    }
}

suspend fun simulateNetworkRequest(data: String): String {
    delay(2000)
    if (data.contains(&quot;Error&quot;)) throw Exception(&quot;네트워크 오류!&quot;)
    return &quot;$data - Response&quot;
}</code></pre><h3 id="결론">결론</h3>
<p>기존의 콜백 기반 비동기 작업은 중첩 문제와 복잡한 에러 처리 로직으로 인해 코드 가독성이 떨어지는 단점이 있었습니다. 반면, 코루틴은 비동기 작업을 동기식 코드처럼 작성할 수 있게 해주어 가독성, 유지보수성, 디버깅 측면에서 훨씬 유리합니다.
실제 프로젝트에서는 코루틴을 사용해 효율적으로 비동기 작업을 관리하는 것이 권장됩니다. 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코루틴 스코프 (Coroutine Scope) & Suspend 함수에 대하여..]]></title>
            <link>https://velog.io/@shin_stealer/%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%8A%A4%EC%BD%94%ED%94%84-Coroutine-Scope-Suspend-%ED%95%A8%EC%88%98%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@shin_stealer/%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%8A%A4%EC%BD%94%ED%94%84-Coroutine-Scope-Suspend-%ED%95%A8%EC%88%98%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Fri, 06 Dec 2024 05:00:55 GMT</pubDate>
            <description><![CDATA[<p>코틀린의 코루틴은 비동기 작업을 효율적이고 간결하게 처리할 수 있는 강력한 도구입니다. 이 글에서는 코루틴 스코프와 suspend 함수에 대해 간단히 설명하고, 왜 이를 사용하는 것이 좋은지 알아보겠습니다.</p>
<h3 id="1-코루틴-스코프란">1. 코루틴 스코프란?</h3>
<p>코루틴 스코프는 코루틴이 실행되는 공간을 의미합니다. 코루틴을 실행하고 관리하며, 모든 작업이 완료되면 스코프는 자동으로 정리(clean-up)됩니다.
여러 비동기 작업을 안전하게 실행하고, 필요할 경우 취소하거나 관리할 수 있습니다.</p>
<p>코루틴 스코프의 주요 역할:
작업 관리: 여러 코루틴을 하나의 스코프 안에서 실행.
자동 정리: 스코프 내 모든 작업이 끝나면 자원을 정리.
구조적 동시성: 비동기 작업을 구조적으로 관리해 코드 복잡도를 줄임.</p>
<p>코루틴 스코프 예시:</p>
<pre><code>fun main() {
    runBlocking { // 코루틴 스코프
        launch { // 코루틴 실행
            val result = fetchData()
            println(result)
        }
        println(&quot;다른 작업을 실행 중입니다!&quot;)
    }
}</code></pre><p>runBlocking: 코루틴 스코프를 생성하고 블록 내의 작업이 모두 끝날 때까지 실행을 유지합니다.
launch: 코루틴을 실행합니다. 코루틴은 비동기로 작동하므로 다른 작업과 동시에 실행됩니다.</p>
<h3 id="1-1-비유-요리사와-레스토랑">1-1. 비유: 요리사와 레스토랑</h3>
<h4 id="코루틴-스코프는-레스토랑의-주방과-같아요">코루틴 스코프는 레스토랑의 주방과 같아요.</h4>
<p>주방에서는 여러 요리사가 동시에 요리를 준비할 수 있어요.
각각의 요리사는 자기 요리를 하다가, 재료가 준비될 때까지 기다리거나 다른 요리를 도와줄 수 있어요.
suspend 함수는 요리사가 잠깐 멈추고 기다리는 것과 같아요.</p>
<p>요리사가 물을 끓이는 동안 물이 끓을 때까지 기다리면서 다른 요리를 준비하거나 쉬어요.
하지만 기다린다고 해서 주방이 멈추는 건 아니에요. 다른 요리사들은 계속해서 요리를 준비하니까요.</p>
<h3 id="2-코루틴의-역할">2. 코루틴의 역할</h3>
<p>프로그래밍에서도 &quot;기다려야 하는 일&quot;이 자주 생겨요. 예를 들어:</p>
<p>인터넷에서 책 정보를 가져오는 일.
파일을 다운로드하는 일.
게임에서 상대방의 행동을 기다리는 일.
기다리는 동안 프로그램이 멈추면 사용자가 불편하겠죠? 코루틴은 프로그램이 멈추지 않고 다른 일을 계속할 수 있도록 도와줘요.</p>
<h3 id="3-코루틴과-suspend-함수의-장점">3. 코루틴과 suspend 함수의 장점</h3>
<p>효율적인 작업 처리:
시간이 오래 걸리는 작업(예: 네트워크 요청, 파일 읽기 등)을 기다리는 동안 다른 작업을 처리할 수 있습니다.
코드 가독성:
콜백(callback) 중첩 없이 비동기 코드를 동기식 코드처럼 작성할 수 있습니다.
사용자 경험 개선:
메인 스레드를 블로킹하지 않기 때문에 앱이 더 부드럽게 작동합니다.</p>
<h3 id="3-1-suspend-함수의-역할-비유">3-1. suspend 함수의 역할 비유</h3>
<p>suspend 함수는 코루틴에서 특별히 멈추고 기다릴 수 있는 함수예요.</p>
<p>예를 들어, 인터넷에서 데이터를 가져오는 함수는 시간이 걸려요. 이런 함수는 suspend로 만들어져야 코루틴에서 사용자가 기다리는 동안 다른 일을 할 수 있어요.</p>
<p>kotlin</p>
<pre><code>suspend fun boilWater(): String {
    // 물을 끓이는 데 시간이 걸린다고 상상해봐요
    delay(3000) // 3초 기다리기 (코루틴에서만 사용 가능)
    return &quot;물이 끓었어요!&quot;
}</code></pre><p>delay(3000)은 3초 동안 기다리라는 의미인데, 이 3초 동안 다른 작업이 멈추지 않도록 코루틴이 알아서 처리해줘요.</p>
<h3 id="4-코루틴-스코프란">4. 코루틴 스코프란?</h3>
<p>코루틴 스코프는 코루틴을 실행하는 공간이에요. 요리사의 주방처럼요.</p>
<p>예를 들어, 레스토랑에 요리사가 여러 명 있고, 각각의 요리사가 코루틴이라고 생각해요. 주방(코루틴 스코프)은 요리사들이 일을 하다가 끝나면 깨끗이 치워주는 역할을 해요.</p>
<p>코루틴 스코프 만들기
kotlin</p>
<pre><code>fun main() {
    runBlocking { // 코루틴 스코프
        launch {
            val result = boilWater()
            println(result)
        }
        println(&quot;다른 작업을 하고 있어요!&quot;)
    }
}</code></pre><p>runBlocking: 주방을 열고 코루틴을 실행해요.
launch: 요리사 한 명이 일을 시작해요.
프로그램은 물이 끓을 동안(3초) 다른 작업(println)도 처리해요.</p>
<h3 id="5-왜-코루틴과-suspend-함수가-좋은-걸까">5. 왜 코루틴과 suspend 함수가 좋은 걸까?</h3>
<p>효율적이에요!: 오래 걸리는 작업(인터넷 요청, 파일 다운로드 등)을 기다리는 동안 다른 작업을 할 수 있어요.
간단해요!: 복잡한 코드를 깔끔하고 읽기 쉽게 만들 수 있어요.
다른 프로그램을 멈추지 않아요!: 사용자는 앱이 끊기지 않고 부드럽게 작동하는 걸 느낄 수 있어요.</p>
<h3 id="6-핵심-요약">6. 핵심 요약</h3>
<p>코루틴 스코프는 여러 일을 동시에 할 수 있게 도와주는 작업 공간이에요.
suspend 함수는 잠깐 멈춰야 하는 함수인데, 멈춰도 프로그램 전체가 멈추지 않게 해줘요.
코루틴 덕분에 복잡한 비동기 작업을 더 쉽고 깔끔하게 처리할 수 있어요.</p>
<h3 id="7-실제-예제">7. 실제 예제</h3>
<p>예를 들어, 사용자가 버튼을 눌러 데이터를 가져오는 동안, 메인 스레드는 UI를 업데이트하거나 애니메이션을 실행할 수 있습니다.</p>
<p>코드 예제
kotlin</p>
<pre><code>import kotlinx.coroutines.*

fun main() {
    runBlocking { // 메인 스레드를 차단하지 않는 코루틴 스코프
        launch {
            // 2초 동안 데이터를 가져오는 작업
            println(&quot;데이터 가져오기 시작&quot;)
            val data = fetchData()
            println(&quot;가져온 데이터: $data&quot;)
        }

        // 동시에 다른 작업도 실행 가능
        launch {
            repeat(5) { i -&gt;
                println(&quot;UI 작업 중... $i&quot;)
                delay(500) // 0.5초 대기
            }
        }
    }
}

suspend fun fetchData(): String {
    delay(2000) // 데이터를 가져오는 데 2초 소요
    return &quot;데이터 가져오기 성공&quot;
}</code></pre><p>실행 결과
위 코드를 실행하면 다음과 같은 결과를 볼 수 있습니다:</p>
<pre><code>데이터 가져오기 시작
UI 작업 중... 0
UI 작업 중... 1
UI 작업 중... 2
UI 작업 중... 3
UI 작업 중... 4
가져온 데이터: 데이터 가져오기 성공</code></pre><p><strong>첫 번째 코루틴: 데이터를 가져오는 데 2초 걸립니다.</strong>
<strong>두 번째 코루틴: 0.5초마다 &quot;UI 작업 중...&quot;이라는 메시지를 출력합니다.</strong></p>
<p>이 두 작업이 동시에 실행되며, 2초가 지나기 전에 UI 작업이 5번 출력됩니다.
메인 스레드의 역할
위에서 UI 작업 중... 메시지는 메인 스레드가 처리할 수 있는 작업을 예로 든 것입니다. </p>
<p>실제 앱에서는:</p>
<p>화면을 렌더링 (예: 리스트를 스크롤하거나 애니메이션 실행).
사용자 입력 처리 (예: 버튼 클릭, 스와이프 동작 등).
네트워크 요청 대기 중에도 앱이 부드럽게 작동.</p>
<h3 id="요약">요약</h3>
<p>코루틴의 suspend 함수는 작업을 잠깐 멈출 수 있지만, 메인 스레드는 멈추지 않고 계속 실행됩니다.
이로 인해 UI 작업과 비동기 작업을 동시에 처리할 수 있어, 사용자 경험이 크게 향상됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[B.Archaive 앱 소개]]></title>
            <link>https://velog.io/@shin_stealer/B.Archaive-%EC%95%B1-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@shin_stealer/B.Archaive-%EC%95%B1-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Fri, 29 Nov 2024 07:07:58 GMT</pubDate>
            <description><![CDATA[<p>도서 검색 앱을 개발 중입니다.
검색 기능에 알라딘 서재 Open Api 를 활용하고 싶습니다.</p>
<p><img src="https://velog.velcdn.com/images/shin_stealer/post/9de84fb6-09a9-49b6-8655-ef7315c5e75b/image.jpeg" alt=""><img src="https://velog.velcdn.com/images/shin_stealer/post/0b850f6e-efdc-4625-a121-e703a17dfd0c/image.jpeg" alt="">
<img src="https://velog.velcdn.com/images/shin_stealer/post/c765747c-b00c-4ca4-9da3-9c45619c54e4/image.jpeg" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>