<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hee_mm_.log</title>
        <link>https://velog.io/</link>
        <description>드문드문 기초 정보를 올리는 블로그</description>
        <lastBuildDate>Mon, 15 Jan 2024 07:17:04 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hee_mm_.log</title>
            <url>https://velog.velcdn.com/images/hee_mm_/profile/441ab84c-a023-48da-a589-ae96aa0ae1b2/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hee_mm_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hee_mm_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[소감기 프로젝트, 출시와 업데이트까지]]></title>
            <link>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B6%9C%EC%8B%9C%EC%99%80-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B6%9C%EC%8B%9C%EC%99%80-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Mon, 15 Jan 2024 07:17:04 GMT</pubDate>
            <description><![CDATA[<p>소감기 프로젝트 진행은 계속하였지만 진행하는 시기가 PlayStore에 따라 중간중간 대기하느라 작업하지 않는 시기도 있어 주간으로 작성하기가 애매하여서 이후에는 적지 않고 따로 작성하고 있었다.</p>
<br/>

<p>현재 신규 기능 및 개선을 포함한 업데이트까지 진행을 해보았기에 릴리즈부터 이후 업데이트까지의 진행되었던 것들을 모아 글로 간단하게 회고하려 한다.</p>
<p><br/><br/>
<br/><br/></p>
<h1 id="앱-출시까지">앱 출시까지</h1>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/78c0cb7b-4a7e-4491-925c-01949a49750f/image.png" alt=""></p>
<p><a href="https://play.google.com/store/apps/details?id=com.mirimhee.cowCold.cow_cold&amp;pcampaignid=web_share">🕹️ PlayStore</a> </p>
<p><br/><br/></p>
<h2 id="20명의-테스터-모으기-문제">20명의 테스터 모으기 문제</h2>
<p>테스터 20명을 채우는 문제의 경우 다행히도 별도의 오픈 카톡 방이 존재하였고, 지인에게 초대받아 들어가 도움을 받아 채울 수 있었다. 외에도 이전 글에 올렸다시피 트위터 상으로 도움을 구하니 다른 개발자분들이 도움을 주시기도 하였다. 이 글은 보시지 못하겠지만.. 정말 다시 한번 도와주신 모든 분에게 감사할 따름이다…
<br/>
덕분에 개발자라는 직업의 특징 중 하나라고 생각하는 정보 공유와 서로 간 도움을 주는 것을 서슴지 않아 하는 문화에 다시 한번 개발자라는 직업을 좋아하게 되는 계기가 되기도 하였다.</p>
<p><br/><br/></p>
<h2 id="원활한-출시">원활한 출시</h2>
<p>출시의 경우 다행히도 리젝한 번 없이 바로 출시될 수 있어서 놀랐다, 한두 번쯤은 누락을 당할 것이라고 생각하였는데…. 덕분에 검토되는 와중에 진행하였던 각종 개선도 포함한 후 다시 버전을 올려 업로드하였고. 1월 4일 릴리즈 할 수 있었다. 
<br/>
한두시간 안으로 바로 업로드되었고, 검색하니 나오는 모습에 다시 한번 뿌듯하였다. 이런 성취감이 계속 개발을 하게 하는 원동력이 되는 것 같다.</p>
<p><br/><br/></p>
<h1 id="첫-업데이트-릴리즈">첫 업데이트 릴리즈</h1>
<p>출시 이후에는 이전에 생각해 두었고, 검토 중에 따로 진행 중이었던 개선과 기능에 대한 추가를 업데이트하였다. 사실 그 간격이 꽤 길어 이미 출시 전에 작업 자체는 다 끝나있던 상황이었다. </p>
<br/>

<p>1월 11일 날 스토어에 업로드되었고. 그 사이 진행한 개선들은 아래와 같다.</p>
<p><br/><br/></p>
<h2 id="진행한-개선">진행한 개선</h2>
<p><br/><br/></p>
<h3 id="1-감상-리액션-기능-추가">1. 감상 리액션 기능 추가</h3>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/660bd07d-d356-47ef-b914-4b8ae260f1d9/image.png" alt=""></p>
<p>각 리액션을 추가한 타인의 감상문, 리액션 추가 버튼을 누른 후 보이는 키보드 ON 상태 이미지이다.
<br/>
다른 유저 감상에 이모지로 리액션을 할 수 있는 기능을 추가하였다. 이때, 자신의 감상에 추가하는 것은 불가하고 타인이 달아둔 감상에 자신 또한 감상을 추가하는 것은 가능하다. 
<br/>
해당 기능의 경우는 간단한 테스트 코드까지 진행을 하였는데, 이제 어느 정도 테스트 코드 작성에도 습관을 기르거나 익숙해지기라도 하자 싶어 점진적으로 다른 기능들도 테스트 코드를 진행하고자 한다.</p>
<p><br/><br/></p>
<h3 id="2-시스템-다크-모드-시-앱-내-테마-설정-미-동작">2. 시스템 다크 모드 시 앱 내 테마 설정 미 동작</h3>
<p>테스트를 진행해 주신 분 중 피드백을 전달해 주신 감사한 분(!) 이 시스템 상에서 이미 다크 모드가 켜져 있을 시 앱 내부의 앱 내 테마 설정이 동작하지 않는다고 전달을 해주셨다.
<br/>
찾아보니 GetX의 고유 이슈임을 확인하였고, 개선 자체는 큰 어려움 없이 해결할 수 있었다.</p>
<p><br/><br/></p>
<h3 id="3감상-바텀-시트-full-width-적용">3.감상 바텀 시트 full width 적용</h3>
<p>개인적으로도 테스트를 진행하니 감상을 자세히 여는 바텀 시트가 한 줄을 넘기지 못할 경우 위젯이 글자 길이만큼의 width를 가지는 문제를 친구가 테스트를 해주며 발견해 주어 추가적으로 개선을 진행하였다. 위젯상으로 설정을 추가만 해주면 되는 문제였으므로 이 또한 큰 어려움은 없었다.
<br/>
아무래도 혼자 테스트를 하다 보니 진행과정이 고착화되어 다양하게 테스트를 못하게 되었는데 이런 부분에서 테스터의 존재가 중요하다는 걸 다시 한번 깨달을 수 있었다.</p>
<p><br/><br/></p>
<h3 id="4-freezed-패키지-적용">4. freezed 패키지 적용</h3>
<p>새로운 모델을 추가할 때마다 작성하는 Class 작성의 귀찮음…(…) 과 데이터 안정성을 조금 더 확보하고자 적용한 패키지로 Class를 Immutable 객체로 생성하기에 확실히 데이터 업데이트 시에는 꽤 불편한 점이 없지 않아 있지만, 그만큼 아무렇게나 수정하지 않을 수 있어 데이터를 보장할 수 있다는 점에서는 좋은 패키지이다.
<br/>
초반 적응과 존재 Function들을 이용하면서는 조금 버벅거리다가도 익숙해지니 오히려 데이터 Class 생성 속도도 빠르고 아주 편하다!</p>
<blockquote>
<p>설명 게시글 
 <a href="https://velog.io/@hee_mm_/flutter-Freezed-%ED%8C%A8%ED%82%A4%EC%A7%80">https://velog.io/@hee_mm_/flutter-Freezed-패키지</a></p>
</blockquote>
<p> <br/><br/><br/><br/></p>
<h1 id="이후-방향성">이후 방향성</h1>
<p>일단 다른 API 호출에 관련한 테스트 코드 작성을 먼저 진행한 후, 생각해 보아야겠다. 현재로서는 이거다 싶은 기능 추가는 없기에 더 자세히 테스트하며 개선사항과 오류를 검수해 나가야 할 것 같다.
<br/></p>
<p>UI도 무언가 아쉬워 개선하면 좋을 것 같다는 생각이 들지만, 디자이너는 아니어서 그런지 영 좋은 방향이 생각나지 않는다. 눈은 높지만 실제 만들지는 못하니… 이러다가 디자인도 공부하게 되는 게 아닐까 싶기도 하지만 그전에 백엔드 쪽을 더 익숙해져야 할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[flutter] get_it ]]></title>
            <link>https://velog.io/@hee_mm_/flutter-getit-%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@hee_mm_/flutter-getit-%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 04 Jan 2024 00:54:24 GMT</pubDate>
            <description><![CDATA[<h1 id="get_it">get_it</h1>
<p>의존성 주입(DI)를 위한 라이브러리로, 객체 간의 의존성을 간단하게 해결할 수 있습니다. </p>
<p>의존성 주입 특유의 장점을 통해 코드 재사용과 테스트를 원활하게 진행할 수 있습니다.</p>
<br/>
<br/>


<h3 id="제공-의존성-유형">제공 의존성 유형</h3>
<p>Singleton, Factory, Lazy Singleton  3가지의 유형을 지원합니다.</p>
<ul>
<li>Singleton : 하나의 객체만 생성, 객체를 최초 한 번만 생성하고 이후에 호출마다 동일한 객체를 사용합니다.</li>
<li>Factory: 객체를 생성하는 팩토리 함수를 제공합니다, 의존성 주입할 때마다 새로운 객체를 생성합니다.</li>
<li>Lazy Singleton : Singleton과 같지만, 객체를 필요할 때까지 생성하지 않고 첫 객체 호출 시에 생성합니다.<ul>
<li>Singleton의 경우 의존성 등록 시 생성합니다.</li>
</ul>
</li>
</ul>
<p><br/><br/><br/></p>
<h2 id="📍의존성-주입di이-뭐죠">📍의존성 주입(DI)이 뭐죠?</h2>
<p>디자인 패턴 중 하나로, 클래스나 모듈 간의 의존성을 <strong>외부에서 주입</strong>하는 것을 의미합니다. </p>
<p>이때 의존성이란, 하나의 객체가 다른 객체의 의존하여 동작하는 것을 이야기합니다. </p>
<br/>

<ul>
<li>*<em>예시 코드 *</em></li>
</ul>
<p>아래 코드에서 예를 들면 Person 객체는 Mouth 객체에 의존합니다, 이때 생성자를 통해 의존성을 주입해 주고 있습니다.</p>
<pre><code class="language-dart"> class Mouth {
      void eat() {
        print(&#39;yummy&#39;);
      }
    }

    // Person 클래스는 Mouth에 의존하며 생성자 주입.
    class Person {
      final Mouth mouth;

      // 생성자 주입을 통해 Mouth 객체를 받음
      Person(this.mouth);

      void hungry() {
        mouth.eat();
        print(&#39;Person is hungry&#39;);
      }
    }</code></pre>
<p><br/><br/></p>
<p>더 쉽게 이야기하면 배가 고파 밥을 먹으려 한다면, 밥이 있어야겠죠? 하지만 밥을 만드려면 시간이 오래 걸리고 요리를 못한다면 아무것도 먹지 못할 수 있습니다.</p>
<br/>

<p>이때 밥을 직접 만들지 않고 레트로트 식품을 먹거나 배달을 시키면 요리를 못하더라도 확정적으로 맛있는 밥을 먹을 수 있게 됩니다.</p>
<br/>

<p>의존성 주입은 이와 비슷합니다. 의존성 주입을 사용하면 객체가 스스로 의존성을 생성하지 않고, 이미 만들어진 의존성을 사용하기 때문에 서로 덜 의존하게 됩니다. 한 객체의 변경이 다른 객체에 미치는 영향이 줄어 코드가 더 유연하고 분리되어 테스트하기에도 쉬워집니다.</p>
<p><br/><br/><br/><br/><br/><br/></p>
<h1 id="get_it-패키지-사용법">get_it 패키지 사용법</h1>
<p><br/><br/></p>
<h2 id="패키지-가져오기">패키지 가져오기</h2>
<hr>
<pre><code class="language-yaml">dependencies:
  flutter:
    sdk: flutter
  get_it: ^x.x.x  # x.x.x는 패키지 버전입니다.</code></pre>
<p><code>pubspec.yaml</code> 파일에 <code>get_it</code> 패키지를 추가한 후 <code>flutter pub get</code> 명령를 통해 패키지를 가져옵니다.</p>
<p><br/><br/></p>
<h2 id="객체-생성-및-등록하기">객체 생성 및 등록하기</h2>
<hr>
<p>공유 대상의 객체를 준비합니다, 저는 3가지 유형의 의존성 주입 제공 방안들을 사용하기 위해 아래와 같은 Model들을 사용할 예정입니다.</p>
<p><br/><br/></p>
<h3 id="singleton-유형에-사용할-모델">Singleton 유형에 사용할 모델.</h3>
<pre><code class="language-dart">class Mouth {
  Mouth(){
    print(&#39;Mouth가 생성되었습니다.&#39;);
  }

  void eat() {
    print(&#39;yummy&#39;);
  }
}</code></pre>
<br/>


<h3 id="lazy-singleton-유형에-사용할-모델">Lazy Singleton 유형에 사용할 모델.</h3>
<pre><code class="language-dart">class Person {
  late Mouth mouth;

  Person(){
        // 해당 Mouth는 Person 이전에 GetIt에 등록이 되어있어야만 합니다.
        // 이러한 형태로 생성자내에서 객체를 가져와 적용하는 것 또한 가능합니다.
    mouth = GetIt.I&lt;Mouth&gt;();
    print(&#39;Person이 생성되었습니다.&#39;);
  }

  void hungry() {
    print(&#39;Person is hungry&#39;);
    mouth.eat();
  }
}</code></pre>
<br/>

<h3 id="factory-유형에-사용할-모델">Factory 유형에 사용할 모델.</h3>
<pre><code class="language-dart">class Group{
  String name;
  int id;

  Group({required this.name,required this.id}){
    print(&#39;새 그룹 생성&#39;);
  }
}</code></pre>
<p><br/><br/></p>
<h3 id="객체-등록">객체 등록</h3>
<pre><code class="language-dart">class GetItLocator{
  static GetItLocator locator = GetItLocator();

  // GetIt 싱긆톤 형식.
  void setupSingleton() {
    if(!GetIt.instance.isRegistered&lt;Mouth&gt;()){
      GetIt.instance.registerSingleton(Mouth());
    }
  }

  // GetIt 팩토리 형식.
  void setUpFactory(){
    if(!GetIt.instance.isRegistered&lt;Group&gt;()){
            // registerFactory의 경우 FactoryFunc 형태로 되어 있어 () =&gt; 반환객체를 매개변수로 넘겨야합니다.
      GetIt.instance.registerFactory&lt;User&gt;(()=&gt; Group(name: &#39;get_it&#39;, id: 99));
    }
  }

  // GetIt lazy 싱글톤 형식.
  void setUpLazySingleton(){
    if(!GetIt.instance.isRegistered&lt;Person&gt;()){
      GetIt.instance.registerLazySingleton(() =&gt; Person());
    }
  }
}</code></pre>
<p>본 예시의 경우 runApp 이후 GetIt을 통해 적용하므로 중복 등록의 문제 <code>GetIt.instance.isRegistered</code> 을 통해 등록이 되어있는지를 확인 후 등록하도록 적용된 코드입니다.</p>
<br/>

<p>➕ 이때 <code>GetIt.instance</code>의 경우 코드를 줄인 <code>GetIt.I</code> 로도 호출이 가능합니다.</p>
<p><br/><br/><br/><br/></p>
<h2 id="singleton-유형">Singleton 유형</h2>
<hr>
<pre><code class="language-dart">GetItLocator.locator.setupSingleton();

// 첫 객체 생성
Mouth mouth1 = GetIt.I&lt;Mouth&gt;();

// 두 번째 객체 생성
Mouth mouth2 = GetIt.I&lt;Mouth&gt;();

// mouth1과 mouth2는 같은 객체인지 확인
print(mouth1 == mouth2); // true</code></pre>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/af40b59a-9781-4d27-b16b-ebce372abd8f/image.png" alt=""></p>
<p>싱글톤으로 적용한 객체를 2번 가져와 비교하면 처음 요청될 때 객체를 생성하고, 그 이후에는 항상 같은 객체를 반환합니다. 
<br/>
코드 진행 후 터미널을 살펴본다면 생성자 문구가 한 번만 진행되었음을 통해 위 사실을 확인할 수 있습니다.</p>
<p><br/><br/></p>
<h2 id="factory-유형">Factory 유형</h2>
<hr>
<pre><code class="language-dart">GetItLocator.locator.setUpFactory();

// 첫 객체 생성
Group group1 = GetIt.I&lt;Group&gt;();
// 생성자를 통해 객체 생성 print가 진행됩니다.

// 두 번째 객체 생성
Group group2 = GetIt.I&lt;Group&gt;();
// 생성자를 통해 객체 생성 print가 진행됩니다.

// group1과 group2는 같은 객체인지 확인
print(group1 == group2); // false</code></pre>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/fd5fd1c7-7210-4634-8a20-38b7ba47b208/image.png" alt=""></p>
<p>매번 새로운 객체를 생성하여 반환하는 팩토리 형식을 Group 객체를 적용합니다.
<br/>
코드 진행 후 터미널을 살펴본다면 생성자 문구가 두 번 찍힘과 동시에, 두 객체를 비교하는 Print를 통해 같은 객체가 아님을 확인할 수 있습니다.</p>
<p><br/><br/></p>
<h2 id="lazysingleton-유형">LazySingleton 유형</h2>
<hr>
<pre><code class="language-dart">GetItLocator.locator.setUpFactory();

// 첫 객체 생성
Person person1 = GetIt.I&lt;Person&gt;();
// 첫 생성시 생성자의 Print 문이 출력됩니다.

// 두 번째 객체 생성
Person person2 = GetIt.I&lt;Person&gt;();

// person1과 person2는 같은 객체인지 확인
print(person1 == person2); // false</code></pre>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/fd404822-d6eb-4452-a888-4f83a33db745/image.png" alt=""></p>
<p>코드 진행 후 터미널을 살펴본다면 생성자 문구가 첫 생성 시 한 번만 찍힘과 동시에, 두 객체를 비교하는 Print를 통해 같은 객체가 같음을 확인할 수 있습니다.</p>
<br/>

<p>이때 객체 생성 코드들을 주석 처리 하고  <code>GetItLocator.locator.setUpFactory();</code> 코드로 등록만 진행한다면 생성자 print가 출력되지 않음으로 필요시에만 객체를 생성함을 확인할 수 있습니다.</p>
<br/>

<p><br/><br/><br/></p>
<blockquote>
<p>Get_It에 관한 별도의 코드를 <a href="https://github.com/LIMMIHEE/velog-exercise/blob/main/lib/presentation/feat/get_it/get_it_screen.dart">Github 레파지토리</a>에서 확인 가능합니다!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[flutter] Freezed 패키지]]></title>
            <link>https://velog.io/@hee_mm_/flutter-Freezed-%ED%8C%A8%ED%82%A4%EC%A7%80</link>
            <guid>https://velog.io/@hee_mm_/flutter-Freezed-%ED%8C%A8%ED%82%A4%EC%A7%80</guid>
            <pubDate>Thu, 21 Dec 2023 06:46:45 GMT</pubDate>
            <description><![CDATA[<p>소감기 프로젝트를 진행하며 새로운 기능을 추가하기 위해 Model을 수정하며 느꼈던 생각이 있습니다.</p>
<br/>
<br/>

<p><strong>아… 길다…</strong></p>
<br/>
<br/>

<p>그렇게 모델을 수정하며 문뜩 머릿속을 지나친 패키지가 바로 이번 글의 주제인 Freezed 패키지입니다. </p>
<br/>

<p>존재 자체는 알고 있었지만 사용해 본 경험은 적어 아는 것이 거의 없는 패키지로, 이번 기회에 적용하며 다시 관련 정보 및 경험을 쌓아두기 위해 해당 글과 함께 어떤 패키지인지 어떤 이점이 있는지 등을 알아보도록 하겠습니다.</p>
<br/>

<p>문서도 한글화가 되어있으니 더욱 자세한 내용을 참고하시자 한다면 <a href="https://github.com/rrousselGit/freezed/blob/master/resources/translations/ko_KR/README.md">링크</a>를 통해 확인 가능합니다.</p>
<br/>
<br/>

<hr>
<br/>
<br/>

<h1 id="그래서-freezed-패키지가-뭐죠">그래서 <strong>Freezed 패키지가 뭐죠?</strong></h1>
<p><strong>Immutable 객체</strong>를 자동 생성 해주는 패키지입니다.</p>
<br/>

<p>해당 패키지를 사용하면 간갈하게 속성과 같은 설정만 선언한 Model도 <code>@freezed</code> 어노테이션 하나만으로도 모델의 Immutable 객체를 만들 수 있기 때문에 코드의 길이를 줄임과 동시에 가독성을 챙기고, Immutable 객체 특성인 오류 발생률을 줄일 수 있습니다.</p>
<br/>

<p>또한 아래의 메소드 정의들도 자동으로 적용되기에 손쉽게 Model을 만들어낼 수 있습니다.</p>
<ul>
<li>생성자 + 속성 정의</li>
<li><code>toString</code>, <code>operator ==</code>, <code>hashCode</code> 오버라이드</li>
<li>객체 복제를 위한 <code>copyWith</code> 메서드 구현</li>
<li><span style='color:gray;'>(옵션) 역/직렬화 메소드 ( ex. fromJson )</span></li>
</ul>
<br/>

<p>공식 문서에 따른 예시 이미지로 살펴보면 아래와 같이 모델 코드가 간결하게 정리되지만, 사용자 정의 모델 파일 안에는 어떤 메서드들이 존재하는지 알기 어려우므로 미리 인지해두어야 하는 한다는 점이 존재하긴 합니다.</p>
<br/>
<br/>

<table>
<thead>
<tr>
<th>기존</th>
<th>적용</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/hee_mm_/post/ef97efa4-1822-4721-82b7-aebecdbe3080/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/hee_mm_/post/ba6e07c8-102b-4284-9c26-99de801561be/image.png" alt=""></td>
</tr>
</tbody></table>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h3 id="➕-immutable-객체는-또-뭐야">➕ Immutable 객체는 또 뭐야?</h3>
<p>한 번 생성되면 변경할 수 없는 객체로, final로 구성된 객체입니다.</p>
<p>해당 값은 final로 구성된 만큼, 첫 생성 후 값을 변경할 수 없으며 생성자에서 값을 받아야만 합니다.</p>
<br/>

<p><strong>예시 코드</strong></p>
<pre><code class="language-dart">// 클래스의 모든 속성을 `final` 키워드로 선언합니다.
final String name;
final int id;

// 클래스의 생성자에서 모든 속성을 초기화합니다.
User({
  required this.name,
  required this.id,
});

// 속성의 값을 변경할 수 없습니다.
void changeName(String newName) {
  name = newName; // 에러 발생
}</code></pre>
<p>첫 선언 이후 값이 변하지 않으므로, 데이터에 대한 신뢰성이 높아진다는 점이 주요 이점입니다. 다만 이에 대한 반사작용으로 단점이 있다면 값 수정이 그만큼 귀찮아진다는 점이 있습니다.</p>
<br/>
<br/>
<br/>

<h1 id="freezed-패키지-사용법"><strong>Freezed 패키지 사용법</strong></h1>
<br/>
<br/>
<br/>

<h2 id="패키지-가져오기">패키지 가져오기</h2>
<hr>
<pre><code class="language-yaml">dependencies:
  flutter:
    sdk: flutter
  freezed_annotation: ^x.x.x  # x.x.x는 패키지 버전입니다.

# fromJson/toJson 생성도 사용시 아래 패키지도 추가
# json_annotation: ^x.x.x 

dev_dependencies:
    build_runner: ^x.x.x
  freezed: ^x.x.x

# fromJson/toJson 생성도 사용시 아래 패키지도 추가
# json_serializable: ^x.x.x</code></pre>
<p>적용할 패키지가 꽤 많은 편입니다만 우선 <code>pubspec.yaml</code> 파일의 dependencies 필드에  <code>freezed_annotation</code> 패키지를 추가한 후 dev_dependencies 필드에 각 <code>build_runner</code> ,<code>freezed</code> 패키지를 추가합니다.</p>
<br/>

<p> 직렬화 ( fromJson/toJson ) 기능에 대한 적용도 필요하다면 필요에 따라 각 필드에 <code>json_annotation</code>  및 <code>json_serializable</code> 패키지를 추가로 적용해 줍니다.</p>
 <br/>

<p><code>flutter pub get</code> 명령를 통해 패키지를 가져옵니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="모델-선언하기"><strong>모델 선언하기</strong></h2>
<hr>
<p>기본 구조는 아래와 같습니다</p>
<br/>

<pre><code class="language-dart">// `파일명.dart`를 Freezed 자동생성 코드와 연결하는 코드
// Build Runner를 통해 모델 생성 전에는 에러가 나타납니다.
part &#39;파일명.freezed.dart&#39;;

// 직렬화 사용시 아래 파일 호출 필요
// part &#39;파일명.g.dart&#39;;

@freezed
class 모델명 with _$모델명 {
  const factory 모델명({
    required String 필드,
  }) = _모델명;

    // 직렬화 사용시 아래 코드 선언 필요
    //factory 모델명.fromJson(Map&lt;String, Object?&gt; json)
    //  =&gt; _$모델명FromJson(json);
}</code></pre>
<br/>

<p>이때 <strong>필수로 지켜야만 하는 규칙</strong>이 있는데, 그것은 아래과 같습니다.</p>
<ul>
<li>@freezed 어노테이션 추가</li>
<li>바로 자동 생성된 파일과 연결하는 <code>part &#39;파일명.freezed.dart&#39;;</code> ****호출</li>
</ul>
<br/>

<p>만약 직렬화를 사용하기 위해서는  <code>part &#39;파일명.g.dart&#39;;</code> 호출 또한 진행되어야 합니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="모델-생성하기"><strong>모델 생성하기</strong></h2>
<hr>
<p>선언한 후의 모델 자동 생성은 간단합니다, 아래와 같은 명령을 프로젝트 단 터미널에서 실행해 주면 완료됩니다.</p>
<br/>

<pre><code class="language-dart">flutter pub run build_runner build</code></pre>
<br/>

<p>해당 실행이 완료되었다면 .freezed.dart, .g.dart 파일 2개가 같은 파일 위치에 생성되는데 해당 파일들이 자동 생성기로 생성된 파일들입니다. 생성 후에 별도로 진행해 주어야 하는 사항은 없습니다. </p>
<br/>

<blockquote>
</blockquote>
<p>별도로 자동 생성된 파일 형식이 궁금하시다면 <a href="https://github.com/LIMMIHEE/velog-exercise/blob/main/lib/data/model/user/user.freezed.dart">Github 레파지토리</a>에서 확인 가능합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter에서 보안을 강화하는 방법들]]></title>
            <link>https://velog.io/@hee_mm_/Flutter%EC%97%90%EC%84%9C-%EB%B3%B4%EC%95%88%EC%9D%84-%EA%B0%95%ED%99%94%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95%EB%93%A4</link>
            <guid>https://velog.io/@hee_mm_/Flutter%EC%97%90%EC%84%9C-%EB%B3%B4%EC%95%88%EC%9D%84-%EA%B0%95%ED%99%94%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95%EB%93%A4</guid>
            <pubDate>Mon, 18 Dec 2023 06:11:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>여러 일정들을 소화하면서 앱에서의 보안에 대한 생각이 늘어났는데 생각만하기보다는 더 자세히 보안 강화방법을 알아보고자 해당 글을 작성하게 되었습니다.</p>
</blockquote>
<br/>
<br/>
<br/>

<hr>
<br/>
<br/>
<br/>

<h2 id="📍-내부-데이터-저장시-flutter_secure_storage-사용하기">📍 내부 데이터 저장시 <strong>flutter_secure_storage 사용하기</strong></h2>
<br/>

<p>앱에서는 자동 로그인 등의 일부 기능을 위해 유저 데이터를 서버에 저장하지 않고 앱 내부에 저장하여 사용하며, 이때 자주 사용되는 패키지는 shared_preferences입니다.</p>
<br/>

<p>하지만 서버 통신에 사용되는 토큰 값 혹은 유저의 ID, PW와 같은 민감정보는 shared_preferences <span style='color:gray'>(혹은 SQLite)</span> 가 아닌 flutter_secure_storage를 통해 저장하여 사용하는 것이 좋습니다.</p>
<br/>
<br/>
<br/>

<h3 id="이유">이유</h3>
<p>shared_preferences 혹은 SQLite는 내부 저장 시에 암호화를 거치지 않고 저장이 되어 유출이 발생 가능합니다.</p>
<p>하지만 flutter_secure_storage를 이용할 경우 데이터를 저장할 때 Android에서는 keystore, iOS에서는 keychain 내부 저장소에 암호화를 거쳐 저장되므로 루팅이나 탈옥을 통한 데이터 유출을 방지할 수 있습니다.</p>
<br/>
<br/>

<h3 id="패키지-링크">패키지 링크</h3>
<p><a href="https://pub.dev/packages/flutter_secure_storage">flutter_secure_storage | Flutter Package</a></p>
<br/>
<br/>
<br/>

<h2 id="📍-flutter_dotenv-로-민감-정보-노출하지-않기">📍 <strong>flutter_dotenv 로 민감 정보 노출하지 않기</strong></h2>
<p>Git에 API 키, 서버 주소 등에 대한 정보 노출 위험 문제와 보안 문제를 피하기 위해서는 코드에 하드코딩하지 않는 것이 보안 및 유지 보수 측면에서 중요하고, 피해야 하는 개발 방식입니다.</p>
<br/>

<p>이때 유용하게 사용될 수 있는 패키지인 flutter_dotenv는 Dart와 Flutter 앱에서 환경 변수를 관리하기 위한 패키지입니다.</p>
<br/>
<br/>

<h3 id="이유-1">이유</h3>
<p>flutter_dotenv 이용 시 환경 변수가 저장된. env 파일을. gitignore에 추가하여 Git 저장소에 올라가지 않도록 할 수 있습니다.</p>
<br/>

<p>환경 변수를 별도의 파일에 저장하면 중요한 정보를 변경할 때 앱 코드들을 변경할 필요 없이. env 파일만 업데이트하여 적용할 수 있으므로, 유지 보수에도 도움이 됩니다. 또한. env 파일로 값이 적용되니 팀으로 작업한다면 모든 팀원은 동일한. env 파일을 사용하여 일관된 환경 설정을 편리하게 유지 및 공유할 수 있습니다.</p>
<br/>
<br/>

<h3 id="패키지-링크-1">패키지 링크</h3>
<p><a href="https://pub.dev/packages/flutter_dotenv">flutter_dotenv | Flutter Package</a></p>
<blockquote>
<p><a href="https://velog.io/@hee_mm_/Flutter-flutterdotenv-%EB%A1%9C-.env-%ED%8C%8C%EC%9D%BC-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90">👉 적용 참고 할 수 있는 블로그 링크</a></p>
</blockquote>
<br/>
<br/>
<br/>

<h2 id="📍-encrypt-패키지로-암호화하기">📍 <strong>encrypt 패키지로 암호화하기</strong></h2>
<p>비밀번호를 통한 토큰 갱신 등 민감 정보를 사용하여 서버와 통신할 경우 허가된 사람들만이 사용 및 인식 가능하도록 기밀성을 유지하여 정보 유출을 방지하여야 합니다.</p>
<br/>

<p>encrypt 패키지는 꾸준히 업데이트되는 양방향 암호화 패키지로 AES, Fernet, SHA1 등 다양한 암호화를 제공하여 선정하였지만 필요에 따라 타 패키지들을 사용하여 암호화를 진행하여도 괜찮습니다. </p>
<p><span style='color:gray'>ex) 해싱 처리 시 crypto 패키지 사용 등
</span></p>
<br/>
<br/>

<h3 id="이유-2">이유</h3>
<p>해당 패키지를 사용해야 하는 이유보다는 암호화를 사용해야 하는 이유에 가깝습니다만, 데이터가 암호화되지 않으면 서버 통신 중 데이터를 탈취하여 악용될 수 있으며, 이에 대한 탈취와 데이터 변조 등에 대한 방지를 위해 암호화를 통해 사전에 해당 문제들을 방지하고 데이터를 주고받는 것이 좋습니다.</p>
<br/>

<p>위와 같은 탈취와 악용의 문제뿐만 아닌 사용자의 개인 정보나 인증 정보와 같은 민감한 데이터는 정보 보호를 위해서도 진행되는 것이 좋은 방향성입니다.</p>
<br/>

<h3 id="패키지-링크-2">패키지 링크</h3>
<p><a href="https://pub.dev/packages/encrypt">encrypt | Dart Package</a></p>
<br/>
<br/>
<br/>

<h2 id="📍-secure_application-로-백그라운드-스냅샷-숨기기">📍 <strong>secure_application 로 백그라운드 스냅샷 숨기기</strong></h2>
<p>앱 화면에서 사용자의 민감 정보가 표시되는 경우 백그라운드 스냅샷(=백그라운드로 실행 중인 앱들에 대한 모음 화면) 에서 해당 데이터를 확인 할 수 없도록 방지해야 합니다.</p>
<p>대표적인 예로 은행 어플의 경우 사용자의 계좌 잔액 등을 노출하지 않는 것이 중요하며 이런 기능을 secure_application 패키지를 통해 간편하게 사용할 수 있습니다.</p>
<br/>
<br/>

<h3 id="이유-3">이유</h3>
<p>해당 패키지를 통해 앱이 백그라운드에서 실행 중일 때 스냅샷이 화면에 표시하지 않도록 진행하지 않는다면 상단에 적은 은행 어플 예와 같이 타인에게 민감 정보 혹은 노출되고 싶지 않은 정보 화면에 보여 사용자의 민감한 정보가 노출될 수 있습니다.</p>
<br/>

<p>그뿐만 아닌 유료 컨텐츠 등을 제공하는 서비스라면 화면 내 텍스트들을 백그라운드를 통해 확인하지 못하도록 기능을 적용시켜 유출에 대한 방지를 진행할 수 있기에 사용자 경험과 보안 강화를 위해 적용해주는 것이 좋습니다.</p>
<p><br/><br/></p>
<h3 id="패키지-링크-3">패키지 링크</h3>
<p><a href="https://pub.dev/packages/secure_application">secure_application | Flutter Package</a></p>
<br/>
<br/>
<br/>

<h2 id="📍-flutter_jailbreak_detection로--루팅과-탈옥-감지하기">📍 flutter_jailbreak_detection로  루팅과 탈옥 감지하기</h2>
<p>각 OS에서는 루팅과 탈옥이라는 개념이 존재합니다, 해당 개념들은 기기의 제한을 우회하여 추가 기능을 활성화하는 것을 의미하며 이 기능들을 통해 이미 적용된 보안들을 회피할 수 있으며 이를 통해 내부 데이터를 조작 및 확인이 가능합니다.</p>
<br/>

<p>flutter_jailbreak_detection 패키지는 위 문제를 가진 Android에서의 루팅과 IOS의 탈옥을 감지할 수 있도록 하는 패키지입니다.</p>
<br/>
<br/>

<h3 id="이유-4">이유</h3>
<p>루팅과 탈옥은 상단에 적은 것과 같이 보안을 회피하고 데이터 조작이 가능하여 보안 정책 위반, 앱의 무단 사용, 데이터 유출, 결제 및 라이선스 문제 등의 문제를 초래할 수 있습니다.</p>
<br/>

<p>특히 결제나 라이센스를 통해 진행되는 앱 기능이 있다면 이에 대한 우회를 가능케하며 사내 이익을 감소시키거나 컨텐츠 제공 어플의 경우 내부 데이터 탈취 등으로 인한 문제가 발생할 수 있으므로 이에 대한 감지를 통해 사용자가 앱을 이용하지 못하도록 차단하는 등의 방안으로 보안성과 안전성을 강화하는 것이 좋습니다.</p>
<p><br/><br/></p>
<h3 id="패키지-링크-4">패키지 링크</h3>
<p><a href="https://pub.dev/packages/flutter_jailbreak_detection">flutter_jailbreak_detection | Flutter Package</a></p>
<br/>
<br/>
<br/>

<h2 id="📍-패키지-및-sdk를-정기적으로-업데이트-하기">📍 패키지 및 SDK를 정기적으로 업데이트 하기</h2>
<p>패키지 및 SDK는 기존 기능의 보안 취약점에 대한 대응이나 각종 최적화와 같은 작업이 진행되거나 새로운 보안 기능이나 개선된 기능을 제공하기 위해 업데이트가 진행되기도 합니다. <span style='color:gray'>( 물론 항상은 아닙니다만 ) </span></p>
<br/>

<p>이러한 업데이트를 통한 새로운 기술이나 각종 대응 사항을 업데이트를 통해 적용하여 앱의 안전성을 향상시킬 수 있으니 꾸준하게 패키지 및 SDK 업데이트 내역을 확인하고 적용한다면 앱의 보안을 점진적으로 향상시킬 수 있습니다. 🙂</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GDG Songdo Devfes 2023 후기]]></title>
            <link>https://velog.io/@hee_mm_/GDG-Songdo-Devfes-2023-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@hee_mm_/GDG-Songdo-Devfes-2023-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Tue, 12 Dec 2023 03:40:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Flutter 세션들을 중점으로 진행되며, 각 세션의 내용보다는 세션을 듣고 느낀 생각에 대한 글입니다.</p>
</blockquote>
<br/>
<br/>
<br/>

<h2 id="처음으로-혼자가는-컨퍼런스">처음으로 혼자가는 컨퍼런스</h2>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/b38d4f49-6704-4c25-bcaf-e4185a42f051/image.gif" alt=""></p>
<br/>

<p>2023년 12월 10월 송도에서 진행된 GDG Songdo Devfest에 다녀왔다.</p>
<br/>

<p>줄곧 Flutter와 관련한 컨퍼런스에 가고 싶다고 생각하고 있었고, 이번에 IT 행사들을 찾아보다 발견하여 참석하게 되었다. 기념품으로 각종 스티커와 티셔츠도 받아 고이 모셔두고 있다.</p>
<br/>

<p>세션은 총 45가지 중 Flutter_build, git_marge 등 그 속에서 카테고리가 나누어져서 매우 다양했는데, 나의 경우는 전부 Flutter 세션으로 채워들었다. 선택해 들었던 내용들은 그리 어려운 내용들이 아니어서 이해하기 어렵지 않았다.</p>
<br/>
<br/>
<br/>

<hr>
<br/>
<br/>
<br/>

<h1 id="참석-세션들">참석 세션들</h1>
<br/>
<br/>

<h2 id="building-your-career-with-developer-communities">building your career with developer communities</h2>
<p>커뮤니티는 왜 중요한지, 커뮤니티를 진행하는 과정에서 어떠한 이점이 있는지 등을 알려주시는 세션이었다. 
<br/></p>
<p>커뮤니티 활동에 대한 중요성은 어느정도만 알고 있었고 ‘ 하면 좋고, 안하면 조금 아쉽다 ’ 정도로 인지하고 있었기에 설명해주신 내용들을 들으면서 생각하지 않았던 방향에 대한 이점등이 있는지 알게되면서 확실히 개발자는 지속적으로 소통하며 해나가야하는 직업이기 때문에 이러한 관점에서는 커뮤니티에 참여하는 것이 좋겠구나… 라고 생각하였다.</p>
<br/>

<p>해당 세션을 듣고 이후 MeetUP을 통해 여러 Flutter 커뮤니티를 가입하며 나름… 커뮤니티를 위한 한 발걸음은 디딜 수 있게된 것 같다. </p>
<br/>
<br/>
<br/>

<h2 id="주니어-배포일지">주니어 배포일지</h2>
<p>flavor를 이용하여 운영, QA, 개발 별 앱을 분리하여 배포하는 방법에 대한 설명을 해주신 세션이다.</p>
<br/>

<p>flavor는 동일한 소스로 다른 버전의 앱을 빌드 할 수 있도록 해주는 개념으로, 하드 코딩이 아닌 build 시 설정을 통해 각 서버 Url, 토큰 값 등을 설정해 주는 것이 가능하다.</p>
<br/>

<p>각 안드로이드, IOS 별로 설명을 진행해 주셨고 안드로이드는 설정할 거리가 적었지만 IOS의 경우 Custom build Configurations, Custom build schemes, Build Setting 등…수정의 양이 적지 않았기에 사실 세션을 보고 바로 이해하기는 어려워 다시 관련한 자료와 메모를 보며 곱씹어 봐야겠다.</p>
<br/>

<p>또한 EventChannel, MethodChannel 과정을 더욱 편하게 진행할 수 있는 pigeon, FFi 바인딩을 위한 FFlgen 등 관련한 작업에 도움이 되는 패키지들도 추가적으로 설명을 해주셨고, 몰랐던 패키지 정보들을 얻게 되어 기뻤다.</p>
<br/>

<p>중간중간 알았던 정보, 몰랐던 정보가 섞여있어 열심히 메모하고 사진을 찍었던 세션 중 한 가지이며 전반적으로는 코드를 중심으로 진행되어 더 이해하기 쉬웠던 것 같다.</p>
<br/>
<br/>

<h2 id="네이티브-지식이-필요한-이유">네이티브 지식이 필요한 이유</h2>
<p>코드적인 부분과 기능적인 부분보다는 세션명 그대로 네이티브 지식이 필요한 이유에 대한 내용과 네이티브 공부 방법, 개발 트렌드를 따라가는 방법 등을 설명해 주신 세션이다.</p>
<br/>

<p>그 안에서 엔지니어라는 직업에 대한 근본적인 이야기와 그렇기에 알아야 하는 네이티브 지식으로 연결하여 말씀해 주시는 부분이 어느 정도 와닿았던 것 같다.</p>
<br/>

<p>세션을 들으며 이전에 안드로이드 개발자로 일했던 경험이 있지만 지금은 거의 다 잊은 상태라는 것과 IOS에 관련한 개발 경험은 없다는 점에서 다시 한번 각 OS 별로 간단한 프로젝트를 만들어보는 게 좋을까.. 하고 생각하게 되는 계기가 되었다.</p>
<br/>
<br/>
<br/>

<h2 id="rive-애니메이션-만들기">Rive 애니메이션 만들기</h2>
<p>Flutter에서 애니메이션이 구현되는지 간략한 설명과 Ticker가 무엇인지, Lottie를 이용한 방법과 Rive를 이용한 방법을 중점으로 이야기하며 실제 여러 OS 들에서 구현되는 라이브 코딩을 통해 진행된 세션이었다.</p>
<br/>

<p>Rive가 어떤 식으로 구성되어 있는지와 함께 전반적인 단점과 팁들과 함께 발표자분께서 사용하게 된 계기를 이야기해 주셨는데 유니티로 만들어 있던 애니메이션… 그것도 플래시로 만들어져있던 애니메이션을 옮기는 과정이었다는 점에서 약간 충격과 신기함이 동시에 느꼈고 그래서 더욱 Rive에 매력을 느끼게 된 것 같다. (…)</p>
<br/>

<p>개인적으로 Rive에 관심이 있어서 직접 만들어본 경험이 있었기에 더욱 흥미 있게 들을 수 있었고, 입담도 좋으셔서 계속 집중하고 들을 수 있었던 세션이었다.</p>
<br/>
<br/>
<br/>

<h2 id="dart-interoperabillity">Dart Interoperabillity</h2>
<p>전반적으로 FFI에 대한 이야기와 Dart와 함께 자바 스크립트를 사용하는 방법, C를 사용하는 방법과 Go 언어를 이용해서 진행하는 방법과 함께 추가로 기본 FFi 와 FFi 패키지에 대한 차이를 이야기해 주시는 세션이었다.</p>
<br/>

<p>개인적으로는 이론적인 부분에서는 조금 어렵다면 어려운 세션이었지만 코드들과 함께하니 마냥 이해하기 어렵지는 않았고 각 정보도 설명을 이해하기 쉽게 덧붙여주시면서 진행되었기에 더욱 좋았다. 중간 크립토 라이브러리 해싱에 대한 내용이 있었는데 사실 Flutter에 크립토 패키지는 따로 있어서 고개가 갸우뚱했지만 이런 식으로 적용하는 것도 방법 중 하나겠다고 생각할 수 있었다.
<br/></p>
<p>세션을 들으면서 어떤 기능 사용할 때 적용하면 좋을 것 같다 등 기능 확장에 대한 생각을 할 수 있어서 오히려 배움을 얻은 세션이었다.</p>
<br/>
<br/><br/>

<h2 id="스토어용-스크린샷-자동생성하기">스토어용 스크린샷 자동생성하기</h2>
<p>앱 스토어에는 앱마다 기능을 설명해 주는 앱 화면이 포함된 이미지들이 존재하지 않은가? 해당 세션은 해당 이미지들을 직접 생성하는 것이 아닌 자동으로, 심지어 이미지 내 글자들까지 번역하여 생생하게 해주는 패키지들과 패키지 사용법에 대해 설명해 주시는 세션이었다.</p>
<br/>

<p>패키지들에 대한 이야기가 나오기 전 해당 기능을 적용하게 된 계기를 말씀해 주셨는데, 그중 앱 이미지가 사용자의 언어와 맞지 않는다면 다운로드 율이 떨어지게 된다는 말씀에 깊이 공감하였다. 내 경우에도 그런 상황이 종종 있었기 때문이다. 그렇기에 해당 세션에 대해 더욱 집중하며 들을 수 있었고 중간중간 사진을 찍고 더욱 메모하려고 노력했었다.
<br/></p>
<p>다만 진행 도중 타야 하는 버스가 얼마 남지 않고 해당 버스를 넘기게 되면 대기시간과 함께 귀가 시간이 너무 늦어지기에 급하게 세션에서 나오게 되었다.</p>
<br/>
<br/>
<br/>

<hr>
<br/>
<br/>
<br/>

<h2 id="후기">후기</h2>
<p>세션들은 전체적으로 아직 주니어인지라 잘 맞을법한 주제들을 골라 들었지만 몇 가지의 경우 생각보다 더 가벼운 내용들이었기에 살짝 아쉬웠다, 동시에 원하는 세션이 진행되는 경우 엄청난 선택의 기로에 머물러 고민하다 결국 사다리 타기로 골라 들어가기도 해서 최대한 발표자분들이 자료를 배포해 주시길 빌고 있다.
<br/></p>
<p>그럼에도 세션들을 듣고 여러 가지 이걸 이용해서 뭘 해보면 좋겠는데? 이 정보를 이용해서 내가 원했던 기능을 구현할 수 있겠는데? 하는 상황들이 있었기에 참석하면서 여러 인사이트와 지식을 얻을 수 있었던 좋은 시간이었다.
<br/></p>
<p>편도 2시간인 교통 이슈와  체력적인 피로로 인해 마지막까지 듣지 못한 점이 굉장히 아쉬웠지만 아쉬운 만큼 만족스러운 경험이었고, 앞으로 Flutter와 관련된 행사가 많이 열렸으면 하는 마음이다.</p>
<br/>
<br/>

<p><span style='color:gray'>사담이지만 케이터링의 빵과 쿠키들 정말 맛있었다… 거의 하루 동안 아무것도 못 먹고 갔다가 케이터링을 보고 속으로 기쁨의 환호를 질렀다.</span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Container는
Const가 불가능하다?]]></title>
            <link>https://velog.io/@hee_mm_/Flutter-Container%EB%8A%94Const%EA%B0%80-%EB%B6%88%EA%B0%80%EB%8A%A5%ED%95%98%EB%8B%A4</link>
            <guid>https://velog.io/@hee_mm_/Flutter-Container%EB%8A%94Const%EA%B0%80-%EB%B6%88%EA%B0%80%EB%8A%A5%ED%95%98%EB%8B%A4</guid>
            <pubDate>Tue, 05 Dec 2023 06:29:38 GMT</pubDate>
            <description><![CDATA[<p>Flutter GDE 분이 올려주시는 Flutter 정보로 인지하지 못했던 정보를 얻게 되었습니다.</p>
<br/>

<p><strong>제목과 같은 Container는 Const가 불가능 하다는 사실.</strong></p>
<blockquote>
<p>정보 출처
<a href="https://twitter.com/biz84/status/1729114620512887056">https://twitter.com/biz84/status/1729114620512887056</a></p>
</blockquote>
<br/>
<br/>

<p>내부 padding, child와 같은 설정에는 const가 가능하지만 Container 자체에 대한 const는 불가능하다는 것입니다. 보자마자 정말 안됐었나? 라고 생각하며 기억을 더듬어봐도… Container 자체에 적용했던 기억이 없었던 것 같아 해당 사항에 대해 더 알아보기 위해 테스트를 진행하며 글을 통해 정리하기로 했습니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="📍--const-사용이-무슨-상관이죠">📍  const 사용이 무슨 상관이죠?</h2>
<p>Flutter에서 <strong><code>const</code></strong> 키워드를 사용해 위젯을 생성하면 성능과 메모리 사용량의 개선이 이루어질 수 있기 때문에 가능한 해당 키워드를 붙여 최적화를 시켜주는 것이 좋습니다. </p>
<p>특히나 상태 변경 등으로 인한 위젯 리빌딩에서 const가 적용되어 있다면 다시 위젯을 생성할 필요가 없는 위젯은 이미 내부에 캐 싱되어 있으므로  리빌딩되지 않아 더 효과적으로 최적화될 수 있습니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<hr>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="container-테스트">Container 테스트</h2>
<p>간단하게 테스트를 진행해 보겠습니다. 아래 코드는 모든 값은 고정되어 있는 Container입니다, 여기서 앞에 const를 붙여주면 어떻게 될까요?</p>
<br/>
<br/>

<pre><code class="language-dart">Container(
            width: 200,
            height: 200,
            padding: const EdgeInsets.all(6),
            child: const Icon(Icons.add),
          ),</code></pre>
<p>const 사용이 불가능하다며 오류가 나게 됩니다.</p>
<br/>


<p><img src="https://velog.velcdn.com/images/hee_mm_/post/f0ee0336-431f-482d-b2be-def29ccc7834/image.png" alt=""></p>
<p>여기서 const를 사용하기 위해서는 어떤 위젯을 사용해야 함을 알아보면 아래와 같습니다.</p>
<br/>

<pre><code class="language-jsx">const SizedBox(
            width: 200,
            height: 200,
            child: Padding(
              padding: EdgeInsets.all(6),
              child: Icon(Icons.add),
            ),
          )</code></pre>
<br/>

<p>기존 사이즈 지정은 <code>SizedBox</code> 를 통해 미리 크기를 지정하고 <code>Padding</code> 으로 내부 패딩을 지정해 주면 child를 포함한 모든 위젯이 const 적용으로 고정됩니다.</p>
<br/>

<p>위 예시에서 알 수 있다시피 <strong>Container를 const 적용이 가능한 위젯들로 대체하는 것은 코드가 더욱 길어지고 편히 알아보기는 어렵지만 성능에는 최적화될 수 있다는 장단점이 동시에 존재합니다.</strong></p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h2 id="대체-위젯">대체 위젯</h2>
<p>추가적으로 Container의 자주 사용되는 설정들을 대체 할 수 있는 위젯들은 아래와 같습니다.
<br/>
<br/></p>
<h3 id="width-height-→-sizedbox">width, height → SizedBox</h3>
<table>
<thead>
<tr>
<th>설정</th>
<th>대체 위젯</th>
</tr>
</thead>
<tbody><tr>
<td>width, height</td>
<td>SizedBox</td>
</tr>
<tr>
<td><br/></td>
<td></td>
</tr>
<tr>
<td><br/></td>
<td></td>
</tr>
</tbody></table>
<h4 id="width-height-예제">width, height 예제</h4>
<pre><code class="language-dart">Container(
                width: 100,
                height: 100,
              ),</code></pre>
<h4 id="sizedbox-예제">SizedBox 예제</h4>
<pre><code class="language-dart">const SizedBox(
                width: 100,
                height: 100,
              ) </code></pre>
<br/>
<br/>
<br/>

<h3 id="color-→-coloredbox">color → ColoredBox</h3>
<table>
<thead>
<tr>
<th>설정</th>
<th>대체 위젯</th>
</tr>
</thead>
<tbody><tr>
<td>color</td>
<td>ColoredBox</td>
</tr>
</tbody></table>
<br/>
<br/>


<h4 id="color-예제">color 예제</h4>
<pre><code class="language-dart"> Container(
                color: Colors.amber,
                child: const Icon(Icons.add),
              ),</code></pre>
<h4 id="coloredbox-예제">ColoredBox 예제</h4>
<pre><code class="language-dart"> const ColoredBox(
                color: Colors.amber,
                child: Icon(Icons.add),
              )</code></pre>
<br/>
<br/>
<br/>

<h3 id="padding-margin-→-padding">padding, margin → Padding</h3>
<table>
<thead>
<tr>
<th>설정</th>
<th>대체 위젯</th>
</tr>
</thead>
<tbody><tr>
<td>padding, margin</td>
<td>Padding</td>
</tr>
<tr>
<td><br/></td>
<td></td>
</tr>
<tr>
<td><br/></td>
<td></td>
</tr>
</tbody></table>
<h4 id="padding-예제">padding 예제</h4>
<pre><code class="language-dart">       Container(
                color: Colors.amber,
                padding: const EdgeInsets.all(8),
                child: Icon(Icons.add),
              ),</code></pre>
<h4 id="padding-대체-예제">Padding 대체 예제</h4>
<pre><code class="language-dart"> const  ColoredBox(
                  color: Colors.amber,
                  child:Padding(
                      padding: EdgeInsets.all(8),
                    child: Icon(Icons.add),
                  )
              )</code></pre>
<br/>
<br/>

<h4 id="margin-예제">margin 예제</h4>
<pre><code class="language-dart">Container(
                margin: const EdgeInsets.all(8),
                child: const Icon(Icons.add),
              ),</code></pre>
<h4 id="padding-대체-예제-1">Padding 대체 예제</h4>
<pre><code class="language-dart">  const Padding(
                padding: EdgeInsets.all(8),
                child: Icon(Icons.add),
              )</code></pre>
<br/>
<br/>
<br/>

<h3 id="alignment-→-align">alignment → Align</h3>
<table>
<thead>
<tr>
<th>설정</th>
<th>대체 위젯</th>
</tr>
</thead>
<tbody><tr>
<td>alignment</td>
<td>Align</td>
</tr>
<tr>
<td><br/></td>
<td></td>
</tr>
<tr>
<td><br/></td>
<td></td>
</tr>
</tbody></table>
<h4 id="alignment-예제">alignment 예제</h4>
<pre><code class="language-dart">Container(
                alignment: Alignment.bottomLeft,
                child: const Icon(Icons.add),
              ),</code></pre>
<h4 id="align-예제">Align 예제</h4>
<pre><code class="language-dart">const Align(
                alignment: Alignment.bottomLeft,
                child: Icon(Icons.add),
              )</code></pre>
<br/>
<br/>
<br/>
<br/>
<br/>



<h3 id="📍-borderradius-와-constraints-대체">📍 borderRadius 와 constraints 대체</h3>
<p>위 설정들과 더해 자주 사용되는  <code>borderRadius</code> 설정은 따로 적용할 수 있는 ClipRRect의 경우 <code>BorderRadius.circular(n)</code>  설정으로 const 사용이 불가하며</p>
<br/>

<p><code>constraints</code> 설정 또한 ConstrainedBox 위젯으로 대체는 가능하나 <code>BoxConstraint</code>선언으로 const 사용이 불가능하여 이에 대한 경우는 </p>
<br/>

<p><strong>Stateless Class를 제작하여 const 설정을 적용하는 것이 유용할 것으로 보입니다.</strong></p>
<br/>
<br/>
<br/>
<br/>

<hr>
<br/>
<br/>
<br/>
<br/>

<p>간단하게 해당 사항에 대해 알아보았지만 최적화를 더욱 철저하게 진행하기 위해서는 위와 같은 과정을 진행하는 것이 좋겠지만 꼭 필요한 과정은 아닙니다. 코드적으로는 복잡스러워지기 때문에 필요에 따라 작업 과정에서의 협업 규정에 따라 선택해 사용하는 것이 좋을 것 같습니다.</p>
<br/>

<p>대체 위젯 코드들은 <a href="https://github.com/LIMMIHEE/velog-exercise/blob/main/lib/presentation/feat/container_const/container_const_screen.dart">Github 레파지토리</a>에서 확인 가능합니다!</p>
<br/>
<br/>
<br/>
<br/>
<br/>]]></description>
        </item>
        <item>
            <title><![CDATA[소감기 프로젝트 개발기 - 5주차 + @]]></title>
            <link>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-5%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-5%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 03 Dec 2023 00:59:03 GMT</pubDate>
            <description><![CDATA[<h1 id="소감기-프로젝트-🐂">소감기 프로젝트 🐂</h1>
<p>웹 소설이나 출판 소설, 게임 스토리 등 내용을 담은 글이라면 모든 상관없이 글을 읽고 느낀 감정에 대한 기록을 남기는 앱.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="12월-3일">12월 3일</h2>
<hr>
<p>앱 아이콘과 네이밍 변경 및 스토어의 각종 앱 설정 및 개인정보처리 방침 문서 제작 등을 처리하였다.</p>
<br/>

<p>생각보다 더 진행해야 하는 것들이 많고 설정해야 하는 것들이 많아 헤매기는 했어도 기존 앱 업데이트 경험과는 비슷하고 추가로 설정할 것만 많을 뿐 어려운 작업들이 많이 존재하지는 않아서 지금은 앱 검토 요청까지 진행 완료하였다. </p>
<br/>
하지만 개인정보 방침 문서를 만들어야 하는 것은 정말 어렵긴 했다 😵‍💫

<br/>
<br/>

<p>첫 앱 릴리즈라서 검토에는 시간이 걸릴듯하여 그 사이 동안 다시 한번 앱 검토 및 추가할 거리를 생각해 보는 시간을 가져야겠다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="12월-4일">12월 4일</h2>
<hr>
<p>실제 기기에서 기본적인 테스트를 돌리다가 UI가 어색한 부분을 다소 발견하여 추가로 개선을 진행하고 있다, 앱 검토 올린 것 또한 경험상 한 번에 넘어가지는 못할 거라는 확신을 가지고 있기에.. (... ) 그 사이 최대한 많이 테스트하고 많이 수정해고 플레이 스토어 측 피드백과 함께 적용하여 개선해야겠다.</p>
<br/>
<br/>
<br/>


<p>회원가입 및 로그인 테스트를 진행하며 아래 오류를 발견하였다.</p>
<br/>

<p><strong>발생에러</strong></p>
<pre><code>package:cloud_firestore/src/query.dart&#39;: Failed assertion: line 746 pos 11: &#39;(value as Iterable).isNotEmpty&#39;: &#39;in&#39; filters require a non-empty [Iterable].</code></pre><p><strong>발생코드</strong></p>
<pre><code class="language-dart">  Future&lt;QuerySnapshot&gt; getInviteWork() async {
    final inviteWorks = PrefsUtils.getStringList(PrefsUtils.inviteWork);

    return await firebaseStore.store
        .collection(&#39;work&#39;)
        .where(&quot;inviteCode&quot;, whereIn: inviteWorks)
        .get();
  }</code></pre>
<p>문제 원인은 <code>.where</code> 부분에서 비교할 데이터가 존재하지 않아서 발생하는 문제이다. 즉, 초대된 정보가 아예 없다면 오류가 발생하는 것. </p>
<p>이전에 <code>isEmpty</code> 로 <code>isNotEmpty</code>에 대한 체크를 추가하고, 필터를 적용하기 전에 조건을 확인하도록 개선 진행하였다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="12월-6일">12월 6일</h2>
<hr>
<p>아직까지 앱 릴리즈가 검토 중이기에 이전에 생각하였던 공감 이모지 추가에 관한 디자인과 DB 처리에 관해 기획하고 있는 중이다. 
<br/></p>
<p>감상 별로 감상에 대한 DB 처리는 큰 어려움이 없을 것으로 보여 오히려 UI에 관해 더욱이 고민하고 있으며, 현재 관련 예제로 생각하는 앱은 슬랙의 이모지 달기 기능이다. Chip 같은 UI를 사용해 적용하는 것이 현재 생각하는 개발 방향이며, 피그마를 통해 더욱 기획을 구체화해야겠다.</p>
<br/>
<br/>


<p>동시에 테스트를 하다가 인터넷 연결 / 미 연결 대한 처리를 진행하지 않았다는 걸 깨달아 해당 부분에 대한 개발을 진행할 예정이다.</p>
<p>최근 일정을 소화하며 앱 내 보안 강화에 대한 생각도 커져서 관련한 자료도 참고해 적용하기로!</p>
<p>아래의 영상을 참조하면 좋을 것 같다.</p>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=DYwn4KciL1U">https://www.youtube.com/watch?v=DYwn4KciL1U</a></p>
</blockquote>
<br/>
<br/>
<br/>
<br/>

<p>플레이 스토어 업로드를 진행하느라 IOS로 테스트하지 않는 사이 인터넷 연결 관련 사항을 개발 진행하려 하니 빌드 시 아래 오류와 마주하였다. 현재 몇 시간째 해결하지 못하고 있어 오늘 밤은 해당 문제와 함께 할 것 같다.</p>
<pre><code>[!] Invalid `Podfile` file: File exists @ syserr_fail2_in -~ cloud_firestore.
</code></pre><p>해당 문제는 다행히 해결하였지만 해결 과정이 상당히 복잡하고 길어 재발을 방지하고자 하여도 적을 수가 없다.... 한 가지를 해결하니 다른 문제가 발생하는 과정을 4번 정도 겪어 약간 피폐해진 상태이다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="12월-7일">12월 7일</h2>
<hr>
<p>ios 폴더 자체를 삭제하고 각종 설정을 다시 설정해 주는 식으로 어찌어찌 해결은 완료되었다, 해당 문제의 원인은 android 확인 진행하며 flutter upgrade를 진행하였는데 해당과 관련해서 별도의 설정 충돌이 있었던 것으로 예상된다.</p>
<br/>
<br/>
<br/>

<p><img src="https://velog.velcdn.com/images/hee_mm_/post/233e30ee-884c-43d6-9e4c-639f61c17432/image.gif" alt=""></p>
<p>인터넷 상태 알림 관련 개선도 진행하였다, 처음에는 snackBar를 통해 알릴까 싶었지만 일시적으로 알려주기 때문에 지속적으로는 유저가 어떤 상태인지 알 수 없으니 화면상에 나타나도록 위와 같이 개선을 진행하였다.</p>
<br/>
<br/>

<p>다음 개선으로는 보안 관련, 현재 <code>SharedPreferences</code> 로 민감정보도 저장되고 있어 민감정보의 경우 <code>flutter_secure_storage</code> 로 저장하도록 개선할 예정이다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="12월-8일">12월 8일</h2>
<hr>
<p><code>flutter_secure_storage</code> 적용 후 테스트를 진행하려니 어제까지 잘 되던 cloud_firestore에서 오류가 발생했다.</p>
<pre><code>The caller does not have permission to execute the specified operation.</code></pre><p>확인하니 firebase databse 를 만들고 30일이 지난 뒤 권한 만료로 인한 것이어서 Firebase에 들어가 Store의 규칙을 아래와 같이 수정하고 나니 잘 작동 중이다.</p>
<pre><code>rules_version = &#39;2&#39;;
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}</code></pre><br/>
<br/>
<br/>


<p><img src="https://velog.velcdn.com/images/hee_mm_/post/f4e3e492-a808-4206-86bf-4f21cf98a302/image.png" alt=""></p>
<p>앱 진행 상황을 확인하니 예상외로 문제가 발생하지 않았다...? 묘한 기분이지만 그 사이 개선한 건들이 있으니 업데이트된 버전으로 다시 적용 후 릴리즈를 진행하게 될 것 같다. 일요일에는 콘퍼런스에 참석해야 하니 내일 최대한 작업하기로.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="12월-9일">12월 9일</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/8c8f22d2-a338-4e36-bfd5-a9e3b17adc33/image.png" alt=""></p>
<p>오전 중에 미리 팀 프로젝트 작업 일부 작업하며 소감기 개선 버전을 다시 빌드 하여 스토어에 비공개 테스트로 업로드해두었다. 오후에는 스토어 심사 결과 확인 후 별문제 없다면 어떻게든... 20명의 테스터를 진행한 후 릴리즈 요청을 신청할 것 같다.  🫨</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="12월-12일">12월 12일</h2>
<hr>
<p>테스터 관련하여 인원이 너무 부족하여 트위터에 도와주실 분을 구하는 트윗을 올렸는데 많은 분들이 도움을 주셔서 혼자 감동의 눈물을 흘렸다.</p>
<br/>

<p>개발자라는 직업은 서로를 돕고 정보를 공유하는 문화가 잘 되어있다는 것이 내가 좋아하는 점 중 한 가지인데 이렇게 크게 경험을 하고 나니 이번 주말 들었던 Devfast의 커뮤니티의 중요성을 다시 한번 생각하게 되었다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="12월-18일">12월 18일</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/162908a9-e0e3-43b1-8e1d-10f01fd8acde/image.png" alt=""></p>
<p>다양한 분들이 도움을 주셔서 17명까지 채울 수 있었다. 남은 인원도 열심히 채워나가야겠다.</p>
<p>소감기 앱에서 책을 추천하는 기능을 추가하고자 최근에는 해당 테스터 문제 이리저리 진행하며 OpenAI 나 Gemini를 다른 앱에서 적용해 보고 있는데, 현재까지 경험한 바로 사용하기에는 존재하지 않는 책을 추천하는 비율이 높고 내용도 정확하지 않아 적절하지 않다는 것만 느낄 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[소감기 프로젝트 개발기 - 4주차]]></title>
            <link>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-4%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-4%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Mon, 27 Nov 2023 00:11:54 GMT</pubDate>
            <description><![CDATA[<h1 id="소감기-프로젝트-🐂">소감기 프로젝트 🐂</h1>
<p>웹 소설이나 출판 소설, 게임 스토리 등 내용을 담은 글이라면 모든 상관없이 글을 읽고 느낀 감정에 대한 기록을 남기는 앱.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-26일">11월 26일</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/1f1a8b42-bd60-48b3-8341-362c56e98e72/image.gif" alt=""></p>
<br/>

<p>이어서 감상 수정 기능을 제작하였다, 수정 분기에 관한 정리만 조금 신경쓰면 되는 어렵지 않은 기능이었기에 관련한 화면들에서의 처리에 조금 더 시간이 든 것 같다.</p>
<br/>

<p>추가로 이전 공유받은 작품에 대해 감상을 적었을 경우 내가 적은 감상과 중복으로 나타나는 문제가 있어 공유 작품에 관한 감상은 자신의 감상을 제하고 가져와 사용하도록 필터링을 걸어두었다.</p>
<p>확실히 아무렇게나 작동시켜 테스트 해보는 것도 디버깅이나 오류 찾기에 좋은 것 같다.</p>
<br/>
<br/>

<p>오전에는 팀 프로젝트를 진행하느라 작업량이 적지만 저녁에도 가족 약속이 있으므로 밤에 진행할 수 있다면 더 진행하기로!</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-27일">11월 27일</h2>
<hr>
<p>어젯밤 중에 간단하게 작품 수정 기능 개발을 진행하여 아침에 빠르게 마무리 진행한 후 STT 기능 도입에 대해 찾아보고 있다.
<br/></p>
<p>찾아보며 고민도 생겼다, 과연 어디까지 해당 기능을 도입하는가.
해당 기능에 대해서는 시작, 완료 최소 2가지 멈춤과 취소를 포함하면 최대 4가지의 컨트롤이 필요로 하다. </p>
<br/>
현재 사용자가 각 텍스트를 입력해야 하는 화면은 4가지이지만 위젯 별로 따지면 휠씬 많아지며 이때 구현해야 하는 기능의 개수가 기하급수적으로 늘어난다. 해당 STT 기능을 가진 Stateful 위젯 자체를 따로 가져간 후 필요시에만 Stack을 통해 사용하는 방안이 생각났지만 일단 더욱더 자료를 찾아봐야겠다.


<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-28일">11월 28일</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/9a21e220-99de-465f-a9cd-c1b879a18e53/image.gif" alt=""></p>
<p>STT 기능을 개발 중이다, 실제 기기에서 테스트하며 진행하는 도중에 안드로이드는 Android에는 무시할 수 없는 시스템 일시 중지 시간이 있다는 고유의 문제가 있어 위와 같이 버튼을 누른 채 이야기하고 있는 와중에도 듣기가 끊어져 버린다. 🤯</p>
<p>일단 듣기 시간과 휴지 시간을 지정해 주는 걸로 어느 정도 방지가 되긴 하지만 완벽하지는 않다.</p>
<blockquote>
<p>이슈란 링크
<a href="https://github.com/csdcorp/speech_to_text/issues/450">https://github.com/csdcorp/speech_to_text/issues/450</a></p>
</blockquote>
<blockquote>
<p><a href="https://stackoverflow.com/questions/73329808/error-speechrecognitionerror-msg-error-speech-timeout-permanent-true-in-fl">https://stackoverflow.com/questions/73329808/error-speechrecognitionerror-msg-error-speech-timeout-permanent-true-in-fl</a></p>
</blockquote>
<br/>
<br/>
<br/>

<p><img src="https://velog.velcdn.com/images/hee_mm_/post/0c92c8ee-b6d6-4252-a02d-470577d32c42/image.gif" alt=""></p>
<p>IOS 실제 기기에서도 테스트를 진행했다, 다행히 안드로이드와는 다르게 별다른 문제가 발생하지 않았다. </p>
<br/>

<p>어느 정도 STT의 기본적인 사용법은 익혔으므로 실제 기능 TextField와 연결을 진행해야 하는데 이 경우 Focus 위젯을 통해 각 입력 필드가 포커스 되었을 경우 음성 입력에 대한 버튼을 추가로 만들 것이다.</p>
<p>이후 버튼이 눌러진다면 BottomSheet를 통해 실질적 음성 인식 추가 위젯을 띄우고 진행에 따른 결괏값을 포커스 된 컨트롤러에 입력하는 방식으로 개발을 진행할 예정이다.</p>
<br/>
<br/>
<br/>

<p>추가로 실제 기기들에서 테스트하며 UI들이 생각보다 크게 적용되어 있어서 전체적으로 줄이는 과정도 진행해야 할 것 같다.</p>
<p>다행이라면 텍스트들은 디자인 시스템 적용으로 일괄적 변경이 가능하다는 것.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-29일">11월 29일</h2>
<hr>
<p>어제 계획한 대로 Focus 위젯을 통해 각 입력 필드가 포커스 되었을 경우가 아닌 FocusNode를 각 필드에 적용하고 addListener를 통해 포커스가 되면 STT 위젯을 띄우는 방법으로 개발을 진행하였다.</p>
<br/>
<br/>

<p>단일로 입력받는 감상 추가의 경우 별다른 문제가 없었지만 타이틀과 설명을 입력받아야 하는 작품 추가에서는 문제가 발생했다.</p>
<br/>

<p>*<em>바텀시트를 만들어 띄우면 이전 선택되어 있던 TextField에 대한 Focus가 false로 변한다는 사실이다. *</em> <span style='color:gray'>나름 바텀 시트 자주 사용하는데도 이 정보는 처음 알았다..</span></p>
<br/>
<br/>

<p>Focus는 바텀 시트를 내리고 나면 다시 돌아오기에 <code>Future.delayed</code> 로 즉시 텍스트 입력을 진행하는 것이 아닌 바텀 시트가 내려오고 Focus가 돌아온 후 진행하도록 개선 진행하였다. </p>
<p>처음에는 감상 추가 때와는 다르게 텍스트가 TextField에 추가되지 않아 당황했지만 로그를 찍어보니 원인을 알 수 있었다. 오늘도 로그와 디버그 모드에게 구원받고 있다.</p>
<br/>
<br/>


<p><img src="https://velog.velcdn.com/images/hee_mm_/post/95ff06fe-89f2-4702-9606-f453d7a7887e/image.gif" alt=""></p>
<br/>

<p>그렇게 완성된 STT 기능은 위와 같다.</p>
<p>우선적으로 메인 기능인 작품 및 감상에는 기능 적용이 완료되어서 코드를 더 다듬고 올린 후 다른 화면들에도 적용 진행해야겠다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-30일">11월 30일</h2>
<hr>
<p>검색 화면에서도 STT 기능을 적용하였다.</p>
<p>또한 위 STT기능 개발 진행하며 발견하였던 작품 중복 오류와 코드를 다듬다가 발생한 감상 화면에서의 작품 선택 불가 문제에 대한 fix를 진행하였다.</p>
<br/>

<p>이제 남은 기능은 설정 화면의 로그아웃 기능과 회원 탈퇴, 다크 모드 정도이며 추가적인 UI 보완도 진행하면 프로젝트는 마무리할 수 있을 것 같다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="12월-1일">12월 1일</h2>
<hr>
<p>어제 오후쯤 UI 보완도 추가로 진행하였고, 오늘은 운동을 다녀온 후 로그아웃과 회원 탈퇴 기능 개발을 진행하였다.</p>
<br/>
<br/>

<p>이제 개발해야 하는 남은 기능은 다크 모드 정도이지만 앱이 기능적으로 아쉽다는 생각이 들어서 추가로 개발하면 좋을 만한 기능으로 뭐가 있을지 고민하면 좋을 것 같다.</p>
<p>현재 생각난 기능은 감상에 대한 공감 이모지 추가 정도이다..</p>
<br/>
<br/>
<br/>
<br/>

<p>밤중에 잘 사용하는 앱 업데이트를 진행하며 문득 떠올랐다.</p>
<p>처음부터 기능을 많이 넣어두려고 하는 것보다는 앱을 먼저 올린 후 유지 보수하면서 기능 업데이트하는 편이 경험상으로도 더 좋은 것이 아닌가?라는 생각.</p>
<br/>
<br/>

<p>협업에서도 원하는 모든 기능을 넣을 수는 없었다, 기획된 사안을 시간 안에 마무리해야 했었으니까. 이 기획은 정해진 마감일은 없었다지만 계속 원하는 기능을 생각하고 붙이기만 진행하면 계속 늘어지기만 할 것 같다는 생각이 들었다.</p>
<p>이전 직장에서 IOS에서 신규 앱을 오픈하는 경험은 진행하였어도 안드로이드에 대한 경험은 없기도 하고 말이다.</p>
<br/>
<br/>

<p>그래서 목표를 바꾸기로 했다.
**
첫째는 앱을 릴리즈 하기, 두 번째는 한두 번이라도 앱에 대한 유지 보수 및 기능 업데이트를 진행해 보기.**</p>
<p>내일은 안드로이드 개발자 친구비를 내야겠다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="12월-2일">12월 2일</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/e4f6883c-6825-40eb-9948-e4838c2fbb8e/image.png" alt=""></p>
<p>개발자 계정을 생성하였다, 오늘 다크 모드 개발을 진행하고 가능한 빠르게 앱 검토를 올려볼 예정이다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<p>다크 모드를 적용하며 앱의 커스텀 theme 설정이 적용되지 않고 계속해서 기본 theme이 적용되는 문제와 맞닥뜨렸다. 꽤나 긴 시간 동안 헤매었지만 문제가 발생하지 않았던 이전 코드와 비교하니 생각보다 어려운 문제가 아닌 단순한 문제였다. 🫨</p>
<br/>


<p>문서를 따라 쓴 <code>Get.changeTheme(ThemeData.light())</code> 코드가 이미 지정된 사용자 ThemeData를 사용하는 것이 아닌 기본 데이터를 사용해서 적용해 준다는 것이다. </p>
<p>이건 사실 자세히 안 본 내 잘못이라 할 말이 없다... 오늘도 이렇게 자세히 확인하기와 코드 클래스 내 주석 확인하기의 중요성을 알아간다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[소감기 프로젝트 개발기 - 3주차]]></title>
            <link>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-3%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-3%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Mon, 20 Nov 2023 05:14:57 GMT</pubDate>
            <description><![CDATA[<h1 id="소감기-프로젝트-🐂">소감기 프로젝트 🐂</h1>
<p>웹 소설이나 출판 소설, 게임 스토리 등 내용을 담은 글이라면 모든 상관없이 글을 읽고 느낀 감정에 대한 기록을 남기는 앱.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-19일">11월 19일</h2>
<hr>
<p>무언가 잘 못 먹었는지 탈이 났다... 운동도 가지 못하고 골골거리며 회복중인지라 오늘은 푹 쉰 후 내일 다시 작업을 시작해야겠다.</p>
<br/>
<br/>
<br/>

<p>추가로 PopupMenuItem 관련으로 Flutter 이슈에 올린 것에 대한 피드백이 와서 확인해 보니 <code>popupMenu</code>의 최소 width 치가 적용된 것이라고 한다. 
<br/></p>
<p>constraints를 사용해 width를 제어하는 방법이 해당 문제를 위해 설계되는 것이라고 알려주며 이후 해당 이슈는 close 되었다. 이번 프로젝트를 진행하며 다양한 경험을 하게 되는 것 같아 기쁘다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-20일">11월 20일</h2>
<hr>
<p><span style='color:gray'>몸 상태가 무리없이 움직일 정도는 되어서 운동 후 개발 진행하였다. </span></p>
<br/>
<br/>

<p>지난주 발견하였던 카테고리 UI 영역을 상위 위젯에서 겹쳐지는 문제의 경우 GridView에 <code>childAspectRatio</code> 로 item 가로 세로 비율을 설정해주는 것으로 해결하였다! </p>
<br/>
GridView의 경우 `childAspectRatio`를 통해 child 크기를 적용되기 때문에 기본 설정인 1:1 값이 적용되어 width를 넘어선 경우는 잘려서 해당 문제가 발생한 것으로 보인다.



<blockquote>
<p>도움이 된 링크
<a href="https://stackoverflow.com/questions/69586316/how-to-prevent-gridview-from-shrinking-in-flutter">https://stackoverflow.com/questions/69586316/how-to-prevent-gridview-from-shrinking-in-flutter</a></p>
</blockquote>
<br/>
<br/>
<br/>


<p>홈 메인 화면에도 <code>GetView</code>를 통한 컨트롤러 사용으로 코드 개선을 진행하고 히스토리 화면 작업을 진행하고 있다.
<br/></p>
<p>다른 부분에서 큰 어려움은 없지만 Clipper에 외곽선과 함께 모양 변형 주는 부분에서 조금 애먹는 중... Clipper는 역시 사용히 어려운 것 같다. 그래도 노력 끝에 원하는 모양은 완성할 수 있었다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-21일">11월 21일</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/aa4c711f-a490-478d-831e-48f1e61872dd/image.gif" alt=""></p>
<p>히스토리 화면 구현 및 작품 상세 화면에 대한 개선을 진행하였다.
<br/></p>
<p>또한 서버에서 받아오는 데이터의 순서가 최신순으로 들어오지 않아 정렬을 하여 최신순으로 확인 가능하도록 수정하였으며, 그 이외에는 별다른 어려움이 존재하지 않았다.</p>
<br/>

<p>화면 상세에서 특정 기능들은 UI만 먼저 구현되어 있는 상태이기 때문에 다음 작업은 해당에 관한 상세 기능 구현으로 아래의 작업을 진행할 예정이다. 💪
<br/></p>
<ul>
<li>작품 수정</li>
<li>작품 삭제</li>
<li>감상 수정</li>
<li>감상 삭제</li>
<li>공통 작성자 공유 코드 Dialog</li>
</ul>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-22일">11월 22일</h2>
<hr>
<p>오후에 따로 일정이 있었다, 모든 걸 떠나서 정말 좋은 자리였다.</p>
<br/>


<p>나에게 부족한 점이 뭔지 자세하게 알 수 있었고 보완해야 할 점을 새로운 시각으로 보며 듣고 다양한 깨달음과 함께 나 자신이 부끄러워졌다. </p>
<p>이렇게 후회할 만한 어린 행동들을 했었구나 하며. 일정을 진행하며 들었던 정보들은 개인적으로 복기하며 보완해 나가야겠다.</p>
<br/>
<br/>
<br/>

<p>일정 소화 후에는 저녁 중에 작품 및 감상 삭제 기능 제작을 진행하였다. </p>
<br/>

<p>삭제 기능에서 다소 헤메기도 하였는데 <code>FirebaseStore</code>의 경우 <code>doc</code>의 하위로 필드까지 들어가면 삭제가 불가능하기에 작품 삭제 후 해당 작품에 대한 감상을 일괄적으로 삭제할 때 아래와 같이 연속해서 삭제 요청을 던져야만 했다.
<br/></p>
<p>해당 방식으로 아래와 같은 코드가 나왔다, 묘하게 복잡스러운 코드로 보인다.</p>
<br/>

<pre><code class="language-dart">  Future&lt;void&gt; deleteReports(String workServerId) async {
    final QuerySnapshot deleteReport = await firebaseStore.store
        .collection(&quot;report&quot;)
        .where(&quot;workServerId&quot;, isEqualTo: workServerId)
        .get(); // 작품코드를 통해 하위 리포트 가져오기

    for (var report in deleteReport.docs) {
      try {
        firebaseStore.store.collection(&quot;report&quot;).doc(report.id).delete();
      } catch (e) {
        rethrow;
      }
    }
  }</code></pre>
<p>더 깔끔하고 최적화하며 구현할 수 있는 방법을 찾아봐야겠다.</p>
<br/>
<br/>
<br/>
<br/>

<p>팀 프로젝트 진행 후 시간이 남아 아래와 같이 개선해 보았다.
내일 관련 코드들 한꺼번에 관련 개선 형태로 전환해야겠다.
<br/></p>
<pre><code class="language-dart">Future&lt;void&gt; deleteReports(String workServerId) async {
    try {
      final QuerySnapshot deleteReport = await firebaseStore.store
          .collection(&quot;report&quot;)
          .where(&quot;workServerId&quot;, isEqualTo: workServerId)
          .get();  // 작품코드를 통해 하위 리포트 가져오기

       // 비동기적으로 여러 문서를 삭제하기를 기다리기
      await Future.wait(
        deleteReport.docs.map((report) async {
          if (report.exists) {  // 문서가 존재하는지 확인 후 삭제
            await firebaseStore.store.collection(&quot;report&quot;).doc(report.id).delete();
          }
        }),
      );
    } catch (e) {
      rethrow;
    }
  }</code></pre>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-23일">11월 23일</h2>
<hr>
<p>provider, controller 코드 개선과 함께 
작품 공통 감상 작성을 위한  작품 코드 확인 및 입력 UI 제작을 진행하였다.
<br/></p>
<p>팀 사이드 프로젝트 쪽을 일정에 맞추어 빠르게 작업하기 위해 일단 UI 먼저 제작하였고, 이후 작품을 공통으로 사용하기 위한 API 연결과 작품을 같이 불러와 어떻게 구분해 사용할지에 대해 고민하고 작업하게 될 것 같다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-24일">11월 24일</h2>
<hr>
<p>공통 작품에 대한 내용은 Firebase 실시간 데이터베이스를 통해 아래와 같은 구조로 저장하고 있었지만 현재 해당 데이터베이스 하위의 데이터를 체크하고 유저를 추가하는 작업을 진행하려 하니 현 구조가 비효율적이라는 걸 알 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/6e30cb2f-63ad-4881-9a8b-075e3925150a/image.png" alt=""></p>
<p>본래는 work 하위로 작품 ID가 Key가 되고 그 하위로 초대 코드와 허용 유저를 저장하는 방식으로 구조를 잡았었다.</p>
<br/>

<p>하지만 앱에서는 초대 코드로 허용 작품을 찾아야 하기에 작품 ID가 Key가 되면 모든 하위를 돌아가며 코드를 확인해야 하는 문제가 생긴다. 즉, 필요 이상으로 데이터 검증이 많이 필요하게 된 것이다.</p>
<br/>


<p>처음에는 해당 문제를 크게 인지하지 못했다가 구현하게 되니 문제를 인지할 수 있게 되어 Key를 작품 ID가 아닌 초대 코드로 두고 하위에 작품 ID를 두는 것으로 데이터 구조 변경 예정이다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<p><img src="https://velog.velcdn.com/images/hee_mm_/post/3504fe9d-8e40-4eaa-890a-c1fa5f9d1255/image.gif" alt=""></p>
<br/>

<p>구조 변경 후 앱에서의 연결을 진행하였다.
<br/></p>
<p>유저 데이터에 추가된 초대 코드 저장 및 로그인 시 로드 후 Prefs로 저장, 초대된 작품 정보 앱 시작 시 가져오기와 초대 코드 등록 후 화면에 등록하는 등 생각보다 추가적으로 진행해야 하는 것들이 많아 시간이 걸렸지만 만족스럽게 작동한다!
<br/></p>
<br/>

<p>그 과정 속에서 초대 작품을 가져오는 기능을 추가하면서 초대 코드들이 <code>List&lt;String&gt;</code>으로 저장되어 있어 해당 방법을 찾는 데 조금 시간이 걸리기도 했다. 해결 방법은 아래와 같이 비교하여 사용하면 리스트 와 리스트를 대조해 결과를 얻을 수 있었다.</p>
<br/>

<pre><code class="language-dart">  Future&lt;QuerySnapshot&gt; getInviteWork() async {
    final inviteWorks = PrefsUtils.getStringList(PrefsUtils.inviteWork);

    return await firebaseStore.store
        .collection(&#39;work&#39;)
        .where(&quot;inviteCode&quot;, whereIn: inviteWorks) 
        .get();
  }</code></pre>
<br/>

<p>위 코드에서 사용된 <code>whereIn</code>은 배열 형태로 여러 값을 받아서 해당 필드가 그중 하나와 일치하는지를 검사하는 기능이다.</p>
<br/>

<p><span style='color:gray'>사실 오래 걸린 이유는 Work에서 사용하는 필드명이 아닌 User에서 필드명으로 적용해서 나온 문제였지만... 덕분에 오늘도 꼼꼼히 확인하는 습관을 늘려야지.. 하며 도움이 된 것 같다.</span></p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-25일">11월 25일</h2>
<hr>
<p>작품 공유 후속으로 감상에 대한 공유도 진행하고 있다, 다만 고민인 것이 생겼다면 감상의 현 데이터 구조로는 바로 원하는 값을 가져올 수 없다는 것.</p>
<br/>
<br/>

<p>데이터 구조는 초대 코드를 가지고 있지 않기 때문에 가져오기 위해서는 작품의 ID가 필요하다. 
<br/></p>
<p>현재 상태로는 개발 방법은 3가지 정도 있다.</p>
<ul>
<li>공유 작품 화면 상세 화면에 들어가야지만 데이터 가져와서 보이고 저장하기</li>
<li>메인화면에서 일괄적으로 가져오지만 각 본인 작성 데이터와 타인 작성 데이터를 따로 저장하기 ( 해당 방법 선택 시 초기 세팅이 조금 복잡 )</li>
<li>감상 데이터에도 초대 코드 넣기</li>
</ul>
<br/>
<br/>

<p>굳이 데이터를 쌓지 않고 필요시에만 가져오는 첫 번째 방안이 그나마 좋은 방향인 것 같아 지금은 추가 좀 기울어져 있지만 위 방법 중 어느 방향으로 진행할지 고민이다. </p>
<br/>
<br/>
<br/>



<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee_mm_/post/3bbbb677-c146-4a85-a931-5d0789205d55/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/hee_mm_/post/121c0729-a9be-47e8-8b10-0e7f409f4865/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/hee_mm_/post/28a4edd7-7631-4623-8e86-8ec1ed0a988d/image.png" alt=""></th>
</tr>
</thead>
</table>
<p>결국 첫 번째 방법을 이용해 개발하였다.
<br/>
공유된 작품에 대한 삭제나 타 유저가 작성한 감상에 대한 수정 및 삭제 불가능하도록 분기 처리를 진행하고 동시에 공유된 작품, 감상인지 유저가 구분할 수 없는 UI였기에 각 위젯 하단에 공유 표시를 보이도록 UI 쪽도 함께 개선 진행하도록 하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[소감기 프로젝트 개발기 - 2주차]]></title>
            <link>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-2%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-2%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Mon, 13 Nov 2023 01:16:29 GMT</pubDate>
            <description><![CDATA[<h1 id="소감기-프로젝트-🐂">소감기 프로젝트 🐂</h1>
<p>웹 소설이나 출판 소설, 게임 스토리 등 내용을 담은 글이라면 모든 상관없이 글을 읽고 느낀 감정에 대한 기록을 남기는 앱.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h2 id="11월-12일">11월 12일</h2>
<hr>
<p>오늘은 UI 구현만을 진행하였다, PopupMenuButton 라는 Widget을 처음 사용해 보았는데 상당히 유용하다. 다만 문제가 있다면 알 수 없는 오른쪽 패딩이 잡힌다는 것이다... 오늘은 이 문제와 함께 시간을 보내게 될 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/37e09ccf-0d2f-4fd5-a601-39d177e95e8a/image.png" alt=""></p>
<br/>


<p>그와중에도 나름 오랫동안 플러터를 사용했다고 생각하다가도 이렇게 모르던 위젯을 계속해서 발견하는 것을 보면 플러터에서 제공하는 위젯이 정말 다양하구나 싶기도 하다.</p>
<br/>
<br/>
<br/>
<br/>

<p>오른쪽 패딩 해결 문제를 찾기는 하였다, 상위 위젯인 PopupMenuButton에 constraints를 적용하는 방법이다. 다만 근본적인 해결 방안은 아니기에 무엇때문에 해당 문제가 발생하였는지 더욱 찾아봐야겠다.
<br/></p>
<pre><code class="language-dart">PopupMenuButton(
        elevation: 0,
        color: Colors.transparent,
        padding: EdgeInsets.zero,
        constraints: const BoxConstraints.tightFor(width: 94), // 해당 라인 추가
        itemBuilder: (context) =&gt; [
          PopupMenuItem(
              padding: EdgeInsets.zero,
              child: WritePopMenuItem(
                text: &#39;감상\n추가&#39;,
                backgroundColor:
                DesignSystem.colors.appPrimary, isLast: false,
              )),
        ],
        icon: Icon(
          Icons.edit,
          color: DesignSystem.colors.white,
          size: 28,
        )))</code></pre>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h2 id="11월-13일">11월 13일</h2>
<hr>
<p>어제 발견한 PopupMenuItem 문제는 내부 코드를 확인하여도 padding은 상위에서 전달된 패딩대로 <code>EdgeInsets.zero</code> 로 적용이 되고 있다. padding 설정을 null 처리를 진행해도 여전히 패딩은 들어가 있고, 다른 설정들을 전부 무효화 처리해도 들어가 있다. </p>
<br/>

<p>dartPad에 해당 코드를 간략화하여 돌려보아도 여전히 패딩이 생겨 일단 flutter Github 이슈로 올려두었다.</p>
<blockquote>
<p><a href="https://github.com/flutter/flutter/issues/138335#issuecomment-1808267809">https://github.com/flutter/flutter/issues/138335#issuecomment-1808267809</a></p>
</blockquote>
<br/>
<br/>
<br/>



<p><img src="https://velog.velcdn.com/images/hee_mm_/post/9635b8d5-1e9d-4592-8ff9-a4e52db26704/image.gif" alt=""></p>
<p><span style='color:gray'> GIF로 올리느라 마지막 홈 화면 데이터 적용 부분이 바로 잘린다...</span></p>
<br/>
<br/>

<p>오늘은 작품 생성 기능 구현 및 메인 화면에 작품 리스트를 간단하게 뿌리는 것까지 작업 진행하였다. Cloud Firestore와 Realtime Database 사용도 아직 버벅거리긴 하지만 그리 복잡스럽지는 않아 오늘은 큰 어려움 없이 빠르게 개발 진행할 수 있었다.</p>
<br/>

<p>커스텀 카테고리 기능에 대해서는 서버에 유저 하위로 넣을지 아니면 앱 내부 저장소로 넣을지가 고민인 상태여서 현재는 미구현된 상태이다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h2 id="11월-14일">11월 14일</h2>
<hr>
<p>커스텀 카테고리는 앱 삭제후 이전에 저장한 커스텀 카테고리가 없으면 불편하게 느낄 수도 있을 것 같아 내부 저장이 아닌 서버 유저 정보 하위에 <code>List&lt;String&gt;</code> 형식으로 저장하기로 정하였다.</p>
<br/>

<p>추가로 오후 중에는 오전 중에 하지 못한 운동과 팀으로 진행하고 있는 사이드 프로젝트에 더욱 속도를 붙이기 위해 오전 중에 미리 몇 가지 UI만 만들어두었다.작업들 처리하고 시간이 남는다면 커스텀 카테고리는 서버 연결을 진행해야겠다. </p>
<br/>

<p>CustomClipper는 이전보다 도형 만드는 시간은 줄어들었지만 아직까지 곡선 만들기는 헷갈리기에 지속적으로 경험을 쌓아야겠다.</p>
<br/>
<br/>

<p><span style='color:gray'>오늘의 작업 UI</span></p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee_mm_/post/858ad9ec-9748-4585-856d-8883c09e3488/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/hee_mm_/post/adb85617-f350-4594-88c8-cb54ec576414/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/hee_mm_/post/f4d372a0-cd80-4707-ae8c-2be2b2feb45a/image.png" alt=""></th>
</tr>
</thead>
</table>
<br/>
<br/>


<p>➕ 어제 Flutter 이슈에 올린 PopupMenuItem 문제에 관해 추가 정보가 있는지 물어봐 주어서 관련해서도 나름대로 더 찾아 답변해야겠다... Flutter 측에 직접적으로 이슈 올리고 주고받는 경험이 처음이라 상당히 떨리지만 이것도 개발하면서 느낄 수 있는 재미 아니겠는가. 💪</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h2 id="11월-15일">11월 15일</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/3d62f7b5-6a5c-4f4d-b664-24c2b81ef932/image.gif" alt=""></p>
<br/>

<p>커스텀 카테고리 기능 개발하였다. 처음에는 서버에만 저장하고 사용하려고 했지만 그렇게 된다면 계속 서버에 정보를 요청해야 하기에 서버와 내부 Prefs에 동시에 저장해서 사용하도록 진행하였고, 만약 앱을 삭제하게 된다면 앱  로그인 시에 가져와 저장하는 진행하였다. 
<br/></p>
<p>UI적으로도 변경을 진행하였는데 기존 Radio UI를 만들어 사용하는 형식에서 Chip을 사용하고 그 상위를 Wrap으로 감싸 카데고리 길이가 넘치는 것에 따라 다음 줄로 넘어가도록 개발하였다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h2 id="11월-16일">11월 16일</h2>
<hr>
<br/>

<p>계획했던 팀 사이드 프로젝트를 일정 안에 미리 완성해 두기 위하여 소감기 작업이 조금 밀렸다. 그래도 방금 이번 주 안으로 진행하기로 한 부분은 전부 완성하여 다른 공부와 해당 작업은 큰 문제 없이 진행 가능할 것 같다.</p>
<br/>

<p>그 사이에 Flutter 깃허브에 올린 UI 이슈도 디자인 팀으로 담당이 정해졌고, 작게나마 개선에 도움이 된 것 같아 기쁘다.</p>
<br/>
<br/>
<br/>

<p><img src="https://velog.velcdn.com/images/hee_mm_/post/6e925dbf-da9c-4561-8c23-9af14e661b68/image.gif" alt=""></p>
<br/>

<p>감상 추가 기능을 개발하였다. 기존에 UI도 만들어두었고 FirebaseStore에 올리는 것은 미리 만들어둔 작품 추가 기능과 거의 동일하여 큰 문제 없이 빠르게 개발 진행 가능했다. </p>
<br/>

<p><img src="https://velog.velcdn.com/images/hee_mm_/post/d05f8a14-7d98-44b4-b483-d70d5e2004fa/image.png" alt=""></p>
<br/>
<br/>

<p>➕ 각 작품(work), 감상(report), 유저(user) 데이터는 데이터 별 개별로 호출되어야하는 부분이 있어 전부 분리되어 작업하고 있다.</p>
<br/>

<p>처음에는 작품 하위로 감상 데이터를 둘까 하였지만 앱 기능 중 최신 감상을 보여주는 기능이 있었고. 해당 기능을 생각하면 감상을 보여주기 위해서 작품 정보 내부의 데이터를 리스트화해 조정한 후 끌어와야만 하는 문제가 있어 분리하여 개발하기로 했다. </p>
<br/>

<p>아직까지는 해당 방안이 현재 기능들에 더 어울린다고 생각하고 있지만 사실 모호한 부분도 있다고 생각한다. 더욱 나은 개선방향이 있을 것이라고 생각하는데 현재 실력으로는 이렇다 할 개선 방안이 생각나지 않는다. 어떤 방안이 더욱 나은 형태일까? 🤔</p>
<br/>
<br/>
<br/>
<br/>

<p>예상보다도 더 빠르게 감상 추가 기능이 개발되어서 이미 화면 파일만은 생성되어 있는 검색 화면을 진행해야겠다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h2 id="11월-17일">11월 17일</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/5e48fcaf-0c63-438e-a9a9-a63787a7055e/image.png" alt=""></p>
<p>검색 화면의 결과 중 아래 깃발 모양을 사용할 때 이전에 제작한 홈 화면의 깃발을 사용할 수는 없을까 싶어 자료를 찾아보던 중 RotatedBox 위젯을 발견하였는데 꽤 흥미롭다.  </p>
<br/>

<p>Transform.rotate의 경우는 기존 레이아웃 영역을 유지한 채로 위젯이 움직인다면 해당 위젯은 레이아웃 영역 자체가 바뀌는 형식이다. 지금 상황에서는 쓸 일이 없지만 반응형으로 화면을 생성할 때는 좋은 위젯이 될 것 같다.</p>
<blockquote>
<p><a href="https://api.flutter.dev/flutter/widgets/RotatedBox-class.html">https://api.flutter.dev/flutter/widgets/RotatedBox-class.html</a></p>
</blockquote>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<p>오늘 개발하며 검색 결과의 데이터가 업데이트 되어도 UI가 업데이트 되지 않는 문제가 있었다. 로그를 찍어보아도 데이터는 변경되어 있어 무엇이 문제인지 꽤나 오래 고민했다.</p>
<br/>

<p>문제의 코드는 아래와 같다.</p>
<br/>

<h4 id="컨트롤러-부분">컨트롤러 부분</h4>
<pre><code class="language-dart">  void searchWork(){
    isSearchStart = true; // 서칭이 시작됬음을 구분하는 일반 bool 변수
    resultList.value = workList.where((work){
      switch (filter.value) {
        case &#39;작품명&#39;:
          return work.title.contains(search.text);
        case &#39;카테고리&#39;:
          return work.category.contains(search.text);
        case &#39;작품설명&#39;:
          return work.description.contains(search.text);
        default:
          return false;
    }).toList(); // 서칭 결과를 저장하는 RxList 변수
  }
}</code></pre>
<h4 id="업데이트-되지-않는-ui">업데이트 되지 않는 UI</h4>
<pre><code class="language-dart"> controller.isSearchStart &amp;&amp; controller.resultList.isNotEmpty
                    ? Expanded(
                        child: Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 25),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.stretch,
                            children: [
                              Text(&#39;검색 결과 ${controller.resultList.length}건&#39;,
                                  style: DesignSystem.typography.body()),
                              ListView.builder(
                                  padding: const EdgeInsets.only(top: 12),
                                  shrinkWrap: true,
                                  itemCount: controller.resultList.length,
                                  itemBuilder: (context, index) {
                                    final work =
                                        controller.resultList.elementAt(index);

                                    return GestureDetector(
                                      onTap: () {},
                                      child: SearchListItem(work: work),
                                    );
                                  })
                            ],
                          ),
                        ),
                      )
                    : Center(
                        child: Text(
                          !controller.isSearchStart
                              ? &quot;검색을 진행해주세요&quot; : &quot;검색 결과가 없습니다.&quot;,
                          style: DesignSystem.typography.body(TextStyle(
                              color: DesignSystem.colors.gray700,
                              fontWeight: FontWeight.w400)),
                        ),
                      )</code></pre>
<br/>
<br/>
<br/>

<p>해결하고 나니 사실 정말 간단한 문제였다. UI 코드에서 아래 단락에서 isSearchStart는 Rx로 설정하지 않은 일반 bool 이었기 때문에 GetX에서는 해당 코드를 먼저 보고 변함이 없구나로 인식하여 뒷단의 controller.resultList의 변경사항이 무시된 것이다.</p>
<br/>
<br/>

<pre><code class="language-dart"> controller.isSearchStart &amp;&amp; controller.resultList.isNotEmpty</code></pre>
<br/>
<br/>

<p>둘의 순서를 바꾸고 나니 UI는 문제없이 업데이트 되었다. 오늘도 이렇게 순서의 중요성과 경험을 쌓아간다...</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h2 id="11월-18일">11월 18일</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/708be851-31a5-4720-929f-eca70c75a54d/image.png" alt=""></p>
<p>상세화면 개발 후 내역 없는 깨끗한 감상 작품에서 테스트 하기 위해 신규 작품을 추가 했더니 카테고리 UI 영역을 상위 위젯에서 영역을 잡지 못하고 다른 아이템과 겹쳐지는 위와 같은 문제가 발생하고 있었다. </p>
<p>상위 위젯을 Wrap이 아닌 Colum으로 사용한다면 <code>A RenderFlex overflowed by 47 pixels on the bottom.</code> 에러도 함께 나는 상황이다.</p>
<br/>

<p><img src="https://velog.velcdn.com/images/hee_mm_/post/68cc9afa-9d8b-4ac5-a6e1-473917a4c67f/image.png" alt=""></p>
<p>선택된 위젯이 카테고리 깃발 위젯인데 Inspector를 보면 위젯 트리에는 잘 존재하고 있다...🤔 
dartPad에 적용해보아도 같은 문제가 나오는 걸 보아 오늘 밤의 고민거리는 해당 문제가 되겠다.</p>
<br/>


<br/>
<br/>
<br/>

<p>➕ <strong>뒤늦게 당일 진행했던 사항에 대해 적어본다.</strong></p>
<p>상세 화면을 진행하면서는 사용해보지 않았던 <code>GetView</code>와 <code>Obx</code>를 통해 구현해보았다. </p>
<p><br/><br/></p>
<p><code>GetView</code>는 클래스에서 <code>GetView&lt;Controller&gt;</code> 형식으로 상속하여 build 메서드 안에서 따로 컨트롤러 호출이나 위젯을 사용하지 않아도 컨트롤러에 접근이 가능하고 자동으로 연결을 해준다.
<br/></p>
<p>이로써 코드가 더욱 간결하게 사용이 가능하게 되며 <code>Obx</code>를 통해 접근하고 있는 값이 변경되었을 때만 특정 위젯만 리로드 진행함으로써 상태 관리를 더욱 간결하게 진행할 수 있게 되었다.
<br/></p>
<p>단, 해당 방식을 사용하기 위해서는 미리 Controller가 Get에 포함되어 있어야 하기 때문에 앱 전체에서 사용하는 경우에만 해당 방식을 통해 구현하고 일발성으로 데이터를 생성하고 삭제하기를 반복해야 하는 경우에는 <code>GetBuilder</code>를 통해 init과 함께 진행하는 것이 좋아 보인다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] CustomClipper와 ClipPath]]></title>
            <link>https://velog.io/@hee_mm_/Flutter-CustomClipper%EC%99%80-ClipPath</link>
            <guid>https://velog.io/@hee_mm_/Flutter-CustomClipper%EC%99%80-ClipPath</guid>
            <pubDate>Fri, 10 Nov 2023 05:59:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>CustomClipper&lt;Path&gt;</code>를 기준으로 설명하는 글 입니다.</p>
</blockquote>
  <br/>
    <br/>
    <br/>

<h1 id="customclipper"><strong>CustomClipper?</strong></h1>
<hr>
<p>사용자가 커스텀 한 모양을 그리기 위한 클래스입니다, 예를 들면 쿠키틀과 같은 역할을 한다고 볼 수 있습니다.
    <br/>
   <br/></p>
<p>기본적인 선언 형식으로 추상 클래스인 <code>CustomClipper&lt;T&gt;</code>를 상속받아 사용하며, T는 원하는 모양의 타입을 넣어주면 됩니다.</p>
<ul>
<li>일반적으로 클리핑 모양 타입은 Path를 사용합니다.</li>
<li>필수적으로 <strong>shouldReclip</strong>와 <strong>getClip</strong> 메서드를 재정의 해야만 합니다.  <br/>

</li>
</ul>
<p><span style='color:gray'> 예시 코드 </span></p>
<pre><code class="language-dart">class CustomClipper extends CustomClipper&lt;Path&gt; {
  @override
  Path getClip(Size size) {
    final path = Path();
    // 원하는 경로 추가
    return path;
  }

    @override
  bool shouldReclip(CustomClipper&lt;Path&gt; oldClipper) {
    return false;
  }
}</code></pre>
<p><br/><br/><br/><br/></p>
<h1 id="shouldreclip">shouldReclip</h1>
<p>도양을 다시 그려야 하는지에 대한 여부를 반환하는 메서드입니다.</p>
<br/>

<p>예를 들어 애니메이션 중에 클리핑 모양이 변경되어야 하는 경우 <strong><code>true</code></strong>를 반환하여 다시 모양을 그릴 수 있습니다.</p>
<br/>
<br/><br/><br/><br/>

<h1 id="getclip">getClip</h1>
<p>getClip 메서드는 필수 재정의 메서드이며, 적용시킬 모양을 반환해야 합니다.</p>
<br/>

<p>Size 매개변수를 통해 해당 크기 내에서 클리핑 모양을 그리며, Size 값은 상위 부모 위젯으로부터 받아 사용합니다.</p>
<br/>
<br/>
<br/>
몇 가지 Path 기초 메서드를 추가로 알아보도록 하겠습니다.
<br/>
<br/>
<br/>


<h2 id="moveto">moveTo</h2>
<p>현재 위치를 지정 좌표로 이동시키는 메서드입니다.
<br/>
x, y의 순서로 좌표가 적용되며, 일반적으로는 해당 메서드를 이용하여 시작점을 설정합니다.</p>
<ul>
<li>이때 좌표를 이동시키기만 할 뿐 선을 잇는다거나 하는 액션은 진행되지 않습니다.</li>
</ul>
<br/>

<p><span style='color:gray'> 시작점을 0, 0 으로 사용하신다면 해당 메서드로 시작점을 설정하실 필요는 없습니다. </span>
<br/></p>
<pre><code class="language-dart">path.moveTo(50, 50); // 좌표를 (x, y)으로 이동</code></pre>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/6fcbbe89-af4f-4f31-874a-68c8f25940d0/image.png" alt=""></p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="lineto">lineTo</h2>
<p>현재 좌표에서 지정한 좌표까지 직선을 그리는 메서드입니다.
<br/></p>
<pre><code class="language-dart">path.lineTo(150, 50); // 현 좌표에서 (x, y)까지 직선 그리기</code></pre>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/74791098-e1c4-4a5b-9447-23f4cc78b6ab/image.png" alt=""></p>
<br/>
<br/>

<p><strong>해당 함수의 경우 단일로 사용되었을 경우 UI에서는 아무것도 보이지 않습니다.</strong></p>
<ul>
<li>오직 단일 선 만으로 이루어져 면으로는 보이지 않음</li>
</ul>
<br/>
<br/>
<br/>

<h3 id="예시-코드-및-구현-이미지">예시 코드 및 구현 이미지</h3>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/dff4f5db-374a-45b6-8130-bef35f28f142/image.png" alt=""></p>
<pre><code class="language-dart">
// 단일 lineTo 예제
class OneLineToClipper extends CustomClipper&lt;Path&gt; {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.lineTo(150, 50);
    path.close();

    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper&lt;Path&gt; oldClipper) {
    return false;
  }
}</code></pre>
<pre><code class="language-dart">// 2중 lineTo 예제
class TwoLineToClipper extends CustomClipper&lt;Path&gt; {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.lineTo(150, 50);
    path.lineTo(0, 50);
    path.close();

    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper&lt;Path&gt; oldClipper) {
    return false;
  }
}</code></pre>
<br/>
<br/>
<br/>

<br/>
<br/>
<br/>

<h2 id="quadraticbezierto">quadraticBezierTo</h2>
<p>현재 위치에서 제어점과 끝 점을 이용해 곡선을 그리는 메서드입니다.</p>
<br/>

<pre><code class="language-dart">path.quadraticBezierTo(100, 100, 150, 0); 
// 현 좌표에서 (x1, y1 x2, y2) 곡선 그리기</code></pre>
<br/>

<p>이때 x1, y1은 곡선의 형태와 방향을 제어하는 제어점의 x y 좌표이며,  x2, y2는 끝 점의 좌표입니다.</p>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/e5bae500-d73c-4d7c-8099-10fa9fcd15fa/image.png" alt=""></p>
<br/>
<br/>
<br/>

<h3 id="예시-코드-및-구현-이미지-1">예시 코드 및 구현 이미지</h3>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/9b50fa37-d074-40ff-ad08-7ef1f5836961/image.png" alt=""></p>
<pre><code class="language-dart">class QuadraticBezierToClipper extends CustomClipper&lt;Path&gt; {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.quadraticBezierTo(100, 100, 150, 0);
    path.close();

    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper&lt;Path&gt; oldClipper) {
    return false;
  }
}</code></pre>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h3 id="close">close</h3>
<p>현재 좌표에서 시작점을 연결하여 경로를 닫는 역할을 합니다. </p>
<br/>

<p>필수 메서드는 아니지만 명시적 하기 위하여 작성해 주는 것이 좋습니다.</p>
<br/>

<pre><code class="language-dart">path.close();</code></pre>
<blockquote>
<p>더욱 다양한 Path 메소드는 공식 문서로 확인 가능합니다. <br/>
<a href="https://api.flutter.dev/flutter/dart-ui/Path-class.html">https://api.flutter.dev/flutter/dart-ui/Path-class.html</a></p>
</blockquote>
<br/>
<br/>
<br/>
<br/>
<br/>

<h3 id="➕-path의-그려지는-순서">➕ Path의 그려지는 순서</h3>
<p>Path는 별도로 정해진 순서 없이 사용자가 정의한 순서대로 선을 이어줍니다, 이는 예제를 통해 쉽게 알아볼 수 있습니다.</p>
<br/>

<p>아래는 다이아몬드 다각형을 그리는 실제 코드와 빌드로 통해 나타는 결과입니다.</p>
<ul>
<li><span style='color:gray'>PolygonClipper 의 부모 위젯의 크기는 150*150의 위젯입니다.</span></li>
</ul>
<br/>

<p><img src="https://velog.velcdn.com/images/hee_mm_/post/034768db-ea1e-46c9-a0de-76b112fe8c50/image.png" alt=""></p>
<br/>

<pre><code class="language-dart">class PolygonClipper extends CustomClipper&lt;Path&gt; {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.moveTo(50, 0); // 시작점 세팅
    path.lineTo(100, 0);
    path.lineTo(150, 50);
    path.lineTo(75, 100);
    path.lineTo(0, 50);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper&lt;Path&gt; oldClipper) {
    return false;
  }
}</code></pre>
<br/>

<p>해당 결과물 위에 선과 좌표, 순서를 참고용으로 적어둔 후 알아보면 아래 같으며 코드와 같이 시계 방향대로 그리고 있음을 알 수 있습니다.</p>
<br/>

<p><img src="https://velog.velcdn.com/images/hee_mm_/post/25b88cad-1041-4017-b432-6798c2e250b4/image.png" alt=""></p>
<br/>

<p>그러면 점을 그리는 시작점과 반시계 방향으로 순서를 바꿔보겠습니다.</p>
<br/>

<pre><code class="language-dart">class PolygonClipper extends CustomClipper&lt;Path&gt; {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.moveTo(75, 100); // 시작점 세팅
    path.lineTo(150, 50);
    path.lineTo(100, 0);
    path.lineTo(50, 0);
    path.lineTo(0, 50);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper&lt;Path&gt; oldClipper) {
    return false;
  }
}</code></pre>
<br/>

<p><img src="blob:https://velog.io/f22fe473-dc30-4a21-8dc4-97701907f7b3" alt="업로드중.."></p>
<p>시작점과 순서가 바뀌었음에도 그리는 도형의 형태는 여전함을 알 수 있습니다, 이처럼 해당 Path에서는 그리는 시작점과 그리는 방향의 중요성보다는 좌표값을 인지하고 원하는 위치에 지정하는 것이 휠씬 중요함을 알 수 있습니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h1 id="clippath"><strong>ClipPath?</strong></h1>
<hr>
<p>customCliper로 정의한 모양을 사용하여 하위 위젯의 모양을 클리핑 할 수 있는 위젯입니다.</p>
<br/>

<p>클리핑 클래스로 만든 쿠키틀을 실제로 찍어낸 후의 결과를 보여주는 위젯이라고 볼 수 있습니다.</p>
<pre><code class="language-dart">ClipPath(
  clipper: CustomClipper(), // 적용할 클리핑 클래스
  child: Container(   // 클리핑 적용되는 자식 위젯
                width: 250,
        height: 250,
        color: Colors.yellow,
  ),
)</code></pre>
<br/>
<br/>

<blockquote>
<p>공식 영상을 통해 이미지와 함께 간단한 설명을 확인할 수 있는 아래 영상도 존재하니 필요시 확인하면 좋을 듯합니다.</p>
<p><a href="https://www.youtube.com/watch?v=oAUebVIb-7s">https://www.youtube.com/watch?v=oAUebVIb-7s</a></p>
</blockquote>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="➕-좀-더-편하게-사용할-수는-없나요">➕ 좀 더 편하게 사용할 수는 없나요?</h2>
<p>다양한 CustomClipper를 제공해 주는 <strong>flutter_custom_clippers</strong> 패키지가 존재하니 해당 패키지에 필요한 Clipper가 존재한다면 해당 패키지를 사용하는 것 또한 개발 시간을 줄 일 수 있는 하나의 방법입니다.</p>
<br/>
<br/>
<br/>

<blockquote>
<p><strong>flutter_custom_clippers 패키지 링크</strong></p>
<p><a href="https://pub.dev/packages/flutter_custom_clippers">https://pub.dev/packages/flutter_custom_clippers</a></p>
</blockquote>
<br/>
<br/>
<br/>

<hr>
<p>해당 코드들의 전문은 <a href="https://github.com/LIMMIHEE/velog-exercise/tree/main/lib/presentation/feat/bloc_exercise">Github 레파지토리</a>에서 확인 가능합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[소감기 프로젝트 개발기 - 1주차]]></title>
            <link>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-1</link>
            <guid>https://velog.io/@hee_mm_/%EC%86%8C%EA%B0%90%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0-1</guid>
            <pubDate>Mon, 06 Nov 2023 01:46:27 GMT</pubDate>
            <description><![CDATA[<h1 id="소감기-프로젝트-🐂">소감기 프로젝트 🐂</h1>
<p>웹 소설이나 출판 소설, 게임 스토리 등 내용을 담은 글이라면 모든 상관없이 글을 읽고 느낀 감정에 대한 기록을 남기는 앱.</p>
<br/>


<span style='color:gray'>
프로젝트 명은 작명 센스가 없는 편이어서 계기가 된 웹 소설 + 감정 기록을 줄인 줄임말로 지었다.</span>


<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="기획-계기">기획 계기</h2>
<p>사실 큰 계기는 아니다, 밤중에 웹 소설을 읽으며 시간을 보내던 와중 내용이 너무 좋아 감정을 주체하기 힘들어 자체적으로 책 보기를 그만두고 차분해지는 시간을 가졌는데 뭔가 아쉽다는 생각이 들었다.
<br/>
개인적으로는 무언가를 좋아한다는 감정은 언젠가 사라지는 것이 대부분이기도 하며, 특히 책에 대한 내용의 경우 여러 책을 읽다 보니 점점 휘발성이 강해져서 잊히는 것이 개인적으로는 빠르게 진행되는 편이어서 특히나 이 정도로 격한 감정의 기억을 잊게 된다는 것은 아쉬웠다.
<br/>
그중 몇몇 책들은 너무나 좋고 감동받았던 기억만은 남아있지만 내용이 흐릿하게만 기억난다거나 제목을 기억하지 못하는 경우가 잦아 나중에 다시 보고 싶어 찾기도 어려웠던 경험들이 생각나기도 했고 말이다.
<br/></p>
<p>이런 감정의 격함이나 나중에 다시 보고 싶어질 것 같은 책에 대한 감상을 저장해두는 앱을 만들어두면 좋겠구나 싶어 기획하고 개발을 진행하고자 한다. 사실 이 글을 쓰는 와중에도 점점 적어두고 싶은 후보가 만들어지고 있어 벌써부터 기대될 따름이다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="기능-및-컨셉-큰-틀-정리">기능 및 컨셉 큰 틀 정리</h2>
<p>일단 본격적인 기획에 들어가기 전 큰 기능 틀을 만들고 제작하는 것이 이후 개발의 방향성을 정하고 디자인의 방향성을 정하는 데에 큰 도움이 된다는 걸 전 프로젝트 진행 경험을 통해 얻었기에 이에 대한 내용 정리를 먼저 진행하고자 한다.</p>
<br/>
<br/>
<br/>


<h3 id="컨셉">컨셉</h3>
<p>디자인 주제와 전체적인 기능의 컨셉은 서점 + 노트에서 따오도록 한다.</p>
<ul>
<li>브라운 톤 색상, 블랙 앤 화이트</li>
<li>노트는 넘기는 듯한 애니메이션 혹은 디자인</li>
<li>베스트셀러 진열대</li>
<li>카테고리별 진열대</li>
</ul>
<br/>
<br/>

<h4 id="베스트-셀러-진열대"><strong>베스트 셀러 진열대</strong></h4>
<p>서점 베스트 셀러 코너 DP와 같이 기록 작품들을 한눈에 볼 수 있도록 한다, 위젯 클릭 시 작품별 관련 기록을 한 번에 확인할 수 있는 리스트와 Item 선택 시 기록에 대한 상세 정보를 볼 수 있는 화면을 보여준다.</p>
<ul>
<li>해당 화면의 경우 설정 화면에서 카테고리 별로 칸을 나누어서 볼지, 카테고리 구분 없이 기록순으로 볼지 설정 가능</li>
</ul>
<br/>
<br/>

<h4 id="가판대"><strong>가판대</strong></h4>
<p>서점에서 책의 내용을 미리 확인할 수 있는 것처럼 기록의 최신순으로도 내역을 간단하게 확인할 수 있으며 진입 시에 해당 기록 상세로 바로 들어갈 것인지 기록 리스트로 이동할지에 대한 선택을 할 수 있도록 한다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<h3 id="기능">기능</h3>
<ul>
<li>감정 기록<ul>
<li>타이핑도 가능하지만 STT를 통한 음성의 텍스트화 지원</li>
</ul>
</li>
<li>검색<ul>
<li>기록 내역</li>
<li>작품명 ( ex. 웹 소설 제목 )</li>
<li>작품명 카테고리 ( ex. 웹 소설, 게임 시나리오, 웹툰 )</li>
<li>카테고리 별 선택 검색</li>
</ul>
</li>
<li>기록 최신 히스토리</li>
<li>작품 모음<ul>
<li>카테고리 별  보기 ( 기본 )</li>
<li>기록순으로 보기</li>
</ul>
</li>
</ul>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<p>개발기는 1주~ 2주에 한 번 월요일에 블로그에 글을 올린 후 그 기간 동안은 같은 글에서 지속적으로 작업 내용이나 개발하며 부딪히게 된 점 들을 적으며 진행하게 될 것 같다. </p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<br/>
<br/>
<br/>

<h2 id="11월-6일">11월 6일</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hee_mm_/post/1e58cd36-c4c5-46b7-95cc-f561ee7f21b9/image.png" alt=""></p>
<p>기획한 기능과 컨셉을 토대로 디자인을 진행하고 있다, 다만 바텀바의 대한 디자인에서 컨셉를 챙길지 깔끔함을 챙길지 고민이 된다.
<br/></p>
<p>전자의 경우 특색있는 디자인에 가깝지만 피로도가 꽤 짙어진다.
후자의 경우 깔끔하기에 피로도가 다소 적어지고 내부 디자인에도 무리가 없을 것 같지만 또 컨셉를 생각하면 전자가 나아 보이기도 하고... 
<br/></p>
<p>일단락 유저 사용성을 생각하여 후자로 진행할 예정이지만 점차 진행하며 확정 지으면 좋을 듯하다.</p>
<br/>
<br/>
<br/>

<p>동시에 같은 작품에 대하여 함께 읽고 감상을 남기는 것도 이용을 지속적으로 하게 하거나 앱 사용량을 늘릴 수 있을 것 같다는 생각이 들었다.</p>
<p>다만 이 방안의 경우 서버 연결과 뒷 동작 방식이 꽤 복잡해지니 방법을 강구해야 할 것 같다. </p>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-7일">11월 7일</h2>
<hr>
<p>이번 프로젝트의 경우 GetX를 이용하여 진행해 보고자 한다, 아직까지 사용해 본 적이 없는 상태 관리 패키지이기에 도전해 보고 싶은 것도 있으며 생각보다 큰 리소스가 들 것 같아 생산성을 줄이기 위해서 이기도 하다.
<br/></p>
<p>서버의 경우 FireBase를 이용하게 될 것 같다, 미리 데이터 구조 등을 짜기 시작해야 이후 앱 개발하면서도 편하게 진행할 수 있기 때문에 현재 진행 중인 디자인을 멈추고 앱의 간단한 스토리 보드를 먼저 짠 후 어떤 데이터들을 각 화면에서 사용할지를 정리하고 모델을 만들어 두는 것에 집중해야겠다.</p>
<br/>

<p>FireBase 참고 문서로는 아래 링크들을 참고하면 좋을 것 같다.</p>
<blockquote>
<p>Firebase 데이터베이스 REST API 문서
<a href="https://firebase.google.com/docs/reference/rest/database?hl=ko">https://firebase.google.com/docs/reference/rest/database?hl=ko</a> <br/></p>
</blockquote>
<br/>
<br/>
<br/>
<br/>

<p><img src="https://velog.velcdn.com/images/hee_mm_/post/8a31a4eb-bdd9-48f1-a82c-3b7451f4d902/image.png" alt=""></p>
<p>간단한 스토리보드 제작과 모델링 다이어그램은 제작 완료하였고, 프로젝트 구조 또한 Clean Architecture를 참고하여 아래와 같이 잡았다.</p>
<br/>

<h3 id="프로젝트-구조">프로젝트 구조</h3>
<br/>

<p><strong>lib/config</strong></p>
<ul>
<li>앱 구성에 필요한 설정을 가지고 있는 폴더. </li>
</ul>
<p><strong>lib/controllers</strong></p>
<ul>
<li>GetX의 Controller를 저장하여 상태 관리를 진행하는 폴더.</li>
</ul>
<p><strong>lib/data</strong></p>
<ul>
<li>Data Class를 보관할 models 폴더, 
실제 API 호출 및 관리를 진행하는 providers 폴더, UI와 데이터 소스를 분리하기 위한 repositories 폴더를 가지고 있는 앱의 데이터 폴더. </li>
</ul>
<p><strong>lib/view</strong></p>
<ul>
<li>전체적인 화면을 가지고 있는 screen 폴더와 부분적으로 사용되는 widget을 각 화면별로 구분하여 저장할 폴더. </li>
</ul>
<br/>
<br/>

<p>위 구조를 적용한 프로젝트는 깃허브에 올려두었으니, 세부적인 디자인 잡기와 GetX 사용법을 공부하며 천천히 개발을 진행하면 될 듯하다.</p>
<blockquote>
<p>프로젝트 깃허브 링크
<a href="https://github.com/LIMMIHEE/cow_cold">https://github.com/LIMMIHEE/cow_cold</a></p>
</blockquote>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-8일">11월 8일</h2>
<hr>
<p>디자인을 어느 정도 세부화한 후 프로젝트 로그인 / 로그아웃 기능을 제작하고 있다.
<br/></p>
<p>본래 Firebase 실시간 데이터베이스와 Authentication만 사용하려 하였지만 회원가입 단계에서 이메일 중복 확인을 회원가입 시도 없이 진행하려면 Cloud Firestore에 유저 정보를 미리 저장해둔 후 가져와 중복 체크하는 방향으로 진행되어야 하기에 어쩌다 보니 해당 기능까지 적용하게 되었다. 😵‍💫</p>
<br/>

<p>추가로 아직 GetX는 화면 이동, 간단한 컨트롤러 기능, 스낵바 정도만 사용해 보았는데 어째서 개발자들이 자주 쓰는지, 간단한 기능을 만들때 추천하는지 알 것 같다. 확실히 코드가 줄어 생선성이 늘어나고 context를 따로 신경 쓰지 않아도 되는 점이 꽤나 편리하다.</p>
<br/>

<p>꽤나 헤매가며 제작하고 있지만 다른 상태 관리와 비교하면서 진행하니 꽤 비슷하면서도 다른 듯한 느낌이다, Provider와 Bloc를 적절히 섞어놓은 느낌이랄까...🤔</p>
<br/>
<br/>
<br/>

<p>체력이 방전되어 오늘의 작업은 마무리하려 한다.</p>
<p>오늘은 Figma 디자인 세부화 + 개발은 스플래시 화면, 시작 및 회원가입 화면 제작 후 Firebase Authentication을 통해 회원가입 기능 개발하였다.
<br/></p>
<br/>
<br/>
<br/>
<br/>
<br/>


<h2 id="11월-9일">11월 9일</h2>
<hr>
<p>어제는 회원가입 기능을 제작할 때 GetX의 특징을 사용하지 않은 단순 상태 관리를 적용하여 아래 예시와 같이 거의 provider와 같은 구조로 개발하였는데</p>
<br/>


<pre><code class="language-dart">class ExampleController extends GetxController {
  int counter = 0;

  void increase() {
    counter++;
    update();
  }
}</code></pre>
<p>오늘은 로그인 기능 개발을 진행하며 GetX의 .obs 설정을 적용해 보았다.
아래와 같이 설정만 해주어도 데이터가 변경되었음을 알리는 <code>update()</code> 를 부르지 않아도 UI 측에서 자동으로 업데이트된다.
<br/></p>
<pre><code class="language-dart">class ExampleController extends GetxController {
  RxInt counter = 0.obs;

  void increase() {
    counter++;
  }
}</code></pre>
<br/>

<p>굳이 개발자가 알리지 않아도 자동으로 업데이트된다는 점에서는 좋지만 아무래도 값이 바뀐 뒤 바로 적용되면 안 되는 상황에서는 전자의 방법을 쓰는 것이 좋을 것일까 생각하니 좋은 생각거리가 되었다.</p>
<p>현재까지는 바로 적용되어야 하는 화면들이기에 어제 개발한 회원가입도. obs  적용 개선 진행하여야겠다.</p>
<br/>
<br/>
<br/>
<br/>


<p><img src="https://velog.velcdn.com/images/hee_mm_/post/2062b254-15bf-4268-8aa3-3d3086d98e1d/image.png" alt=""></p>
<p>현재 바텀 네비게이터를 개발하고 있다.</p>
<p>탭 바의 모양이 특이하여 클리퍼 위젯을 이용한 개발이 필요하지만 현재까지는 해당 기능을 사용할 일이 적었기에 그다지 알고있는 것이 없고 잠시 코드와 대치 중인 상황이다.</p>
<p>이 김에 해당 기능에 대한 사용방법을 공부하고 진행해야겠다. </p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-10일">11월 10일</h2>
<hr>
<p>기획에 필요한 CustomClipper와 ClipPath에 대하여 간단하게 공부와 코드를 통한 실습을 진행하고 복습하기 위해 내용 정리 후 블로그에 글을 올려두었다.
<br/></p>
<p><a href="https://velog.io/@hee_mm_/Flutter-CustomClipper%EC%99%80-ClipPath">https://velog.io/@hee_mm_/Flutter-CustomClipper%EC%99%80-ClipPath</a></p>
<br/>

<p>확실히 글을 쓰면 할 일이 더 생길지언정 머릿속에 더 잘 남아지기도 하고 나중에 기억이 사라지거나 헷갈리는 일이 생겼을 때 큰 도움이 되기에 최근에는 자주 글을 쓰게 되는 것 같다.</p>
<p>잡설은 이쯤하고 실제 프로젝트에 적용해 봐야겠다.</p>
<br/>

<p>추가로 7일날 적었던 공통 감상 작성에 대한 아이디어에 대해서는 작성하는 작품별 초대 코드를 생성하고 작품별 별도로 초대된 유저 ID를 저장하는 필드를 만들어 진행하는 방향으로 진행하려 한다.</p>
<br/>
<br/>
<br/>
<br/>


<p><img src="https://velog.velcdn.com/images/hee_mm_/post/93fe1ddd-ac39-4487-8458-672ed72671ed/image.gif" alt=""></p>
<br/>

<p>바텀바 디자인을 프로젝트에 적용 진행하였다.
<br/></p>
<p>다만 탭을 변경할 때 딱딱한 느낌이 없지 않아 들어 별도의 애니메이션을 적용해 주면 좋을 것 같다. 현시점에서는 이거다 싶은 것이 떠오르지 않아 조금 더 자료를 찾아보고 이후에 적용해야겠다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="11월-11일">11월 11일</h2>
<hr>
<p>회원가입 시 Cloud Firestore에 유저 ID만 추가 하지 않고 User Model 클래스를 제작하여 해당 클래스 데이터 형식에 맞춰 업로드 하도록 개선과 더불어 Firebase Realtime Database 연결을 진행하였다.</p>
<br/>

<p>Realtime Database를 사용하지 않고 Cloud Firestore만을 이용해 개발하는 것도 가능하고 한 가지 기능만 사용하는 것이 더 통일된 방법이지만 Firebase를 적극적으로 활용하는 프로젝트이기에 이김에 해당 기능까지 이용해 보기로 하였다.</p>
<br/>
<br/>
<br/>

<p>추가로 GetxController에서 직접적으로 Firebase를 통해 업로드 진행하던 것을 각 데이터 별 Repository, Provider를 제작하여 분리하여 사용하도록 개선을 진행하였는데... </p>
<p>Provider까지 분리 적용하기에는 Firebase를 통해 요청만 보내는 것이므로 모호하다고 느끼다가도 혹시 모를 이후 기능 추가를 위해 파일을 분리하기로 하였다.</p>
<br/>
<br/>



<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 오픈소스 패키지 기여 회고]]></title>
            <link>https://velog.io/@hee_mm_/Flutter-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%ED%8C%A8%ED%82%A4%EC%A7%80-%EA%B8%B0%EC%97%AC-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@hee_mm_/Flutter-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%ED%8C%A8%ED%82%A4%EC%A7%80-%EA%B8%B0%EC%97%AC-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 02 Nov 2023 06:46:41 GMT</pubDate>
            <description><![CDATA[<p>최근 어쩌다 보니 필요에 의해 오픈 소스 패키지에 작은 기여를 하게 되었다.</p>
<br/>

<p>이러한 경험들은 개발자로서도 색다르기도 하였고 협업하는 과정에 대한 깨달음을 얻기도 하여서 이에 대한 생각과 경험을 담아 본문은 해당 기여 내용과 각 느낀 점을 적어가며 회고하는 글이 되겠다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<h1 id="flutter_wechat_assets_picker">flutter_wechat_assets_picker</h1>
<hr>
<p>해당 패키지는 핸드폰의 이미지나 동영상 등의 정보를 가져오거나 카메라로 촬영하는 등의 기능을 제공하는 패키지이다.</p>
<br/>
<br/>

<h2 id="기여-계기">기여 계기</h2>
<p>당시 이미지를 가져오거나 사진을 찍는 등의 기능이 필요하여 가능한 적은 리소스로 제작하기 위해 이미지 가져오기에 대한 여러 패키지를 비교 후 사용하게 된 패키지였다.</p>
<br/>

<p>다만, 다른 원하는 기능이 거의 다 부합하였지만 아쉽게도 한국어가 지원하지 않는 상태였다는 것이 문제였다.</p>
<br/>

<p>한국어 지원에 관한 PR이 존재는 하였지만 일부분만 진행된 후 더 이상 진행된 바가 없는 상태였고, 이를 참조하여 이전 개발자가 올린 한국어 번역을 사용자가 부담을 느끼지 않는 방향으로 더욱 보완한 다음 PR 규칙을 따라 코드 점검 후 PR을 올리게 되었다.</p>
<br/>
<br/>

<h2 id="배운-점">배운 점</h2>
<br/>

<h4 id="talkbackvoiceover-테스트-요청"><strong>TalkBack/VoiceOver 테스트 요청</strong></h4>
<p>비장애인인 사람으로서는 예상조차 하지 못했던 TalkBack/VoiceOver를 패키지 개발자 쪽에서 요청해 주었다.
<br/></p>
<p>처음에는 해당 기능의 존재 자체는 알고 있었어도 개발과정에서 테스트할 생각은 전혀 하지 못했던 나 자신에게 새로운 충격과 편협적인 시각에 대한 부끄러움을 알게 해준 요청이었다.
<br/></p>
<p>시각 장애인들에 대한 대응을 고려조차 해본 적이 없었던 것을 깨달았고 이를 통해 내가 아닌 타인의 시각에서 보는 피드백의 중요성과 다양한 사용자를 위한 대응의 중요성을 배우게 되었다.</p>
<br/>
<br/>
<br/>

<h4 id="타-국가권에-대한-고려"><strong>타 국가권에 대한 고려</strong></h4>
<p>iana 국가 코드를 따라 ‘kr’ 대신 ‘ko’로 사용하는 것은 어떻겠느냐는 코드 리뷰를 받았다.
<br/></p>
<p>이제껏 한국에서는 kr로만 사용하고 적용하는 것이 익숙하였기에 당연하게 국가 코드를 kr로 진행하였다, 생각해 보면 다른 나라에서는 어떤 코드를 사용하고 있는지조차 고려하지 않았던 것 같다.
<br/></p>
<p>이를 깨닫고 iana 국가 코드들을 찾아보니 다행스럽게도 대문자가 아닌 kr과 겹치는 코드는 없었지만 개발된 패키지의 개발자는 해당 기준에 따라 국가 코드를 적용한 것 같았기에, 해당 코드 리뷰를 따라 국가 코드를 ‘ko’로 변경 진행하며 너무나 당연하게 생각하고 있는 것들에 대한 생각의 한계를 깨트리고 동시에 고려할 수 있는 방법을 배울 수 있었다.</p>
<br/>
<br/>
<br/>
<br/>

<h2 id="느낀-점">느낀 점</h2>
<p>처음으로 아예 모르는 타인, 심지어 언어권이 다른 외국인과의 코드적인 의견을 나누며 상단의 배운 점에 적었던 정보들과 같이 내가 알지 못하고 고려하지 못한 정보를 듣는 등의 액션을 통해 생각의 폭이 조금이나마 커진 것 같다는 생각이 들었다.</p>
<br/>

<p>또한 처음에는 떨리는 마음으로 PR 버튼을 눌렀을지언정 나중 가서는 패키지 개발자와 의견 및 테스트 피드백 등을 핑퐁 핑퐁 주고받으며 점점 두려움은 없어졌다.</p>
<br/>

<p>해당 패키지에 대한 기여는 한국어 번역 지원으로 고차원적인 기능적 기여는 아니었지만, 이 경험은 오픈소스 기여에 대한 두려움을 없애고 타 언어권 개발자와도 교류할 수 있다는 생각을 심어준 아주 작으면서도 큰 경험이었다고 느낀다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h1 id="➕-현재-확인-대기-중인-패키지">➕ 현재 확인 대기 중인 패키지</h1>
<br/>
<br/>


<h2 id="tutorial_coach_mark">tutorial_coach_mark</h2>
<hr>
<p>해당 패키지는 튜토리얼 화면 제작을 간단하게 진행할 수 있는 패키지이다.</p>
<br/>
<br/>


<h3 id="기여-계기-1">기여 계기</h3>
<p>사이드 프로젝트를 진행하며 기능 설명을 원활하게 하기 위한 튜토리얼 화면에 대한 구현이 필요하여 사용하게 된 패키지이다.</p>
<br/>

<p>해당 패키지를 사용하여 개발 후 디자인과 기획 측 동료분들에게 피드백을 받은 결과 애니메이션의 속도 조절에 대한 피드백이 넘어왔기에 확인해 보니 위젯 매개 변수만을 확인하면 개선이 필요한 애니메이션에 대한 속도 조절 기능이 보이지 않았다.</p>
<br/>

<p>다만 패키지 예시 문서에서는 해당 매개변수에 대한 정보가 존재하였고, Changelog를 확인해 보니 업데이트를 진행하며 해당 기능에 대한 매개변수가 누락된 것으로 확인되어 이에 대한 기능 복구를 진행 후 PR을 올리게 되었다.</p>
<br/>
<br/>
<br/>
<br/>

<h3 id="배운-점-1">배운 점</h3>
<br/>

<h4 id="읽기만이-아닌-실전으로"><strong>읽기만이 아닌 실전으로</strong></h4>
<p>패키지 문서에는 적혀 있더라도 개발자의 누락 등 오로 인해 실제로는 구동 방법이 다르거나 OFF된 기능이 있을 수 있다는 것, 문서와는 다르게 실제 사용해 봐야만 알 수 있는 정보가 있기에 직접 사용해 보는 것이 가장 좋다는 것을 깨달았다.</p>
<br/>

<p>이러한 경험을 통해 필요한 패키지가 생겨 비슷한 기능을 가진 여러 패키지를 비교하며 정해야 할 때 문서만을 통해 정하는 것이 아닌 실제로 사용 후 진행하는 것이 이후 개발의 속도의 지체를 막을 수 있겠구나를 배우게 되었다.</p>
<br/>

<p><span style='color:gray'>...이후 추가 업데이트 예정입니다</span></p>
<br/>
<br/>

<h3 id="느낀-점-1">느낀 점</h3>
<br/>
<br/>

<p><span style='color:gray'>...이후 추가 업데이트 예정입니다</span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] flutter_dotenv 로 .env 파일 적용해보자]]></title>
            <link>https://velog.io/@hee_mm_/Flutter-flutterdotenv-%EB%A1%9C-.env-%ED%8C%8C%EC%9D%BC-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@hee_mm_/Flutter-flutterdotenv-%EB%A1%9C-.env-%ED%8C%8C%EC%9D%BC-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Wed, 01 Nov 2023 06:15:42 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-env-파일이-뭐죠">🤔 .env 파일이 뭐죠?</h2>
<br/>

<p>앱의 각종 설정 정보를 저장할 때 사용되는 텍스트 파일로, 서버 환경 옵션이나 API KEY와 같이 민감하거나 노출되어서는 안되는 설정을 저장하여 사용하는 데 도움이 됩니다.</p>
<br/>

<p>일반적으로 파일명은 ‘ .env ’ 로 설정하며 내부 값은 키 -  값으로 구성되어 있음과 동시에 해당 파일은 숨김 파일로 처리되기에 탐색기에서 보이지 않습니다. </p>
<br/>
<br/>

<p><span style='color:gray'>내부 값 예시</span></p>
<pre><code>API_KEY=&#39;api_key&#39;
SERVER_MODE=&#39;product&#39;</code></pre><br/>
<br/>
<br/>
<br/>

<h2 id="패키지-가져오기">패키지 가져오기</h2>
<hr>
<pre><code class="language-yaml">dependencies:
  flutter:
    sdk: flutter
  flutter_dotenv: ^x.x.x  # x.x.x는 패키지 버전입니다.
</code></pre>
<p><code>pubspec.yaml</code> 파일에 <code>flutter_dotenv</code> 패키지를 추가한 후 <code>flutter pub get</code> 명령를 통해 패키지를 가져옵니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>



<h2 id="패키지-설정하기">패키지 설정하기</h2>
<hr>
<br/>



<h3 id="env-파일-제작하기">.env 파일 제작하기</h3>
<pre><code class="language-jsx">API_KEY=&#39;api_key&#39;
SERVER_MODE=&#39;product&#39;</code></pre>
<p>원하는 프로젝트 파일 속에 .env 파일을 생성하고 내부에 필요한 변수를 작성합니다.</p>
<p>해당 변수명과 값은 이후 Map&lt;String, String&gt;의 형식으로 사용됩니다.
<br/><br/><br/></p>
<h3 id="env-파일-로드-하기">.env 파일 로드 하기</h3>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;
import &#39;package:flutter_dotenv/flutter_dotenv.dart&#39;;

Future&lt;void&gt; main() async {
  await dotenv.load(fileName: &#39;자신의 .env 파일 위치&#39;);
    // ex) dotenv.load(fileName: &#39;assets/config/.env&#39;)
  runApp(MyApp());
}
</code></pre>
<p>.env 파일을 사용하기 위해서는 패키지를 통해 해당 파일을 먼저 가져와야만 합니다.</p>
<p>해당 작업을 진행하지 않는다면 오류가 발생하므로, 위와 같이 앱 시작 시 main.dart 파일에서 로드를 설정해 줍니다.</p>
  <br/>
<br/><br/>

<p>⚠️ <span style='color:gray'> 아래와 같이 <code>pubspec.yaml</code> 파일에서 assets을 설정해줘야만 합니다.</span></p>
<pre><code class="language-yaml">assets:
    - assets/config/.env</code></pre>
<p><br/><br/><br/><br/><br/></p>
<h2 id="env-설정-사용하기">.env 설정 사용하기</h2>
<hr>
<pre><code class="language-jsx">import &#39;package:flutter_dotenv/flutter_dotenv.dart&#39;;

void getServerMode(){
    String serverMode = dotenv.env[&#39;SERVER_MODE&#39;];

    print(&quot;now server mode is $serverMode&quot;);
}</code></pre>
  <br/>
<br/>

<p>위의 과정을 전부 거쳤다면 이후에는  <code>dotenv.env[’원하는 설정 KEY 값’]</code> 을 통해 .env 값을 불러와 사용 가능합니다.</p>
  <br/>
<br/>
  <br/>
<br/>
  <br/>
<br/>
  <br/>
<br/>

<blockquote>
<p>해당 패키지 깃허브 README.md 문서에 test 혹은  Null safety 등 더욱 정교한 사용 정보들이 있으니 필요에 따라 아래 문서를 읽으시는 것도 추천드립니다.</p>
</blockquote>
<p><a href="https://github.com/java-james/flutter_dotenv">https://github.com/java-james/flutter_dotenv</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Provider 예제]]></title>
            <link>https://velog.io/@hee_mm_/Flutter-Provider-%EC%98%88%EC%A0%9C</link>
            <guid>https://velog.io/@hee_mm_/Flutter-Provider-%EC%98%88%EC%A0%9C</guid>
            <pubDate>Fri, 27 Oct 2023 04:25:23 GMT</pubDate>
            <description><![CDATA[<p>기초 사용법을 익히기 위해 Provider를 통한 + , - 카운터 앱 예제를 만들어보도록 하겠습니다.</p>
<p>Bloc를 사용한 예제는 아래 링크를 통해 확인 가능합니다.</p>
<p><a href="https://velog.io/@hee_mm_/Flutter-Bloc-%EC%98%88%EC%A0%9C">https://velog.io/@hee_mm_/Flutter-Bloc-예제</a></p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="카운터-앱-제작">카운터 앱 제작</h2>
<hr>
<pre><code class="language-jsx">dependencies:
  flutter:
    sdk: flutter
  provider:</code></pre>
<p>우선, provider 패키지를 프로젝트에 추가하기 위해  <code>pubspec.yaml</code> 파일에 위와 같은 코드를 추가하고 패키지를 가져옵니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="count_provider">count_provider</h2>
<hr>
<pre><code class="language-jsx">import &#39;package:flutter/material.dart&#39;;

class CountProvider with ChangeNotifier {
  int _count = 0;

  int get count =&gt; _count;

  void increment() {
    _count++;
    notifyListeners(); // 값 증가 후 상태 변경 알림
  }

  void decrement() {
    _count--;
    notifyListeners(); // 값 감소 후 상태 변경 알림
  }
}</code></pre>
<p><code>decrement();</code></p>
<p><code>CountProvider</code> 내에서 상태의 count 값이 감소시키고 UI 변경을 알립니다.</p>
<br/>
<br/>

<p><code>increment();</code></p>
<p><code>CountProvider</code> 내에서 상태의 count 값이 증가시키고 UI 변경을 알립니다.</p>
<br/>
<br/>

<p><code>notifyListeners()</code></p>
<p>상태가 변경된 것을 구독하고 있는 위젯들에게 알리는 역할을 합니다.</p>
<p>해당 메서드를 호출하지 않는다면 Provider는 상태가 변경되더라도 UI가 리로드 되지 않습니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="호출-적용-화면"><strong>호출 적용 화면</strong></h2>
<hr>
<pre><code class="language-jsx">class ProviderScreen extends StatelessWidget {
  const ProviderScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (BuildContext context) =&gt; CountProvider(), // 정의한 Provider
            lazy: true, // true의 경우 기본값, 해당 예제는 설명을 위해 정의되었습니다.
      child: const ScaffoldLayout(
      child: ProviderLayout(),
      ),
    );
  }
}</code></pre>
<p><code>ChangeNotifierProvider</code></p>
<p>위젯 트리에 <code>ChangeNotifier</code>를 제공하고, 하위 위젯들이 해당 상태를 사용할 수 있도록 해주며, 변경된 상태를 감지하고 이때 위젯을 다시 그리는 Widget입니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h3 id="➕-changenotifierprovider-매개변수">➕ ChangeNotifierProvider 매개변수</h3>
<p><strong>create (필수)</strong></p>
<p>상태 관리 클래스의 인스턴스를 생성함과 동시에 위젯 트리에 새로운 <code>ChangeNotifier</code> 를 제공해 주는 함수로, 필수 매개변수 입니다.</p>
<br/>
<br/>

<p><strong>lazy</strong></p>
<p>해당 설정은 create의 <strong><strong><strong><strong>****</strong></strong></strong></strong>지정된 <strong><strong><strong><strong>****</strong></strong></strong></strong>상태 클래스를 위젯 트리가 처음 빌드 될 때 바로 생성할지 혹은 필요시에 생성할지 설정하는 매개변수입니다.</p>
<p>기본 설정은 <code>true</code> 이며 이는 실제 필요한 경우에만 생성하여 앱의 리소스를 절약할 수 있습니다.</p>
<p><code>false</code> 설정 시 위젯 트리가 처음 빌드 될 때 상태 클래스가 생성되며, 필요에 따라 초기 상태 설정이 가능합니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h3 id="providerlayout"><strong>ProviderLayout</strong></h3>
<pre><code class="language-jsx">import &#39;package:flutter/material.dart&#39;;
import &#39;package:provider/provider.dart&#39;;
import &#39;package:velog_exercise/presentation/feat/provider/provider/count_provider.dart&#39;;

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

  @override
  Widget build(BuildContext context) {
    final counter = Provider.of&lt;CountProvider&gt;(context);

    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          &#39;${counter.count}&#39;,
          style: const TextStyle(fontSize: 50),
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            FloatingActionButton(
              heroTag: &#39;provider_decrement&#39;,
              onPressed: () {
                counter.decrement();
              },
              child: const Icon(Icons.remove),
            ),
            FloatingActionButton(
              heroTag: &#39;provider_increment&#39;,
              onPressed: () {
                counter.increment();
              },
              child: const Icon(Icons.add),
            ),
          ],
        )
      ],
    );
  }
}</code></pre>
<p><code>counter.count</code></p>
<p>상태 클래스의 count 값을 가져와 사용합니다.</p>
<p><code>counter.decrement();</code></p>
<p>상태 클래스의 count 감소 메서드 호출합니다.</p>
<p><code>counter.increment();</code></p>
<p>상태 클래스의 count 증가 메서드 호출합니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h3 id="📍-providerofcontext">📍 Provider.of&lt;&gt;(context)?</h3>
<br/>
상태가 변경될 때 위젯을 다시 그리도록 하는 Widget입니다.

<br/>
<br/>

<p>Provider 패키지에서 제공되는 <code>Provider.of</code> 메서드를 사용하여 상태 클래스인 <code>CountProvider</code>를 현재 <code>context</code>에서 가져오는 역할을 합니다. </p>
<p>이를 통해 가져온 상태 클래스의 데이터나 메서드에 접근 가능하며, 위젯에서 상태 변경이나 값을 불러오는 등의 작업이 가능합니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h3 id="🤔-screen과-layout은-왜-나누어져-있나요">🤔 Screen과 Layout은 왜 나누어져 있나요?</h3>
<br/>

<p>ChangeNotifierProvider를 통해 먼저 <code>ChangeNotifier</code> 를 제공받아야만 하기 때문입니다.</p>
<pre><code class="language-jsx"> final counter = Provider.of&lt;CountProvider&gt;(context);</code></pre>
<p>위 코드와 같이 context를 통해 위젯 트리 속 상태 관리 클래스의 인스턴스를 찾아 구독을 하게 되는데 </p>
<br/>

<p>이를 아래과 같이 진행하게 된다면 상태 관리 클래스가 생성되기도 전에 구독을 시도하기에 에러가 발생합니다.</p>
<p>즉, 존재하지 않는 것에 대한 방황을 하게 된다고 볼 수 있습니다.</p>
<br/>


<h4 id="에러-발생-예제-코드">에러 발생 예제 코드</h4>
<pre><code class="language-jsx">class ProviderScreen extends StatelessWidget {
  const ProviderScreen({super.key});

  @override
  Widget build(BuildContext context) {
        final counter = Provider.of&lt;CountProvider&gt;(context);

    return ChangeNotifierProvider(
      create: (BuildContext context) =&gt; CountProvider(),
      child: const ScaffoldLayout(
      child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  &#39;${counter.count}&#39;,
                  style: const TextStyle(fontSize: 50),
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: &lt;Widget&gt;[
                    FloatingActionButton(
                      heroTag: &#39;provider_decrement&#39;,
                      onPressed: () {
                        counter.decrement();
                      },
                      child: const Icon(Icons.remove),
                    ),
                    FloatingActionButton(
                      heroTag: &#39;provider_increment&#39;,
                      onPressed: () {
                        counter.increment();
                      },
                      child: const Icon(Icons.add),
                    ),
                  ],
                )
              ],
            )
      ),
    );
  }
}</code></pre>
<hr>
<br/>

<p>에러 발생 코드를 제외한 해당 코드들의 전문은 <a href="https://github.com/LIMMIHEE/velog-exercise/tree/main/lib/presentation/feat/provider">Github 레파지토리</a>에서 확인 가능합니다!</p>
<br/>
<br/>


<blockquote>
<p><strong>공식 문서</strong>
하위 링크를 통해 더욱 다양한 예제와 위젯을 볼 수 있으니 참고하시면 좋을 듯 합니다!</p>
<p><a href="https://github.com/rrousselGit/provider/blob/master/resources/translations/ko-KR/README.md">Provider 공식 한글화 문서</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Provider란]]></title>
            <link>https://velog.io/@hee_mm_/Flutter-Provider%EB%9E%80</link>
            <guid>https://velog.io/@hee_mm_/Flutter-Provider%EB%9E%80</guid>
            <pubDate>Fri, 20 Oct 2023 16:09:25 GMT</pubDate>
            <description><![CDATA[<h1 id="provider란">Provider란?</h1>
<hr>
<p>Flutter의 상태 관리 패턴 중 한 가지로, 앱의 <strong>상태</strong>를 관리하고 이에 관한 위젯을 제공하는 패키지입니다.</p>
<br/>


<p><strong>InheritedWidget</strong>를 기반으로 앱의 상태를 위젯 트리 전체에서 공유하며, 데이터 변경 시에 해당 데이터를 사용하는 위젯에 알려줍니다. </p>
<br/>

<p>비교적 상태 관리가 간편하기에 복잡한 상태 관리 코드를 작성하지 않아도 적은 코드로 관리가 가능합니다. 또한 특정 부분만 데이터 변경 시에 다시 업데이트 가능한 부분 업데이트를 제공하고 있습니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<blockquote>
<p>한글 깃허브 문서</p>
<p><a href="https://github.com/rrousselGit/provider/blob/master/resources/translations/ko-KR/README.md">Provider Github Link</a></p>
</blockquote>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="📍-inheritedwidget">📍 InheritedWidget?</h2>
<hr>
<p>위젯 트리를 통해 부모 위젯에서 자식 위젯으로 데이터 공유 및 전달이 가능한 특별한 위젯입니다.</p>
<br/>

<p>BuildContext를 이용하여 데이터에 관한 엑서스와 트리 내 위치를 파악하며, 특정 데이터가 변경될 때 해당 데이터에 의존하는 위젯만 다시 그려지도록 성능을 최적화할 수 있습니다.</p>
<br/>

<p>해당 위젯은 테마 혹은 언어 설정과 같이 앱 전체에서 사용되는 전역 데이터의 관리 혹은 사용자 로그인 정보, 토큰과 같은 데이터를 공유를 사용 예로 볼 수 있습니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="provider의-장단점">Provider의 장단점</h2>
<hr>
<h3 id="장점"><strong>장점</strong></h3>
<ul>
<li><p>상태 관리가 간단하고 직관적이며, 학습 난도가 낮습니다.</p>
</li>
<li><p>위젯 트리를 통해 데이터가 공유되므로 데이터 공유가 필요하다면 모든 위젯이 쉽게 접근 가능합니다.</p>
</li>
<li><p>변경된 데이터만 다시 렌더링 가능하므로 성능 최적화에 도움이 됩니다.</p>
</li>
<li><p>Flutter 초창기부터 존재한 패키지로, 풍부한 자료와 예제들을 찾아볼 수 있습니다.</p>
</li>
</ul>
<br/>

<h3 id="단점"><strong>단점</strong></h3>
<ul>
<li>위젯 트리를 통해 상태 관리가 진행되므로, 앱의 규모가 커지거나 중첩적인 사용이 일어난다면 어떤 데이터가 어떤 위젯에서 사용되고 관리되는지 한 눈에 파악하기 어려울 수 있습니다.</li>
</ul>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h2 id="타-상태관리-패키지와의-차이">타 상태관리 패키지와의 차이</h2>
<hr>
<br/>

<h3 id="bloc"><strong>BLoC</strong></h3>
<p>Provider에 비하여 학습 난도가 높으며 코드 구현량이 늘어나지만 상태별 관리가 분리되어 있어 비즈니스 로직이 명확하고, 이로 인해 모듈화 및 테스트에 이점이 존재합니다.</p>
<br/>

<h3 id="getx"><strong>GetX</strong></h3>
<p>상태관리, 라우팅 등 통합적인 상태 관리 패키지인 GetX는 가볍고 단순한 상태 관리를 제공하는 Provider에 비하여 비교적 커버 범위가 넓으며, 데이터 변경 시 자동으로 상태가 업데이트됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Dart] 비동기 프로그래밍와 예외처리]]></title>
            <link>https://velog.io/@hee_mm_/Dart-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%99%80-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@hee_mm_/Dart-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%99%80-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Fri, 13 Oct 2023 10:06:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🚧 <strong>해당 게시글은 FastCampus의 15개 프로젝트로 실무까지 끝내는 Dart &amp; Flutter 앱 개발 강의를 기반으로 작성되었습니다.</strong></p>
</blockquote>
<p>강의 링크 👉 <a href="https://fastcampus.co.kr/dev_online_dartflutter">https://fastcampus.co.kr/dev_online_dartflutter</a></p>
<br/>
<br/>
<br/>
<br/>
<br/>



<h1 id="비동기">비동기</h1>
<hr>
<p>비동기란 코드가 동시다발적으로 진행되는 형태로, 순차적 진행을 보장할 수 없습니다. </p>
<p>사용되는 주요 개념으로 <code>Future</code>와 <code>Stream</code>이 있습니다. </p>
<br/>

<p><code>Future</code>는 단일 비동기 작업의 결과를 나타내는 데, <code>Stream</code>은 여러 값 또는 이벤트의 시퀀스를 나타내는 데 사용됩니다. </p>
<br/>
<br/>
<br/>
<br/>
<br/>


<h1 id="future">Future</h1>
<hr>
<p>1회만 응답을 돌려받는 경우 사용되는 비동기 객체</p>
<p><span style='color:gray'> ex) 서버에 응답을 받는 경우 </span></p>
<br/>
<br/>


<p><code>Future</code> 를 이용하여 비동기 작업을 시작하면 결과는 <code>Future</code> 객체에 저장됩니다. 
<br/></p>
<p>문제없이 진행된 후의 결과를 받는  <code>then</code> 함수 예외가 발생할 경우를 대비할 수 있는 <code>catchError</code> 함수 등으로 각 결과들에 대한 처리를 정의할 수 있습니다.</p>
<br/>
<br/>

<pre><code class="language-jsx">Future&lt;int&gt; fetchData() async {
  await Future.delayed(Duration(seconds: 5));
  return 5; // 비동기 작업 결과
}

void main() {
  fetchData().then((result) {
    print(&#39;비동기 작업 결과: $result&#39;);
  }).catchError((error) {
    print(&#39;에러 발생: $error&#39;);
  });
}</code></pre>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="📍-async--await-는-뭐죠">📍 async , await 는 뭐죠?</h2>
<hr>
<br/>

<h3 id="async"><strong>async</strong></h3>
<p>비동기 함수임을 정의하는 키워드입니다. </p>
<p>정의된 함수는 비동기 작업을 수행할 수 있습니다.</p>
<pre><code class="language-jsx">Future&lt;리턴타입&gt; 함수명() async {
  // 비동기 작업을 수행하는 코드
}

// 리턴 타입이 없는 void의 경우 아래와 같이 정의가 가능합니다
void 함수명() async {
  // 비동기 작업을 수행하는 코드
}</code></pre>
<br/>


<h3 id="await"><strong>await</strong></h3>
<p>비동기 함수 내에서 사용되는 키워드입니다.</p>
<p>작업이 완료될 때까지 함수의 실행을 중단하고 대기합니다.</p>
<pre><code class="language-jsx">Future&lt;void&gt; fetchData() async { 
  var result = await AsyncFunction();
  // 이후는 AsyncFunction()이 완료된 후 실행
}</code></pre>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="📍future로-동시에-비동기-작업-실행은-불가능한가요">📍Future로 동시에 비동기 작업 실행은 불가능한가요?</h2>
<hr>
<br/>

<p>여러 비동기 작업을 동시에 실행하려면 <code>Future.wait</code> 메서드를 사용하여 구현할 수 있습니다. </p>
<br/>
<br/>
<br/>

<h3 id="futurewait"><strong>Future.wait?</strong></h3>
<p>여러 <code>Future</code> 객체를 동시에 실행하고 모든 <code>Future</code>가 완료될 때까지 대기 후, 결과를 한꺼번에 반환하는 메서드입니다.</p>
<pre><code class="language-jsx">Future&lt;String&gt; someAsyncFunction1() async {
  await Future.delayed(Duration(seconds: 1));
  return &#39;Function1&#39;;
}

Future&lt;String&gt; someAsyncFunction2() async {
  await Future.delayed(Duration(seconds: 5));
  return &#39;Function2&#39;;
}

void fetchMultipleData() async {
  final results = await Future.wait([someAsyncFunction1(), someAsyncFunction2()]);
   // results에 각 작업의 결과가 저장됨
    print(&#39;모든 작업 완료: $results&#39;);
}</code></pre>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="stream">Stream</h2>
<hr>
<p>지속적으로 응답을 돌려받는 경우 혹은 이벤트의 시퀀스 처리에 사용되는 비동기 객체</p>
<p><span style='color:gray'> ex) 타이머와 같이 지속적으로 일정주기로 반복해야 하는 경우 </span></p>
<br/>
<br/>

<p><code>Stream</code>은 스트림을 생성하고 각 값 또는 이벤트가 도착할 때마다 처리할 수 있기 때문에 실시간 이벤트 처리, 스트리밍 데이터 및 대용량 데이터 처리에 유용합니다.</p>
<br/>
<br/>

<pre><code class="language-jsx">Stream&lt;int&gt; countStream() async* {
  for (int i = 0; i &lt;= 10; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i; // 스트림에 값 전달
  }
}

void getStream() async {
  await for (int count in countStream()) {
    print(&#39;스트림 값: $count&#39;);
  }
}</code></pre>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<h2 id="📍-async--yield-는-뭐죠">📍 async* , <strong>yield</strong> 는 뭐죠?</h2>
<hr>
<br/>

<h3 id="async-1"><strong>async*</strong></h3>
<p>스트림을 생성하고 async와 같이 값을 하나씩 전달할 수 있는 제너레이터 비동기 함수임을 정의할 때 사용되는 키워드입니다. </p>
<br/>

<pre><code class="language-jsx">Stream&lt;리턴타입&gt; 함수명() async* {
  // 비동기 작업 코드
}</code></pre>
<br/>

<h3 id="yield">yield</h3>
<p><strong>async*</strong> 함수 내에서 스트림으로 값을 전달할 때 사용되는 키워드입니다.</p>
<p>return 과 같은 역할을 한다고 볼 수 있습니다.</p>
<br/>
<br/>
<br/>



<br/>
<br/>
<br/>
<br/>

<h1 id="예외처리">예외처리</h1>
<hr>
<p>프로그램이 진행 중일 때, 의도하거나 의도치 않은 상황에서 오류가 발생했을 때 대처하기 위한 기능입니다.
<br/></p>
<p>프로그램 안정성 유지와 오류 관리에 중요한 역할을 하며, 적절한 처리를 통해 오류를 대처하고 필요에 따라서 사용자에게 오류 메시지를 제공할 수 있습니다.</p>
<br/>
<br/>
<br/>
<br/>

<h2 id="try-catch문">try-catch문</h2>
<p>가장 자주 쓰이는 기본적인 예외처리문</p>
<p><strong><code>try</code></strong> 블록 내에서 예외가 발생할 수 있는 코드를 감싸고, <strong><code>catch</code></strong> 블록에서 예외를 처리하며 해당 <strong><code>catch</code></strong>는 타입을 가리지 않습니다.</p>
<br/>

<pre><code class="language-jsx">try {
  // 예외가 발생할 수 있는 코드
} catch (e) {
  print(&quot;예외가 발생했습니다: $e&quot;);
}</code></pre>
<br/>
<br/>
<br/>

<h2 id="on-문">on 문</h2>
<p>특정 에러 타입을 잡을 때 사용되는 예외처리문</p>
<p>중복으로 사용 가능하기에 에러 타입에 따라 각기 다른 처리를 할 수 있습니다.</p>
<br/>

<pre><code class="language-jsx">try {
  // 예외가 발생할 수 있는 코드
} on Exception1 catch (e) {
  // Exception1 유형의 예외 처리
} on Exception2 catch (e) {
  // Exception2 유형의 예외 처리
} catch (e) {
  // 다른 모든 예외 유형을 처리
}</code></pre>
<br/>
<br/>

<h2 id="finally-문">finally 문</h2>
<p>try - catch와 함께 사용하는 예외처리문</p>
<p>예외가 발생했던 하지 않았던, try - catch 이후 반드시 코드를 실행할 때 사용합니다.</p>
<pre><code class="language-jsx">try {
  // 예외가 발생할 수 있는 코드
} catch (e) {
  // 예외 처리
} finally {
  // 항상 실행되는 코드
}</code></pre>
<br/>
<br/><br/>

<h2 id="throw-문">throw 문</h2>
<p>예외(에러)를 만들어서 던지는 키워드</p>
<p>사용자 정의 에러를 발생시킬 수 있으며, 잘 사용한다면 코드가 의도하지 않은 방향으로 진행되는 것을 방지할 수 있습니다.</p>
<pre><code class="language-jsx">void myFunction() {
  // 예외 발생
  throw Exception(&#39;사용자 정의 예외&#39;);
}</code></pre>
<br/>
<br/>

<h2 id="rethrow-문">rethrow 문</h2>
<p>이미 catch한 에러를 재발생 시키는 작업을 하는 키워드.</p>
<pre><code class="language-jsx">try {
  // 예외가 발생할 수 있는 코드
} catch (e) {
  // 예외 처리
  rethrow; // 현재 예외를 다시 발생
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Test 시 Shared Preferences 무한 로딩]]></title>
            <link>https://velog.io/@hee_mm_/Flutter-Test-%EC%8B%9C-Shared-Preferences-%EB%AC%B4%ED%95%9C-%EB%A1%9C%EB%94%A9-pq9kkr5h</link>
            <guid>https://velog.io/@hee_mm_/Flutter-Test-%EC%8B%9C-Shared-Preferences-%EB%AC%B4%ED%95%9C-%EB%A1%9C%EB%94%A9-pq9kkr5h</guid>
            <pubDate>Tue, 10 Oct 2023 09:38:43 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<hr>
<br/>

<p>Flutter에서 Shared Preferences를 포함하여 테스팅을 진행할 때, 아래와 같이 코드를 작성하고 실행하면 아무런 에러나 경고 없이 코드가 돌아가지만 계속 로딩만 될 뿐 실제 진행되지는 않았습니다.</p>
<br/>
<br/>


<pre><code class="language-jsx">
testWidgets(&#39;SharedPreferences Test&#39;, (WidgetTester tester) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    await tester.pumpWidget(
      // 이하 개별 code
    );

  });</code></pre>
<br/>
<br/>
<br/>
<br/>

<h2 id="해결">해결</h2>
<hr>
<br/>

<p>이때는 <code>SharedPreferences.getInstance()</code>  이전에 아래와 같이 초기 Mock 값을 넣어주면 무한 로딩 없이 작동하며, 해당 Mock 데이터를 기반으로 테스팅이 정상적으로 진행됩니다.</p>
<br/>
아래 코드의 경우 값을 비워두었기에 SharedPreferences는 빈 상태로 작동되게 됩니다.
<br/>

<pre><code class="language-jsx">SharedPreferences.setMockInitialValues({});</code></pre>
<br/>
<br/>
전체 코드

<pre><code class="language-jsx">testWidgets(&#39;SharedPreferences Test&#39;, (WidgetTester tester) async {
        SharedPreferences.setMockInitialValues({});
    SharedPreferences prefs = await SharedPreferences.getInstance();

    await tester.pumpWidget(
      // 이하 개별 code
    );

  });</code></pre>
<br/>
<br/>
<br/>

<hr>
<br/>

<h3 id="📍-왜-이렇게-설정해-줘야만-동작하는-걸까요">📍 왜 이렇게 설정해 줘야만 동작하는 걸까요?</h3>
<br/>
이유는 아래와 같습니다.

<br/>

<ul>
<li>테스트 케이스 간에 데이터가 공유되지 않도록 하기 위해</li>
<li>특정 시나리오에 대한 예상된 초기 데이터 상태를 설정하기 위해</li>
</ul>
<br/>


<p>종합적으로 간단하게 정리하면, 테스트 환경 당 예상되는 데이터 상태를 정의하고 개별적으로 값을 제어할 수 있으므로 테스트를 효과적으로 수행할 수 있도록 하기 위함입니다.</p>
<br/>]]></description>
        </item>
        <item>
            <title><![CDATA[[Dart] 변수와 타입]]></title>
            <link>https://velog.io/@hee_mm_/Dart-%EB%B3%80%EC%88%98%EC%99%80-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@hee_mm_/Dart-%EB%B3%80%EC%88%98%EC%99%80-%ED%83%80%EC%9E%85</guid>
            <pubDate>Sat, 07 Oct 2023 11:17:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🚧 <strong>해당 게시글은 FastCampus의 15개 프로젝트로 실무까지 끝내는 Dart &amp; Flutter 앱 개발 강의를 기반으로 작성되었습니다.</strong></p>
</blockquote>
<p>강의 링크 👉 <a href="https://fastcampus.co.kr/dev_online_dartflutter">https://fastcampus.co.kr/dev_online_dartflutter</a></p>
<br/>
<br/>
<br/>
<br/>
<br/>



<h1 id="변수">변수</h1>
<hr>
<pre><code class="language-jsx">// 타입 변수명;
string testVariable = &quot;저는 테스트 변수예요.&quot;; // 선언시

testVariable = &quot;저는 값이 변경되었어요.&quot;; // 값 변경시</code></pre>
<br/>
<span style='color:gray'>📍 보통 변수 명의 경우 두 개 이상의 단어가 붙어있을 경우 첫 번째 단어는 소문자, 두 번째 단어의 시작을 대문자로 시작하는 것이 암묵적인 룰입니다.</span>

<br/>

<br/>

<br/>

<br/>


<p><strong>특정 데이터(값)을 담아두는 그릇</strong></p>
<p>해당 변수들은 기본적으로 생성과 동시에 타입을 정해 선언되며 선언 시에 적용된 타입과 같은 값을 할당할 수 있습니다.</p>
<p>변수는 값이 할당된 후에도 수정할 수 있는 것이 특징입니다.</p>
<br/>

<br/>


<p>다트에서 타입은 꼭 정의하지 않아도 되며</p>
<p>해당 미정의 <strong>가변형</strong>을 위한 키워드들도 존재하지만 프로그래밍의 특성상 타입이 명확해야 추후 코드 관리와 협업에 더욱 용이합니다.</p>
<br/>

<br/>



<br/>
<br/>
<br/>
<br/>
<br/>
<br/>


<h1 id="타입">타입</h1>
<hr>
<p>데이터의 유형을 이야기하며 기본형, 자료형, 가변형 등으로 나뉘며 해당 글에서는 기초적인 세 가지 유형의 타입에 관하여 정리합니다.</p>
<br/>

<br/>
<br/>

<br/>

<h2 id="기본형">기본형</h2>
<ul>
<li>String</li>
<li>int</li>
<li>double</li>
<li>bool</li>
<li>null</li>
</ul>
<br/>


<h3 id="string">String</h3>
<pre><code class="language-jsx">String name = &#39;mirimhee&#39;;
String sentence = &quot;Hi, friend!&quot;;</code></pre>
<p>문자열을 나타내는 데이터 타입.</p>
<p>작은 타옴표 ( ‘ ’ )  혹은 큰따옴표 ( “ “ )로 문자열을 작성할 수 있습니다.</p>
<br/>


<h3 id="int">int</h3>
<pre><code class="language-jsx">int age = 99;
int minusNumber = -1;</code></pre>
<p>정수를 나타내는 데이터 타입.  </p>
<br/>

<h3 id="double"><strong>double</strong></h3>
<pre><code class="language-jsx">double piValue = 3.14;</code></pre>
<p>소수점 숫자를 나타내는 데이터 타입.</p>
<p>소수 부분을 포함하는 숫자를 표현할 수 있습니다.</p>
<br/>

<h3 id="null"><strong>null</strong></h3>
<pre><code class="language-jsx"> // nullableString은 null을 가질 수 있음
String? nullableString;

// nullableBool에 직접 null 할당
bool? nullableBool = null; </code></pre>
<p>값이 존재하지 않음을 나타내는 타입. </p>
<p>변수에 아무 값도 할당하지 않거나 변수를 초기화하지 않았을 때 사용됩니다.</p>
<br/>


<hr>
<br/>

<br/>

<h2 id="자료형"><strong>자료형</strong></h2>
<ul>
<li>List</li>
<li>Set</li>
<li>Map</li>
</ul>
<br/>

<h3 id="list"><strong>List</strong></h3>
<pre><code class="language-jsx">// List : List&lt;타입&gt;

int&lt;String&gt; newList = [1, 2, 3, 4, 5];

// 리스트에 &quot;신규 값&quot; 추가 =&gt; [&quot;신규 값&quot;]
newList.add(5); 

// 리스트에서 &quot;신규 값&quot; 삭제 =&gt; []
newList.remove(5); 

// newList에서 첫번째 숫서인 값을 가져와 element는 1이 됩니다.
int element = newList[0]; 
</code></pre>
<p>순서가 있는 데이터 컬렉션 타입, 동일한 타입의 요소를 저장합니다.</p>
<p>인덱스를 사용하여 개별 요소에 액세스할 수 있습니다.</p>
<br/>

<h3 id="set"><strong>Set</strong></h3>
<pre><code class="language-jsx">// Set : Set&lt;타입&gt;
Set&lt;String&gt; uniqueNames = {&#39;A&#39;, &#39;B&#39;, &#39;A&#39;};

// 중복된 요소가 자동으로 제거
// uniqueNames에는 {&#39;A&#39;, &#39;B&#39;}만 저장됩니다.

// 요소 추가 =&gt; {&#39;A&#39;, &#39;B&#39;, &#39;C&#39;}
uniqueNames.add(&#39;C&#39;); 

// 요소 제거 =&gt; { &#39;B&#39;, &#39;C&#39;}
uniqueNames.remove(&#39;A&#39;);
</code></pre>
<p>순서가 없고 중복된 요소가 허용되지 않는 데이터 컬렉션 타입입니다.</p>
<br/>

<h3 id="map">Map</h3>
<pre><code class="language-jsx">// Map : Map&lt;키 타입, 값 타입&gt;

Map&lt;String, int&gt; scores = {
  &#39;A&#39;: 95,
  &#39;B&#39;: 85,
  &#39;C&#39;: 75,
};

// 새로운 키-값 추가
scores[&#39;D&#39;] = 65;

// 특정 키에 대한 값 조회
// score는 65가 됩니다.
int score = scores[&#39;D&#39;]; 

// 키-값 쌍 제거
scores.remove(&#39;C&#39;);</code></pre>
<p>키-값 쌍을 저장하는 데이터 구조 타입, 각 키는 고유해야 합니다.
특정 키를 사용하여 해당 키에 연결된 값을 검색할 수 있습니다.</p>
<br/>

<br/>

<br/>

<hr>
<br/>

<h2 id="가변형"><strong>가변형</strong></h2>
<ul>
<li>var</li>
<li>dynamic</li>
</ul>
<br/>

<h3 id="var">var</h3>
<pre><code class="language-jsx">// var

 // String 타입 부여
var newVariable = &quot;새로운 변수&quot;;

// 같은 String 값 할당시 에러 X
newVariable = &quot;새롭게 값 할당&quot;; 

// 처음 할당한 String 타입이 아닌 값이므로 에러 발생
newVariable = false; </code></pre>
<p>최초 부여된 타입을 고정으로 사용하는 타입으로, 최초 타입 아닌 값을 할당 시 에러가 발생합니다.</p>
<br/>

<h3 id="dynamic">dynamic</h3>
<pre><code class="language-jsx">// dynamic

// String 타입 부여
dynamic dynamicVariable = &quot;새로운 다이나믹&quot;; 

 // int 값 할당에도 에러 X
dynamicVariable = 1;

 // bool 값 할당에도 에러 X
dynamicVariable = false; </code></pre>
<p>타입이 코드 진행 중 언제라도 타입 변환 가능한 타입입니다.</p>
<br/>

<br/>

<hr>
<br/>

<br/>

<h3 id="💡-변수-외로-값을-저장할-수-없나요"><strong>💡 변수 외로 값을 저장할 수 없나요?</strong></h3>
<br/>



<p>값의 수정이 가능한 변수 외로는 한 번 할당된 후 바뀌지 않는 값인 상수가 존재합니다.</p>
<p>Dart에서는 const와 final 두 가지의 상수 타입이 존재하며 두 가지 타입의 차이가 궁금하시다면 아래 링크를 참조해 주시면 감사하겠습니다.</p>
<br/>



<blockquote>
<p><a href="https://velog.io/@hee_mm_/%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-const-%EC%99%80-final%EC%9D%98-%EC%B0%A8%EC%9D%B4">간단하게 알아보는 const 와 final의 차이</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Bloc 예제]]></title>
            <link>https://velog.io/@hee_mm_/Flutter-Bloc-%EC%98%88%EC%A0%9C</link>
            <guid>https://velog.io/@hee_mm_/Flutter-Bloc-%EC%98%88%EC%A0%9C</guid>
            <pubDate>Wed, 04 Oct 2023 06:06:49 GMT</pubDate>
            <description><![CDATA[<p>이전에 Bloc에 대한 기초 정보를 알아보았으니, 더욱 깊게 알아보기 위해</p>
<p>기초 Bloc 사용법을 익히기 위해 Bloc, State, Event 파일로 나누어진  + , - 카운터 앱 예제를 만들어보도록 하겠습니다.</p>
<br/>
<br/>
<br/>
<br/>
<br/>


<h3 id="📍-왜-bloc-state-event--파일로-나뉘나요">📍 왜 <strong>Bloc, State, Event  파일로 나뉘나요?</strong></h3>
<hr>
<br/>

<p>구조화와 관리를 위한 이유로 앱이 커지고 복잡해지는 경우를 위한 이유입니다.</p>
<br/>

<p>이러한 분리된 구조를 사용하면 각 파일은 자신만의 역할을 담당하므로 앱의 로직이 명확하게 분리되며 각 역할에 맞게 구성되므로 코드의 가독성, 앱의 개발 및 유지 관리가 훨씬 쉬워집니다. 또한 다른 컴포넌트 간의 결합도를 낮추어 앱의 확장성을 향상시킬 수 있습니다.</p>
<br/>
<br/>
<br/>
<br/>

<h3 id="💡-그럼-각-파일은-무슨-역할을-담당하나요">💡 <strong>그럼 각 파일은 무슨 역할을 담당하나요?</strong></h3>
<hr>
<br/>

<p><strong>Bloc (비즈니스 로직 컴포넌트)</strong></p>
<p>Bloc 파일은 비즈니스 로직을 정의하고 상태와 이벤트를 관리합니다. </p>
<p>이벤트를 받아 상태를 업데이트하고, 특정 이벤트가 발생할 때 상태가 어떻게 변경되는지를 정의합니다. 이러한 역할을 통해 앱의 동작을 조율하고 데이터 흐름을 관리합니다.</p>
<br/>

<p><strong>State (상태)</strong></p>
<p>State 파일은 앱의 현재 상태를 정의합니다. </p>
<p>UI에 표시되는 데이터나 화면의 상태와 관련된 정보를 정의합니다, 본 글에서 제작할 카운터 앱에서 현재 카운터 값 등을 포함할 수 있습니다.</p>
<br/>

<p><strong>Event (이벤트)</strong></p>
<p>Event 파일은 앱에서 발생하는 이벤트나 액션을 정의합니다. </p>
<p>주로 사용자 동작 또는 시스템 이벤트에 관한 정보를 정의합니다, 카운터 앱에서는 &quot;+&quot; 버튼을 누르는 이벤트나 &quot;-&quot; 버튼을 누르는 이벤트를 정의할 수 있습니다.</p>
<br/>
<br/>
<br/>



<br/>
<br/>
<br/>


<h2 id="카운터-앱-제작">카운터 앱 제작</h2>
<hr>
<pre><code class="language-jsx">dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: </code></pre>
<br/>

<p>우선, <code>flutter_bloc</code> 패키지를 프로젝트에 추가하기 위해  <code>pubspec.yaml</code> 파일에 위와 같은 코드를 추가하고 패키지를 가져옵니다.</p>
<br/>
<br/>
<br/>
<br/>

<h3 id="counter_event"><strong>counter_event</strong></h3>
<pre><code class="language-jsx">// 이벤트 정의
abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}

class DecrementEvent extends CounterEvent {}</code></pre>
<br/>
카운터의 증가와 감소에 관한 이벤트를 정의해 줍니다.

<p>이때 abstract Class를 만들어 일관성을 유지하고, 이후 이벤트 확장을 편리하게 만들어줍니다.</p>
<p>또한 기능에 따라 이벤트들이 특정 규칙을 따르도록 만들 수 있습니다.</p>
<br/>
<br/>
<br/>
<br/>

<h3 id="counter_state">counter_state</h3>
<pre><code class="language-jsx">// 상태 정의
class CounterState {
  final int count;

  CounterState({this.count = 0});

  CounterState copyWith({
    int? count,
  }) {
    return CounterState(
      count: count ?? this.count,
    );
  }
}</code></pre>
<br/>

<p>카운트에 필요한 count 상태를 정의해 줍니다.</p>
<p>copyWith를 정의함으로써 불변성을 유지하면서 객체의 일부 필드를 업데이트할 수 있도록 해줍니다.</p>
<p>해당 메서드를 사용하여 새로운 상태를 생성할 때, 변경하려는 <strong><code>count</code></strong> 값을 전달하면 해당 필드만 변경되며 만약 <strong><code>count</code></strong> 값을 전달하지 않으면 현재의 <strong><code>count</code></strong> 값은 그대로 유지됩니다.</p>
<br/>
<br/>
<br/>
<br/>

<h3 id="counter_bloc"><strong>counter_bloc</strong></h3>
<pre><code class="language-jsx">import &#39;package:flutter_bloc/flutter_bloc.dart&#39;;
import &#39;counter_event.dart&#39;;
import &#39;counter_state.dart&#39;;

// 이벤트 별 동작 정의
class CounterBloc extends Bloc&lt;CounterEvent, CounterState&gt; {
  CounterBloc() : super(CounterState()) {
    on&lt;IncrementEvent&gt;(_handleIncrementEvent);
    on&lt;DecrementEvent&gt;(_handleDecrementEvent);
  }

  void _handleIncrementEvent(
    IncrementEvent event,
    Emitter&lt;CounterState&gt; emit,
  ) {
    print(&#39;IncrementEvent 발생&#39;);
    emit(state.copyWith(count: state.count + 1));
  }

  void _handleDecrementEvent(
    DecrementEvent event,
    Emitter&lt;CounterState&gt; emit,
  ) {
    print(&#39;DecrementEvent 발생&#39;);
    emit(state.copyWith(count: state.count - 1));
  }
}</code></pre>
<br/>

<p><code>Bloc&lt;CounterEvent, CounterState&gt;</code> </p>
<p>각 이벤트와 상태 클래스를 상속하여 카운터 앱의 비즈니스 로직을 정의합니다.</p>
<br/>
<br/>

<p><code>CounterBloc() : super(CounterState())</code> </p>
<p>초기 상태를 기본 초기화 상태로 적용해 줍니다.</p>
<br/>
<br/>

<p><code>on&lt;IncrementEvent&gt;(_handleIncrementEvent)</code></p>
<p><code>IncrementEvent</code> 이벤트가 발생했을 때 <code>_handleIncrementEvent</code> 메서드를 호출하도록 등록합니다. </p>
<br/>
<br/>

<p> <code>emit(state.copyWith(count: state.count + 1))</code></p>
<p>현재 상태를 변경하지 않고 현 상태의 카운터 값을 1 증가시킨 새로운 상태를 반환하고 새로운 상태는 UI로 전달해 카운터 값이 업데이트합니다.</p>
<br/>
<br/>
<br/>
<br/>


<h3 id="📍--on--emit-">📍  on ? emit ?</h3>
<hr>
<h4 id="on">on</h4>
<p>Bloc 클래스의 생성자 내에서 호출되며, 이벤트와 핸들러 메서드를 연결하는 역할을 합니다.</p>
<p>핸들러 메서드는 이벤트가 발생했을 때 실행되며, 이벤트와 현재 상태에 따라 특정 작업을 수행하고 새로운 상태를 업데이트하는 역할을 합니다.</p>
<br/>
<br/>

<h4 id="emit">emit</h4>
<p>기본적으로 상태를 변경하지 않고 새로운 상태를 반환합니다.</p>
<p>해당 메서드를 호출하면 새로운 상태는 Bloc의 상태 스트림에 추가되고, 이 스트림을 구독하고 있는 위젯이나 컴포넌트가 새로운 상태를 수신하여 UI를 업데이트하는 역할을 합니다.</p>
<br/>
<br/>




<br/>
<br/>
<br/>
<br/>


<h3 id="호출-적용-화면">호출 적용 화면</h3>
<hr>
<pre><code class="language-jsx">class BlocExerciseScreen extends StatelessWidget {
  const BlocExerciseScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return ScaffoldLayout(
      child: BlocProvider(
        create: (context) =&gt; CounterBloc(),
        child: Center(
          child: BlocBuilder&lt;CounterBloc, CounterState&gt;(
            builder: (context, state) {
              return Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    &#39;${state.count}&#39;,
                    style: const TextStyle(fontSize: 50),
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: &lt;Widget&gt;[
                      FloatingActionButton(
                        onPressed: () {
                          context.read&lt;CounterBloc&gt;().add(DecrementEvent());
                        },
                        child: const Icon(Icons.remove),
                      ),
                      FloatingActionButton(
                        onPressed: () {
                          context.read&lt;CounterBloc&gt;().add(IncrementEvent());
                        },
                        child: const Icon(Icons.add),
                      ),
                    ],
                  )
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}</code></pre>
<br/>
<br/>

<p><code>BlocProvider(create: (context) =&gt; CounterBloc(), ~</code> </p>
<p><code>CounterBloc</code>을 생성하고 이를 현재 화면의 context에서 사용할 수 있게 합니다.</p>
<br/>
<br/>

<p><code>BlocBuilder&lt;CounterBloc, CounterState&gt;</code> </p>
<p> <code>CounterBloc</code>의 상태를 감시하고, 상태가 변경될 때마다 UI를 업데이트합니다. </p>
<p>이때, <code>builder</code> 함수 내에서 현재 상태를 전달하고 UI를 빌드 합니다.</p>
<br/>
<br/>

<p><code>context.read&lt;CounterBloc&gt;().add(IncrementEvent())</code></p>
<p><code>IncrementEvent</code> 를 발생시켜,  <code>CounterBloc</code> 내에서 상태의 count 값이 증가하고 UI가 변경됩니다.</p>
<br/>
<br/>

<p><code>context.read&lt;CounterBloc&gt;().add(DecrementEvent())</code></p>
<p><code>DecrementEvent</code> 를 발생시켜,  <code>CounterBloc</code> 내에서 상태의 count 값이 감소하고 UI가 변경됩니다.</p>
<br/>
<br/>
<br/>
<br/>

<h3 id="💡-blocprovider--blocbuilder">💡 BlocProvider?  BlocBuilder?</h3>
<hr>
<br/>

<h4 id="blocprovider">BlocProvider</h4>
<p>앱의 위젯 트리 중간에 Bloc 인스턴스를 제공하고 관리하기 위한 Widget</p>
<ul>
<li>지연 생성 가능 (lazy)</li>
<li>하위 계층 위젯 접근 가능</li>
<li>Bloc 생성 후 메모리 반환은 자동으로 진행</li>
</ul>
<br/>
<br/>

<h4 id="blocbuilder">BlocBuilder</h4>
<p>BlocProvider를 이용해 생성된 Bloc를 사용할때 쓰는 widget</p>
<ul>
<li><strong>Bloc 옵션 미설정으로 사용시 현 context로 Bloc를 찾아 변화를 감지 합니다.</strong>
Bloc를 지정하는 케이스의 경우는 특이사항 케이스에서만 사용하기를 권장 합니다. ex. 다이얼로그</li>
<li>BuildWhen 옵션을 통해 필요한 조건일때만 변화를 줄 수 있습니다.</li>
</ul>
<br/>
<br/>




<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

<hr>
<br/>

<p>해당 코드들의 전문은 <a href="https://github.com/LIMMIHEE/velog-exercise/tree/main/lib/presentation/feat/bloc">Github 레파지토리</a>에서 확인 가능합니다!</p>
<br/>
<br/>
<br/>
<br/>
<br/>

<blockquote>
<p><strong>공식 사이트</strong>
하위 사이트에서 더욱 다양한 예제를 볼 수 있으니 참고하시면 좋을 듯 합니다!</p>
<p><a href="https://bloclibrary.dev/#/">Bloc State Management Site</a></p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>