<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>memo_00.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 05 Mar 2026 11:37:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>memo_00.log</title>
            <url>https://velog.velcdn.com/images/memo_00/profile/2ef7a76a-ca4f-4cba-8244-03fa7aac68db/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. memo_00.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/memo_00" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[의존성 주입이란 무엇이고, 왜 사용하나?]]></title>
            <link>https://velog.io/@memo_00/%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%82%98</link>
            <guid>https://velog.io/@memo_00/%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%82%98</guid>
            <pubDate>Thu, 05 Mar 2026 11:37:30 GMT</pubDate>
            <description><![CDATA[<p>2026.03.05 (목)</p>
<ul>
<li>의존성 주입</li>
<li>관련 라이브러리</li>
</ul>
<hr>
<h3 id="오늘의-공부-내용-이미지-💫">오늘의 공부 내용 이미지 💫</h3>
<p><img src="https://velog.velcdn.com/images/memo_00/post/062a8cbb-8413-4b11-a52e-3fce5d70e7d4/image.png" alt=""></p>
<hr>
<h2 id="🤔-의존성-주입이란-di">🤔 의존성 주입이란 (DI)</h2>
<p>의존성 주입(Dependency Injection, DI)은 객체 간의 의존성을 외부에서 주입하는 설계 패턴이다.
클래스가 직접 다른 객체를 생성하는 것이 아니라, 외부에서 제공된 객체를 사용하는 것이다. </p>
<h2 id="😲-왜-사용할까">😲 왜 사용할까</h2>
<p>1) 결합도를 낮추기 위해서
객체 내부에서 다른 객체를 직접 생성하면, 그 객체가 바뀔 때마다 내 코드도 수정을 해야 한다. 외부에서 넣어준다면 내부 코드는 바뀔 필요가 없어져서 변화에 유연하다.</p>
<p>2) 테스트가 쉬워지기 위해서
실제 데이터베이스 대신 가짜 데이터(Mock)를 넣어 테스트 하고 싶을 때, DI가 되어 있다면 코드 수정 없이 가짜 데이터를 주입해서 테스트 할 수 있다.</p>
<p>3) 재사용성과 유지보수성 향상
한 번 만들어둔 로직을 여기저기서 필요한 부품만 바꿔 끼우며 다시 사용할 수 있다.</p>
<h3 id="▪️-코드-예시">▪️ 코드 예시</h3>
<pre><code class="language-dart">class Engine {}

class Car {
  final Engine engine;
  Car(this.engine); // 생성자로 직접 받음
}

// 사용 시
final myCar = Car(Engine());</code></pre>
<hr>
<h2 id="🥚-수동-di의-한계">🥚 수동 DI의 한계</h2>
<p>단순 생성자 주입만 사용하면, 부모 위젯에서 아주 먼 자식 위젯으로 데이터를 전달할 때 중간 위젯들이 사용하지도 않는 데이터를 단순히 전달만 해야 하는 <code>Prop Drilling</code> 현상이 발생한다.</p>
<h2 id="🍳-그래서-라이브러리를-활용해서-di를-사용한다">🍳 그래서 라이브러리를 활용해서 DI를 사용한다.</h2>
<h3 id="▫️-provider를-활용한-주입">▫️ Provider를 활용한 주입</h3>
<p>위젯 트리 상단에서 공급하고, 하위 트리에서 context를 통해 찾아 쓰는 방식
-&gt; <code>BuildContext</code>기반으로 위젯 트리 구조에 의존한다.</p>
<pre><code class="language-dart">// 1. 공급 (위젯 트리 상단)
Provider(
  create: (_) =&gt; AuthService(),
  child: MyApp(),
)

// 2. 주입 (하위 위젯)
final auth = Provider.of&lt;AuthService&gt;(context);
// 또는 context.read&lt;AuthService&gt;();</code></pre>
<h3 id="▫️-riverpod을-활용한-주입">▫️ Riverpod을 활용한 주입</h3>
<p>전역 변수로 Provider를 선언하고, ref를 통해 안전하게 주입
-&gt; <code>Compile-safe</code> 위젯 트리와 독립적이며 <code>Provider</code>의 단점을 모두 개선함</p>
<pre><code class="language-dart">// 1. 전역 선언
final repositoryProvider = Provider((ref) =&gt; Repository());

// 2. 주입 (위젯 내부)
Widget build(BuildContext context, WidgetRef ref) {
  final repo = ref.watch(repositoryProvider); // 주입 완료
  return Text(repo.data);
}</code></pre>
<h3 id="▫️-getit을-활용한-패턴">▫️ GetIt을 활용한 패턴</h3>
<p>전역적인 &#39;저장소&#39;에 등록해두고 어디서든(위젯 밖에서도) 꺼내 쓰는 방식
-&gt; <code>BuildContext</code>없이 어디서든 접근 가능하며, 테스트 시에 <code>Mock</code> 교체가 쉽다.</p>
<pre><code class="language-dart">final locator = GetIt.instance;

// 1. 등록 (Main함수 등에서)
locator.registerSingleton&lt;ApiService&gt;(ApiService());

// 2. 주입 (어디서든)
final api = locator&lt;ApiService&gt;();</code></pre>
<hr>
<h2 id="🍇-핵심-요약">🍇 핵심 요약</h2>
<p>🍒 의존성 주입(DI)은 객체가 직접 의존성을 생성하는 것이 아니라, 외부에서 생성된 객체를 전달받는 디자인 패턴으로 이를 사용하는 이유는 객체 간의 결합도를 낮추어 코드의 유연성을 높이고, 특히 단위 테스트 시 가짜 객체를 주입하기 쉬워져 테스트 용이성을 확보할 수 있기 때문이다.!</p>
<p>🍒 Flutter에서 의존성 주입은 객체 간의 결합도를 낮추기 위해 필수적이다. 
기본적으로는 생성자 주입 방식을 사용하지만, 앱 규모가 커지면 하위 위젯으로 데이터를 반복 전달해야 하는 Prop Drilling 문제가 발생할 수 있어서 이를 해결하기 위해서는 GetIt이나 Provider, Riverpod 같은 도구를 활용하여 의존성을 관리 할 수 있다.</p>
<p>🍒 결과적으로 코드가 더 깨끗해지고, 단위 테스트 시 Mock 객체를 주입하기 매우 유리한 구조가 되어 유지보수성이 크게 향상한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter Web이 작동하는 원리]]></title>
            <link>https://velog.io/@memo_00/Flutter-Web%EC%9D%B4-%EC%9E%91%EB%8F%99%ED%95%98%EB%8A%94-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@memo_00/Flutter-Web%EC%9D%B4-%EC%9E%91%EB%8F%99%ED%95%98%EB%8A%94-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Wed, 04 Mar 2026 11:45:11 GMT</pubDate>
            <description><![CDATA[<p>2026.03.04 (수)
Flutter Web이 작동하는 원리에 대해 설명</p>
<hr>
<h3 id="오늘의-공부-내용-이미지-💫">오늘의 공부 내용 이미지 💫</h3>
<p><img src="https://velog.velcdn.com/images/memo_00/post/85f0ae7b-0c1d-4ec6-a485-47040684d8c3/image.png" alt=""></p>
<hr>
<h2 id="🍔-구조화를-해서-설명하기">🍔 구조화를 해서 설명하기</h2>
<p>크게 컴파일 과정과 렌더링 방식 두가지로 설명</p>
<h3 id="🥔-컴파일-과정">🥔 컴파일 과정</h3>
<p>-&gt; 내가 쓴 Dart 언어를 브라우저가 알아듣는 언어로 번역하는 과정</p>
<ul>
<li>Dart to JavaScript
브라우저는 Dart 언어를 직접 해석할 수 없다. 따라서 Flutter는 <code>dart2js</code>컴파일러를 사용하여 작성한 Dart 코드를 브라우저가 이해할 수 있는 JavaScript로 변환한다.</li>
<li>패키징
앱의 비지니스 로직과 Flutter 프레임워크 자체를 하나의 JS 파일로 묶어 브라우저에서 실행 가능하게 만든다.</li>
</ul>
<h3 id="🍟-렌더링-계층">🍟 렌더링 계층</h3>
<p>-&gt; 번역이 끝났다면 이제 화면에 UI를 그린다. </p>
<p>Flutter Web은 화면을 그릴 때 두 가지 엔진 중 하나를 선택한다.</p>
<ul>
<li><p>HTML 렌더러
(브라우저가 기본적으로 가지고 있는 재료 HTML태그, CSS 효과 들을 조합해서 화면을 만든다.)</p>
<ul>
<li>기술: HTML 요소, CSS, Canvas 2D 사용</li>
<li>장점: 파일 크기가 작음, 로딩 속도가 빠름</li>
<li>단점: 복잡한 그래픽에서 성능 저하 기능</li>
<li>용도: 텍스트 위주의 웹사이트, 저사양 환경</li>
</ul>
</li>
<li><p>CanvasKit 렌더러
(브라우저의 기본 재료를 무시하고, Skia라는 강력한 그래픽 엔진을 웹으로 가져와서 픽셀을 하나씩 그린다.)</p>
<ul>
<li>기술: WebAssembly(Wasm) + Skia 엔진</li>
<li>장점: 모바일과 100% 동일한 품질, 성능 우수</li>
<li>단점: 엔진 다운로드로 초기 로딩이 길다</li>
<li>용도: 화려한 애니메이션, 복잡한 UI 앱</li>
</ul>
</li>
</ul>
<h3 id="🍻-브라우저와의-통신">🍻 브라우저와의 통신</h3>
<ul>
<li><p>입력처리
마우스 클릭, 휠 스크롤, 키보드 입력 등 브라우저 이벤트를 Flutter 포인터 데이터로 변환</p>
</li>
<li><p>접근성
Flutter는 화면의 픽셀만 그리는 것이 아니라, 웹 표준에 맞게 Semantics Tree를 생성하여 스크린 리더(시각장애인용 서비스)가 작동하도록 돕는다.</p>
</li>
</ul>
<hr>
<h2 id="🔹-flutter-web-장점-단점">🔹 Flutter Web 장점 단점</h2>
<h3 id="🔔-장점">🔔 장점</h3>
<p>브라우저의 레이아웃 엔진에 의존하지 않고 Flutter가 직접 픽셀을 제어한다. 
한번의 작성으로 모든 브라우저에서 동일한 UI를 완벽하게 구현할 수 있다.</p>
<h3 id="🔕-단점">🔕 단점</h3>
<p>❗️ <strong>지연로딩</strong>
초기 로딩 속도가 느릴 수 있다.</p>
<p>❗️ <strong>검색 엔진 최적화의 어려움</strong>
구글이나 네이버 같은 검색 엔진 로봇은 HTML 텍스트를 읽어서 정보를 수집하는데 Flutter Web은 화면을 통째로 그리기 때문에 로봇이 내용을 읽기가 매우 어려울 수 있다. 네이버나 구글 검색 결과 상단에 우리 사이트를 노출시키기가 힘들다.</p>
<h2 id="🔷-단점에-대한-해결-방안은">🔷 단점에 대한 해결 방안은?</h2>
<p>❗️🔔 <strong>지연로딩 활용</strong>
앱의 모든 기능을 한 번에 불러오지 않고, 사용자가 클릭할 때만 필요한 코드를 다운로드하게 설정한다.
예를 들어, 관리자 설정 페이지는 사용자가 들어갈 때만 불러오도록 코드를 쪼개는 것이 방법이다.
➕ Asset 최적화를 할 수 있다. 고용량 이미지는 외부 서버나 Firebase Storage에 두고 호출하여 초기 패키지 크기를 줄일 수 있다.</p>
<p>❗️🔔 <strong>검색 최적화 문제 극복</strong>
검색 노출이 중요한 랜딩 페이지나 블로그는 SEO에 강한 기존 웹 기술(HTML, React..)로 만들고, 실제 복잡한 기능을 수행하는 메인 서비스는 Flutter Web으로 개발하여 연결한다.
<code>seo_renderer</code>같은 라이브러리를 사용해 검색 로봇이 읽을 수 있는 최소한의 HTML텍스트를 생성하여 보완할 수 있다.</p>
<hr>
<h2 id="📝-핵심-요약">📝 핵심 요약</h2>
<p>Flutter Web은 Dart 코드를 브라우저용 JavaScript를 번역하여 실행된다. 화면을 그릴 때는 성능과 용량 사이의 균형을 맞추기 위햐 표준 웹 기술을 쓰는 HTML 방식과 직접 픽셀을 그리는 CanvasKit 방식 중 최적의 경로를 선택해서 작동한다.</p>
<p>기존 웹 개발은 브라우저마다 CSS 해석 방식이 달라 크로스 브라우징 이슈가 잦았다. 하지만 Flutter Web은 브라우저의 레이아웃 엔진에 의존하지 않고 Flutter가 직접 픽셀을 제어한다. 덕분에 한번의 작성으로 모든 브라우저에서 동일한 UI를 완벽하게 구현할 수 있다는 점이 가장 큰 장점이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Entity와 DTO 분리 및 데이터 바인딩]]></title>
            <link>https://velog.io/@memo_00/Entity%EC%99%80-DTO-%EB%B6%84%EB%A6%AC-%EB%B0%8F-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%94%EC%9D%B8%EB%94%A9</link>
            <guid>https://velog.io/@memo_00/Entity%EC%99%80-DTO-%EB%B6%84%EB%A6%AC-%EB%B0%8F-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%94%EC%9D%B8%EB%94%A9</guid>
            <pubDate>Thu, 12 Feb 2026 12:14:18 GMT</pubDate>
            <description><![CDATA[<p>2026.02.12 (목)
Troble Shooting: Entity와 DTO의 분리 및 데이터 바인딩</p>
<hr>
<h3 id="오늘의-공부-내용-이미지-💫">오늘의 공부 내용 이미지 💫</h3>
<p><img src="https://velog.velcdn.com/images/memo_00/post/def2069a-0198-4199-abfb-a8bc16784f12/image.png" alt=""></p>
<hr>
<h2 id="😟-문제-상황">😟 문제 상황</h2>
<p>팀 프로젝트 Healthy bag 진행 중, 외부 API나 Firebase에서 받아오는 데이터 구조 (DTO)와 앱의 핵심 비지니스 로직에서 사용하는 데이터 구조 (Entity)를 하나로 합쳐서 사용할지, 분리해야 할지에 대한 혼란이 발생함. 
특히 <code>Comment</code>와 <code>Feed</code> 데이터를 UI에 바인딩하는 과정에서 모델의 역할이 모호해지는 문제</p>
<h2 id="🤯-원인-분석">🤯 원인 분석</h2>
<ul>
<li>의존성 문제: DTO를 그대로 UI나 UseCase에서 사용하면, 서버 데이터 구조가 변경될 때마다 앱 전체 코드를 수정해야 하는 리스크가 있음</li>
<li>클린 아키텍처 원칙: 데이터 계층(Data Layer)과 도메인 계층(Domain Layer)은 엄격히 분리되어야 하며, 각 계층에 맞는 최적화된 모델이 필요함.</li>
</ul>
<h2 id="🥹-해결-방법">🥹 해결 방법</h2>
<p>Model(Entity) 정의: Comment와 Feed를 순수한 Dart 객체인 Entity로 정의하여 도메인 계층에 위치시킴. (UI는 오직 이 Entity만 바라보게 함)</p>
<p>DTO 및 Mapper 활용: fromJson, toJson 등을 포함한 DTO를 데이터 계층에 두고, 이를 Entity로 변환해 주는 별도의 로직을 구현.</p>
<p>UI 데이터 바인딩 분리: 기존의 복잡한 홈페이지 UI 코드에서 데이터 로직을 분리하여, 전달받은 Entity를 위젯에 주입하는 방식으로 리팩토링 진행.</p>
<hr>
<h3 id="오늘의-배운-점-😵💫">오늘의 배운 점 😵‍💫</h3>
<p>클린 아키텍처는 처음 설계할 때는 번거롭지만, 프로젝트가 커질수록 유지보수에서 강력한 힘을 발휘한다..!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RAG 맛보기]]></title>
            <link>https://velog.io/@memo_00/RAG-%EB%A7%9B%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@memo_00/RAG-%EB%A7%9B%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 11 Feb 2026 12:30:22 GMT</pubDate>
            <description><![CDATA[<p>2026.02.11 (수)
RAG 란?
RAG와 firebase 연동하기</p>
<hr>
<h2 id="rag">RAG</h2>
<p>RAG (Retrieval-Augmented Generation, 검색 증강 생성) 는 LLM(거대 언어 모델)이 학습하지 않은 외부 데이터를 참조하여 답변의 정확성을 높이는 기술입니다. 기존 LLM의 한계인 할루시네이션(거짓 답변)을 줄이고, 최신 정보나 비공개 데이터(기업 내부 문서 등)를 활용하여 답변할 수 있게 해줍니다.</p>
<h2 id="rag의-핵심-프로세스">RAG의 핵심 프로세스</h2>
<p>RAG는 크게 세 단계로 작동한다.</p>
<ul>
<li><p>검색 (Retrieval): 사용자의 질문과 관련된 문서를 외부 데이터베이스(벡터 DB 등)에서 찾아낸다.</p>
</li>
<li><p>증강 (Augmentation): 찾아낸 관련 정보를 사용자의 원래 질문과 합쳐서 모델에게 전달할 &#39;맥락(Context)&#39;을 만든다.</p>
</li>
<li><p>생성 (Generation): 모델이 제공된 맥락을 바탕으로 최종 답변을 작성한다.</p>
</li>
</ul>
<h2 id="왜-rag가-필요한가">왜 RAG가 필요한가?</h2>
<p>학습된 데이터에만 의존하는 기존 LLM의 한계를 다음과 같이 보완한다.</p>
<ul>
<li><p>환각(Hallucination) 방지: 모델이 모르는 것을 지어내지 않고, 제공된 근거 데이터를 기반으로 답변하게 유도한다</p>
</li>
<li><p>최신 정보 반영: 모델을 매번 새로 학습(Fine-tuning)시키지 않아도, 외부 데이터베이스만 업데이트하면 실시간 정보나 최신 뉴스를 답변에 반영할 수 있다</p>
</li>
<li><p>보안 및 전문성: 기업 내부 문서나 특정 분야의 전문 데이터를 연동하여 보안을 유지하면서도 고도로 전문적인 답변을 생성할 수 있다</p>
</li>
</ul>
<hr>
<h2 id="firebase-기반-rag-아키텍처">Firebase 기반 RAG 아키텍처</h2>
<p>RAG의 핵심은 &quot;질문에 맞는 데이터를 찾아(Search), 질문과 함께 AI에게 전달(Prompt)&quot;하는 것</p>
<h2 id="단계별-구현-방법">단계별 구현 방법</h2>
<h3 id="step-1-지식-베이스-구축-vector-search-설정">Step 1: 지식 베이스 구축 (Vector Search 설정)</h3>
<p>일반적인 텍스트 검색과 달리, AI는 단어의 의미를 수치화한 <strong>&#39;벡터(Vector)&#39;</strong>를 비교해 검색합니다.</p>
<p>1) Firebase 확장 프로그램 설치: Firebase Console에서 &quot;Vector Search with Firestore&quot; 확장을 설치합니다.</p>
<p>2) 데이터 임베딩(Embedding): PDF나 텍스트 문서를 작은 조각(Chunk)으로 나눈 뒤, Google의 text-embedding-004 모델 등을 사용해 벡터 데이터로 변환하여 Firestore에 저장합니다.</p>
<p>이 확장을 쓰면 특정 컬렉션에 문서를 넣을 때 자동으로 벡터 값을 생성해 줍니다.</p>
<h3 id="step-2-flutter-앱-설정">Step 2: Flutter 앱 설정</h3>
<p>Flutter 프로젝트에 필요한 패키지들을 추가하기</p>
<pre><code class="language-python">dependencies:
  firebase_core: ^3.0.0
  cloud_firestore: ^5.0.0
  firebase_vertex_ai: ^1.0.0 # Firebase용 Gemini SDK</code></pre>
<h3 id="step-3-질문에-맞는-데이터-찾기-retrieval">Step 3: 질문에 맞는 데이터 찾기 (Retrieval)</h3>
<p>사용자가 질문을 던지면, 그 질문 역시 벡터로 변환하여 Firestore에서 가장 유사한 문서를 찾아오기</p>
<pre><code class="language-python">// 예시: 유사한 문서 3개 가져오기
final querySnapshot = await FirebaseFirestore.instance
    .collection(&#39;knowledge_base&#39;)
    .findNearest(
      vectorField: &#39;embedding&#39;,
      queryVector: userQueryVector, // 사용자 질문의 벡터값
      distanceMeasure: DistanceMeasure.cosine,
      limit: 3,
    )
    .get();</code></pre>
<h3 id="step-4-ai에게-맥락과-함께-질문하기">Step 4: AI에게 맥락과 함께 질문하기</h3>
<pre><code class="language-python">final model = FirebaseVertexAI.instance.generativeModel(model: &#39;gemini-1.5-flash&#39;);

// 검색된 데이터를 맥락(Context)으로 결합
String context = querySnapshot.docs.map((doc) =&gt; doc[&#39;text&#39;]).join(&#39;\n&#39;);
String fullPrompt = &quot;다음 정보를 바탕으로 답변해줘:\n$context\n\n질문: ${userController.text}&quot;;

final response = await model.generateContent([Content.text(fullPrompt)]);
print(response.text); // 증강된 답변 출력</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로세스와 스레드]]></title>
            <link>https://velog.io/@memo_00/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C</link>
            <guid>https://velog.io/@memo_00/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C</guid>
            <pubDate>Tue, 10 Feb 2026 12:11:39 GMT</pubDate>
            <description><![CDATA[<p>2026.02.10 (화)
프로그램, 프로세스, 스레드의 개념</p>
<hr>
<h2 id="🎛-프로그램-프로세스-스레드의-정의">🎛 프로그램, 프로세스, 스레드의 정의</h2>
<h4 id="프로그램-program">프로그램 (Program)</h4>
<p>정의: 실행 가능한 코드의 집합 (파일)
상태: 멈춰 있음 (정적)
비유하자면 일한 내용이 담긴 창고?</p>
<h4 id="프로세스-process">프로세스 (Process)</h4>
<p>정의: 실행 중인 프로그램의 인스턴스
상태: 살아있음 (동적)
비유해보면?? 시키고 있는 감독관</p>
<h4 id="스레드-thread">스레드 (Thread)</h4>
<p>정의: 프로세스 내에서 실행되는 흐름의 단위
상태: 일하고 있음 (동적)
비유하자면 시키는대로 일하는 노동꾼 느낌</p>
<h3 id="핵심-차이점">핵심 차이점</h3>
<ul>
<li>자원 공유: 프로세스는 각각 독립된 메모리 공간을 가진다. 반면, 한 프로세스 내부의 스레드들은 메모리를 서로 공유하고 있다.</li>
<li>영향도: 스레드 하나가 오류로 종료되면 같은 프로세스 내의 다른 스레드들도 영향을 받을 수 있다. 하지만 프로세스 간에는 서로 영향을 주지 않는다.</li>
</ul>
<hr>
<h2 id="🔧-dart의-isolate란">🔧 Dart의 Isolate란?</h2>
<p>일반적인 언어의 스레드와 달리, Dart는 Isolate 라는 개념을 사용한다. 이름되로 &#39;격리된&#39; 실행 단위를 뜻한다.</p>
<h3 id="isolate-특징">Isolate 특징</h3>
<ul>
<li>독립된 메모리
각 Isolate는 자신만의 메모리를 가진다. 스레드와 달리 메모리를 공유하지 않는다.</li>
<li>싱글 스레드 기반
기본적으로 Flutter 앱은 하나의 Main Isolate에서 실행된다. 이 안에서 이벤트 루프가 돌아가며 UI를 그리고 사용자의 입력을 처리한다.</li>
<li>통신 방식
메모리를 공유하지 않기 때문에, 서로 데이터를 주고받으려면 메시지 패싱 방식을 사용해야 한다.</li>
</ul>
<hr>
<h2 id="⚒-왜-스레드-대신-isolate를-쓸까">⚒ 왜 스레드 대신 Isolate를 쓸까?</h2>
<p>일반적인 멀티 스레드 환경에서는 여러 스레드가 동시에 같은 메모리에 접근할 때 발생하는 <strong>데이터 충돌</strong>을 막기 위해 Lock을 거는 복잡한 과정이 필요하다. 하지만 Isolate는 메모리를 아예 분리해버림으로써 이런 복잡함을 없애고 성능 최적화를 이룬다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[대칭키, 비대칭키 암호화의 방식]]></title>
            <link>https://velog.io/@memo_00/%EB%8C%80%EC%B9%AD%ED%82%A4-%EB%B9%84%EB%8C%80%EC%B9%AD%ED%82%A4-%EC%95%94%ED%98%B8%ED%99%94%EC%9D%98-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@memo_00/%EB%8C%80%EC%B9%AD%ED%82%A4-%EB%B9%84%EB%8C%80%EC%B9%AD%ED%82%A4-%EC%95%94%ED%98%B8%ED%99%94%EC%9D%98-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Mon, 09 Feb 2026 10:33:14 GMT</pubDate>
            <description><![CDATA[<p>2026.02.09 (월)
대칭키와 비대칭키 암호화 방식</p>
<hr>
<h2 id="📭-대칭키-암호화">📭 대칭키 암호화</h2>
<ul>
<li>암호화와 복호화에 동일한 키를 사용하는 방식</li>
<li>방식: 송신자와 수신자가 동일한 하나의 비밀키를 공유한다.</li>
<li>장점: 속도가 빠르다.</li>
<li>단점: 키를 안전하게 전달하기 어렵다. / 사용자가 늘어날수록 관리해야 할 키의 개수가 기하급수적으로 늘어난다.</li>
</ul>
<h2 id="📫-비대칭키-암호화">📫 비대칭키 암호화</h2>
<ul>
<li>암호화와 복호화에 서로 다른 키를 사용하는 방식</li>
<li>방식: 모든 사용자는 공개키와 개인키 한 쌍을 가진다.<ul>
<li>공개키: 누구나 알 수 있는 키</li>
<li>개인키: 본인만 알고 있는 키</li>
</ul>
</li>
<li>장점: 키를 안전하게 전달하기 쉽다.</li>
<li>단점: 속도가 느리다.</li>
</ul>
<h2 id="🗂-장단점-비교">🗂 장단점 비교</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>대칭키 암호화</th>
<th>비대칭키 암호화</th>
</tr>
</thead>
<tbody><tr>
<td>암호화/복호화 키</td>
<td>동일한 키 사용</td>
<td>서로 다른 키 사용</td>
</tr>
<tr>
<td>속도</td>
<td>빠름</td>
<td>느림</td>
</tr>
<tr>
<td>키 전달</td>
<td>어려움</td>
<td>쉬움</td>
</tr>
</tbody></table>
<hr>
<h2 id="📚-https와-암호화의-결합">📚 HTTPS와 암호화의 결합</h2>
<p>HTTPS는 대칭키의 속도와 비대칭키의 안전한 키 전달이라는 장점만을 결합한 &#39;하이브리드 암호화&#39; 방식을 사용한다.</p>
<h3 id="🖇-연결-과정-ssltls">🖇 연결 과정 (SSL/TLS)</h3>
<h4 id="1-공개키-전달">1. 공개키 전달</h4>
<p>서버는 자신의 <strong>비대칭키(공개키)</strong>가 담긴 인증서를 클라이언트에게 보낸다.</p>
<h4 id="2-대칭키-생성-및-암호화">2. 대칭키 생성 및 암호화</h4>
<p>클라이언트는 데이터를 빠르게 암호화할 때 쓸 <strong>대칭키(세션키)</strong>를 무작위로 생성한다. 이 대칭키를 서버의 공개키로 암호화하여 서버에 보낸다</p>
<h4 id="3-키-복호화">3. 키 복호화</h4>
<p>서버는 오직 자신만 가진 <strong>비대칭키(개인키)</strong> 로 암호화된 대칭키를 복호화하여 안전하게 꺼낸다.</p>
<h4 id="4-데이터-통신">4. 데이터 통신</h4>
<p>이제 서버와 클라이언트는 서로 공유된 동일한 대칭키를 사용하여 실제 데이터를 암호화/복호화하며 빠르게 통신한다.</p>
<h3 id="요약">요약</h3>
<p>비대칭키는 대칭키를 안전하게 공유하기 위한 수단으로 쓰이고, 실제 데이터 통신은 대칭키가 담당한다.</p>
<hr>
<h2 id="◾️-대칭키-암호화-알고리즘">◾️ 대칭키 암호화 알고리즘</h2>
<p>대칭키는 <strong>금고와 하나의 열쇠</strong>
넣을 때(암호화)와 열 때 (복호화) 같은 열쇠를 쓴다.</p>
<h3 id="aes-advanced-encryption-standard">AES (Advanced Encryption Standard)</h3>
<ul>
<li>특징: 현재 전 세계에ㅔ서 가장 많이 쓰이는 알고리즘
정교하고 튼튼한 최신형 도어락 같은 느낌</li>
<li>장점: 매우 빠르고, 보안성도 강력하다. 컴퓨터 CPU 안에 AES 처리를 돕는 부품이 따로 있을 정도라서 대용량 파일(영화, 게임 등)을 암호화 할 때 필수이다</li>
<li>단점 : 열쇠(비밀키)를 상대방에게 전달하다가 중간에 도둑맞으면.. ㄲ끝.이다</li>
</ul>
<h3 id="des--3des-data-encryption-standard">DES / 3DES (Data Encryption Standard)</h3>
<ul>
<li>특징: 과거의 표준으로 많이 사용했으나 지금은 보안이 취약해져서 잘 쓰이지 않음</li>
</ul>
<h2 id="◽️-비대칭키-암호화-알고리즘">◽️ 비대칭키 암호화 알고리즘</h2>
<p>비대칭키는 &#39;자물쇠(공개키)와 열쇠(개인키)&#39; 세트이다. 누구나 내 자물쇠를 가져가서 상자를 잠글 수는 있으나 열 수 있는 열쇠는 오직 나만 가지고 있다.</p>
<h3 id="rsa-rivest-shamir-adleman">RSA (Rivest-Shamir-Adleman)</h3>
<ul>
<li>특징: 공개키 암호화에서 가장 유명한 알고리즘이다. 아주 큰 숫자를 소인수분해하는 것이 어렵다는 수학적인 원리를 이용한다.
($15 = 3 \times 5$ 는 쉽지만, 수백 자리 숫자를 소인수분해하려면 슈퍼컴퓨터로도 오래 걸린다.)</li>
<li>용도: 주로 아주 작은 데이터(대칭키를 암호화할 때)나 디지털 서명에 사용된다.</li>
</ul>
<h3 id="ecc-ellipptic-curve-cryptography">ECC (Ellipptic Curve Cryptography)</h3>
<ul>
<li>특징: 타원곡선 그래프를 이용한 최신 알고리즘
RSA보다 훨씬 작고 가벼우면서 보안력은 더 강한 &#39;스마트한 열쇠&#39;</li>
<li>장점: RSA보다 키의 길이가 훨씬 짧다. 스마트폰처럼 리소스가 제한된 기기에서 웹사이트 접속할 때 성능을 높여주기 때문에 최근 HTTPS에서 아주 선호된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP와 HTTPS]]></title>
            <link>https://velog.io/@memo_00/HTTP%EC%99%80-HTTPS</link>
            <guid>https://velog.io/@memo_00/HTTP%EC%99%80-HTTPS</guid>
            <pubDate>Fri, 06 Feb 2026 12:19:34 GMT</pubDate>
            <description><![CDATA[<p>2026.02.06 (금)</p>
<h1 id="http와-https의-차이점은-무엇인가">HTTP와 HTTPS의 차이점은 무엇인가?</h1>
<h2 id="http란">HTTP란?</h2>
<p>HTTP는 Hyper Text Transfer Protocol의 약자로, 암호화 과정 없이 데이터를 인터넷으로 전송하는 프로토콜입니다. 누구나 쉽게 볼 수 있는 상태</p>
<h2 id="https란">HTTPS란?</h2>
<p>HTTPS는 Hyper Text Transfer Protocol Secure의 약자로, HTTP 프로토콜에 보안 기능을 추가한 것
HTTPS를 사용하면 모든 HTTP 요청과 응답 데이터는 네트워크로 보내지기 전에 암호화 된다.
기존의 HTTP 프로토콜에 SSL/TLS 프로토콜을 더해 데이터 전송의 보안을 강화한 통신이다.</p>
<ul>
<li>TLS : Transport Layer Security의 약자로, SSL의 후속 프로토콜</li>
<li>SSL : Secure Sockets Layer의 약자로, HTTPS를 구현하기 위한 프로토콜<blockquote>
<p>SSL/TLS는 HTTPS를 사용하기 위한 안전한 보안 채널을 생성해주는 포로토콜</p>
</blockquote>
</li>
</ul>
<hr>
<h2 id="http와-https">HTTP와 HTTPS</h2>
<p>HTTP는 암호화가 없어 보안에 취약한 반면, HTTPS는 안전하게 데이터를 주고 받을 수 있다. 하지만 암호화/복호화 과정이 필요하기 때문에 HTTP보다 속도가 느리다. 인증서 발급하고 유지하기 위한 추가 비용이 발생한다. 
개인 정보와 같은 민감한 데이터를 주고 받아야 한다면 HTTPS를 이용해야 하지만, 노출이 되어도 괜찮은 단순한 정보 조회 등 만을 처리하고 있다면 HTTP를 이용하면 된다.</p>
<hr>
<h2 id="https의-특징">HTTPS의 특징</h2>
<h3 id="데이터-암호화">데이터 암호화</h3>
<p> HTTPS는 전송되는 데이터를 암호화해서 제3자가 내용을 보더라도 내용을 알 수 없음</p>
<ul>
<li><p>공개키 암호화 (비대칭키): 연결 초기 단계에서 대칭키를 안전하게 교환하기 위해 사용</p>
</li>
<li><p>대칭키 암호화: 실제 데이터 전송 시 사용, 속도가 빠름</p>
<h3 id="데이터-무결성">데이터 무결성</h3>
<p>전송 중에 데이터가 변조되었는지 확인하는 기능. 누군가 수정했다면 수신 측에서 감지하고 해당 데이터 폐기</p>
<h3 id="서버-인증">서버 인증</h3>
<p>클라이언트가 접속하려는 서버가 신뢰할 수 있는 서버인지 확인</p>
</li>
</ul>
<hr>
<h3 id="tcp-3-way-handshake">TCP 3-Way Handshake</h3>
<p><strong>핸드셰이크로 보장하는 것</strong></p>
<ul>
<li>양쪽 모두 통신 가능 확인</li>
<li>데이터 순서 보장</li>
<li>손실 시 재전송 가능</li>
<li>신뢰성 있는 연결 수립</li>
</ul>
<hr>
<p>HTTP - 80포트 사용
HTTPS - 443포트 사용</p>
<hr>
<h3 id="통신-과정">통신 과정</h3>
<ol>
<li>사용자가 로그인 정보 입력 (로그인 요청)</li>
<li>서버 검증하고 인증서 생성</li>
<li>CA의 비밀키로 인증서 서명</li>
<li>서버에 서명된 인증서 발급</li>
<li>웹 브라우저에 CA의 공개키 전달</li>
<li>서버(웹사이트) 접속 요청</li>
<li>발급받은 인증서 전달</li>
<li>CA의 공개키로 인증서 검증</li>
<li>서버 공개키로 대칭키 암호화</li>
<li>서버로 암호화한 대칭키 전송</li>
<li>서버 비밀키로 대칭키 복호화</li>
<li>대칭키를 이용하여 암호화된 정보를 주고받는다</li>
</ol>
<hr>
<h3 id="피드백">피드백</h3>
<ul>
<li>네이버로 HTTP/HTTPS 통신을 했을 때 어떻게 데이터 통신이 되는지 (암호화 되는지)
사용자/서버 예시로 들어서 naver에 로그인하는 과정을 설명해보기</li>
<li>공개키/개인키 암호화 방식에 대해 자세하게 공부해보기</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🎹 정렬 알고리즘 🎹]]></title>
            <link>https://velog.io/@memo_00/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-1pph3y70</link>
            <guid>https://velog.io/@memo_00/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-1pph3y70</guid>
            <pubDate>Thu, 05 Feb 2026 11:28:48 GMT</pubDate>
            <description><![CDATA[<p>2026.02.05 (목)
정렬 알고리즘</p>
<hr>
<p><img src="https://velog.velcdn.com/images/memo_00/post/280cd11f-4da1-4a3d-9612-4a929e95d22c/image.png" alt=""></p>
<p>정렬 알고리즘에 대해 자세하게 공부해보자!
어제 공부한 내용 다시 한번 공부하면서 더 잘 이해할 수 있도록 그래프를 그려보며 공부해봤다</p>
<hr>
<h2 id="🚥-정렬-알고리즘">🚥 정렬 알고리즘</h2>
<p><strong>정렬 알고리즘</strong> 무작위로 나열된 데이터 집합을 특정 기준(오른차순 또는 내림차순)에 따라 일정한 순서로 재배열하는 일련의 과정을 의미한다.</p>
<p>데이터의 형태에 따라 숫자 크기순, 문자 사전순 등으로 정렬할 수 있으며, 효율적인 정렬은 이후의 데이터 처리 속도를 결정짓는 핵심적인 선행 단계이다.</p>
<hr>
<h2 id="📄-정렬-알고리즘을-사용하는-핵심-이유">📄 정렬 알고리즘을 사용하는 핵심 이유</h2>
<p>단순히 보기 좋게 나열하는 것을 넘어, 정렬은 시스템의 전반적인 성능 최적화를 위해 필수적이다. </p>
<h3 id="📕-탐색-성능의-극대화">📕 탐색 성능의 극대화</h3>
<p>데이터가 정렬되어 있지 않으면 특정 값을 찾기 위해 처음부터 끝까지 확인하는 선형 탐색을 해야한다. 하지만 정렬된 데이터에서는 <strong>이진탐색</strong>을 사용할 수 있어 탐색 속도가 빨라진다.</p>
<ul>
<li>미정렬 상태 : $O(n)$</li>
<li>정렬 상태 : $O(\log n)$</li>
</ul>
<h3 id="📗-데이터-처리-효율성-향상">📗 데이터 처리 효율성 향상</h3>
<ul>
<li>중복 제거 : 정렬된 데이터에서는 인접한 요소끼리 비교하여 중복된 항목을 쉽게 찾아낼 수 있다.</li>
<li>그룹화: 특정 기준에 따라 데이터를 모아서 분석하거나 통계를 낼 때 정렬이 되어 있으면 처리가 간결해진다.</li>
</ul>
<h3 id="📘-다른-알고리즘의-기반-기술">📘 다른 알고리즘의 기반 기술</h3>
<p>많은 효율적인 알고리즘들이 데이터가 정렬되어 있음을 전제로 작동한다. 예를 들어, 두 데이터 집합의 교집합을 구한거나 최솟값/최댓값을 빠르게 추출해야 하는 로직에서 정렬은 필수적인 전처리 과정이다.</p>
<blockquote>
<p>정렬 알고리즘을 선택할 때는 단순히 속도뿐만 아니라, <strong>시간 복잡도($O(n \log n)$ vs $O(n^2)$)</strong>와 공간 복잡도, 그리고 동일한 값의 상대적 순서가 유지되는지 여부인 <strong>안정성(Stability)</strong>을 종합적으로 고려해야 한다!</p>
</blockquote>
<hr>
<h2 id="📚-정렬의-기준에-따라-정리해보자">📚 정렬의 기준에 따라 정리해보자</h2>
<h3 id="📕-안정성-기준">📕 안정성 기준</h3>
<p>값이 같은 데이터가 있을 때, 정렬 전의 순서가 유지되는가?</p>
<ul>
<li>안정 정렬: 병합 정렬, 삽입 정렬<ul>
<li>안정 정렬을 쓴다면 가격이 같은 상품들끼리 여전히 <strong>주문 일자순</strong>을 유지할 수 있다.</li>
</ul>
</li>
<li>불안정 정렬 : 퀵정렬, 힙 정렬<ul>
<li>불안정 정렬을 쓴다면 가격이 같은 상품들의 주문 일자 순서가 뒤죽박죽 섞일 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="📗-제자리-정렬">📗 제자리 정렬</h3>
<p>데이터 정렬을 위해 추가적인 메모리 공간이 얼마나 필요한가?</p>
<ul>
<li>제자리 정렬 : 퀵정렬, 힙 정렬, 선택 정렬</li>
<li>제자리 정렬이 아닌 것 : 병합 정렬</li>
</ul>
<p>아주 작은 센서 장치에서는 추가적인 메모리를 사용하는 병합 정렬보다는 기존 배열 내에서 위치만 바꾸는 제자리 정렬 방식이 훨씬 유리하다.</p>
<h3 id="📘시간-복잡도-기준">📘시간 복잡도 기준</h3>
<p>데이터 양이 많아질 때 얼마나 정체되는가?</p>
<ul>
<li>단순한 정렬 $O(n^2)$ : 거품 정렬, 선택 정렬</li>
<li>효율적인 정렬 $O(n \log n)$ : 퀵 정렬, 병합 정렬</li>
</ul>
<p>데이터가 적을 때 : 알고리즘이 복잡한 퀵 정렬보다 오히려 단순한 삽입 정렬이 더 빠를 수 있다. 실제로 많은 표준 라이브러리들이 데이터가 적을 땐 삽입 정렬을 섞어서 사용한다고 한다..!</p>
<p>데이터가 많다면? 반드시 $O(n \log n)$의 효율을 가진 정렬을 써야 시스템이 멈추지 않느다!</p>
<h3 id="📙데이터의-성격-기준">📙데이터의 성격 기준</h3>
<p>데이터를 서로 비교하는가? 아니면 값의 특성을 이용하는가?</p>
<ul>
<li>비교 정렬: 우리가 흔히 아는 대부분의 정렬 (퀵, 병합 등)</li>
<li>비비교 정렬 : 계수 정렬, 기수 정렬</li>
</ul>
<p>수능 성적표를 처리할 때, 점수는 0점에서 100점 사이로 범위가 정해져 있다. 이럴 때는 숫자를 하나하나 비교하는 것보다 0점부터 100점까지의 칸을 미리 만들어두고 점수별로 개수를 세는 계수 정렬을 쓰면 $O(n)$이라는 압도적인 속도로 정렬이 끝난다.</p>
<hr>
<h2 id="on-상향-그래프">$O(n)$ 상향 그래프</h2>
<p><img src="https://velog.velcdn.com/images/memo_00/post/43deb990-c8e8-4967-9b38-739b3a8d71cd/image.png" alt=""></p>
<h2 id="on-log-n-상향-그래프">$O(n \log n)$ 상향 그래프</h2>
<p><img src="https://velog.velcdn.com/images/memo_00/post/e254d93e-cea8-43ac-bc1b-8a25005c88f7/image.png" alt=""></p>
<p>다음에는 시간 복잡도와 공간 복잡도에 따른 그래프를 그려봐야겠다..!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[정렬 알고리즘]]></title>
            <link>https://velog.io/@memo_00/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@memo_00/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Wed, 04 Feb 2026 13:18:26 GMT</pubDate>
            <description><![CDATA[<p>2026.02.04 (수)
정렬 알고리즘
선택 정렬
버블 정렬</p>
<hr>
<h2 id="📶-정렬-알고리즘">📶 정렬 알고리즘</h2>
<p><strong>정렬(Sorting)</strong>은 흩어져 있는 데이터를 정해진 기준에 따라 차례대로 나열하는 것이다. 예를 들어, [3, 1, 4, 2]라는 무작위 리스트를 <a href="%EC%98%A4%EB%A6%84%EC%B0%A8%EC%88%9C">1, 2, 3, 4</a> 또는 <a href="%EB%82%B4%EB%A6%BC%EC%B0%A8%EC%88%9C">4, 3, 2, 1</a>로 만드는 과정이다.</p>
<h2 id="어디에-사용되는가">어디에 사용되는가?</h2>
<p>일상에서 사용되는 거의 모든 디지털 서비스에는 정렬 알고리즘을 사용하고 있다.
예를 들어서 쇼핑몰, SNS, 금융/은행, 내비게이션 등</p>
<p><strong>낮은 가격순</strong>, <strong>리뷰많은 순</strong> <strong>최신 등록순</strong> 필터링
타임라인에 게시물을 <strong>최신순</strong>으로 나열하거나 좋아요가 많은 <strong>인기순</strong>으로 노출
거래 내역을 날짜별로 정리하거나 계좌 잔액 기준 정렬 등</p>
<hr>
<h2 id="🫧-버블-정렬">🫧 버블 정렬</h2>
<p>큰 원소가 뒤로 <strong>거품</strong>처럼 밀려 올라가는 모습에서 유래되었다. 인접한 두 원소를 비교하여 조건에 맞지 않으면 자리를 바꾸는 방식!</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/001f97f1-d98a-407e-82a9-b3423cb9d6f6/image.png" alt=""></p>
<h3 id="동작방식">동작방식</h3>
<ol>
<li>첫번째 원소와 두 번째 원소를 비교한다.</li>
<li>앞의 원소가 더 크면 자리를 바꾼다.</li>
<li>위의 행동을 마지막 원소까지 반복하면 가장 큰 원소가 맨 뒤로 고정된다.</li>
<li>과정된 원소를 제외하고 다시 반복한다.</li>
</ol>
<pre><code class="language-python">void bubbleSort(List&lt;int&gt; list) {
  int n = list.length;
  for (int i = 0; i &lt; n - 1; i++) {
    for (int j = 0; j &lt; n - i - 1; j++) {
      if (list[j] &gt; list[j + 1]) {
        // 데이터 위치 교환
        int temp = list[j];
        list[j] = list[j + 1];
        list[j + 1] = temp;
      }
    }
  }
}</code></pre>
<hr>
<h2 id="📌-선택-정렬">📌 선택 정렬</h2>
<p>전체 원소 중 최솟값을 찾아 맨 앞에 있는 원소와 교체하는 방식이다</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/c85d1e15-aef2-45ca-aab1-e8664a274175/image.png" alt=""></p>
<h3 id="동작-방식">동작 방식</h3>
<ol>
<li>주어진 리스트에서 최솟값을 찾는다.</li>
<li>그 값을 맨 앞에 있는 값과 교체한다.</li>
<li>맨 앞을 제외한 나머지 리스트에서 다시 최솟값을 찾아 두 번째 위치와 교체한다.</li>
<li>이 과정을 리스트의 끝까지 반복한다</li>
</ol>
<pre><code class="language-python">void selectionSort(List&lt;int&gt; list) {
  int n = list.length;
  for (int i = 0; i &lt; n - 1; i++) {
    int minIndex = i;
    for (int j = i + 1; j &lt; n; j++) {
      if (list[j] &lt; list[minIndex]) {
        minIndex = j;
      }
    }
    // 최솟값을 현재 위치로 교환
    int temp = list[minIndex];
    list[minIndex] = list[i];
    list[i] = temp;
  }
}</code></pre>
<hr>
<h2 id="🔖-삽입-정렬">🔖 삽입 정렬</h2>
<p>두번째 원소부터 시작하여 그 앞의 정렬된 부분과 비교해서 자신의 위치를 찾아 삽입하는 방식이다. 손 안의 카드를 정렬하는 방식과 유사하다</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/82bf03a6-4e32-421f-81bd-9335736cb219/image.png" alt=""></p>
<h3 id="작동-방식">작동 방식</h3>
<ol>
<li>두 번째 원소를 &#39;Key&#39;로 잡고 이전 원소들과 비교한다.</li>
<li>Key가 이전 원소보다 작으면 이전 원소를 뒤로 밀어낸다</li>
<li>/Key가 들어갈 적절한 위치를 찾으면 그 자리에 삽입</li>
<li>마지막 원소까지 반복</li>
</ol>
<pre><code class="language-python">void insertionSort(List&lt;int&gt; list) {
  int n = list.length;
  for (int i = 1; i &lt; n; i++) {
    int key = list[i];
    int j = i - 1;

    // key보다 큰 원소들을 오른쪽으로 한 칸씩 이동
    while (j &gt;= 0 &amp;&amp; list[j] &gt; key) {
      list[j + 1] = list[j];
      j--;
    }
    list[j + 1] = key;
  }
}</code></pre>
<hr>
<h2 id="알고리즘-비교-및-사용-이유">알고리즘 비교 및 사용 이유</h2>
<table>
<thead>
<tr>
<th align="left">알고리즘</th>
<th align="left">시간 복잡도</th>
<th align="left">장점</th>
<th align="left">단점</th>
</tr>
</thead>
<tbody><tr>
<td align="left">버블 정렬</td>
<td align="left">$O(n^2)$</td>
<td align="left">코드가 매우 단순함</td>
<td align="left">교환 횟수가 많아 가장 느림</td>
</tr>
<tr>
<td align="left">선택 정렬</td>
<td align="left">$O(n^2)$</td>
<td align="left">교환 횟수 적음</td>
<td align="left">데이터 상태와 상관없이 항상 일정한 시간 소요</td>
</tr>
<tr>
<td align="left">삽입 정렬</td>
<td align="left">$O(n^2)$</td>
<td align="left">이미 정렬된 상태라면 매우 빠름</td>
<td align="left">데이터가 많을수록 성능 급격 저하</td>
</tr>
</tbody></table>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Test Code Review]]></title>
            <link>https://velog.io/@memo_00/Test-Code-Review-7o1hryao</link>
            <guid>https://velog.io/@memo_00/Test-Code-Review-7o1hryao</guid>
            <pubDate>Tue, 03 Feb 2026 10:49:09 GMT</pubDate>
            <description><![CDATA[<p>2026.02.03 (화)
리펙토링하면서 테스트 코드 다시 손보기
테스트 코드 리뷰</p>
<hr>
<h2 id="flutter-test-종류-3">Flutter test 종류 3</h2>
<h3 id="1-단위-테스트-unit-tests">1. 단위 테스트 (Unit tests)</h3>
<p>단위 테스트는 메서드나 클래스의 동작을 확인, 특정 코드 단위를 분리하고 테스트 하기 때문에 다른 부분의 영향을 받지 않고 테스트할 수 있고 잘 작동하는지 확인 가능
목적: 특정 코드 단위의 동작을 확인해서 코드의 품질을 개선하고 버그를 방지</p>
<h3 id="2-위젯-테스트-widget-tests">2. 위젯 테스트 (Widget tests)</h3>
<p>위젯 테스트는 UI 요소의 개별 동작을 테스트, 버튼이 제대로 클릭되는지 텍스트 필드에 입력이 제대로 반영되는지 등을 확인함. 주로 위젯의 렌더링, 상태변화, 사용자 입력 등을 테스트</p>
<h3 id="3-통합-테스트-integration-tests">3. 통합 테스트 (Integration tests)</h3>
<p>통합 테스트는 애플리케이션의 여러 부분을 통합하여 전체 애플리케이션의 동작을 테스트함. 여러 위젯이나 화면 간의 상호 작용 및 전환, 데이트 흐름 등을 테스트함. 실제 디바이스나 시뮬레이터를 실행해서 앱의 실제 환경에서 테스트를 진행함.</p>
<hr>
<h2 id="리펙토링한-테스트-코드">리펙토링한 테스트 코드</h2>
<h2 id="1--ui--home--home_screen_testdart">1.  ui / home / home_screen_test.dart</h2>
<p>-&gt; HomeScreen 위젯이 날씨 데이터 로딩 상태에 따라 사용자가 보는 화면 (ui)가 잘 그려지는지 확인하기 위한 테스트 코드</p>
<ul>
<li><p>MockWeatherRepository 클래스는 WeatherRepository를 흉내 내는 가짜 클래스</p>
</li>
<li><p>테스트에서 사용할 가짜 저장소를 선언합니다. late는 나중에(setUp에서) 초기화하겠다는 의미</p>
</li>
<li><p>setUp -&gt; 각 테스트 케이스가 실행되기 전 항상 실행되는 블록!
매 테스트마다 새로운 객체를 생성해 테스트 간 간섭을 방지</p>
</li>
<li><p>테스트 할 위젯을 감싸서 반환하는 헬퍼 함수</p>
</li>
<li><p>여기서 Rivderpod의 핵심 기능인데요, 실제 Provider가 mockRepositoryfmf 바라보도록 덮어씌움</p>
</li>
</ul>
<h3 id="테스트-전용-주요-메서드">테스트 전용 주요 메서드</h3>
<h3 id="when--thenanswer">when(... ~ .thenAnswer)</h3>
<p>만약 이 함수가 ~ 실행되면, <del>이렇게 대답해. 행동 정의 메서드
사용 방법은 when(</del>.thenAnwser)
<img src="https://velog.velcdn.com/images/memo_00/post/1efad8be-7e9a-4342-9abe-b2202a107dd3/image.png" alt=""></p>
<h3 id="any">any()</h3>
<p>어떤 인자 값이 들어오든 상관없이 매칭하겠다 라는 의미
<img src="https://velog.velcdn.com/images/memo_00/post/166c686b-8f7e-4c8e-ac69-81bd991cfca8/image.png" alt=""></p>
<h3 id="testerpubwidget">tester.pubWidget()</h3>
<p>주어진 위젯을 테스트 환경의 가상 화면에 렌더링
<img src="https://velog.velcdn.com/images/memo_00/post/8101869b-a917-485f-a18e-5f72957ffd24/image.png" alt=""></p>
<h3 id="testerpumpandsettle">tester.pumpAndSettle()</h3>
<p>애니메이션이나 연속적인 프레임 변화가 모두 멈출 때까지 반복해서 새로고침
<img src="https://velog.velcdn.com/images/memo_00/post/748724c4-d0c8-4735-9644-0b8c203be8af/image.png" alt=""></p>
<hr>
<h2 id="2-ui--home--home_view_model_testdart">2. ui / home / home_view_model_test.dart</h2>
<p>-&gt; 위에 살펴본 코드가 화면이 어떻게 보이는가를 확인하는 위젯 테스트였다면 이번 코드는 화면 없이 데이터 로직이 정확히 계산되고 상태를 잘 바꾸는가를 확인하는 단위 테스트</p>
<h3 id="테스트-전용-주요-메서드-1">테스트 전용 주요 메서드</h3>
<h3 id="providercontainer">ProviderContainer</h3>
<p>UI 없이 Provider의 상태를 관리하는 엔진. 단위 테스트의 핵심 도구임
<img src="https://velog.velcdn.com/images/memo_00/post/5560704c-81d0-490a-b819-db54f1dc502c/image.png" alt=""></p>
<h3 id="addteardown">addTearDown</h3>
<p>테스트가 성공하든 실패하든 마지막에 꼭 실행해야 할 정리 작업을 등록
<img src="https://velog.velcdn.com/images/memo_00/post/a2e12f77-a573-4288-9ff8-2bff7de40105/image.png" alt=""></p>
<h3 id="containerlisten">container.listen</h3>
<p>상태가 변할 때마다 특정 동작을 수행하게 합니다. UI의 ref.listen과 비슷</p>
<h3 id="verifycalledn">verify(...).called(n)</h3>
<p>&quot;이 함수가 정말 n번 실행됐어?&quot;라고 확인하는 감사(Audit) 기능
<img src="https://velog.velcdn.com/images/memo_00/post/3209004d-19b4-47f7-87b5-296240eba663/image.png" alt=""></p>
<hr>
<h3 id="mocking-가짜-객체-사용의-이점">Mocking (가짜 객체 사용)의 이점</h3>
<ul>
<li>격리성: 실제 API 서버가 점검 중잉거나 인터넷이 안 되어도 테스트를 수행 할 수 있다.</li>
<li>속도 : 실제 네트워크 통신을 기다리지 않으므로 테스트 속도가 빠르다</li>
<li>통제 가능: 에러 상황이나 로딩 중인 상황 등 현실에서 재현하기 어려운 시나리오를 강제로 만들어서 테스트 할 수 있다.</li>
</ul>
<h3 id="riverpod-overrides의-이점">Riverpod overrides의 이점</h3>
<ul>
<li>코드 수정 최소화: UI 코드 (HomeScreen)을 전혀 수정하지 않고, 테스트 코드에서만 데이터를 바꿔치기 할 수 있어 깔끔한 아키텍처를 유지해줍니다.</li>
</ul>
<h3 id="상태별-분리-테스트-이점">상태별 분리 테스트 이점</h3>
<p>로딩 -&gt; 성공 -&gt; 에러 이어지는 사용자 경험(UX)의 모든 흐름을 코드로 확인할 수 있다.
나중에 UI 를 수정하더라도 테스트만 통과하면 기능은 여전히 잘 작동한다 라는 홗신을 가질 수 있다.</p>
<hr>
<h3 id="providercontainer-사용의-이점">ProviderContainer 사용의 이점</h3>
<ul>
<li>순수 로직 집중: 화면 렌더링(버튼, 텍스트 등)에 신경 쓰지 않고 오직 데이터가 Loading -&gt; Data 또는 Loading -&gt; Error로 잘 변하는지만 확인할 수 있습니다.
리소스 절약: 위젯 테스트보다 훨씬 빠르고 가볍게 실행됩니다.</li>
</ul>
<h3 id="addteardown의-이점">addTearDown의 이점</h3>
<p>테스트마다 깨끗한 환경을 보장합니다. 이전 테스트에서 썼던 데이터가 다음 테스트에 영향을 주지 않도록 막아줍니다.</p>
<hr>
<h2 id="flutter-테스트-작성-5단계-프로세스">Flutter 테스트 작성 5단계 프로세스</h2>
<h3 id="①-테스트-목적-정의-test-함수">① 테스트 목적 정의 (test 함수)</h3>
<p>무엇을 테스트할지 명확한 문장으로 적어보기</p>
<h3 id="②-환경-설정-및-가짜-객체-주입-arrange">② 환경 설정 및 가짜 객체 주입 (Arrange)</h3>
<p> Mock(가짜) 객체를 만들고, 필요한 데이터를 미리 심어두기</p>
<p>사용 도구: Mocktail, Mockito, ProviderContainer</p>
<h3 id="③-동작-수행-act">③ 동작 수행 (Act)</h3>
<p>테스트 대상이 되는 기능을 실행하기</p>
<p>Unit Test: 함수 실행 (calculator.add(1, 2))</p>
<p>Widget Test: 위젯 렌더링 및 클릭 (tester.pumpWidget(), tester.tap())</p>
<h3 id="④-결과-확인-assert">④ 결과 확인 (Assert)</h3>
<p>expect 함수를 사용해 실제 값(Actual)과 기대 값(Matcher)을 비교
expect(결과값, 3);
expect(find.text(&#39;성공&#39;), findsOneWidget);</p>
<h3 id="⑤-정리-teardown">⑤ 정리 (TearDown)</h3>
<p>테스트가 끝난 후 사용한 리소스를 해제해주기
addTearDown(() =&gt; container.dispose());</p>
<p>반드시 해야 하는 경우: ProviderContainer, StreamController, TextEditingController, AnimationController 등을 생성했을 때.</p>
<p>안 해도 되는 경우: 단순히 숫자나 문자열을 계산하는 순수 함수(Pure Function) 테스트일 때.</p>
<hr>
<h2 id="초보자를-위한-작성-팁-💡">초보자를 위한 작성 팁 💡</h2>
<p>한 번에 하나만 테스트
하나의 test 블록 안에서 너무 많은 것을 검증하려고 하면, 실패했을 때 원인을 찾기 힘들다</p>
<p>실패하는 케이스도 테스트해보기
&quot;정상 작동&quot;뿐만 아니라 &quot;에러가 났을 때 화면에 에러 메시지가 잘 뜨는지&quot; 확인하는 것이 더 중요한 경우가 많다</p>
<blockquote>
<p>테스트 가능한 코드(Testable Code)를 만들기
코드를 짤 때부터 &quot;이걸 나중에 어떻게 테스트하지?&quot;라고 고민하면 자연스럽게 의존성이 분리된 깨끗한 코드(Clean Code)가 나오게 되어있따~</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[GoRouter Path Parameter 데이터 타입 Troble Shooting_과제 todo App 리펙토링]]></title>
            <link>https://velog.io/@memo_00/GoRouter-Path-Parameter-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85-Troble-Shooting%EA%B3%BC%EC%A0%9C-todo-App-%EB%A6%AC%ED%8E%99%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@memo_00/GoRouter-Path-Parameter-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85-Troble-Shooting%EA%B3%BC%EC%A0%9C-todo-App-%EB%A6%AC%ED%8E%99%ED%86%A0%EB%A7%81</guid>
            <pubDate>Mon, 02 Feb 2026 11:27:59 GMT</pubDate>
            <description><![CDATA[<p>2026.02.02 (월)
GoRouter Path Parameter 데이터 타입
Troble Shooting</p>
<hr>
<h2 id="go-router-path-parameter">Go Router Path Parameter</h2>
<h3 id="🏃➡️-문제-상황">🏃‍➡️ 문제 상황</h3>
<p><code>GoRouter</code> 를 사용하여 상세 페이지(DetailPage)로 이동하는 라우팅을 설정하던 중, URL 파라미터인 <code>id</code>값을 <code>int.parse()</code>를 이용해 숫자로 변환하려고 시도했다. 하지만 시뮬레이터 화면에 FormatException: Invalid radix-10 number라는 레드 스크린(에러 화면)이 출력되었다..</p>
<h4 id="📝-원인-분석">📝 [원인 분석]</h4>
<p><img src="https://velog.velcdn.com/images/memo_00/post/8f115b71-7619-42fe-b004-70c84ced8512/image.png" alt=""></p>
<p>이때까지만 해도 git graph를 확인 시에 바꾼 것이 <code>GoRouter</code> 기능 추가한 것 밖에 없는데 <code>id</code> 값을 만져서 일어난 문제 같아서 id 값을 바꾼 곳을 유심히 살펴보았다. 🤔</p>
<p>그리고 에러를 확인해보니, <code>state.pathParameters[&#39;id&#39;]</code>를 통해 들어온 값이 <code>&quot;nHy0mO...&quot;</code>와 같은 문자열 <code>String</code> 형태였다.</p>
<p><code>int.parse()</code> 함수는 숫자로 바꿀 수 없는 문자(알파벳 등)가 포함되어 있으면 즉시 에러를 발생시키며 앱의 흐름을 끊겨버린다.</p>
<h4 id="📝-중간-검증-왜-에러-페이지가-안-떴을까">📝 중간 검증: 왜 에러 페이지가 안 떴을까?</h4>
<p>그리고 의문,,🤔 처음에는 에러가 발생했을 때 설정한 <code>errorBuilder</code>의 <code>ErrorPage</code>
가 보이지 않고 시스템 에러 화면이 떴댜.? 
자세히 보니, 이유는 라우팅 자체의 실패가 아니라, builder 내부의 로직(Dart 코드)에서 예외가 발생했기 때문인 것으로 확인되었다.</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/f5b814c6-5623-4aac-bc08-55da96ae5ef8/image.png" alt=""></p>
<p>혹시 몰라서 이것을 확인하기 위해 코드를 수정해보았다.</p>
<pre><code class="language-python">final id = int.tryParse(state.pathParameters[&#39;id&#39;] ?? &#39;&#39;);
if (id == null) {
  return ErrorPage();
}</code></pre>
<p><code>int.tryParse()</code>의 역할: 문자열을 숫자로 바꿀 수 없더라도 에러를 내지 않고 null을 반환</p>
<p>결과: 숫자가 아닌 값이 들어왔을 때 id가 null이 되어, 명시적으로 ErrorPage()를 리턴하게 되는 것을 확인.!
이 과정을 통해 <strong>&quot;현재 들어오는 데이터가 숫자가 아니어서 발생하는 문제&quot;</strong>임을 정확히 확신할 수 있었다</p>
<hr>
<h4 id="최종-해결-데이터-타입의-유연한-활용">최종 해결: 데이터 타입의 유연한 활용</h4>
<p><code>id</code> 값을 굳이 <code>int</code>로 변환할 필요가 있는지 다시 검토해봤다 ,,🤔
<code>DetailPage</code>에서 해당 <code>id</code>를 단순히 식별자나 텍스트로만 사용한다면, 굳이 위험하게 형변환(Parsing)을 할 필요가 없는 것 같아서 <code>String</code> 타입으로 변환 받기로 하고 다시 수정했더니 에러 해결!</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/9274157b-4a5a-4393-8156-aab7364c5ae7/image.png" alt=""></p>
<p>[최종 수정 코드]</p>
<pre><code class="language-python">GoRoute(
  path: &#39;detail/:id&#39;,
  builder: (context, state) {
    // 문자열 그대로 전달하여 에러 발생 가능성을 원천 차단
    final id = state.pathParameters[&#39;id&#39;] ?? &#39;&#39;;
    return DetailPage(todoId: id);
  },
),</code></pre>
<p>🌟 <strong>해결 로직:</strong><code>pathParameters</code>에서 받은 데이터를 그대로 <code>String</code>타입으로 사용.
🌟 <strong>결과:</strong> 복잡한 문자열로 된 ID 값도 정상적으로 전달되어 상세 페이지가 에러 없이 출력 확인.</p>
<h4 id="💡-오늘의-교훈">💡 오늘의 교훈</h4>
<p><strong>데이터 타입의 엄격함</strong>
Dart는 정적 타입 언어이므로 <code>String</code>을 <code>int</code>로 바꿀 때는 항상 데이터의 형식을 확인하자</p>
<p><strong>parse 보다는 tryParse</strong>
입력값이 확실한 숫자가 아니라면, 앱이 멈추는 것을 방지하기 위해 <code>tryParse</code>를 사용하는 것이 안전하다!</p>
<p><strong>불필요한 형변환 지양</strong>
전달받은 데이터를 그대로 사용할 수 있다면, 가급적 원래의 타입(String)을 유지하는 것이 코드의 안정성을 높인다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[날씨 앱을 클린 아키텍처로 리펙토링 🌈]]></title>
            <link>https://velog.io/@memo_00/%EB%82%A0%EC%94%A8-%EC%95%B1%EC%9D%84-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%A1%9C-%EB%A6%AC%ED%8E%99%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@memo_00/%EB%82%A0%EC%94%A8-%EC%95%B1%EC%9D%84-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%A1%9C-%EB%A6%AC%ED%8E%99%ED%86%A0%EB%A7%81</guid>
            <pubDate>Thu, 29 Jan 2026 11:57:02 GMT</pubDate>
            <description><![CDATA[<p>2026.01.29 (목)
MVVM에서 클린 아키텍처로 .. gogo</p>
<hr>
<p>클린 아키텍처 마스터를 위해 기존에 MVVM 형식의 만들어진 날씨 앱을 클린 아키텍처를 사용해서 리펙토링 해보았다..!
master..🌟</p>
<h3 id=""></h3>
<p><img src="https://velog.velcdn.com/images/memo_00/post/21ad1efc-6152-4323-aee9-0c030189f130/image.png" alt=""></p>
<hr>
<p><img src="https://velog.velcdn.com/images/memo_00/post/a49e0c66-71bf-45e6-93ad-09c69eadb6ab/image.webp" alt=""></p>
<p>강의에 나온 이 그림을 보고 리팩토링을 시작했다 🌟</p>
<h2 id="☔️-리팩토링-작업-순서">☔️ 리팩토링 작업 순서</h2>
<p><img src="https://velog.velcdn.com/images/memo_00/post/d0cbbdc3-f825-44cf-a217-c33789807049/image.png" alt=""></p>
<h4 id="1-dto-분리">1. DTO 분리</h4>
<p><code>data/dto/weather_dto.dart</code> 서버 데이터(JSON) 형식을 그대로 담는 객체</p>
<h4 id="2-entity-정의">2. Entity 정의</h4>
<p><code>domain/entity/weather.dart</code> 앱 UI와 로직에서 실제로 사용할 순수 데이터 모델을 정리</p>
<h4 id="3-data-layer-작업">3. Data Layer 작업</h4>
<p><code>data/data_source/weather_data_source.dart</code>
<code>data/data_source/weather_data_source_impl.dart</code>
외부 API 통신 인터페이스 및 실제 구현체(http.Client 사용)</p>
<h4 id="4-repository-정의">4. Repository 정의</h4>
<p><code>domain/repository/weather_repository.dart</code> 파일에서 도메인이 필요로 하는 데이터 기능을 인터페이스로 선언</p>
<h4 id="5-repository-구현">5. Repository 구현</h4>
<p><code>data/repository/weather_repository_impl.dart</code> 파일에서 Data Source를 사용해 DTO를 받고, 이를 Entity로 변환하여 반환</p>
<h4 id="6-use-case">6. Use Case</h4>
<p><code>domain/use_case/fetch_weather_usecase.dart</code> 파일에서 사용자의 핵심 동작(날씨 조회)을 단일 책임 클래스로 만들기</p>
<h4 id="7-provider-정의">7. Provider 정의</h4>
<p>main.dart  등 앱 전체의 각 레이어의 의존성을 주입하기 위한 Riverpod 설정
<code>데이터 소스 Provider</code>, <code>레포지토리 Provider</code>, <code>유스케이스 Provider</code></p>
<h4 id="8-viewmodel-연결">8. ViewModel 연결</h4>
<p><code>presentation/home/home_view_model.dart</code> 파일에서 Use Case를 호출하여 UI 상태를 관리</p>
<h4 id="9-ui-바인딩">9. UI 바인딩</h4>
<p><code>presentation/ui/home_screen.dart</code> 파일에서 ViewModel의 상태를 감시(watch)하여 화면에 렌더링</p>
<p>UI에 뿌려주기까지 성공..!</p>
<hr>
<h2 id="🌦-단계별-구현">🌦 단계별 구현</h2>
<h3 id="▶️-데이터의-구분-dto-vs-entity">▶️ 데이터의 구분 (DTO vs Entity)</h3>
<p>서버에서 주는 데이터(DTO)와 내가 만드는 앱이 실제로 쓰고 싶은 데이터(Entity)를 분리한다.</p>
<h4 id="dto-data-transfer-object--서버의-입맛-json-구조와-동일">DTO (Data Transfer Object) : 서버의 입맛 (JSON 구조와 동일)</h4>
<pre><code class="language-python">class WeatherDto {
  final double temperature;
  final double windspeed;
  final double winddirection;
  final int weathercode;
  final int isDay;
  final String time;

  const WeatherDto({
    required this.temperature,
    required this.windspeed,
    required this.winddirection,
    required this.weathercode,
    required this.isDay,
    required this.time,
  });
  factory WeatherDto.fromJson(Map&lt;String, dynamic&gt; json) {
    return WeatherDto(
      temperature: (json[&#39;temperature&#39;] as num).toDouble(),
      windspeed: (json[&#39;windspeed&#39;] as num).toDouble(),
      winddirection: (json[&#39;winddirection&#39;] as num).toDouble(),
      weathercode: json[&#39;weathercode&#39;] as int,
      isDay: json[&#39;is_day&#39;] as int,
      time: json[&#39;time&#39;] as String,
    );
  }

  Map&lt;String, dynamic&gt; toJson() {
    return {
      &#39;temperature&#39;: temperature,
      &#39;windspeed&#39;: windspeed,
      &#39;winddirection&#39;: winddirection,
      &#39;weathercode&#39;: weathercode,
      &#39;is_day&#39;: isDay,
      &#39;time&#39;: time,
    };
  }
}</code></pre>
<h4 id="entity--앱의-입맛-비지니스-로직에-최적화">Entity : 앱의 입맛 (비지니스 로직에 최적화)</h4>
<pre><code class="language-python">class Weather {
  final double latitude;
  final double longitude;
  final double temperature;
  final double windSpeed; 
  final int weatherCode;
  final bool isDay; 
  final String time;

  const Weather({
    required this.latitude,
    required this.longitude,
    required this.temperature,
    required this.windSpeed,
    required this.weatherCode,
    required this.isDay,
    required this.time,
  });
}</code></pre>
<h3 id="▶️-data-source-레이어">▶️ Data Source 레이어</h3>
<p>외부 세계(API)와 직접 통신하는 계층</p>
<h4 id="datadata_sourceweather_data_sourcedart">data/data_source/weather_data_source.dart</h4>
<p><code>Interface</code></p>
<pre><code class="language-python">abstract interface class WeatherDataSource {
  Future&lt;CurrentWeatherDto&gt; fetchWeather(double lat, double lng);
}</code></pre>
<h4 id="datadata_sourceweather_data_source_impldart">data/data_source/weather_data_source_impl.dart</h4>
<p><code>Implementation</code></p>
<pre><code class="language-python">class WeatherDataSourceImpl implements WeatherDataSource {
  final http.Client _client;

  WeatherDataSourceImpl([http.Client? client])
    : _client = client ?? http.Client();

  @override
  Future&lt;CurrentWeatherDto&gt; fetchWeather(double lat, double lng) async {
    final url = Uri.parse(
    ...생략</code></pre>
<h3 id="▶️-repository-레이어">▶️ Repository 레이어</h3>
<p> <code>domain/repository/weather_repository.dart</code></p>
<pre><code class="language-python">abstract interface class WeatherRepository {
  Future&lt;Weather&gt; fetchWeather(double lat, double lng);
}</code></pre>
<p><code>data/repository/weather_repository_impl.dart</code></p>
<pre><code class="language-python">class WeatherRepositoryImpl implements WeatherRepository {
  final WeatherDataSource _dataSource;

  WeatherRepositoryImpl(this._dataSource);

  @override
  Future&lt;Weather&gt; fetchWeather(double lat, double lng) async {
    final dto = await _dataSource.fetchWeather(lat, lng);
    return Weather(
 (... 생략)
     };
   }
 }</code></pre>
<h3 id="▶️-use-case--provider-실행-및-조립">▶️ Use Case &amp; Provider (실행 및 조립)</h3>
<p>앱의 핵심 기능을 클래스화하고 의존성을 주입하기
<code>domain/use_case/fetch_weather_usecase.dart</code></p>
<pre><code class="language-python">class FetchWeatherUsecase {
  final WeatherRepository _repository;

  FetchWeatherUsecase(this._repository);

  Future&lt;Weather&gt; call(double lat, double lng) {
    return _repository.fetchWeather(lat, lng);
  }
}</code></pre>
<pre><code class="language-python">main.dart (Provider 정의)
// 데이터 소스 Provider
final weatherDataSourceProvider = Provider&lt;WeatherDataSource&gt;((ref) {
  return WeatherDataSourceImpl();
});

// 레포지토리 Provider
final weatherRepositoryProvider = Provider&lt;WeatherRepository&gt;((ref) {
  return WeatherRepositoryImpl(ref.read(weatherDataSourceProvider));
});

// 유스케이스 Provider
final fetchWeatherUsecaseProvider = Provider&lt;FetchWeatherUsecase&gt;((ref) {
  return FetchWeatherUsecase(ref.read(weatherRepositoryProvider));
});
</code></pre>
<h3 id="▶️-presentation-layer">▶️ Presentation Layer</h3>
<p><code>presentation/home/home_view_model.dart</code></p>
<pre><code class="language-python">final homeViewModelProvider = AsyncNotifierProvider&lt;HomeViewModel, Weather&gt;(HomeViewModel.new);

class HomeViewModel extends AsyncNotifier&lt;Weather&gt; {
  @override
  Future&lt;Weather&gt; build() {
    return _fetchWeather();
  }

  Future&lt;Weather&gt; _fetchWeather() {
    final useCase = ref.read(fetchWeatherUsecaseProvider);
    return useCase.call(37.57, 126.98);
  }

  Future&lt;void&gt; fetchWeather() async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() =&gt; _fetchWeather());
  }
}</code></pre>
<p><code>presentation/ui/home_screen.dart</code></p>
<pre><code class="language-python">Widget build(BuildContext context, WidgetRef ref) {
  final weatherState = ref.watch(homeViewModelProvider);

  return Scaffold(
    body: weatherState.when(
      data: (weather) =&gt; Text(&#39;기온: ${weather?.temperature}&#39;),
      loading: () =&gt; CircularProgressIndicator(),
      error: (e, st) =&gt; Text(&#39;에러 발생&#39;),
    ),
  );
}</code></pre>
<hr>
<h2 id="🌤-레이어-작업을-하는-이유">🌤 레이어 작업을 하는 이유</h2>
<ul>
<li>Data Layer
데이터를 어떻게 가져올 것인가? (네트워크, 로컬 DB 등)</li>
<li>Domain Layer
우리 앱은 어떤 일을 하는가? (가장 중요하며 순수해야 함)</li>
<li>Presentation Layer
사용자에게 어떻게 보여줄 것인가? (UI, 상태 관리 등)</li>
</ul>
<p>처음에는 파일 개수도 많아지고 구조가 복잡해 보여서 과연 이게 효율적인가 라는 의문이 들었지만 작업을 마치고, TIL 작성 하려고 다시 보니 각 클래스가 자신의 일만 명확하게 수행하는 모습을 확인할 수 있었다아</p>
<h2 id="🌞-왜-클린-아키텍처인가">🌞 왜 클린 아키텍처인가?</h2>
<h4 id="⚡️-도메인-중심-설계-domain-centric">⚡️ 도메인 중심 설계 (Domain-Centric)</h4>
<p>외부 환경(API, DB, UI)이 어떻게 변하든 앱의 핵심 로직(날씨를 가져온다)은 변하지 않아야 할 것..! 
모든 데이터 흐름은 정해진 통로 (Repository -&gt; UseCase)를 거쳐야 되니까 코드가 예측 가능할 거 같ㅌ다.
도메인을 가장 안쪽에 보호함으로써 외부의 변화가 내부로 침투할 수 없는 구조를 만들었다.</p>
<h4 id="⚡️-의존성-역전-dependency-inversion">⚡️ 의존성 역전 (Dependency Inversion)</h4>
<p>인터페이스를 통해 &quot;무엇을 할지&quot;만 정의하고, &quot;어떻게 할지&quot;는 외부에서 주입한다. 덕분에 테스트가 쉬워지고 코드 간의 결합도가 낮아진다.</p>
<p>🌟
아직 테스트 작업을 못 해봤다.. 테스트에 용이한 클린 아키텍처지만 파일 분리하고 layer 작업 후 바로 에러 잡기 급급했던 거 같다. 다음에는 테스트도 함께 사용해서 클린 아키텍처 마스터를.. 해 ㅂ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SOLID 원칙]]></title>
            <link>https://velog.io/@memo_00/SOLID-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@memo_00/SOLID-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Wed, 28 Jan 2026 13:33:11 GMT</pubDate>
            <description><![CDATA[<p>2026.01.28 (수)
SOLID 원칙이란/</p>
<hr>
<h3 id="오늘의-공부-내용-이미지-💫">오늘의 공부 내용 이미지 💫</h3>
<p><img src="https://velog.velcdn.com/images/memo_00/post/b03de03b-147d-4e84-b97c-66d84a269f3d/image.png" alt=""></p>
<hr>
<h2 id="⛄️-solid-원칙이란">⛄️ SOLID 원칙이란?</h2>
<h3 id="❄️-srp-단일-책임-원칙-single-responsibility-principle">❄️ SRP: 단일 책임 원칙 (Single Responsibility Principle)</h3>
<p>클래스가 너무 많은 일을 하면 버그가 발생했을 때 수정하기 어렵고, 코드의 가독성이 떨어지기 때문에 하나의 클래스에 하나의 책임만 부여한다</p>
<h4 id="🛵-배달-앱으로-이해하는-solid-원칙">🛵 배달 앱으로 이해하는 SOLID 원칙</h4>
<p><strong>1. SRP (단일 책임)</strong> &quot;요리사는 요리만, 라이더는 배달만&quot;</p>
<blockquote>
<p><code>직원</code>이라는 클래스가 요리도 하고, 배달도 하고, 결제도 다 하는 상황에
배달 방식이 오토바이에서 자전거로 바뀌었는데, 갑자기 요리하는 코드까지 건드려야 하는 상황이 생겼을 경우,</p>
<blockquote>
<p> 해결: 요리사 클래스, 라이더 클래스, 결제원 클래스로 각각 나누자. 자기 할 일만 잘하면 된다!</p>
</blockquote>
</blockquote>
<p><strong>2. OCP (개방-폐쇄)</strong> &quot;메뉴 추가는 자유롭게, 주문 시스템은 그대로&quot;</p>
<blockquote>
<p>새로운 메뉴로 &#39;마라탕&#39;이 추가가 되었는데, 메뉴가 추가될 때마다 주문 앱의 전체 코드를 다시 짜야 한다면..?</p>
<blockquote>
<p>해결: 메뉴라는 큰 틀(인터페이스)을 만들다. 치킨이든 마라탕이든 메뉴라는 틀만 지키면 주문 시스템은 수정 없이 그대로 새 메뉴를 받아들일 수 있다..!</p>
</blockquote>
</blockquote>
<p><strong>3. LSP (리스코프 치환)</strong> &quot;어떤 탈것이든 배달은 가능해야 한다&quot;</p>
<blockquote>
<p>배달 수단이라는 부모가 있고, 자식으로 오토바이와 자전거가 있을 경우,
&quot;오토바이는 되는데 자전거는 배달을 못 해!&quot;라고 프로그램이 멈춰버리면 안 된다.</p>
<blockquote>
<p>해결: 부모인 배달 수단 자리에 자전거를 넣어도 배달 로직은 정상적으로 &#39;이동&#39;하고 &#39;도착&#39;해야 한다.</p>
</blockquote>
</blockquote>
<p><strong>4. ISP (인터페이스 분리)</strong> &quot;손님 화면에 &#39;주문 수락&#39; 버튼을 넣지 마라&quot;</p>
<blockquote>
<p>배달 앱 인터페이스 하나에 &#39;주문하기&#39;, &#39;리뷰쓰기&#39;, &#39;주문 수락하기&#39;, &#39;음식 완료 알림&#39; 버튼을 다 넣었을 경우, 손님은 &#39;주문 수락&#39; 기능을 전혀 안 쓰는데 이 버튼 때문에 화면이 복잡해졌다. 이럴 때 해결은?</p>
<blockquote>
<p>해결: 손님용 인터페이스(주문, 리뷰)와 사장님용 인터페이스(수락, 알림)를 따로 분리해보자</p>
</blockquote>
</blockquote>
<p><strong>5. DIP (의존성 역전)</strong> &quot;결제 방식은 언제든 갈아 끼울 수 있어야 한다&quot;</p>
<blockquote>
<p>앱이 &#39;네이버페이&#39; 결제 시스템에 직접 납땜(직접 연결)되어 있었을 경우, 갑자기 &#39;카카오페이&#39;로 바꿔야 하면 앱을 거의 새로 만들어야 하는 상황이 생긴다. 이럴 때?</p>
<blockquote>
<p>해결: 중간에 결제 시스템이라는 <strong>플러그(어댑터)</strong>를 둡니다. 앱은 플러그에만 연결하고, 거기에 네이버페이를 꽂든 카카오페이를 꽂든 앱은 상관없게 만드는 것 이다!</p>
</blockquote>
</blockquote>
<hr>
<h3 id="❄️-클린-아키텍처와-연결하기">❄️ 클린 아키텍처와 연결하기</h3>
<p>클린 아키텍처는 이 SOLID 원칙들을 층층이 쌓아 올린 것과 같다.
가장 중요한 것은 <strong>비즈니스 로직(배달 주문)</strong>을 가운데 보호하는 것이 핵심!!</p>
<h4 id="결제-시스템-dip-원칙-적용">결제 시스템 (DIP 원칙 적용)</h4>
<pre><code class="language-python">// 1. 추상화 (DIP: 플러그 만들기)
// 어떤 결제 수단이든 이 규칙을 따라야 함
abstract class PaymentGateway {
  void pay(int amount);
}

// 2. 저수준 모듈 (실제 구현체: 플러그에 꽂을 제품들)
class KakaoPay implements PaymentGateway {
  @override
  void pay(int amount) =&gt; print(&quot;카카오페이로 $amount원 결제&quot;);
}

class TossPay implements PaymentGateway {
  @override
  void pay(int amount) =&gt; print(&quot;토스페이로 $amount원 결제&quot;);
}

// 3. 고수준 모듈 (Clean Architecture의 UseCase: 배달 주문 로직)
// SRP: 주문하는 일만 책임짐
class OrderUseCase {
  final PaymentGateway paymentMethod; // DIP: 구체적인 페이가 아닌 &#39;플러그&#39;에 의존

  OrderUseCase(this.paymentMethod);

  void completeOrder(int price) {
    // 주문 완료 로직...
    paymentMethod.pay(price);
  }
}</code></pre>
<h3 id="왜-이렇게-쓸까">왜 이렇게 쓸까?</h3>
<p><strong>변경에 강함</strong>
내일 당장 사장님이 &quot;우리 이제 토스페이만 쓰자!&quot;라고 해도 OrderUseCase 코드는 한 줄도 고칠 필요가 없다. (OCP)</p>
<p><strong>테스트하기 좋음</strong>
실제 결제를 안 일으키고 가짜 결제(Mock) 플러그를 꽂아서 주문 로직이 잘 작동하는지 테스트할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MVVM, 클린 아키텍처]]></title>
            <link>https://velog.io/@memo_00/MVVM-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98</link>
            <guid>https://velog.io/@memo_00/MVVM-%ED%81%B4%EB%A6%B0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98</guid>
            <pubDate>Tue, 27 Jan 2026 08:48:30 GMT</pubDate>
            <description><![CDATA[<p>2026.01.27 (화)
MVVM / 클린 아키텍처
의존 역전 원칙</p>
<hr>
<h3 id="오늘의-공부-내용-이미지-💫">오늘의 공부 내용 이미지 💫</h3>
<p><img src="https://velog.velcdn.com/images/memo_00/post/305bb98c-d284-4d34-803b-687125f72ec6/image.png" alt=""></p>
<hr>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">MVVM</th>
<th align="left">클린 아키텍처+MVVM</th>
</tr>
</thead>
<tbody><tr>
<td align="left">ViewModel의 역할</td>
<td align="left">비지니스 로직+데이터요청+상태 관리</td>
<td align="left">상태관리 및 Use Case 호출만</td>
</tr>
<tr>
<td align="left">비지니스 로직</td>
<td align="left">ViewModel 내부에 존재함</td>
<td align="left"><strong>Use case</strong>로 완전 분리됨</td>
</tr>
<tr>
<td align="left">데이터 소스 변경</td>
<td align="left">ViewModel 코드를 다 수정해줘야함</td>
<td align="left">Data 계층만 수정, ViewModel은 영향 없음</td>
</tr>
<tr>
<td align="left">의존성 방향</td>
<td align="left">ViewModel -&gt; RepositoryAPI</td>
<td align="left">외부 -&gt; 내부만 수정</td>
</tr>
</tbody></table>
<hr>
<h2 id="🛸-클린-아키텍처">🛸 클린 아키텍처</h2>
<p>소프트웨어의 핵심 로직을 UI, 데이터베이스, 프레임워크와 같은 외부 요소로부터 완전히 분리하여 어떤 환경에서도 독립적으로 동작 가능하고 테스트가 가능한 구조로 만드는 것이 핵심이다 🌟</p>
<h3 id="🔹-핵심-구조">🔹 핵심 구조</h3>
<p>핵심 구조는 전에 팀프로젝트로 진행한 DoranDoran APP으로 기억하기 쉽도록 정리해봤다</p>
<h4 id="1-entities--순수-데이터-본질">1. Entities : 순수 데이터 본질</h4>
<p>가장 안쪽 계층이고 앱의 핵심 데이터 구조</p>
<ul>
<li><strong>예시</strong> : <code>ChatMessage</code> 클래스</li>
<li><strong>내용</strong> : 메시지 보낸 내용, 보낸 사람 ID, 전송 시간 등 아주 기본적인 정보만 담는 곳</li>
<li><strong>특징</strong> : 이 코드는 Firebase를 쓰든 내 서버를 쓰든 절대 변하지 않는 순수한 Dart 클래스</li>
</ul>
<pre><code class="language-python">class ChatMessage {
  ChatMessage ({
   required this.id,
   required this.text,
   required this.creatAt,
   });

   final String id;
   final String text;
   final DateTime creatAt;
   }</code></pre>
<h4 id="2-use-case--구체적인-기능-로직">2. Use Case : 구체적인 기능 로직</h4>
<p>사용자가 하려는 &#39;행동&#39; 그 자체 정의하는 것</p>
<ul>
<li><strong>예시</strong> : <code>SendMessage</code> </li>
<li><strong>내용</strong> : 사용자가 앱에서 수행하려는 구체적인 행위(비지니스 로직)를 정의한다.</li>
<li><strong>특징</strong> : 데이터를 어떻게 가져오는지 모르고, 단지 <strong>저장</strong>하라는 명령만 내린다. 엔티티를 사용해서 앱의 기능을 수행하며 데이터가 어떻게 흐를지 결정한다.</li>
</ul>
<pre><code class="language-python">class SendMessage {
  SendMessage(this.repository);

  final ChatRepository repository;

  Future&lt;void&gt; execute(String text) async {
    if (text.isEmpty) return;
    await repository.saveMessage(text);
    }
  }</code></pre>
<h4 id="3-interface-adapters--mvvm의-viewmodel">3. Interface Adapters : MVVM의 ViewModel</h4>
<p>내부(Use Case)와 외부(UI/DB)를 연결해주는 통역사</p>
<ul>
<li><strong>예시</strong> : <code>ChatViewModel</code></li>
<li><strong>내용</strong> : 사용자가 화면에서 버튼을 누르면 유즈케이스를 실행시키고, 결과가 오면 화면에 보여줄 &#39;상태&#39;를 바꾼다</li>
<li><strong>기존 MVVM과 연결</strong> : 이 계층이 MVVM의 ViewModel과 Repository 인터페이스가 위치하는 곳</li>
<li><strong>주로 사용 및 역할</strong> : Riverpod, Provider, 등 여기서 상태 관리를 담당함. 외부 데이터(JSON 등)를 내부에서 사용하기 좋은 엔티티 형태로 바꾸거나 그 반대의 역할을 수행한다.</li>
</ul>
<h4 id="4-frameworks--drivers--실제-기술-도구">4. Frameworks &amp; Drivers : 실제 기술 도구</h4>
<p>가장 바깥 쪽으로 실제 화면을 그리고 데이터를 전송하는 도구</p>
<ul>
<li><strong>예시</strong> : <code>ChatView</code>(UI), <code>FirebaseChatRepository</code> (구체적인 DB 구현), HTTP 클라이언트 등 실제 도구들이 위치함</li>
<li><strong>내용</strong> : Flutter의 <code>ListView</code>로 채팅창을 그리거나, Firebase SDK를 사용해 데이터를 실제로 날리는 코드가 위치한다.</li>
<li><strong>특징</strong> : 언제든 교체될 수 있는 영역이다.</li>
</ul>
<hr>
<h2 id="🛩-mvvm과-클린-아키텍처-🛸">🛩 MVVM과 클린 아키텍처 🛸</h2>
<p>기존의 MVVM은 주로 화면(UI)를 어떻게 깔끔하게 관리할까에 집중했다면, 클린 아키텍처는 앱 전체의 코드가 Framework에 오염되지 않고 어떻게 깔끔하게 격리할까? 에 집중한다.</p>
<h3 id="🔸-viewmodel의-일거리">🔸 ViewModel의 일거리</h3>
<p>기존 MVVM에서는 ViewModel이 API를 호출하고, 데이터를 가공하고, 에러를 처리하는 등 너무 많은 일을 했다면 클린 아키텍처를 통해 이 복잡한 로직을 Use Case가 가져간다.</p>
<ul>
<li>기존 : <code>ViewModel</code> -&gt;<code>API 호출</code> -&gt; <code>데이터 변환</code> -&gt; <code>화면 갱신</code></li>
<li>이후 : <code>ViewModel</code> -&gt; <code>Use Case 실행</code> -&gt; <code>결과 받아서 화면 갱신</code></li>
</ul>
<h3 id="🔸-기존-mvvm-방식-viewmodel이-모든-일을-할-때">🔸 기존 MVVM 방식 (ViewModel이 모든 일을 할 때)</h3>
<p>기존에는 ViewModel이 <strong>화면 상태 관리 + 데이터 로직 + 외부 서버 통신</strong>을 모두 진행했음, 코드가 한 곳에 모여 있어서 편했지만, 기능이 늘어날수록 ViewModel이 커진다는 문제점이 있다.</p>
<pre><code class="language-python">class ChatViewModel extends StateNotifier&lt;ChatState&gt; {
  final FirebaseFirestore _firestore = FirebaseFirestore.instance; // 직접 외부에 의존

  // 메시지 전송 로직이 ViewModel 안에 직접 들어있음
  Future&lt;void&gt; sendMessage(String text, String userId) async {
    if (text.isEmpty) return; // 비즈니스 로직 1 (빈 메시지 체크)

    try {
      // 비즈니스 로직 2 (데이터 구조 생성 및 저장)
      await _firestore.collection(&#39;messages&#39;).add({
        &#39;text&#39;: text,
        &#39;senderId&#39;: userId,
        &#39;createdAt&#39;: DateTime.now(),
      });
    } catch (e) {
      state = state.copyWith(errorMessage: &quot;전송 실패!&quot;);
    }
  }
}</code></pre>
<p>예시) Firebase가 아닌 다른 DB로 바꾼다면?...
<code>ChatViewModel</code>의 코드를 통째로 고쳐야함</p>
<h3 id="🔸-클린-아키텍처--mvvm-역할을-분담할-때">🔸 클린 아키텍처 + MVVM (역할을 분담할 때)</h3>
<p>클린아키텍처를 적용하면 코드가 세 덩어리로 쪼개진다. <strong>무엇을(Entity)</strong>, <strong>어떻게(Use Case)</strong>, <strong>어디에(Repository)</strong>로 분리하는 것이 핵심!!</p>
<h4 id="①-domain-계층-가장-안-쪽-순수로직">① Domain 계층 (가장 안 쪽: 순수로직)</h4>
<pre><code class="language-python">// 1. Entity: 메시지의 본질
class Message {
Message({
required this.text, 
required this.senderId, 
required this.time
});
  final String text;
  final String senderId;
  final DateTime time;

}

// 2. Use Case: 메시지 전송이라는 &#39;행위&#39; 그 자체
class SendMessageUseCase {
  final ChatRepository repository; // &#39;도구&#39;가 아닌 &#39;기능&#39;의 이름에 의존
  SendMessageUseCase(this.repository);

  Future&lt;void&gt; execute(String text, String userId) async {
    if (text.isEmpty) return; // 비즈니스 로직은 여기서 처리!
    final message = Message(text: text, senderId: userId, time: DateTime.now());
    await repository.saveMessage(message);
  }
}</code></pre>
<h4 id="②-data-계층-바깥쪽-실제-기술">② Data 계층 (바깥쪽: 실제 기술)</h4>
<p>Firebase를 쓰든 로컬 DB로 쓰든 여기서 구현한다</p>
<pre><code class="language-python">// 3. Repository Implementation: 실제 Firebase로 저장하는 상세 방법
class FirebaseChatRepository implements ChatRepository {
  @override
  Future&lt;void&gt; saveMessage(Message msg) async {
    await FirebaseFirestore.instance.collection(&#39;messages&#39;).add({
      &#39;text&#39;: msg.text,
      &#39;senderId&#39;: msg.senderId,
      &#39;time&#39;: msg.time,
    });
  }
}</code></pre>
<h4 id="③-presentation-계층-바깥쪽-mvvm의-viewmodel">③ Presentation 계층 (바깥쪽: MVVM의 ViewModel)</h4>
<p>이제 ViewModel은 유즈케이스를 호출만하고 화면에 보여줄 상태만 관리한다.</p>
<pre><code class="language-python">// 4. ViewModel: 많이 깔끔해짐
class ChatViewModel extends StateNotifier&lt;ChatState&gt; {
  final SendMessageUseCase _sendMessageUseCase; // 유즈케이스를 주입받음

  ChatViewModel(this._sendMessageUseCase) : super(ChatState());

  Future&lt;void&gt; onSendPressed(String text, String userId) async {
    // ViewModel은 &quot;전송해줘!&quot;라고 시키기만 함
    await _sendMessageUseCase.execute(text, userId);
  }
}</code></pre>
<h4 id="▪️정리하자면">▪️정리하자면?</h4>
<p>MVVM은 <code>View</code>를 위한 모델을 만드는 것, 클린 아키텍처는 <strong>도메인을 중심으로 모든 것을 분리</strong>하는 시스템이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[REST API]]></title>
            <link>https://velog.io/@memo_00/REST-API-2n6ko0nd</link>
            <guid>https://velog.io/@memo_00/REST-API-2n6ko0nd</guid>
            <pubDate>Mon, 26 Jan 2026 11:55:52 GMT</pubDate>
            <description><![CDATA[<p>2026.01.26 (월)</p>
<p>REST API</p>
<hr>
<h3 id="오늘의-공부-이미지-💫">오늘의 공부 이미지 💫</h3>
<p><img src="https://velog.velcdn.com/images/memo_00/post/0811ec89-91b1-4fd4-bf3d-6c49b6b62138/image.png" alt=""></p>
<hr>
<h2 id="👉🏼👈🏼-rest-api-">👉🏼👈🏼 REST API ?</h2>
<p>REST API는 HTTP 프로토콜의 장점을 최대한 활용하여, 서버에 있는 <strong>자원(Resource)</strong>에 접근하는 방식을 정의한다.</p>
<ul>
<li>자원(Resource): 서버에 저장된 데이터(예: 사용자 정보, 게시글, 이미지 등)를 의미한다.</li>
<li>행위(Verb): HTTP 메서드(GET, POST 등)를 통해 자원에 무엇을 할지 결정한다.</li>
<li>표현(Representation): 자원의 상태를 JSON 같은 형태로 전달한다.</li>
</ul>
<hr>
<h2 id="💾-주요-구성-요소-http-메서드">💾 주요 구성 요소 (HTTP 메서드)</h2>
<p>▪️ <strong>GET</strong> 데이터 조회: 서버에서 정보를 가져올 때 사용함
▪️ <strong>POST</strong> 데이터 생성: 서버에 새로운 정보를 등록할 때 사용함
▪️ <strong>PUT</strong> 데이터 전체 수정: 기존 정보를 새로운 정보로 완전히 교체할 때 사용함
▪️ <strong>PATCH</strong> 데이터 일부 수정: 기존 정보 중 특정 부분만 바꿀 때 사용함
▪️ <strong>DELETE</strong> 데이터 삭제: 서버의 특정 정보를 삭제할 때 사용함 </p>
<hr>
<h2 id="💾-rest-api-왜-사용할까">💾 REST API 왜 사용할까?</h2>
<ul>
<li>독립성: 클라이언트(앱)와 서버가 서로 어떻게 구현되어 있는지 몰라도 상관없다 API 명세만 맞으면 통신이 가능하기 때문!</li>
<li>범용성: HTTP를 사용하기 때문에 웹이나 모바일 기기 등 어디서나 사용 가능하다</li>
<li>확장성: 서버 구조를 변경해도 API 주소만 유지된다면 클라이언트는 영향받지 않는다</li>
</ul>
<hr>
<h2 id="💾-rest-api-언제-사용할까">💾 REST API 언제 사용할까?</h2>
<ul>
<li>Flutter 앱에서 서버에 저장된 사용자 프로필 정보를 가져올 때</li>
<li>쇼핑몰 앱에서 상품 목록을 불러오거나 주문을 전송할 때</li>
<li>기상청 서버로부터 실시간 날씨 데이터를 받아올 때</li>
</ul>
<hr>
<h2 id="🕹-사용방법">🕹 사용방법</h2>
<pre><code class="language-python">// 1. 서버 주소(URL) 정의
var url = Uri.parse(&#39;https://api.example.com/users/1&#39;);

// 2. GET 요청 보내기
var response = await http.get(url);

// 3. 응답 처리
if (response.statusCode == 200) {
  // 성공 (200 OK)
  print(&#39;데이터: ${response.body}&#39;);
} else {
  // 실패
  print(&#39;에러 발생: ${response.statusCode}&#39;);
}</code></pre>
<h3 id="⌨️-주요-상태-코드">⌨️ 주요 상태 코드</h3>
<ul>
<li><code>200</code>  - <code>OK</code> 요청 성공.</li>
<li><code>201</code> - <code>Created</code> 요청 성공. 새로운 리소스가 생성되었을 때</li>
<li><code>400</code> - <code>Bad Request</code> 요청 실패. 클라이언트가 요청 Body에 잘못된 데이터 줬을때</li>
<li><code>401</code> - <code>Unauthorized</code> 인증되지 않음. 로그인 안되었을때.(로그인 후 Header에 정보 넣어줘야함)</li>
<li><code>403</code> - <code>Forbidden</code>  리소스 접근 금지됨. 로그인 했지만 접근할 권한 없을때. 예를들어 다른 회원의 블로그글 수정하려할 때</li>
<li><code>404</code> - <code>Not Found</code> 리소스 없음. URL이 잘못되었거나 URL은 정상이지만 서버 내 데이터가 없을 때</li>
<li><code>405</code> - <code>Method Not Allowed</code> 메소드 잘못 보냈을때</li>
<li><code>409</code> - <code>Conflict</code> 요청 수행 시 서버에서 충돌 날 때. 예를들어 회원가입 시 id에 중복된 아이디로 회원가입 시도할 경우</li>
<li><code>500</code> - <code>Internal Server Error</code> 서버에서 에러 났을때</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[MVVM_(DoranDoran App_TP)_Flutter 숙련주차 15일_]]></title>
            <link>https://velog.io/@memo_00/MVVMDoranDoran-AppTPFlutter-%EC%88%99%EB%A0%A8%EC%A3%BC%EC%B0%A8-15%EC%9D%BC</link>
            <guid>https://velog.io/@memo_00/MVVMDoranDoran-AppTPFlutter-%EC%88%99%EB%A0%A8%EC%A3%BC%EC%B0%A8-15%EC%9D%BC</guid>
            <pubDate>Fri, 23 Jan 2026 13:28:34 GMT</pubDate>
            <description><![CDATA[<p>2026.01.23 (금)
DoranDoran APP 팀프로젝트 완료 후 KTP 회고 및 트러블 슈팅</p>
<hr>
<h3 id="이번-주-학습-요약">이번 주 학습 요약</h3>
<p>이번 주는 MVVM 패턴 기반으로 Riverpod과 Firebase를 연동해서 실시간 채팅 기능까지 구현해봤다. 데이터 흐름을 이해하고 UI와 로직을 분리하는 과정에서 발생한 다양한 트러블슈팅을 통해 설계의 중요성을 배웠다</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/3a718929-82e0-4ddc-8ebd-01388960133e/image.png" alt=""></p>
<ul>
<li>프로젝트: 위치 기반 채팅 앱 &#39;도란도란&#39;</li>
<li>내가 맡은 주요 기능: 실시간 채팅 시스템 구축</li>
<li>사용 기술: Flutter, Dart, Riverpod(State Management), Firebase Firestore(NoSQL)</li>
<li>핵심 키워드: MVVM 패턴, Stream, AsyncValue, 모델링(Data Modeling)</li>
</ul>
<hr>
<h1 id="trouble-shooting">Trouble Shooting</h1>
<h2 id="asyncvaluewhen-매개변수-누락-문제">AsyncValue.when 매개변수 누락 문제</h2>
<h3 id="문제-상황">[문제 상황]</h3>
<p><code>chatRoomsAsync.when</code>을 사용하여 실시간 채팅방 목록을 불러오려 했으나, 컴파일 에러가 발생했다</p>
<p>에러 메시지: The named parameter &#39;error&#39; is required, but there&#39;s no corresponding argument.</p>
<h3 id="원인-분석">[원인 분석]</h3>
<p><code>Riverpod</code>의 <code>AsyncValue</code>는 비동기 데이터를 처리할 때 <strong>Data(데이터 로드 성공), Error(에러 발생), Loading(로딩 중)</strong>이라는 세 가지 상태를 반드시 모두 처리해주어야 했다. 스크린샷에서는 data 부분만 작성되어 있어 나머지 필수 상태가 누락된 것이 원인으로ㅓ 확인했다</p>
<h3 id="해결-방법">[해결 방법]</h3>
<p><code>error</code>와 <code>loading</code> 콜백을 추가하여 모든 상태에 대응하도록 코드를 수정해서 해결 완료!</p>
<pre><code class="language-python">body: chatRoomsAsync.when(
  data: (rooms) =&gt; _buildRoomList(rooms),
  error: (err, stack) =&gt; Center(child: Text(&#39;에러 발생: $err&#39;)), // 에러 상태 추가
  loading: () =&gt; Center(child: CircularProgressIndicator()), // 로딩 상태 추가
),</code></pre>
<hr>
<h2 id="widgetfunction-매개변수-개수-불일치">Widget/Function 매개변수 개수 불일치</h2>
<h3 id="문제-상황-1">[문제 상황]</h3>
<p>ListView 내에서 개별 아이템을 렌더링하기 위해 item(context, room)을 호출했으나 에러가 발생했다</p>
<p>에러 메시지: Too many positional arguments: 1 expected, but 2 found.</p>
<h3 id="원인-분석-1">[원인 분석]</h3>
<p>작성된 item 위젯(또는 함수)은 매개변수를 1개만 받도록 설계되어 있었으나, 실제 호출 시에는 context와 room 2개를 전달하고 있었던 걸로 확인되었다</p>
<h3 id="해결-방법-1">[해결 방법]</h3>
<p>item 함수의 정의 부분을 확인하여 매개변수를 (context, room) 두 개를 받도록 수정하거나, 호출부에서 불필요한 인자를 제거하여 일치시켰다ㅣ</p>
<hr>
<h2 id="model과-firestore-데이터-불일치-required-parameter">Model과 Firestore 데이터 불일치 (Required Parameter)</h2>
<h3 id="문제-상황-2">[문제 상황]</h3>
<p>Firestore에서 데이터를 가져와 ChatRoomModel 객체로 변환하는 fromMap 팩토리 생성자에서 에러가 발생했었다</p>
<p>에러 메시지: The named parameter &#39;des&#39; is required, but there&#39;s no corresponding argument.</p>
<h3 id="원인-분석-2">[원인 분석]</h3>
<p>ChatRoomModel 정의부에서 des(설명) 필드를 required로 설정했지만, fromMap 함수 내에서 해당 값을 할당해주지 않았다. 데이터베이스(Entity) 구조와 앱 내부 모델(Model) 구조 사이의 차이를 간과한 결과였던 걸로 확인했다</p>
<h3 id="해결-방법-2">[해결 방법]</h3>
<p>fromMap 생성자에서 des 값을 맵 데이터로부터 할당하거나, 기본값을 제공하도록 수정해서 해결했다!</p>
<pre><code class="language-python">factory ChatRoomModel.fromMap(Map&lt;String, dynamic&gt; map, String id) {
  return ChatRoomModel(
    roomId: id,
    title: map[&#39;title&#39;] ?? &#39;&#39;,
    des: map[&#39;des&#39;] ?? &#39;설명 없음&#39;, // 누락된 필드 추가
    // ... 나머지 필드들
  );
}</code></pre>
<h2 id="새롭게-깨달은-점">새롭게 깨달은 점</h2>
<ul>
<li><p>Entity vs Model
데이터베이스에 저장되는 형태(Entity)와 UI에서 사용되는 형태(Model)를 분리하여 관리하는 이유를 이해했다. 이는 데이터 구조가 변경되어도 UI 로직에 미치는 영향을 최소화할 수 있다.</p>
</li>
<li><p>실시간 데이터 처리
Stream과 snapshots()를 활용해 Firestore의 데이터 변화를 실시간으로 감지하고 UI를 자동 갱신하는 Riverpod의 강력함을 경험했다.</p>
</li>
<li><p>꼼꼼한 에러 체크
Dart 컴파일러의 에러 메시지는 매우 정확합니다. &quot;Required&quot;라는 단어가 보이면 누락된 값이 없는지 모델 클래스부터 확인하는 습관을 갖게 되었다!</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TeamProject_도란도란_Flutter_숙련주차 11일]]></title>
            <link>https://velog.io/@memo_00/TeamProject%EB%8F%84%EB%9E%80%EB%8F%84%EB%9E%80Flutter%EC%88%99%EB%A0%A8%EC%A3%BC%EC%B0%A8-11%EC%9D%BC</link>
            <guid>https://velog.io/@memo_00/TeamProject%EB%8F%84%EB%9E%80%EB%8F%84%EB%9E%80Flutter%EC%88%99%EB%A0%A8%EC%A3%BC%EC%B0%A8-11%EC%9D%BC</guid>
            <pubDate>Mon, 19 Jan 2026 14:18:15 GMT</pubDate>
            <description><![CDATA[<p>2026.01.19 (월)
TeamProject
Chatting Page 구현</p>
<hr>
<p>오늘은 정말 정신없이 chatPage UI 구현하느라고 TIL 작성을 못 했다...
그래도 오늘 작업한 내용은 사진으로 남겨보려고 한다..</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/c8addbcd-e870-49b3-b9e1-ab8352e6fef7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/54ded81d-1443-4f64-aea0-786a27257049/image.png" alt=""></p>
<p>이제 파일 정리가 안 되면 벌써 짜증난다...
어디어디어디어디로</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/1ec52928-6d88-4f42-8e3e-3fcca2e4e24a/image.png" alt=""></p>
<p>내일은.. 더....</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TeamProgect_도란도란_Flutter 숙련주차 10일]]></title>
            <link>https://velog.io/@memo_00/TeamProgect%EB%8F%84%EB%9E%80%EB%8F%84%EB%9E%80Flutter-%EC%88%99%EB%A0%A8%EC%A3%BC%EC%B0%A8-10%EC%9D%BC</link>
            <guid>https://velog.io/@memo_00/TeamProgect%EB%8F%84%EB%9E%80%EB%8F%84%EB%9E%80Flutter-%EC%88%99%EB%A0%A8%EC%A3%BC%EC%B0%A8-10%EC%9D%BC</guid>
            <pubDate>Fri, 16 Jan 2026 11:39:51 GMT</pubDate>
            <description><![CDATA[<p>2026.01.16 (금)
개인 과제 끝과
Team Project 시작</p>
<hr>
<p>이번 한 주도 마찬가지로 빠르게 지나갔다. 벌써 1월의 반을 달려왔다... 🏃🏻‍♀️‍➡️
하루하루 눈 깜짝하면 점심 먹을시간.. 눈 깜짝하면 저녁 먹을 시간.. 이렇게 하루가 지나간다 🥱 소듕한 시간..
이번 한 주 배운 내용 간단하게 정리!
새로 시작한 팀프로젝트를 정리하는 시간을 가져보자!</p>
<h3 id="이번-주의-공부-내용-이미지-💫">이번 주의 공부 내용 이미지 💫</h3>
<p>이번 주의 공부한 내용을 제미나이한테 그려달라고 해보았다</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/9fde91d5-8069-4dd8-b948-736f27b7e4ed/image.png" alt=""></p>
<hr>
<p>간단하게 정리해보자</p>
<h2 id="🧤-디자인-패턴과-아키텍처-도입-mvvm--singleton">🧤 디자인 패턴과 아키텍처 도입 (MVVM &amp; Singleton)</h2>
<h4 id="학습-내용">학습 내용</h4>
<p>유지보수가 쉬운 앱을 만들기 위한 MVVM 아키텍처와 싱글톤(Singleton) 패턴</p>
<h4 id="주요-포인트">주요 포인트</h4>
<p><strong>MVVM</strong> UI(View), 비즈니스 로직(ViewModel), 데이터(Model)를 분리하여 코드의 유지보수, 독립적으로 관리하여 사용</p>
<h4 id="singleton">Singleton</h4>
<p>앱 전역에서 단 하나의 인스턴스만 공유하여 데이터 일관성 유지.</p>
<h4 id="배우면서">배우면서..</h4>
<p>단순한 기능 구현을 넘어, 큰 프로젝트에서 구조적인 설계와 깔끔한 코드에 대해서 생각해 볼 수 있었다.</p>
<hr>
<h2 id="🧣-보안-및-인증-메커니즘-oauth-20--암호화">🧣 보안 및 인증 메커니즘 (OAuth 2.0 &amp; 암호화)</h2>
<h4 id="학습-내용-1">학습 내용</h4>
<p>사용자 데이터를 안전하게 보호하기 위한 보안 기초와 OAuth 2.0 인증 방식</p>
<h4 id="포인트">포인트</h4>
<p>대칭키/비대칭키 암호화: 데이터 전송 시 보안을 강화하는 핵심 기술!
OAuth 2.0: 소셜 로그인 등 외부 서비스와의 안전한 데이터 공유 프로세스 이해.</p>
<h4 id="느낀-점">느낀 점</h4>
<p>내가 여태 쓰던 모든 로그인 방식이잖아..? 인증하라고 할 때마다 철수와 영희가 생각 날 듯 싶다..! 우체국에 가지 않고, 공개키와 비공개키로 인해 박스를 열어보자</p>
<hr>
<h2 id="🧦-클라우드-데이터베이스-연동-firebase--firestore">🧦 클라우드 데이터베이스 연동 (Firebase &amp; Firestore)</h2>
<h4 id="학습-내용-2">학습 내용</h4>
<p>백엔드 구축 없이 데이터를 관리할 수 있는 Firebase와 실시간 DB인 Firestore를 연동 배우기</p>
<h4 id="포인트-1">포인트</h4>
<p>Firebase 프로젝트 설정 및 Flutter 패키지 연결.
Firestore를 통한 데이터의 CRUD(생성, 읽기, 수정, 삭제) 로직 구현.</p>
<h4 id="배우면서-1">배우면서</h4>
<p>아직도 익숙하지는 않다. 파일 왔다 갔다.. 조금 더 많이 만져봐야한다</p>
<hr>
<h2 id="👖-상태-관리-riverpod--freezed">👖 상태 관리 (Riverpod &amp; Freezed)</h2>
<h4 id="학습-내용-3">학습 내용</h4>
<p>Flutter의 강력한 상태 관리 라이브러리인 Riverpod과 불변 객체 생성을 돕는 Freezed를 학습</p>
<h4 id="포인트-2">포인트</h4>
<p><code>StateNotifier</code>를 활용한 반응형 프로그래밍.</p>
<p>Freezed를 사용해 데이터 모델의 불변성을 배우고 코드 생성 기능을 통한 코드의 생산성을 높여봤다
과제 프로젝트였던 메모 앱과 ToDo 앱에 이 스택을 적용하여 코드의 안정성을 높였ㄷ다!</p>
<hr>
<h2 id="team-project-🧍🏼🧍🏼🧍🏼🧍🏼♀️">Team Project 🧍🏼🧍🏼🧍🏼🧍🏼‍♀️</h2>
<p><img src="https://velog.velcdn.com/images/memo_00/post/59147b0d-7d58-4a0a-9d6e-93796e3345b5/image.png" alt=""></p>
<h3 id="doran-doran-도란도란앱-🧑🧑🧒">Doran Doran 도란도란앱 🧑‍🧑‍🧒</h3>
<p>우리 앱은 모임 어플이다. 요즘 경찰과 도둑, 러닝 모임, 독서 모임 등 사람들과 함께 취미와 운동을 즐길 수 있는 앱을 만들어볼 것이다..!! 도란도란 모여서 같이 취미하자~!!</p>
<h3 id="금일-작업-내용">금일 작업 내용</h3>
<ul>
<li>디자인 기획 및 브랜딩 작업</li>
<li>대시보드 작성</li>
<li>와이어 프레임 작성</li>
<li>Git 연동</li>
<li>firebase 연동
등등..</li>
</ul>
<p><img src="https://velog.velcdn.com/images/memo_00/post/bb405ddf-faf6-4d65-ab94-4ecec37e332b/image.png" alt=""></p>
<p>생각보다 팀원들과 손발이 척척 잘 맞게 프로젝트를 시작했다. 나의 가장 큰 걱정은 나의 담당 페이지..ㅎㅎ 
나의 임무는 채팅페이지와 지도페이지... 쉽진 않지만 어렵지 않은 임무라고 하는데 ㅎㅎ 잘 해내고 싶다.
Je peux faire.. vraiment</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ToDoApp_Trobleshooting_Flutter 숙련주차 9일]]></title>
            <link>https://velog.io/@memo_00/ToDoAppTrobleShootingFlutter-%EC%88%99%EB%A0%A8%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@memo_00/ToDoAppTrobleShootingFlutter-%EC%88%99%EB%A0%A8%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Thu, 15 Jan 2026 02:48:28 GMT</pubDate>
            <description><![CDATA[<p>2026.01.15 (목)
과제 - ToDo App (with Firebase &amp; MVVM)
Troubleshooting</p>
<hr>
<p><img src="https://velog.velcdn.com/images/memo_00/post/2500b215-7888-488b-9cfc-6eee25e5e9f5/image.jpg" alt=""></p>
<hr>
<h1 id="트러블슈팅-todo-삭제-기능의-ui-데이터베이스-불일치-해결">[트러블슈팅] ToDo 삭제 기능의 UI-데이터베이스 불일치 해결</h1>
<h2 id="1️⃣-문제-상황-problem">1️⃣. 문제 상황 (Problem)</h2>
<p>새로운 ToDo 항목을 추가한 직후, 삭제 버튼을 눌렀을 때 다음과 같은 문제가 발생했다.</p>
<h3 id="▪️-현상-1">▪️ 현상 1</h3>
<p>UI상에서는 해당 항목이 즉시 사라지지만, Firebase Firestore 실제 데이터는 삭제되지 않음.</p>
<h3 id="▪️-현상-2">▪️ 현상 2</h3>
<p>앱을 새로고침하면 삭제했던 항목이 다시 나타남 (데이터가 살아있음).</p>
<h3 id="▪️-현상-3">▪️ 현상 3</h3>
<p>터미널에 Invalid argument(s): A document path must be a non-empty string 에러 발생.</p>
<h3 id="▪️-특이사항">▪️ 특이사항</h3>
<p>앱을 재시작한 후 방금 추가한 항목은 삭제가 되지만, 기존 데이터는 삭제가 안 되는 상황</p>
<hr>
<h2 id="2️⃣-원인-분석-analysis">2️⃣. 원인 분석 (Analysis)</h2>
<p>문제의 핵심은 ID 값의 부재 및 불일치였다..!</p>
<h3 id="▪️-21-초기-코드의-흐름">▪️ 2.1 초기 코드의 흐름</h3>
<h4 id="uiviewmodel">UI/ViewModel</h4>
<p>새로운 ToDo를 만들 때 <code>id : &#39;&#39;</code> (빈 문자열)로 설정하여 Repository로 전달.</p>
<h4 id="repository">Repository</h4>
<p>Firebase에 저장할 때 문서 ID를 자동으로 생성하도록 처리.</p>
<h4 id="결과">결과</h4>
<p><img src="https://velog.velcdn.com/images/memo_00/post/b4a460fb-2ce7-4db2-b88a-75c7d548d96b/image.png" alt=""></p>
<p>Firebase에는 고유 ID(예: tJjtv...)가 생성되어 저장되지만, <strong>앱 내의 현재 상태(State)에 들어있는 데이터의 ID는 여전히 빈 문자열(&#39;&#39;)</strong>임.</p>
<h3 id="▪️-22-왜-삭제가-실패했을까">▪️ 2.2 왜 삭제가 실패했을까?</h3>
<p><code>삭제 함수(deleteToDo)</code>를 호출할 때 리스트에 있는 ToDo의 <code>id</code>를 넘겨주는데, 방금 추가된 데이터는 ID가 빈 값이었다 Firebase는 <strong>&quot;어떤 문서를 삭제할지 경로(ID)를 알려줘야 하는데 왜 빈 값을 주느냐&quot;</strong> 하고 기존의 내용은 삭제 불가, 새로 추가한 항목만 삭제를 할 수 있게 되어버렸었다..
새로고침하면 기존 삭제했던 항목도 다시 뜸..</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/a253918d-74b2-496e-8f7b-ccbcd112bd88/image.png" alt=""></p>
<blockquote>
<p>에러 로그 확인: 
삭제 요청 아이디: 뒤에 아무것도 없거나 빈 값이 전달되어 Invalid argument 에러가 발생하는 것</p>
</blockquote>
<hr>
<h2 id="3️⃣-해결-방법-solution">3️⃣. 해결 방법 (Solution)</h2>
<p>데이터가 생성되는 시점에 Firebase가 사용할 ID를 미리 생성하여 UI 상태와 DB를 일치시키는 방식으로 해결했다.</p>
<h3 id="▪️-해결-순서-1">▪️ 해결 순서 1.</h3>
<p>ViewModel에서 ID 미리 생성하기1!
추가할 데이터를 만들기 전, Firestore에서 제공하는 <code>.doc().id</code>를 사용하여 고유 ID를 미리 발급받는다</p>
<p><img src="https://velog.velcdn.com/images/memo_00/post/ec1b715d-1f32-4144-a4c9-be3716b5205c/image.png" alt=""></p>
<h3 id="코드">코드</h3>
<pre><code class="language-python">// HomeViewModel.dart 수정
final docId = FirebaseFirestore.instance.collection(&#39;todos&#39;).doc().id; // ID 선제적 생성

final ToDoEntity newtodo = ToDoEntity(
  id: docId, // 위에서 만든 ID를 주입
  title: title,
  description: description,
  isFavorite: isFavorite,
  isDone: false,
);</code></pre>
<h3 id="▪️-해결-순서-2">▪️ 해결 순서 2.</h3>
<p>Repository 저장 로직 동기화
Repository의 insert 함수에서 전달받은 엔티티의 ID를 그대로 Firebase의 문서 ID로 사용하도록 아이디값 바꿔치기
<img src="https://velog.velcdn.com/images/memo_00/post/9bed11f1-86a4-4ed5-905d-3d20db3ce2c5/image.png" alt=""></p>
<h3 id="코드-1">코드</h3>
<pre><code class="language-python">// TodoRepository.dart 수정
Future&lt;void&gt; insert({required ToDoEntity todo}) async {
  final collectionRef = firestore.collection(&#39;todos&#39;);
  // todo.id(이미 발급된 ID)를 문서 경로로 지정하여 저장
  await collectionRef.doc(todo.id).set(todo.toJson());
}</code></pre>
<h3 id="▪️-해결-순서-3-결과-확인">▪️ 해결 순서 3 [결과 확인]</h3>
<p>이렇게 수정하면 <code>state = [newtodo, ...state]</code>를 통해 UI에 반영되는 데이터도 이미 정확한 ID를 가지고 있게 되었다.
새로고침 후에 삭제를 눌러도 정확한 ID 경로를 찾아가 삭제가 될 수 있었다..! 😯</p>
<hr>
<h2 id="4️⃣-학습-포인트-및-회고-retrospective">4️⃣. 학습 포인트 및 회고 (Retrospective)</h2>
<h3 id="▪️-id-동기화의-중요성">▪️ ID 동기화의 중요성</h3>
<p>App의 상태 데이터와 서버(Firebase)의 데이터는 언제나 같은 식별자(ID)를 공유해야 한다! ⚡️</p>
<h3 id="▪️-업데이트optimistic-update의-주의점">▪️ 업데이트(Optimistic Update)의 주의점</h3>
<p>UI에 먼저 반영할 때는 해당 데이터가 서버에 저장될 때와 완전히 동일한 형태를 갖추고 있어야 오류를 방지할 수 있다 ⚡️</p>
<h3 id="▪️-해결">▪️ 해결</h3>
<p>서버에서 ID가 생성되기를 기다렸다가 UI를 갱신하는 방법도 있지만, 이번처럼 <strong>앱에서 ID를 미리 생성</strong> 하여 주입하는 방식으로 새로운 방법을 알았다..! ⚡️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ToDoApp_Firebase 연동 과제_Flutter 숙련주차 8일]]></title>
            <link>https://velog.io/@memo_00/ToDoAppFirebase-%EC%97%B0%EB%8F%99-%EA%B3%BC%EC%A0%9CFlutter-%EC%88%99%EB%A0%A8%EC%A3%BC%EC%B0%A8-8%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@memo_00/ToDoAppFirebase-%EC%97%B0%EB%8F%99-%EA%B3%BC%EC%A0%9CFlutter-%EC%88%99%EB%A0%A8%EC%A3%BC%EC%B0%A8-8%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 14 Jan 2026 13:51:16 GMT</pubDate>
            <description><![CDATA[<p>2026.01.14 (수)
과제_ToDo App 완료
금일 구현 작업 TIL</p>
<hr>
<h3 id="오늘의-공부-이미지-💫">오늘의 공부 이미지 💫</h3>
<p><img src="https://velog.velcdn.com/images/memo_00/post/62dadca0-2deb-48cb-a4ee-dfb5a87bb208/image.png" alt=""></p>
<p>내가 설명을 제대로 안 해서 그런가.. 요즘 좀 대충 그려주는 듯 Gemini.....</p>
<hr>
<h2 id="🗓-할-일-완료-⭐️-즐겨찾기-구현-완료">🗓 할 일 완료, ⭐️ 즐겨찾기 구현 완료</h2>
<p>ToDo App의 할 일 완료, 즐겨찾기가 실제 데이터 베이스에 반영되는 것을 마지막으로 과제가 마무리가 되었다.!!
오늘 작업한 내용을 TIL로 작성해보려고 한다 🌞</p>
<h3 id="🎢-ui---viewmodel---repository-흐름-분석하기">🎢 UI - ViewModel - Repository 흐름 분석하기</h3>
<h4 id="📎-전체적인-흐름과-분석">📎 전체적인 흐름과 분석</h4>
<p><strong>사용자가 클릭 시에 실제 데이터 베이스에 반영될 때의 경로는?</strong>
View (UI) -&gt; ViewModel (State) -&gt; Repository -&gt; Firestore</p>
<p><strong>개발자 입장에서의 코드를 짤 때 경로는?</strong>
Repository 구현 -&gt; ViewModel 구현 -&gt; View (UI) 작업</p>
<h4 id="📎-기능별-상세-진행-순서">📎 기능별 상세 진행 순서</h4>
<p>나는 코드를 짰으니, 내가 짠 코드대로 정리해본당</p>
<p><strong>1. Repository</strong>
[할 일 완료 기능]</p>
<pre><code class="language-python">Future&lt;void&gt; updateToDo({required ToDoEntity todo}) async {
    FirebaseFirestore firestore = FirebaseFirestore.instance;
    final collectionRef = firestore.collection(&#39;todos&#39;);
    final docRef = collectionRef.doc(todo.id);
    await docRef.update(todo.toJson());
  }</code></pre>
<p>[즐겨찾기 기능]</p>
<pre><code class="language-python">Future&lt;void&gt; toggleFavorite({required ToDoEntity todo}) async {
    FirebaseFirestore firestore = FirebaseFirestore.instance;
    final collectionRef = firestore.collection(&#39;todos&#39;);
    final docRef = collectionRef.doc(todo.id);
    await docRef.update({&#39;isFavorite&#39;: todo.isFavorite});
  }</code></pre>
<ul>
<li>할 일 완료 이벤트 발생 시에 업데이트 되게 할 Insert 구현해주기</li>
</ul>
<p><strong>2. ViewModel</strong>
[할 일 완료 기능]</p>
<pre><code class="language-python">Future&lt;void&gt; toggleDone({required bool isDone, required String id}) async {
    final newTodo = state.firstWhere((todo) =&gt; todo.id == id);
    final updateTodo = newTodo.copyWith(isDone: isDone);
    await todoRepo.updateToDo(todo: updateTodo);
    state = state.map((todo) =&gt; todo.id == id ? updateTodo : todo).toList();
  }</code></pre>
<p>[즐겨찾기 기능]</p>
<pre><code class="language-python">Future&lt;void&gt; toggleFavorite({required bool isFavorite, required String id}) async {
    final newTodo = state.firstWhere((todo) =&gt; todo.id == id);
    final leFavoriteTodo = newTodo.copyWith(isFavorite: isFavorite);
    await todoRepo.toggleFavorite(todo: leFavoriteTodo);
    state = state.map((todo) =&gt; todo.id == id? leFavoriteTodo : todo).toList();
  }</code></pre>
<ul>
<li><code>state.firstWhere</code>을 사용해서 해당 ID를 가진 객체를 찾을 수 있게 한다.</li>
<li>copyWith<code>를 사용해서 받아온 ID만</code>newTodo`로 바꿔서 기존 객체는 그대로, 상태를 복사한다.</li>
<li>Repository의 <code>updateTodo</code>를 비동기로 호출</li>
</ul>
<p><strong>3. UI (ListView)</strong>
[할 일 완료 기능]</p>
<pre><code class="language-python">class TodoListView extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
  final vm = ref.read(homeViewModelProvider.notifier);
    final todos = ref.watch(homeViewModelProvider);

 (... 생략 ...)

// [할 일 완료]
IconButton( 
 onPressed: () {
  vm.toggleDone(isDone: !item.isDone, id: item.id,);
  },
   icon: Icon(
    item.isDone
    ? Icons.check_circle
    : Icons.radio_button_unchecked,
     color: item.isDone 
     ? Colors.green 
     : Colors.grey,
 ),
),

// [즐겨찾기]
IconButton(
 onPressed: () {
  vm.toggleFavorite(isFavorite: !item.isFavorite, id: item.id);
  },
   icon: Icon(
    item.isFavorite 
    ? Icons.star 
    : Icons.star_border,
     color: item.isFavorite 
     ? Colors.amber 
     : Colors.grey,
    ),
   ),</code></pre>
<ul>
<li><code>ConsumerWidget</code> 확인</li>
<li><code>vm</code> 함수 만들기 </li>
<li><code>IconButton</code>의 <code>onPressed</code> 발생 시에 현재 <code>item.isDone</code> / <code>item.isFavorite</code>상태를 <code>!item.isDone</code> / <code>!item.isFavorite</code>으로 반전시켜서 <code>vm.toggleDone</code> 함수로 전달하기</li>
</ul>
<hr>
<h3 id="🏗-작업-내용">🏗 작업 내용</h3>
<h4 id="1단계-data-model-entity-설계">1단계: Data Model (Entity) 설계</h4>
<p>가장 먼저 할 일은 내가 다룰 데이터는 어떻게 생겼는가 정의하기
TodoEntity 클래스를 생성한다.</p>
<ul>
<li>데이터 구조가 결정되어야 Repository에서 저장할 형식을 알 수 있고, ViewModel에서 어떤 값을 관리할 지 결정할 수 있다.</li>
<li>핵심은 <code>id</code> ,<code>title</code>, <code>isDone</code>, <code>isFavorite</code> 등의 필드를 정의하고, Firestore와 통신ㄴ하기 위한 <code>fromJson</code>    / <code>toJson</code> 메서드를 만든다 ⭐️</li>
</ul>
<h4 id="2단계-repositoty-구현">2단계: Repositoty 구현</h4>
<p>데이터 (FireStore)와 통신하는 통로를 만든다</p>
<ul>
<li><code>TodoRepository</code>를 만들고 각 <code>insert</code> <code>update</code> 등등 구현할 함수를 만든다</li>
<li>실제 데이터가 잘 들어가고 나가는지 확인 필요, UI가 없어도 <code>print</code>해보기!</li>
</ul>
<h4 id="3단계-viewmodel-구현">3단계: ViewModel 구현</h4>
<p>데이터를 UI가 쓰기 좋은 형태로 가공하고 상태를 관리하기</p>
<ul>
<li>Riverpod의 <code>Notifier</code> 등을 사용ㅎ해서 <code>HomeViewModel</code>을 작성한다</li>
<li>Repository에서 가져온 리스트 형태나 필터링하는 로직을 미리 준비한다</li>
</ul>
<h4 id="4단계-view-결합-ui">4단계: View 결합 (UI)</h4>
<p>마지막으로 사용자에게 보여줄 화면을 그리고, ViewModel에 연결하기</p>
<ul>
<li><code>TodoLisiView</code>, <code>IconButton</code> 등을 만들고, <code>ref.watch</code>를 통해 ViewModel의 상태를 구독한다</li>
</ul>
<h2 id="📲-구현-완료">📲 구현 완료~!</h2>
<hr>
<h2 id="-🌞-추가-모닝스터디-정리">+ 🌞 추가 모닝스터디 정리</h2>
<p>과제 하면서 추가적으로 모닝스터디때 도움이 되었던 내용을 정리해보려고오오 한닷</p>
<h3 id="map-이란"><code>.map()</code> 이란?</h3>
<p><code>.map()</code>은 리스트 안에 있는 모든 데이터를 하나하나 꺼내서, 내가 정한 규칙에 따라 새로운 형태로 바꾸어 주는 역할을 한다!</p>
<p>⭐️ 중요한 점은!
원본 리스트는 건드리지 않고! 변형된 데이터들로 이루어진 새로운 묶음으로 만들어준다</p>
<p>예를 들어서 Repository에서 객체로 바꿔줘야 할 때, <code>.map()</code>을 사용해서 코드를 간단하게 만들 수 있다!</p>
<h4 id="리스트-안에-있는-내용을-하나씩-돌면서-확인-후-원하는-데이터만-바꿔야-한다면">리스트 안에 있는 내용을 하나씩 돌면서 확인 후 원하는 데이터만 바꿔야 한다면?</h4>
<p>원래라면 <code>for</code>문을 사용했을거다..!
[<code>for</code>문]</p>
<pre><code class="language-python">List&lt;Matzip&gt; matzipList = [];
    for (var i = 0; i &lt; box.length; i++) { 
      Map&lt;String, dynamic&gt; e = box[i];
      Matzip m = convertMatzip(e);
      matzipList.add(m);
    }</code></pre>
<p>하지만 간결하게 만들고 싶다면?
[<code>.map()</code> 메서드]</p>
<pre><code class="language-python">List&lt;Matzip&gt; matzipList2 = box.map((e) {
      return Matzip.fromJson(e);
    },).toList ();</code></pre>
<p>=&gt; 요렇게 바꿔줄 수 있다.
⛔️ 주의 사항은?
<code>.map()</code> 의 결과값은 리스트가 아니라, <code>Iterable</code> 이라는 형태이다. 그래서 꼭!! <strong><code>.toList()</code></strong>를 붙여서 다시 리스트로 만들어줘야한다!!</p>
]]></description>
        </item>
    </channel>
</rss>