<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hodu_angel.log</title>
        <link>https://velog.io/</link>
        <description>Flutter developer</description>
        <lastBuildDate>Wed, 04 Mar 2026 04:42:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hodu_angel.log</title>
            <url>https://velog.velcdn.com/images/hodu_angel/profile/2defb940-300e-4e01-821c-37fd69ff4d77/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hodu_angel.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hodu_angel" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Flutter] v3.32 ~ v3.8 migration]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-v3.32-v3.8-migration</link>
            <guid>https://velog.io/@hodu_angel/Flutter-v3.32-v3.8-migration</guid>
            <pubDate>Wed, 04 Mar 2026 04:42:23 GMT</pubDate>
            <description><![CDATA[<h2 id="dot-shorthands">Dot shorthands</h2>
<p>길었던 문법을 짧게 줄여 간소화할 수 있도록 변경되었습니다.</p>
<pre><code class="language-dart">// With shorthands
Column(
  mainAxisAlignment: .start,
  crossAxisAlignment: .center,
  children: [ /* ... */ ],
),

// Without shorthands
Column(
  mainAxisAlignment: MainAxisAlignment.start,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [ /* … */ ],
),

// With shorthands
Padding(
  padding: .all(8.0),
  child: Text(&#39;Hello world&#39;),
),

// Without shorthands
Padding(
  padding: EdgeInsets.all(8.0),
  child: Text(&#39;Hello world&#39;),
),</code></pre>
<br>

<h2 id="badgecount-maxcount-속성-추가">Badge.count maxCount 속성 추가</h2>
<p>배지에 표시될 수 있는 개수의 최대치를 정하여 n+ 개로 표시할 수 있도록 개선하였습니다.
<strong>알림이 100개인 경우, maxCount를 99로 지정하면, 99+로 표시</strong>됩니다.
<img src="https://velog.velcdn.com/images/hodu_angel/post/6fe240df-87ff-466d-88d0-6c9bd81f0de9/image.png" alt=""></p>
<br>

<h2 id="slivermainaxisgroup-헤더-오버-스크롤-성능-개선">SliverMainAxisGroup 헤더 오버 스크롤 성능 개선</h2>
<p>참고: <a href="https://api.flutter.dev/flutter/widgets/SliverMainAxisGroup-class.html">https://api.flutter.dev/flutter/widgets/SliverMainAxisGroup-class.html</a></p>
<p>그룹 헤더 위로 오버 스크롤 되던 이슈를 개선했습니다.</p>
<br>

<h2 id="ios-26-완전-지원">iOS 26 완전 지원</h2>
<p>iOS26 부터는 UIScene life cycle이 필수 입니다.
그렇지 않으면 launch 되지 않습니다.</p>
<h3 id="수동-migration">수동 migration</h3>
<p>UIScene: <a href="https://docs.flutter.dev/release/breaking-changes/uiscenedelegate#migration-guide-for-flutter-apps">https://docs.flutter.dev/release/breaking-changes/uiscenedelegate#migration-guide-for-flutter-apps</a></p>
<h3 id="migration-기능-활성화-설정v338--명령어-실행-가능">migration 기능 활성화 설정(v3.38 ~ 명령어 실행 가능)</h3>
<p>Flutter tooling이 iOS Scene 기반 lifecycle을 지원하도록 옵션을 켭니다.</p>
<pre><code class="language-bash">flutter config --enable-uiscene-migration</code></pre>
<p><br><br></p>
<h2 id="android-sdk-version">Android Sdk Version</h2>
<blockquote>
<p>flutter.compileSdkVersion (API 36)
flutter.targetSdkVersion (API 36)
flutter.minSdkVersion (API 24) or higher</p>
</blockquote>
<p><br><br></p>
<h2 id="ides-widget-previews">IDEs Widget Previews</h2>
<p>현재 바라보고 있는 .dart 페이지의 UI를 즉시 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/hodu_angel/post/193b85e7-ecf0-4b18-b090-9947ace4de1f/image.gif" alt=""></p>
<p><br><br></p>
<h2 id="lint-추가">lint 추가</h2>
<p>더 이상 사용되지 않는 요소를 감지해 줌으로써, 최신 버전에 대응하도록 개선하였습니다.</p>
<pre><code class="language-yaml">linter:
  rules:
      - remove_deprecations_in_breaking_versions</code></pre>
<p><br><br></p>
<h2 id="dart-environment">Dart environment</h2>
<p>flutter 3.38부터는 dart 3.10.0 이상 지원함으로, 충돌 가능성을 낮추기 위해
아래와 같이 범위를 사용하는 것을 권장드립니다.</p>
<pre><code class="language-yaml">environment:
  sdk: &quot;&gt;=3.10.0 &lt;4.0.0&quot;</code></pre>
<p><br><br></p>
<p>참고 문서:
<a href="https://blog.flutter.dev/whats-new-in-flutter-3-38-3f7b258f7228">https://blog.flutter.dev/whats-new-in-flutter-3-38-3f7b258f7228</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Cute Kitty]]></title>
            <link>https://velog.io/@hodu_angel/Cute-Kitty</link>
            <guid>https://velog.io/@hodu_angel/Cute-Kitty</guid>
            <pubDate>Thu, 13 Nov 2025 08:30:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hodu_angel/post/60636dd7-a862-4084-82b7-35ad7387bc4c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Dart] v3.x Additional Syntax]]></title>
            <link>https://velog.io/@hodu_angel/Dart-v3.7-v3.8-Additional-Syntax</link>
            <guid>https://velog.io/@hodu_angel/Dart-v3.7-v3.8-Additional-Syntax</guid>
            <pubDate>Thu, 30 Oct 2025 11:13:51 GMT</pubDate>
            <description><![CDATA[<h2 id="docimport">@docImport</h2>
<p><code>@docImport</code> 으로 문서에서 import 하지 않아도 패키지를 그대로 사용할 수 있습니다.</p>
<blockquote>
<p>참고 문서: <a href="https://dart.dev/tools/doc-comments/references">https://dart.dev/tools/doc-comments/references</a></p>
</blockquote>
<br>

<h2 id="null-aware">null-aware</h2>
<p><code>null-aware</code> 문법 중 <code>?&lt;expression&gt;</code> syntax가 추가 되었습니다.
값을 연산, 접근/할당 시 null 값을 안전하게 처리합니다.</p>
<pre><code class="language-dart">/// a의 값이 null이라면 할당하지 않습니다.
int? a;
var b = [4, 5];
var list = [1 , 2, 3, ?a, ...b]; // [1, 2, 3, 4, 5]</code></pre>
<blockquote>
<p>참고 문서: <a href="https://dart.dev/language/collections#null-aware-element">https://dart.dev/language/collections#null-aware-element</a></p>
</blockquote>
<br>

<h2 id="label">Label</h2>
<p>goto 유사 문법인 <code>Label</code> 에 <code>break</code> 와 <code>continue</code> control flow statement가 추가 되었습니다.</p>
<p>조건문 혹은 루프 문에서 특정 위치에서 break/continue 하기 위함입니다.</p>
<pre><code class="language-dart">outerLoop: for (var i = 0; i &lt; 5; i++) {
  for (var j = 0; j &lt; 5; j++) {
    if (i + j &gt; 5) break outerLoop;
  }
}</code></pre>
<blockquote>
<p>참고 문서: <a href="https://dart.dev/language/loops#labels">https://dart.dev/language/loops#labels</a></p>
</blockquote>
<br>

<h2 id="record">Record</h2>
<p>(…) 형태를 <code>record literal</code> 이라고 합니다.</p>
<pre><code class="language-dart">final buttons = [ 
    (
        label: &quot;Button I&quot;, 
        icon: const Icon(Icons.upload_file), 
        onPressed: () =&gt; print(&quot;Action -&gt; Button I&quot;), 
    ),
    ( 
        label: &quot;Button II&quot;, 
        icon: const Icon(Icons.info), 
        onPressed: () =&gt; print(&quot;Action -&gt; Button II&quot;),
     ),
];</code></pre>
<p>위와 같이 buttons 가 있을 때,</p>
<p><span style="color:red"><strong>리스트 전체</strong></span>인 buttons 는 <code>List&lt;({String label, Icon icon, void Function() onPressed})&gt;</code> 타입을 갖으며
<span style="color:red"><strong>단일 요소</strong></span>로는 <code>({String label, Icon icon, void Function() onPressed})</code> 타입을 갖습니다.
<br></p>
<p>위와 같이 암묵적인 타입을 갖겠지만, 리스트 타입을 명시적으로 지정해주면 코드 수정에 더 용이할 것 같습니다.</p>
<pre><code class="language-dart">typedef ButtonItem = ({String label, Icon icon, void Function()? onPressed});
final List&lt;ButtonItem&gt; buttons = [
  // ...
];</code></pre>
<p>그러나 이렇게 보면 이전의 타입을 명시하여 작성했던 코드처럼 보입니다.
맞습니다. 결국 타입을 명시하지 않고 간결하게 작성하도록 개선된 것입니다.</p>
<p>즉, 타입을 명시하지 않은 경우, 아래와 같은 단점을 갖게 됩니다.</p>
<table>
<thead>
<tr>
<th></th>
<th>desc</th>
</tr>
</thead>
<tbody><tr>
<td>타입 안전성 부족</td>
<td><code>var</code> 또는 <code>List&lt;record&gt;</code>로 선언하지 않으면, 필드 타입이 명시되지 않아 헷갈릴 수 있음</td>
</tr>
<tr>
<td>자동 완성 제한</td>
<td>IDE에서 클래스처럼 <code>buttons[0].label</code> 입력 시 자동 완성이 덜 직관적일 수 있음</td>
</tr>
<tr>
<td>재사용 어려움</td>
<td>record는 재사용 구조/상속 불가 → 복잡한 로직에는 부적합</td>
</tr>
<tr>
<td>패턴 매칭 필요 시</td>
<td>필드 이름이 변경되면 모든 코드 수정 필요</td>
</tr>
</tbody></table>
<blockquote>
<p>참고 문서: <a href="https://dart.dev/language/records#records-as-simple-data-structures">https://dart.dev/language/records#records-as-simple-data-structures</a></p>
</blockquote>
<br>

<h2 id="implicit-downcast">Implicit downcast</h2>
<p>Dart v2.9 이후 부터 런타임 오류 가능성으로 인해, 암묵적 downcast를 권장하지 않습니다.</p>
<p><code>analysis_options.yaml</code>에 아래와 같이 추가하여 더 엄격한 prevent가 가능합니다.</p>
<pre><code class="language-yaml">analyzer:
  language:
    strict-casts: true</code></pre>
<p><strong>Before</strong></p>
<pre><code class="language-dart">dynamic value = &quot;hello&quot;;
String text = value; // OK, 암묵적 downcast 허용
</code></pre>
<p><strong>After</strong></p>
<pre><code class="language-dart">dynamic value = &quot;hello&quot;;
String text = value; // ❌ 오류: implicit downcast from dynamic
String text2 = value as String; // ✅ 명시적 cast 필요</code></pre>
<blockquote>
<p>참고 문서: <a href="https://dart.dev/language/type-system#implicit-downcasts-from-dynamic">https://dart.dev/language/type-system#implicit-downcasts-from-dynamic</a></p>
</blockquote>
<br>

<h2 id="trailing-comma">trailing comma</h2>
<p>새롭게 추가된 <code>trailing comma</code>로 인해, 만일 설정해 놓은 코드의 max length를 넘어간다면
comma가 자동 생성되어 줄바꿈이 되며,</p>
<p>이전 코드의 length와 현재 코드의 length의 길이 합이 최대 길이보다 작으면
줄바꿈을 위해 작성했던 comma가 삭제되고, 강제로 이전 코드 라인으로 줄 변경이 이뤄집니다.</p>
<p>만일 comma대로 개행이 되길 원한다면 <code>pubspec.yaml</code> 파일에서 dart sdk의 최소 버전을
v3.6.0 이후로 지정해 줍니다.</p>
<pre><code class="language-yaml">environment:
  sdk: &quot;&gt;=3.6.0 &lt;4.0.0&quot;</code></pre>
<blockquote>
<p>참고 문서: <a href="https://github.com/dart-lang/dart_style/issues/1253">https://github.com/dart-lang/dart_style/issues/1253</a></p>
</blockquote>
<blockquote>
<p><strong>Dart v3.8 이후</strong> 부터 analysis_options.yaml 파일에서 아래와 같이
comma를 보존한다는 옵션을 작성한다면,</p>
<p>개행을 위해 명시적으로 작성했던 comma가 사라지지 않지만
comma로 구분하지 않고 이어 작성했던 코드도 comma가 생성되어 강제 개행이 됩니다. </p>
<pre><code class="language-yaml">formatter:
  trailing_commas: preserve</code></pre>
</blockquote>
<pre><code>&gt; &lt;details&gt;
&lt;summary&gt;&lt;span style=&quot;color:blue&quot;&gt;Example&lt;/span&gt;&lt;/summary&gt;
&gt;
&gt; Before use
&gt;```dart
runApp(
    ShowCaseWidget(builder: (context) {
        return MultiProvider(
          providers: getProviders(),
          child: const MyApp(),
        );
      }),
  );</code></pre><blockquote>
<p>After use</p>
</blockquote>
<pre><code class="language-dart">/// builder 라인이 comma 보존 옵션에 의해 개행됩니다.
runApp(
    ShowCaseWidget(
      builder: (context) {
        return MultiProvider(
          providers: getProviders(),
          child: const MyApp(),
        );
      },
    ),
  );</code></pre>
</details>

<br>

<h2 id="page-width">page width</h2>
<p><code>analysis_options.yaml</code> 에서 최대 코드 길이를 설정하는 옵션이 새롭게 추가 되었습니다.</p>
<pre><code class="language-yaml">formatter:
  page_width: 100 </code></pre>
<p><span style="color:red">top-level</span>에서 동작하기 때문에 IDE의 max length와 <code>setting.json</code>의 설정 보다도 위에서 동작합니다.
단, 코드 길이 가이드 라인은 IDE에서 조정해야 변경됩니다.</p>
<blockquote>
<p>참고 문서: <a href="https://dart.dev/tools/dart-format#configuring-formatter-page-width">https://dart.dev/tools/dart-format#configuring-formatter-page-width</a></p>
</blockquote>
<br>

<h2 id="switch-state">switch (state)</h2>
<p>이전 버전에서는 <span style="color:red">값 기반</span> 비교였지만 이후 부터는 <span style="color:red">객체 패턴</span>도 매치가 가능해졌습니다.</p>
<pre><code class="language-dart">sealed class State {}

class Loading extends State {}
class Success extends State {}
class Error extends State {}

void main() {
  State state = Loading();

  switch (state) {
    case Loading():
      print(&#39;loading...&#39;);
    case Success():
      print(&#39;successful!&#39;);
    case Error():
      print(&#39;error!&#39;);
  }
}</code></pre>
<br>

<h2 id="final">:final</h2>
<p>아래와 같이 사용합니다.</p>
<pre><code class="language-dart">ClassName(:final fieldName)</code></pre>
<p>named field가 있는 객체에서 필드를 추출할 때 사용됩니다.</p>
<pre><code class="language-dart">class Person {
  final String name;
  final int age;
  Person(this.name, this.age);
}

void main() {
  Person p = Person(&#39;h&#39;, 25);

  switch (p) {
    case Person(:final name, :final age):
      print(&#39;$name, $age&#39;);
  }
}
/// result: h, 25</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] What’s new in Flutter 3.7]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-Whats-new-in-Flutter-3.7</link>
            <guid>https://velog.io/@hodu_angel/Flutter-Whats-new-in-Flutter-3.7</guid>
            <pubDate>Mon, 06 Oct 2025 07:26:04 GMT</pubDate>
            <description><![CDATA[<p>flutter v3.7.0으로 업데이트 되면서 많은 내용들이 수정 및 추가 되었습니다.
참고: <a href="https://docs.flutter.dev/release/release-notes/release-notes-3.7.0">https://docs.flutter.dev/release/release-notes/release-notes-3.7.0</a></p>
<p>UI관련하여 내용을 살펴보겠습니다.</p>
<h3 id="icon-badge">Icon badge</h3>
<p>Stack 위젯으로 우측 상단에 notification을 표시하는 것을 간소화 하여 아래와 같이 표현할 수 있게 되었습니다.</p>
<blockquote>
<p><a href="https://api.flutter.dev/flutter/material/Badge-class.html">https://api.flutter.dev/flutter/material/Badge-class.html</a>
<img src="https://velog.velcdn.com/images/hodu_angel/post/5d4184d1-89ca-44ee-afcf-62b562223a16/image.png" alt=""></p>
</blockquote>
<h3 id="filledbutton">FilledButton</h3>
<p>배경색 채움 및 border-radius가 100인 버튼을 생성하려면 
그림자가 기본으로 내장되어 있는 ElevatedButton, border-radius가 0인 InkWell과 같은 위젯에서 수정했어야 됐지만 
FilledButton으로 간단하게 표현이 가능하게 되었습니다.</p>
<blockquote>
<p><a href="https://api.flutter.dev/flutter/material/FilledButton-class.html">https://api.flutter.dev/flutter/material/FilledButton-class.html</a>
<img src="https://velog.velcdn.com/images/hodu_angel/post/e0b01b3f-f6fd-4e91-b225-516e83401c95/image.png" alt=""></p>
</blockquote>
<h3 id="segmentedbutton">SegmentedButton</h3>
<p>flutter_advanced_segment 패키지를 사용해, segment버튼을 표현할 수 있었지만 애니메이션 효과가 요구되지 않는다면, 
그룹 버튼 중 일부 선택 (ex. 모드 선택) 등을 간편하게 아래와 같이 작성할 수 있게 되었습니다.</p>
<blockquote>
<p><a href="https://api.flutter.dev/flutter/material/SegmentedButton-class.html">https://api.flutter.dev/flutter/material/SegmentedButton-class.html</a>
<img src="https://velog.velcdn.com/images/hodu_angel/post/5ae31f9a-efbb-4311-82fd-47ac4b6e542d/image.png" alt=""></p>
</blockquote>
<h3 id="divider">Divider</h3>
<pre><code class="language-dart">/// indent: 좌측으로 부터의 간격
/// endIndent: 우측으로 부터의 간격
/// thickness: divider 두께
/// height: divider 높이
///
/// 만일, 위 아래 간격이 8이며, divier 두께가 4이고 싶다면 height는 20로 작성합니다.
const Divider(height: 20, thickness: 20, indent: 5, endIndent: 0, color: Colors.black)</code></pre>
<blockquote>
<p><a href="https://api.flutter.dev/flutter/material/Divider-class.html">https://api.flutter.dev/flutter/material/Divider-class.html</a>
<img src="https://velog.velcdn.com/images/hodu_angel/post/3fa32a70-3fab-4f41-b42f-17873728647c/image.png" alt=""></p>
</blockquote>
<h3 id="dropdownmenu">DropdownMenu</h3>
<p>드롭다운을 클릭 시, TextField가 활성화 됩니다. 
requestFocusOnTap 속성을 false로 하면 TextField가 비활성화 됩니다.</p>
<blockquote>
<p><a href="https://api.flutter.dev/flutter/material/DropdownMenu-class.html">https://api.flutter.dev/flutter/material/DropdownMenu-class.html</a>
<img src="https://velog.velcdn.com/images/hodu_angel/post/bef01cf6-60b6-4e01-bace-f6cb6764697b/image.png" alt=""></p>
</blockquote>
<p>참고 문서: <a href="https://blog.flutter.dev/whats-new-in-flutter-3-7-38cbea71133c">https://blog.flutter.dev/whats-new-in-flutter-3-7-38cbea71133c</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 라이프 사이클을 관찰 하는 WidgetsBindingObserver]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4%EC%9D%84-%EA%B4%80%EC%B0%B0-%ED%95%98%EB%8A%94WidgetsBindingObserver</link>
            <guid>https://velog.io/@hodu_angel/Flutter-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4%EC%9D%84-%EA%B4%80%EC%B0%B0-%ED%95%98%EB%8A%94WidgetsBindingObserver</guid>
            <pubDate>Fri, 29 Mar 2024 04:53:05 GMT</pubDate>
            <description><![CDATA[<p>앱을 구현하다 보면, 백그라운드 및 기본 위젯이 제공하는 override 메서드들 외에도
추가적인 감지 기능이 필요할 때가 있습니다.</p>
<p>그를 위한 것이, WidgetsBindingObserver 입니다.</p>
<blockquote>
<p><a href="https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html">https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html</a></p>
</blockquote>
<br>

<h2 id="methods">Methods</h2>
<pre><code class="language-dart">didChangeAccessibilityFeatures()
- 접근성 설정 변경 감지 
- 접근성 기능 활성화/비활성화 감지
- 테마 변경 감지 (다크 모드/라이트 모드)

didChangeAppLifecycleState(AppLifecycleState state)
- 앱 활성화/비활성화 감지
- 앱 종료 감지
- 앱 상태 관리

didChangeLocales(List&lt;Locale&gt;? locales)
- 언어 변경 감지
- 지역별 리소스 관리
- 다국어 지원

didChangeMetrics()
- 디바이스 방향 변경 감지
- 디바이스 해상도 변경 감지
- 화면 크기 변경 감지

didChangePlatformBrightness()
- 시스템 테마 변경 감지
- 밝기 모드 변경 감지
- 테마 및 스타일 변경 감지

didChangeTextScaleFactor()
- 텍스트 크기 변경 감지

didHaveMemoryPressure() 
- 메모리가 부족한 상태 감지

didPopRoute()
- 화면 이동 감지(이전 화면으로 돌아가는 경우)

didPushRoute(String route) 
- 화면 이동 감지

didPushRouteInformation(RouteInformation routeInformation) 
- 새로운 라우트 정보 푸시 감지

didRequestAppExit() 
- 앱 종료 감지</code></pre>
<br>

<h2 id="code">Code</h2>
<pre><code class="language-dart">class ExamplePage extends StatefulWidget {
    @override
      State&lt;ExamplePage&gt; createState() =&gt; _ExamplePageState();
}

// with WidgetsBindingObserver 를 추가
class _ExamplePageState extends State&lt;ExamplePage&gt; with WidgetsBindingObserver {
    @override
    void initState() {
        super.initState();
        // 앱의 생명주기를 관찰할 수 있도록 WidgetsBinding에 현재 인스턴스를 등록
        WidgetsBinding.instance.addObserver(this);        
    }

    // 위 Methods에서 사용하려는 메서드 override
    @override
    void didChangeMetrics() {
          // 기능 작성
    }

      @override
    void dispose() {
        // 메모리 누수를 방지하기 위해 해제
        WidgetsBinding.instance.removeObserver(this);
        super.dispose();
      }
}
</code></pre>
<p><br><br></p>
<p>참고 문서:
<a href="https://velog.io/@dev_seonhan/Flutter-WidgetsBindingObserver">https://velog.io/@dev_seonhan/Flutter-WidgetsBindingObserver</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] hooks_riverpod]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-hooksriverpod</link>
            <guid>https://velog.io/@hodu_angel/Flutter-hooksriverpod</guid>
            <pubDate>Sun, 17 Mar 2024 09:20:25 GMT</pubDate>
            <description><![CDATA[<p><code>Riverpod</code> 는 여러 종류의 패키지가 있습니다. </p>
<p>각 패키지마다 사용 목적이 다르며, 어떤 패키지를 설치 할 것인지는 만드려는 앱에 따라 다릅니다.</p>
<ul>
<li><strong>flutter_riverpod</strong>: Flutter 앱에 Riverpod 을 사용할 경우의 기본 패키지.</li>
<li><strong>riverpod</strong>: Flutter 에 관련된 모든 클래스가 완전히 제거된 Riverpod 패키지.</li>
<li><strong>hooks_riverpod</strong> : flutter_hooks 와 Rivperpod 를 함께 사용하는 패키지.
<img src="https://velog.velcdn.com/images/hodu_angel/post/fa535ff4-d431-4b7e-9f6b-a097ef428eff/image.png" alt="riverpod_list"><br>

</li>
</ul>
<h3 id="refwatch">ref.watch</h3>
<p>provider 의 값을 얻어서 변화를 모니터링 할 때 사용합니다. </p>
<p>값이 변경되면, widget 을 re_build 하거나 값을 구독하고 있는 위치에 상태 값을 전달합니다.</p>
<p>상태 변화를 화면에 즉각 반영해야 할 때 사용합니다.</p>
<pre><code class="language-dart">final counterProvider = StateProvider((ref) =&gt; 0);

class HomeView extends ConsumerWidget {
  const HomeView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // ref를 사용하여 provider 구독
    final counter = ref.watch(counterProvider);

    return Text(&#39;$counter&#39;);
  }
}</code></pre>
<br>

<h3 id="reflisten">ref.listen</h3>
<p>watch 와 동일하게 변화를 모니터링 하지만, widget 을 re_build 하거나 다른 곳에 상태값을 전달하지는 않습니다.</p>
<p>상태 변경이 있을 때, 커스텀한 어떠한 행위를 취해야 할 경우 사용합니다.</p>
<p>아래와 같이 상태 변화 시, SnackBar 등을 호출 할 때, 사용할 수 있습니다.</p>
<pre><code class="language-dart">final counterProvider = StateNotifierProvider&lt;Counter, int&gt;((ref) =&gt; Counter(ref));

class HomeView extends ConsumerWidget {
  const HomeView({Key? key}): super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.listen&lt;int&gt;(counterProvider, (int? previousCount, int newCount) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(&#39;Value is ${current.state}&#39;)),
      );
    });

    return Scaffold(...);
  }
}</code></pre>
<br>

<h3 id="refread">ref.read</h3>
<p>어떤 부가적인 효과 없이 provider 의 상태 값을 가지고 올 때 사용합니다.</p>
<p>read 는 일반적으로 사용자 상호작용으로 발생가능한 트리거 함수 내부에서 주로 사용합니다.</p>
<p>주로, provider 내부의 <code>callback</code> 함수에 접근할 때 사용합니다.</p>
<pre><code class="language-dart">final counterProvider =
    StateNotifierProvider&lt;Counter, int&gt;((ref) =&gt; Counter(ref));

class HomeView extends ConsumerWidget {
  const HomeView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // counterProvider의 increment() 메소드를 호출합니다.
          ref.read(counterProvider.notifier).increment();
        },
      ),
    );
  }
}</code></pre>
<p><br><br></p>
<h2 id="설치">설치</h2>
<p><a href="https://pub.dev/packages/hooks_riverpod">https://pub.dev/packages/hooks_riverpod</a></p>
<p>hooks_riverpod</p>
<pre><code class="language-dart">flutter pub add hooks_riverpod</code></pre>
<p>g.dart를 생성하기 위한 패키지</p>
<pre><code class="language-dart">flutter pub add riverpod_generator</code></pre>
<p>generator를 사용하기 위한 패키지</p>
<pre><code class="language-dart">flutter pub add build_runner</code></pre>
<p>annotation 패키지</p>
<pre><code class="language-dart">flutter pub add riverpod_annotation</code></pre>
<p>적용</p>
<pre><code class="language-dart">flutter pub get</code></pre>
<p>generator commaned</p>
<pre><code class="language-dart">dart run build_runner build --delete-conflicting-outputs</code></pre>
<br>

<h2 id="코드">코드</h2>
<ul>
<li>기본 구조<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;
import &#39;package:hooks_riverpod/hooks_riverpod.dart&#39;;
</code></pre>
</li>
</ul>
<p>class HooksRiverpodEx extends HookConsumerWidget {
  const HooksRiverpodEx({super.key});</p>
<p>  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Container();
  }
}</p>
<pre><code>
- main.dart
```dart
void main() {
  runApp(const ProviderScope(child: MyApp()));
}</code></pre><ul>
<li>counter_provider.dart<pre><code class="language-dart">import &#39;package:riverpod_annotation/riverpod_annotation.dart&#39;;
</code></pre>
</li>
</ul>
<p>part &#39;counter_provider.g.dart&#39;;</p>
<p>@riverpod
class Counter extends _$Counter {
    // state로 관리
  int fetchItem(int value) {
    // 초기화 당시 실행될 로직
    return state = value;
  }</p>
<p>  @override
  int build() {
    return fetchItem(0);
  }</p>
<p>  // state값 0으로 초기화
  void reset() {
    state = 0;
  }</p>
<p>  // state값 1 증가
  void increase() {
    state++;
  }
}</p>
<pre><code>
변수를 생성하여 사용하는 것이 아닌, provider를 통해 상태를 생성하고,

이 상태를 앱 전반에 걸쳐 읽거나 변경할 수 있습니다.
&lt;br&gt;

- hooks_riverpod_ex.dart
FloatingActionButton 버튼을 통해 counter 값을 1씩 증가시킵니다.
```dart
class HooksRiverpodEx extends HookConsumerWidget {
  const HooksRiverpodEx({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    var viewModel = ref.watch(counterProvider);

    return Scaffold(
      body: SafeArea(
          child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(viewModel.toString()),
            ElevatedButton(
                onPressed: () {
                  ref.read(counterProvider.notifier).reset();
                },
                child: const Text(&#39;초기화&#39;)),
          ],
        ),
      )),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ref.read(counterProvider.notifier).increase();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}</code></pre><br>

<img src='https://velog.velcdn.com/images/hodu_angel/post/e44a5dc3-1ec9-4617-b553-5beb7f7b1555/image.png' width=50>

<p><br><br></p>
<h2 id="참고">참고</h2>
<p>비동기인 경우, 아래와 같이 <strong>데이터 결과, 로딩, 에러</strong>를 한번에 보여주는 위젯을 제공합니다.</p>
<pre><code class="language-dart">viewModel.when(
            data: (data) {
              return ListView.builder(
                itemCount: data.length,
                itemBuilder: (context, index) {
                  var item = data[index];
                  return ListTile(
                    dense: true,
                    title: Text(&#39;item1&#39;),
                    shape: Border(bottom: BorderSide(width: 1)),
                  );
                },
              );
            },
            error: (error, stack) =&gt; Center(
              child: CustomErrorWidget(
                error: error,
              ),
            ),
            loading: () =&gt; const LoadingIndicator(),
          )</code></pre>
<p><br><br>
참고 문서:
<a href="https://pub.dev/packages/hooks_riverpod">https://pub.dev/packages/hooks_riverpod</a></p>
<p><a href="https://velog.io/@udong85/Flutter-Riverpod-%EB%A1%9C-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%ED%95%98%EA%B8%B0-1">https://velog.io/@udong85/Flutter-Riverpod-%EB%A1%9C-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%ED%95%98%EA%B8%B0-1</a></p>
<p><a href="https://anpigon.tistory.com/349">https://anpigon.tistory.com/349</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Widget을 Method / Class 로 분리의 장단점]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-Widget%EC%9D%84-Method-Class-%EB%A1%9C-%EB%B6%84%EB%A6%AC%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90</link>
            <guid>https://velog.io/@hodu_angel/Flutter-Widget%EC%9D%84-Method-Class-%EB%A1%9C-%EB%B6%84%EB%A6%AC%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90</guid>
            <pubDate>Fri, 15 Mar 2024 04:59:57 GMT</pubDate>
            <description><![CDATA[<p>각 화면을 이루는 page.dart 안에 여러 위젯을 한 번에 작성하여 가독성을 저하시키지 않고,</p>
<p><strong>Method</strong> 혹은 <strong>Class</strong>로 분리하여 <strong>가독성을 높일</strong> 뿐만이 아니며 <strong>성능 향상</strong>에도 도움을 줄 수 있도록 코드를 작성하실 것입니다.</p>
<p>상황에 따라 어떤 방식으로 작성하는 것이 좋을지,
각, Method와 Class로 분리할 때의 장단점에 대해 알아보겠습니다.
<br></p>
<h2 id="method-방식">Method 방식</h2>
<h3 id="장점">장점</h3>
<ul>
<li><p><strong>간단한 UI 변경 사항</strong>이나 <strong>재사용 로직을 구현할 때 빠르고 직관적</strong>입니다.</p>
</li>
<li><p>부모 위젯의 context에 쉽게 접근할 수 있어, <strong>부모 위젯의 상태를 직접 참조</strong>하거나 <strong>수정하기 용이</strong>합니다.</p>
</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li><p><strong>복잡한 UI 구조에서 메서드를 사용하면 코드가 지저분</strong>해지고, <strong>가독성이 떨어질</strong> 수 있습니다.</p>
</li>
<li><p>메서드는 <strong>새로운 위젯을 생성하지 않기 때문</strong>에, Flutter의 위젯 트리에서 <strong>독립적인 엔티티로 관리되지 않습니다.</strong> 따라서 <strong>성능 최적화 측면</strong>에서 위젯 트리의 재구성이 필요한 상황에서 <strong>비효율</strong>적일 수 있습니다.
<br><br></p>
</li>
</ul>
<h2 id="class-방식">Class 방식</h2>
<h3 id="장점-1">장점</h3>
<ul>
<li><p>복잡한 UI 구성 요소를 위젯으로 분리함으로써, <strong>코드의 가독성</strong>과 <strong>유지보수성</strong>이 크게 향상됩니다.</p>
</li>
<li><p><strong>독립된 위젯으로 관리</strong>되므로, Flutter의 위젯 트리에서 <strong>재사용과 성능 최적화 측면에서 이점</strong>을 가집니다. 특히, <code>const</code> 생성자를 사용할 경우 위젯의 <strong>재빌드를 효율적으로 줄일 수 있</strong>습니다.</p>
</li>
<li><p>동일한 UI 구성 요소를 쉽게 재사용할 수 있습니다.</p>
</li>
</ul>
<h3 id="단점-1">단점</h3>
<ul>
<li><p>단순한 UI 변경이나 작은 구성 요소에 대해 별도의 위젯을 만드는 것은</p>
</li>
<li><p><em>오버엔지니어링*</em>으로 여겨질 수 있으며, <strong>개발 과정을 불필요하게 복잡</strong>하게 만들 수 있습니다.</p>
</li>
<li><p>부모 위젯의 상태에 접근하기 위해서는 콜백 함수나 InheritedWidget, Provider 같은</p>
</li>
<li><p><em>상태 관리 라이브러리를 사용*</em>해야 될 수 있으며, 이는 <strong>추가적인 구현이 필요</strong>합니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] SingleChildScrollView with Column & ListView]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-SingleChildScrollView-with-Column-ListView</link>
            <guid>https://velog.io/@hodu_angel/Flutter-SingleChildScrollView-with-Column-ListView</guid>
            <pubDate>Thu, 14 Mar 2024 08:47:07 GMT</pubDate>
            <description><![CDATA[<p>SingleChildScrollView에 child로 Column을 넣어 사용하다,
문득 ListView와의 어떤 차이가 있는지 궁금해졌습니다.</p>
<p>어떤 상황에 어느 방법으로 사용할지 알아보겠습니다.</p>
<h2 id="singlechildscrollview-with-column">SingleChildScrollView with Column</h2>
<h3 id="장점">장점</h3>
<ul>
<li><p><strong>복잡한 레이아웃 구성이 가능</strong>합니다.
Column 내부에 다양한 위젯과 레이아웃을 조합하여 복잡한 UI를 구성할 수 있습니다.</p>
</li>
<li><p>스크롤 가능한 영역 내에서 <strong>정적인 요소</strong>들이 함께 있을 경우 유용합니다. 
예를 들어, 스크롤 영역의 상단이나 하단에 고정된 버튼이나 텍스트가 있는 경우 등입니다.</p>
</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li><p><strong>모든 위젯이 한 번에 렌더링</strong>됩니다.
자식 위젯이 많은 경우, <strong>앱의 성능에 부정적인 영향</strong>을 미칠 수 있습니다.</p>
</li>
<li><p><strong>메모리 사용량</strong>이 많아질 수 있으며, <strong>대규모의 아이템 목록을 다루는 경우</strong>, <strong>비효율</strong>적일 수 있습니다.
<br><br></p>
</li>
</ul>
<h2 id="listview">ListView</h2>
<h3 id="장점-1">장점</h3>
<ul>
<li><p><strong>대량의 데이터를 다룰 때, 최적화</strong>되어 있습니다. </p>
</li>
<li><p><em>화면에 보이는 항목만 렌더링*</em>하여 성능과 메모리 사용량을 개선합니다.</p>
</li>
<li><p>아이템 재사용을 통해 <strong>스크롤 성능이 매우 우수</strong>합니다. 
스크롤 시에도 부드러운 사용자 경험을 제공합니다.</p>
</li>
<li><p><strong>내장된 스크롤 기능을 사용</strong>하기 때문에 추가적인 스크롤 위젯을 사용할 필요가 없습니다.</p>
</li>
</ul>
<h3 id="단점-1">단점</h3>
<ul>
<li><p>내부에 <strong>복잡한 레이아웃을 구성</strong>하기 어렵습니다. 
모든 자식이 리스트의 항목으로 구성되어야 하며, 복잡한 레이아웃을 위해서는 ListView.builder 등을 사용하여 커스텀 위젯을 구현해야 합니다.</p>
</li>
<li><p>ListView의 헤더나 푸터 같은 고정된 요소를 추가하기 위해서는 ListTile 등을 활용하거나, ListView.builder의 itemCount를 조정하여 직접 구현해야 합니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 'WillPopScope' is deprecated and shouldn't be used. Use PopScope instead. This feature was deprecated after v3.12.0-1.0.pre.]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-WillPopScope-is-deprecated-and-shouldnt-be-used.-Use-PopScope-instead.-This-feature-was-deprecated-after-v3.12.0-1.0.pre</link>
            <guid>https://velog.io/@hodu_angel/Flutter-WillPopScope-is-deprecated-and-shouldnt-be-used.-Use-PopScope-instead.-This-feature-was-deprecated-after-v3.12.0-1.0.pre</guid>
            <pubDate>Thu, 15 Feb 2024 01:14:23 GMT</pubDate>
            <description><![CDATA[<p>Flutter, Dart 버전을 마이그레이션 하면서 일부 Widget의 변화가 있었습니다.</p>
<p>그 중, 가장 크게 눈에 띄었던 것은 <code>WillPopScope</code> 였습니다.</p>
<p><del>WillPopScope</del> 과 같이 보여지며, 아래의 메시지를 보여주었습니다.</p>
<blockquote>
<p>&#39;WillPopScope&#39; is deprecated and shouldn&#39;t be used. Use PopScope instead. This feature was deprecated after v3.12.0-1.0.pre.</p>
</blockquote>
<p>어떠한 이유로 더 이상 사용하지 않도록 변경을 했는지에 대한 설명은 공식문서에 표기되어 있지 않지만,</p>
<p>추측하건대, <strong>API의 일관성과 명확성을 높이기</strong> 위함과 
새로운 위젯으로 기존 위젯보다 더 많은 기능을 제공하거나, 기존 문제를 해결할 수 있다는 <strong>기능적 개선</strong>이라는 점에서</p>
<p><code>PopScope</code> 는 WillPopScope의 기능을 포함하면서도 추가적인 이점이 있을 수 있습니다.
<br></p>
<p>마이그레이션한 버전은 아래와 같습니다.</p>
<ul>
<li><p>Before</p>
<pre><code class="language-dart">Flutter v 3.10.6
Dart v 3.0.6
DevTool v 2.23.1</code></pre>
</li>
<li><p>After</p>
<pre><code class="language-dart">Flutter v 3.16.8
Dart v 3.2.5
DevTool v 2.28.5</code></pre>
<br>

</li>
</ul>
<p>위의 메시지와 같이, 이제는 WillPopScope를 PopScope로 대체하여 사용해야 합니다.</p>
<ul>
<li><p>Before</p>
<pre><code class="language-dart">WillPopScope(
onWillPop: () async {
  // 뒤로 가기 버튼을 눌렀을 때의 로직
  return true; // 또는 false;
},
child: Scaffold(
  // ...
),
);</code></pre>
</li>
<li><p>After</p>
<pre><code class="language-dart">PopScope(
canPop: false,    // onPopInvoked가 작동하기 위해선 false 되어야 합니다.
onPopInvoked: (didPop) async {
if (didPop) {
  return;
}
// 뒤로 가기 버튼을 눌렀을 때의 로직
},</code></pre>
<p><br><br>
참고 문서:
<a href="https://stackoverflow.com/questions/77529904/willpopscope-is-deprecated-in-flutter">https://stackoverflow.com/questions/77529904/willpopscope-is-deprecated-in-flutter</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] go_router]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-gorouter</link>
            <guid>https://velog.io/@hodu_angel/Flutter-gorouter</guid>
            <pubDate>Tue, 13 Feb 2024 11:00:26 GMT</pubDate>
            <description><![CDATA[<h1 id="gorouter">GoRouter</h1>
<p>go_router 패키지는 선언적 Navigation을 지원하여 더 간결하고 직관적인 라우팅 방식을 제공합니다.</p>
<p>앱 내에서 페이지 간의 이동을 더 쉽게 관리할 수 있을 뿐더러, 
고유한 URL 패턴과 <strong>딥 링크</strong>가 화면 간 탐색을 Handling 해줍니다.</p>
<br>

<h2 id="기본-셋팅">기본 셋팅</h2>
<pre><code class="language-dart">var goRouter = GoRouter(routes: [
  GoRoute(
    path: &#39;/&#39;,
    builder: (context, state) =&gt; const MainPage(),
  ),
   GoRoute(
    path: &#39;/goRouterPage&#39;,
    builder: (context, state) =&gt; const GoRouterPage(),
  ),
]);

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      routerConfig: goRouter,
    );
  }
}</code></pre>
<h3 id="parameters을-전달하는-경우">parameters을 전달하는 경우.</h3>
<p>기존의 웹 기반 라우팅 라이브러리와 같은 방식으로 제공하고 있어서 사용방법이 유사합니다.</p>
<ul>
<li><p>state.params
URL 경로의 일부로 동적으로 전달되는 값 입니다.</p>
</li>
<li><p>state.queryParams
URL의 쿼리문인 물음표(?)를 기점으로 이후에 위치하며,
URL의 쿼리 문자열 부분에서 사용되는 키-값 입니다.
만일 <code>?filter=hodu</code> 에서 filter는 key이고 hodu는 value에 해당하는 값 입니다.
페이지 혹은 데이터를 필터링하는 것에 자주 사용됩니다.</p>
</li>
</ul>
<pre><code class="language-dart">GoRoute(
    path: &#39;/home/:userId?filter=hodu&#39;,
    builder: (context, state) =&gt;
        MainHomePage(
            userId: state.params[&#39;userId&#39;],
            filter: state.queryParams[filter]
        )
)</code></pre>
<h3 id="어디로든-페이지-이동하고-싶은-경우">어디로든 페이지 이동하고 싶은 경우.</h3>
<p>GoRouter.of(context).go(&#39;경로&#39;) 혹은 context.go(&#39;경로&#39;) 로 이동이 가능합니다.</p>
<pre><code class="language-dart">ElevatedButton(
    child: const Text(&#39;&#39;),
    onPressed: () =&gt; GoRouter.of(context).go(&#39;/&#39;), // 혹은 context.go(&#39;/&#39;)
)</code></pre>
<h3 id="bottomnavigation이-필요한-경우">bottomNavigation이 필요한 경우.</h3>
<p>ShellRoute로 UI Shell을 Rendering 하기 때문에 UI Shell(bottomNavigation)이 탐색 시에도 그대로 유지됩니다.</p>
<pre><code class="language-dart">var goRouter = GoRouter(routes: [
  ShellRoute(
    routes: [
      GoRoute(
        path: &#39;/&#39;,
        builder: (context, state) =&gt; const HomePage(),
      ),
      GoRoute(
        name: RouterName.flutterHooks.name,
        path: &#39;/flutterHooks&#39;,
        builder: (context, state) =&gt; const FlutterHooksExPage(),
      ),
    ],
    builder: (context, state, child) {
      return Scaffold(
        body: child,
        bottomNavigationBar: BottomNavigationBar(currentIndex: 0, items: const [
          BottomNavigationBarItem(label: &#39;home&#39;, icon: Icon(Icons.heart_broken)),
          BottomNavigationBarItem(label: &#39;home2&#39;, icon: Icon(Icons.ac_unit_outlined)),
        ]),
      );
    },
  ),</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] flutter_hooks useCallback(4)]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-flutterhooks-useCallback4</link>
            <guid>https://velog.io/@hodu_angel/Flutter-flutterhooks-useCallback4</guid>
            <pubDate>Tue, 13 Feb 2024 09:39:25 GMT</pubDate>
            <description><![CDATA[<h1 id="usecallback">useCallback</h1>
<p>일반적인 함수처럼 사용됩니다. 아래와 같이 메서드를 인자로 받아 사용합니다.</p>
<pre><code class="language-dart">useCallback(T callback, [List&lt;Object?&gt; keys = const &lt;Object&gt;[]])</code></pre>
<h3 id="예시">예시</h3>
<p>a와 b의 값을 각각 증가시켜 합을 반환하는 useCallback을 사용한 sumATOB 예시입니다.</p>
<pre><code class="language-dart">class UsecallbackEx extends HookWidget {
  const UsecallbackEx({super.key});

  @override
  Widget build(BuildContext context) {
    var a = useState(0);
    var b = useState(0);

    var sumATOB = useCallback((a, b) {
      return a + b;
    }, [a, b]);

    return Scaffold(
      appBar: AppBar(),
      body: SafeArea(
          child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(sumATOB(a.value, b.value).toString()),
            const SizedBox(height: 10),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                ElevatedButton(
                    onPressed: () {
                      a.value++;
                    },
                    child: const Text(&#39;a&#39;)),
                ElevatedButton(
                    onPressed: () {
                      b.value++;
                    },
                    child: const Text(&#39;b&#39;)),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(a.value.toString()),
                const SizedBox(width: 60),
                Text(b.value.toString()),
              ],
            )
          ],
        ),
      )),
    );
  }
}</code></pre>
<img width=300 src= https://velog.velcdn.com/images/hodu_angel/post/c589ced1-184c-427f-b784-5f6e32333c12/image.png >]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] flutter_hooks useMemoized(3)]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-flutterhooks-useMemoized3-agbkb0zf</link>
            <guid>https://velog.io/@hodu_angel/Flutter-flutterhooks-useMemoized3-agbkb0zf</guid>
            <pubDate>Tue, 13 Feb 2024 09:10:56 GMT</pubDate>
            <description><![CDATA[<h1 id="usememoized">useMemoized</h1>
<p>Hook은 useMemoized 함수를 통해 계산 비용이 높은 함수의 반환 값을 저장하고,
동일한 인자로 함수가 재호출 될 때, 이전에 저장한 값을 재사용하여 성능을 최적화 하는데 도움을 줍니다.</p>
<p>함수가 반환하는 값을 <strong>기억</strong>하여 불필요한 재계산을 방지합니다.</p>
<h3 id="예시">예시</h3>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;
import &#39;package:flutter_hooks/flutter_hooks.dart&#39;;

class SquareCalculatorWidget extends HookWidget {
  const SquareCalculatorWidget({super.key});

  @override
  Widget build(BuildContext context) {
    // 사용자 입력을 저장하는 변수.
    final inputController = useTextEditingController();

    // 입력 값에 따라 제곱 값을 계산하고 기억합니다.
    final square = useMemoized(() {
      final input = int.tryParse(inputController.text);
      if (input == null) return 0; // 입력이 유효하지 않으면 0을 반환합니다.
      return input * input; // 입력 값이 있다면 제곱 값을 계산합니다.
    }, [inputController.text]); // inputController.text가 변경될 때만 useMemoized가 실행됩니다.

    return Scaffold(
      appBar: AppBar(
        title: const Text(&#39;Square Calculator&#39;),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: inputController,
              decoration: const InputDecoration(
                labelText: &#39;Enter a number plz&#39;,
              ),
              keyboardType: TextInputType.number,
            ),
            const SizedBox(height: 20),
            Text(
              &#39;Square: $square&#39;,
              style: const TextStyle(fontSize: 20),
            ),
          ],
        ),
      ),
    );
  }
}</code></pre>
<p><br><br></p>
<p>참고 문서:
<a href="https://www.youtube.com/watch?v=WFF8Y2-E_NA">https://www.youtube.com/watch?v=WFF8Y2-E_NA</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] flutter_hooks useEffect(2)]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-flutterhooks-useEffect2</link>
            <guid>https://velog.io/@hodu_angel/Flutter-flutterhooks-useEffect2</guid>
            <pubDate>Sat, 10 Feb 2024 05:26:53 GMT</pubDate>
            <description><![CDATA[<h1 id="useeffect">useEffect</h1>
<p>useEffect는 <code>StatelessWidget</code> 및 <code>StatefulWidget</code> 의 <code>initState</code> 부터 <code>dispose</code> 까지를 사용이 가능하도록 만들어진 함수입니다.</p>
<ul>
<li>initState 처럼 사용.</li>
<li>매번 useEffect를 호출.</li>
<li>특정 변수의 변동사항이 있을 경우 호출.</li>
</ul>
<p>등의 상황에 따라 useEffect를 다양한 방법으로 호출할 수 있습니다.
<br></p>
<h2 id="initstate-처럼-사용한-경우"><code>initState</code> 처럼 사용한 경우.</h2>
<pre><code class="language-dart">import &#39;dart:developer&#39;;

import &#39;package:flutter/material.dart&#39;;
import &#39;package:flutter_hooks/flutter_hooks.dart&#39;;

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

  @override
  Widget build(BuildContext context) {
    // StatelessWidget 및 StatefultWidget의 initState와 동일하게 실행됩니다.
      useEffect(() {
        log(&#39;initState 효과&#39;);
        return; // 혹은 return (){};
    }, []);

    return Container();
  }
}</code></pre>
<br>

<h2 id="매번-useeffect를-호출하는-경우">매번 useEffect를 호출하는 경우.</h2>
<p>이런 경우 메서드를 호출하는거나 다름이 없습니다.</p>
<pre><code class="language-dart">import &#39;dart:developer&#39;;

import &#39;package:flutter/material.dart&#39;;
import &#39;package:flutter_hooks/flutter_hooks.dart&#39;;

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

  @override
  Widget build(BuildContext context) {
      // useEffect가 void useEffect(void Function()? Function() effect, [List&lt;Object?&gt;? keys]) 타입 입니다.
    // keys값을 입력하지 않으면 매 순간 useEffect를 호출하게 됩니다.
    useEffect(() {
      log(&#39;메서드 하나 만드시는 것과 동일한 효과를 지닙니다.&#39;);
      return;
    });

    return Container();
  }
}</code></pre>
<br>

<h2 id="특정-변수의-변동사항이-있을-때-호출되는-경우">특정 변수의 변동사항이 있을 때 호출되는 경우.</h2>
<p>Flutter의 <code>flutter_hooks</code> 라이브러리 사용 시, 매개변수로 받아온 값에 대한 변동이 생길 경우에 <code>useEffect</code> 메서드를 실행하도록 코드를 작성하는 와중에,</p>
<p><code>useState</code> 함수로 매개변수의 값을 초기화한 변수의 값을 Text 위젯에 넣어 표기되도록 하려 했으나, 값의 변화가 반영이 되지 않는 이슈를 발견하게 되었습니다.</p>
<p>이유를 찾고보니 Flutter의 <code>flutter_hooks</code> 라이브러리에서 제공하는 <code>use</code> 로 시작되는 함수들은 대부분 객체가 빌드 사이클 동안 유지된다고 합니다.</p>
<p>이는 React의 hook 모델을 따르며, 위젯이 재빌드 될 때마다 상태나 객체가 초기화 되지 않도록 설계되었습니다.
<br><br></p>
<blockquote>
<p> 아래의 데모는 위의 값이 매개변수로 보낼 값 및 증가되는 값, 아래 값이 매개변수로 받는 값 입니다.</p>
</blockquote>
<h3 id="statelesswidget-으로-작성된-경우">StatelessWidget 으로 작성된 경우.</h3>
<p>매개변수를 그대로 가져오기 때문에 값의 변동대로 표기됩니다.</p>
<pre><code class="language-dart">class BasicCounterEx extends StatelessWidget {
  const BasicCounterEx({super.key, required this.outCounter});
  final int outCounter;

  @override
  Widget build(BuildContext context) {
    var counter = outCounter;

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(counter.toString()),
        ],
      ),
    );
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/hodu_angel/post/2f4707c7-0239-47bc-8c2f-bcbcc4d04908/image.gif" alt=""></p>
<h3 id="매개변수값을-usestate-함수에-초기화-한-경우">매개변수값을 useState 함수에 초기화 한 경우.</h3>
<p><code>use</code> 로 시작되는 메서드들은 대부분 빌드 사이클 동안 객체가 유지 되기 때문에, 
재빌드 되더라도 <code>var counter</code>의 값은 <code>outCounter</code>의 초기화 값 그대로 유지됩니다.
outCounter의 값은 변경되어 전달되기 때문에 변동이 있을 때마다 useEffect가 실행됩니다.</p>
<pre><code class="language-dart">class SendCounterEx extends HookWidget {
  const SendCounterEx({super.key, required this.outCounter});
  final int outCounter;

  @override
  Widget build(BuildContext context) {
    // use로 시작되는 메서드들은 대부분 빌드 사이클 동안 객체가 유지됩니다.
    // 따라서 outCounter의 값이 변경되어 매개변수값을 전달하여 재빌드 되더라도 
    // counter의 값은 outCounter의 초기화 값 그대로 표기되어 변경되지 않고 보여집니다.
    var counter = useState(outCounter);

    useEffect(() {
      log(&#39;sendCounter useEffect 발동 ${counter.value}&#39;);
      return;
      // outCounter의 변경이 있을 경우 재빌드 합니다.
      // useEffect도 재실행 됩니다.
    }, [outCounter]);

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(counter.value.toString()),
        ],
      ),
    );
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/hodu_angel/post/fbd63388-f154-44df-85d7-c6b7a51f31bb/image.gif" alt=""></p>
<h3 id="매개변수-값이-변경될-때마다-useeffect-실행-및-변동-값-표기-반영한-경우">매개변수 값이 변경될 때마다 useEffect 실행 및 변동 값 표기 반영한 경우.</h3>
<pre><code class="language-dart">class SendCounterEx extends HookWidget {
  const SendCounterEx({super.key, required this.outCounter});
  final int outCounter;

  @override
  Widget build(BuildContext context) {
    useEffect(() {
      log(&#39;sendCounter useEffect 발동&#39;);
      return;
      // outCounter의 값이 변경되어 들어올때마다 useEffect가 실행됩니다.
      // build 메서드가 재빌드 됩니다.
    }, [outCounter]);

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(outCounter.toString()),
        ],
      ),
    );
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/hodu_angel/post/7a695b46-8f81-4fe7-9558-3562121c7fb8/image.gif" alt=""></p>
<br>

<h3 id="전체-소스-코드">전체 소스 코드</h3>
<pre><code class="language-dart">class UseEffectExPage extends HookWidget {
  const UseEffectExPage({super.key});

  @override
  Widget build(BuildContext context) {
    final counter = useState(1);

    // useEffect가 void useEffect(void Function()? Function() effect, [List&lt;Object?&gt;? keys])
    // 이므로 keys에 배열로 들어간다.
    // keys 안의 값은 build 메서드 위젯 안에서의 값은 넣을 수 있지만
    // 값의 변동사항이 생기더라도 useEffect가 호출되지 않는다.
    // class 멤버 변수인 경우에만 변동사항이 있을 경우 호출되는 것 같다.
    useEffect(() {
      log(&#39;useEffect 발동&#39;);
      return;
      // outCounter의 변경이 있을 경우 재빌드 합니다.
      // useEffect도 재실행 됩니다.
    }, []);

    return Scaffold(
      appBar: AppBar(),
      body: SafeArea(
          child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(counter.value.toString()),
            const SizedBox(height: 30),
            ElevatedButton(
              onPressed: () {
                counter.value++;
              },
              child: const Text(&#39;increase counter&#39;),
            ),
            const SizedBox(height: 50),
            SendCounterEx(outCounter: counter.value),
            // BasicCounterEx(outCounter: counter.value),
          ],
        ),
      )),
    );
  }
}

class SendCounterEx extends HookWidget {
  const SendCounterEx({super.key, required this.outCounter});
  final int outCounter;

  @override
  Widget build(BuildContext context) {
    // use로 시작되는 메서드들은 대부분 빌드 사이클 동안 객체가 유지됩니다.
    // var counter = useState(outCounter);

    // useEffect(() {
    //   log(&#39;sendCounter useEffect 발동 ${counter.value}&#39;);
    //   return;
    // }, [outCounter]);

    useEffect(() {
      log(&#39;sendCounter useEffect 발동&#39;);
      return;
    }, [outCounter]);

      // return Center(
    //  child: Column(
    //   mainAxisAlignment: MainAxisAlignment.center,
    //   children: [
    //     Text(counter.value.toString()),
    //   ],
    // ),
    );
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(outCounter.toString()),
        ],
      ),
    );
  }
}</code></pre>
<p><br><br>
참고 문서:
<a href="https://www.youtube.com/watch?v=66ACBjV58YQ">https://www.youtube.com/watch?v=66ACBjV58YQ</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] flutter_hooks 개념(1)]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-flutterhook-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@hodu_angel/Flutter-flutterhook-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Fri, 09 Feb 2024 09:54:00 GMT</pubDate>
            <description><![CDATA[<h3 id="flutter-hooks-이란">Flutter hooks 이란?</h3>
<p>먼저, Hook이란 <strong>갈고리</strong> 라는 뜻을 의미합니다.
일반적으로 프로그래밍에서 &#39;원래 존재하는 어떤 기능에 갈고리를 거는 것 처럼 끼어 들어가 같이 수행하는 것&#39; 을 의미합니다.</p>
<p>React에서 &#39;state와 생명주기 기능에 갈고리를 걸어 원하는 시점에 정해진 함수를 실행 되도록 만든 것&#39; 의 의미로 사용됩니다.</p>
<p>이처럼 React에서 사용되는 hooks라는 기능에서 영감을 받아, 
flutter에 맞도록 구현된 패키지 입니다.
<br></p>
<p>flutter에서는 대표적으로 <code>StatelessWidget</code>과 <code>StatefulWidget이</code> 있습니다.</p>
<p>상태관리를 사용할 경우에는 StatefulWidget을 사용하고,
그렇지 않은 경우에는 StatelessWidget을 사용합니다.
(상태관리 라이브러리를 별도로 사용하는 경우에는 StatelessWidget을 사용할 것 입니다.)
<br></p>
<p>또한, 애니메이션 처리를 한다거나, 라이프사이클을 필요로 할때 StatefulWidget을 사용합니다.</p>
<p>이러한 것을 HookWidget으로 모든 것을 사용할 수 있도록 하였습니다.
<br></p>
<h3 id="장점">장점</h3>
<ul>
<li>라이플 사이클을 전혀 고려하지 않아도 자동으로 처리를 해줍니다.</li>
<li>UI와 상태관리에 초점을 맞춰서 개발을 할 수 있습니다.</li>
<li>보일러 플레이트 코드를 없애주기 때문에 고민없이 사용할 수 있습니다.</li>
</ul>
<br>

<p>이처럼 아래와 같이 기존의 코드를 간략하게 사용할 수 있습니다.</p>
<h3 id="before">before</h3>
<pre><code class="language-dart">class Example extends StatefulWidget {
    final Duration duration;

    const Example({key? key, required this.duration}) : super(key: key);

    @override
    _ExampleState createState() =&gt; _ExampleState();

    class _ExampleState extends State&lt;Example&gt; with SingleTickerProviderStateMixin {
        AnmiationController? _controller;

        @override
        void initState() {
            super.initState();
            _controller = AnimationController(vsync: this, duration: widget.duration);
        }

        @override
        void didUpdateWidget(Example oldWidget){
            super.didUpdateWidget(oldWidget);
            if(widget.duration != oldWidget.duration) {
                _controller!.duration = widget.duration;
            }
        }

        @override
        void dispose(){
            _controller!.dispose();
            super.dispose();            
        }

        @override
        Widget build(BuildContext context){
            return Container();
        }
    }
}</code></pre>
<h3 id="after">After</h3>
<pre><code class="language-dart">class Example extends HookWidget {
    const Example({Key? key, required this.duartion}) : super(key: key);

    final Duration duration;

    @override
    Widget build(BuildContext context){
        /* useAnimationController의 메서드로 인해 
         애니메이션 컨트롤러의 생성부터 update, dispose까지 모두 처리가 가능합니다. */
        final controller = useAnimationController(duration: duration);
        return Container();
    }
}</code></pre>
<p><br><br>
참고 문서: 
<a href="https://pub.dev/packages/flutter_hooks">https://pub.dev/packages/flutter_hooks</a>
<a href="https://www.youtube.com/watch?v=GBVBLKESogU">https://www.youtube.com/watch?v=GBVBLKESogU</a>
<a href="https://thinkmath2020.tistory.com/3536">https://thinkmath2020.tistory.com/3536</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Firebase] com.google.android.recaptcha:recaptcha:18.1.2]]></title>
            <link>https://velog.io/@hodu_angel/Firebase-com.google.android.recaptcharecaptcha18.1.2</link>
            <guid>https://velog.io/@hodu_angel/Firebase-com.google.android.recaptcharecaptcha18.1.2</guid>
            <pubDate>Fri, 19 Jan 2024 08:50:56 GMT</pubDate>
            <description><![CDATA[<p>버전을 올리면서 구글 플레이 스토어에서 아래와 같은 메시지를 받게 되었습니다.</p>
<blockquote>
<p>이 SDK 버전에는 SDK 개발자의 메모가 포함되어 있습니다. SDK 개발자가 신고한 내용은 다음과 같습니다.</p>
<p>A critical security vulnerability was discovered in reCAPTCHA Enterprise for Mobile. The vulnerability has been patched in the latest SDK release. Customers will need to update their Android application with the reCAPTCHA Enterprise for Mobile SDK, version 18.4.0 or above. We strongly recommend you update to the latest version as soon as possible.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hodu_angel/post/05eed603-9265-40e3-9944-b6f847f796a7/image.png" alt="img"></p>
<p>라이브러리를 만든 개발자가 남긴 메시지를 보여주고 있습니다.</p>
<p>아래 참고 문서에 따르면, 이는 마지막 버전의 Firebase 인증이 <code>recaptcha:18.1.2</code> 를 사용하기 때문에 발생하는 것이라고 합니다.</p>
<p>이를 해결하기 위한 2가지 방법은 아래와 같습니다.
<br><br></p>
<h3 id="해결-방안">해결 방안</h3>
<ol>
<li><p><code>recaptcha_enterprise_flutter</code> 라이브러리 추가.
<a href="https://pub.dev/packages/recaptcha_enterprise_flutter/install">https://pub.dev/packages/recaptcha_enterprise_flutter/install</a></p>
<br>
</li>
<li><p><code>implementation &#39;com.google.android.recaptcha:recaptcha:18.4.0&#39;</code> 을 build.gradle 파일의 종속성 섹션에 reCaptcha 버전을 재정의.</p>
</li>
</ol>
<p><br><br><br>
참고 문서: <a href="https://stackoverflow.com/questions/77798636/critical-security-vulnerability-in-recaptcha-enterprise">https://stackoverflow.com/questions/77798636/critical-security-vulnerability-in-recaptcha-enterprise</a></p>
<p><a href="https://github.com/GoogleCloudPlatform/recaptcha-enterprise-mobile-sdk">https://github.com/GoogleCloudPlatform/recaptcha-enterprise-mobile-sdk</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] “iproxy” cannot be opened because the developer cannot be verified]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-iproxy-cannot-be-opened-because-the-developer-cannot-be-verified</link>
            <guid>https://velog.io/@hodu_angel/Flutter-iproxy-cannot-be-opened-because-the-developer-cannot-be-verified</guid>
            <pubDate>Mon, 04 Dec 2023 14:35:10 GMT</pubDate>
            <description><![CDATA[<p>아이폰 기기로 Flutter 프로젝트를 실행해보려다가 아래와 같은 메시지가 뜨면서 앱 실행이 되지 않았습니다.</p>
<pre><code>“iproxy” cannot be opened because the developer cannot be verified</code></pre><p>시스템 설정의 개인정보 및 보안에서 무시하기가 있어서 클릭을 해도, 여전히 실행이 되지 않았습니다.</p>
<p>그러던 중, 참고 문서의 조언으로 flutter sdk 폴더를 들어가서 아래와 같은 명령어를 실행해주고 앱을 다시 실행해보니, 정상적으로 잘 작동했습니다.
<br></p>
<ol>
<li><p>터미널에서 flutter sdk 들어가기</p>
<pre><code>cd flutter/bin/cache/artifacts/usbmuxd</code></pre><br>
</li>
<li><p>아래 명령어 실행으로 해당 속성 삭제하기</p>
<pre><code>sudo xattr -d com.apple.quarantine iproxy</code></pre><p><br><br></p>
</li>
</ol>
<p>참고 문서: <a href="https://stackoverflow.com/questions/71359062/iproxy-cannot-be-opened-because-the-developer-cannot-be-verified">https://stackoverflow.com/questions/71359062/iproxy-cannot-be-opened-because-the-developer-cannot-be-verified</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] ..연산자 Cascade]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-..%EC%97%B0%EC%82%B0%EC%9E%90-Cascade</link>
            <guid>https://velog.io/@hodu_angel/Flutter-..%EC%97%B0%EC%82%B0%EC%9E%90-Cascade</guid>
            <pubDate>Wed, 01 Nov 2023 00:04:23 GMT</pubDate>
            <description><![CDATA[<p>Dart언어에서 <code>Cascade</code> 연산자인 <code>..</code> 는 동일한 객체에서 여러 메서드 또는 멤버에 접근이 가능합니다. </p>
<p>이 연산자는 코드를 더 간결하게 만들어 줍니다.</p>
<h3 id="before">before</h3>
<pre><code class="language-dart">var people = People();
people.name = &#39;hodu&#39;;
people.age = 10;
people.height = 160.5;</code></pre>
<h3 id="after">after</h3>
<pre><code class="language-dart">var people = People();
    ..name = &#39;hodu&#39;;
    ..age = 10;
    ..height = 160.5;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] FAILURE: Build failed with an exception.]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-FAILURE-Build-failed-with-an-exception</link>
            <guid>https://velog.io/@hodu_angel/Flutter-FAILURE-Build-failed-with-an-exception</guid>
            <pubDate>Tue, 24 Oct 2023 05:10:52 GMT</pubDate>
            <description><![CDATA[<p>기존 repo를 삭제하고 새로 clone 하고 실행하다가 아래와 같은 메시지를 만나게 되었습니다.</p>
<pre><code class="language-dart">FAILURE: Build failed with an exception.

* Where:
Build file &#39;/Users/me/Documents/projects/android/app/build.gradle&#39; line: 66

* What went wrong:
A problem occurred evaluating project &#39;:app&#39;.
&gt; path may not be null or empty string. path=&#39;null&#39;</code></pre>
<p>위 에러는 key.properties 파일 경로를 찾지 못해서 나오는 것 이었습니다.
<br></p>
<h2 id="해결방안">해결방안</h2>
<p>android 폴더 하위에 key.properties 파일을 생성하여 필요한 내용을 작성하여 디버깅 하였더니, 정상적으로 실행되었습니다.</p>
<p><br><br>
참고 문서
<a href="https://young-duck.tistory.com/54">https://young-duck.tistory.com/54</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 애뮬레이터에서 인앱결제 에러 임시 해결방안]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-%EC%95%A0%EB%AE%AC%EB%A0%88%EC%9D%B4%ED%84%B0%EC%97%90%EC%84%9C-%EC%9D%B8%EC%95%B1%EA%B2%B0%EC%A0%9C-%EC%97%90%EB%9F%AC-%EC%9E%84%EC%8B%9C-%ED%95%B4%EA%B2%B0%EB%B0%A9%EC%95%88</link>
            <guid>https://velog.io/@hodu_angel/Flutter-%EC%95%A0%EB%AE%AC%EB%A0%88%EC%9D%B4%ED%84%B0%EC%97%90%EC%84%9C-%EC%9D%B8%EC%95%B1%EA%B2%B0%EC%A0%9C-%EC%97%90%EB%9F%AC-%EC%9E%84%EC%8B%9C-%ED%95%B4%EA%B2%B0%EB%B0%A9%EC%95%88</guid>
            <pubDate>Sat, 08 Jul 2023 08:37:24 GMT</pubDate>
            <description><![CDATA[<p>애뮬레이터에서 인앱결제가 포함된 프로젝트를 실행하려고하면 런타임에러가 발생한다.</p>
<pre><code class="language-dart">E/flutter (20728): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] 
Unhandled Exception: PlatformException(error, Attempt to invoke virtual method 
&#39;void android.app.Activity.startActivity(android.content.Intent)&#39;
on a null object reference, null, java.lang.NullPointerException:
Attempt to invoke virtual method &#39;void android.app.Activity.startActivity(android.content.Intent)&#39; on a null object reference</code></pre>
<br>

<h2 id="에러가-발생하는-이유">에러가 발생하는 이유</h2>
<ol>
<li><p>테스트용 결제 환경
 playstore등의 설치 환경이 애뮬레이터에서 제공되어 있지 않습니다.</p>
</li>
<li><p>결제 서비스 제한
 일부 인앱 결제 서비스 제공업체는 애뮬레이터에서의 인앱 결제를 제한합니다.
 보안 및 불법 결제 방지를 위한 조치입니다.</p>
</li>
<li><p>결제 테스트 제한
 외부 앱(카드사 / 간편결제 앱)으로 이동해 결제 테스트하는 것이 제한됩니다.
 이동이 가능하더라도 애뮬레이터에서는 공동 인증서(구 공인인증서) 설정이 안되니,
 (일반결제가 아니고서야) 인증 자체가 안됩니다.
<br><br></p>
</li>
</ol>
<h2 id="임시-해결-방안">임시 해결 방안</h2>
<p>코드에 인앱결제가 포함되어 있다면 실기기에서 실행이 잘 되겠지만, 애뮬레이터에서 실행하고 싶다면, 애뮬레이터 빌드 시, 인앱결제 코드가 실행되지 않도록 방지할 수 있습니다.</p>
<ol>
<li><p>라이브러리 추가
<a href="https://pub.dev/packages/device_info_plus">https://pub.dev/packages/device_info_plus</a></p>
</li>
<li><p>코드 작성</p>
<pre><code class="language-dart">import &#39;package:device_info/device_info.dart&#39;;
import &#39;package:flutter/services.dart&#39;;
</code></pre>
</li>
</ol>
<p>Future<void> makeInAppPurchase() async {
  DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
  if (!await deviceInfo.isPhysicalDevice) {
    // 애뮬레이터 또는 시뮬레이터에서 실행 중인 경우 예외 처리
    throw PlatformException(
      code: &#39;NOT_SUPPORTED&#39;,
      message: &#39;인앱 결제는 실제 디바이스에서만 지원됩니다.&#39;,
    );
  }</p>
<p>  // 인앱 결제 코드 실행
}</p>
<p>```</p>
<p><br><br><br></p>
<p>참고 문서:
<a href="https://github.com/iamport/iamport_flutter/issues/24">https://github.com/iamport/iamport_flutter/issues/24</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Lint를 사용하는 이유 및 적용]]></title>
            <link>https://velog.io/@hodu_angel/Flutter-Lint%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EB%B0%8F-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@hodu_angel/Flutter-Lint%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EB%B0%8F-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Wed, 05 Jul 2023 05:23:56 GMT</pubDate>
            <description><![CDATA[<p>Lint란, 소스 코드에서 잠재적인 문제나 버그를 감지하고 개선하는 데 도움을 주는 정적 분석 도구입니다.</p>
<p>즉, Lint 설정을 통해 규칙적인 코딩 스타일을 만들 뿐 아니라, 런타임 에러를 줄일 수 있습니다.
<br><br></p>
<p>팀 단위로 프로젝트를 진행하게 될 경우, 개개인의 코드 스타일이 소스에 녹여 들어가게 되는데요.</p>
<p>그것이 결과물에 영향을 주진 않지만, 협업의 일정 부분 영향을 주게 됩니다.
<br></p>
<p>자신의 코드 스타일에 대해서는 소스가 한눈에 들어와 지지만,
협업하는 개발자 분의 소스는 코드 스타일이 달라 눈에 잘 들어와 지지 않고 집중해서 봐야 되는 경우가 생깁니다.
<br></p>
<p>그렇기에, Lint의 코드 룰을 설정하여 <strong>개발 스타일 온도차를 최대한 줄여, 퍼포먼스를 높일 수 있도록</strong> 도와주어, 팀 협업 강화 등의 다양한 이점을 얻을 수 있습니다.
<br><br></p>
<h3 id="요약">[요약]</h3>
<ul>
<li><p><strong>코드의 일관성 유지</strong>
  코드 스타일 가이드라인을 적용하고 일관된 코드 작성을 촉진합니다. 
이는, 팀 내에서 동일한 코드 스타일을 따르고, 가독성이 높은 코드를 유지할 수 있도록 도와줍니다.</p>
<br>
</li>
<li><p><strong>잠재적인 버그 감지</strong>
잠재적인 버그를 찾아내고 경고 또는 오류 메시지를 통해 개발자에게 알려줍니다.
이로, 런타임 에러를 방지하고 안전한 코드를 작성할 수 있습니다.</p>
<br>
</li>
<li><p><strong>성능 개선</strong>
비효율적인 코드 패턴을 식별하고 개선 방법을 제안하여 앱의 성능을 향상시킬 수 있습니다.
불필요한 연산 혹은 메모리 누수와 같은 문제를 감지하고 최적화 할 수 있습니다.</p>
<br>
</li>
<li><p><strong>팀 협업 강화</strong>
팀 내에서 코드 리뷰를 용이하게 만들어줍니다. 
모든 개발자가 동일한 Lint 규칙을 따르면 코드 리뷰 프로세스가 원활해 지고 코드 품질이 향상됩니다.
<br><br></p>
</li>
</ul>
<h3 id="lint-적용">[Lint 적용]</h3>
<ol>
<li>프로젝트를 생성하게 되면 <code>pubspec.yaml</code> 파일에서 기본적으로 <code>flutter_lints</code>가 적용되어 있습니다.
<img src="https://velog.velcdn.com/images/hodu_angel/post/7cd5706f-85cf-402e-aa28-92fdc6b6367c/image.png" alt=""></li>
</ol>
<ol start="2">
<li><code>analysis_options.yaml</code> 파일에서 아래의 코드를 추가해줍니다.
<em>exclude는 lint적용을 제외시킬 파일을 적어둔 곳 입니다.</em><pre><code class="language-dart">linter:
rules:
 - prefer_const_constructors
 - always_declare_return_types
 - avoid_types_on_closure_parameters
 - avoid_annotating_with_dynamic
 - avoid_escaping_inner_quotes
 - avoid_function_literals_in_foreach_calls
 - avoid_private_typedef_functions
 - combinators_ordering
 - curly_braces_in_flow_control_structures
 - omit_local_variable_types
 - annotate_overrides
 - sort_constructors_first
 - unawaited_futures
 - directives_ordering
 - unnecessary_final
 - unnecessary_parenthesis
 - conditional_uri_does_not_exist
 - prefer_single_quotes
 - await_only_futures
 - always_use_package_imports
 - avoid_empty_else
 - comment_references
 - prefer_void_to_null
 - use_key_in_widget_constructors
 - no_wildcard_variable_uses
</code></pre>
</li>
</ol>
<p>analyzer:
  strong-mode:
    # implicit-casts: false
    # implicit-dynamic: false
  exclude:
    - lib/<strong>.freezed.dart
    - lib/</strong>.g.dart
    - lib/generated
    - lib/firebase_options.dart
  errors:
    invalid_annotation_target: ignore
    todo: ignore</p>
<p>```
<br></p>
<ol start="3">
<li>Lint가 적용되기 위해 <code>IDE</code>를 종료하고 다시 켜주세요.
터미널에 <code>flutter analyze</code> 를 작성하면 lint가 어디에서 적용되지 않고 있는지 체크할 수 있습니다.</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>