<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>coolgamja_.log</title>
        <link>https://velog.io/</link>
        <description>난멋져</description>
        <lastBuildDate>Mon, 28 Jul 2025 09:56:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>coolgamja_.log</title>
            <url>https://velog.velcdn.com/images/coolgamja_/profile/cb27a510-042e-49e5-9453-3228f0ae3578/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. coolgamja_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/coolgamja_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Flutter] 2개 이상 store 관리]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-2%EA%B0%9C-%EC%9D%B4%EC%83%81-store-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@coolgamja_/Flutter-2%EA%B0%9C-%EC%9D%B4%EC%83%81-store-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Mon, 28 Jul 2025 09:56:16 GMT</pubDate>
            <description><![CDATA[<p><code>state</code> 성격별로 <code>store</code>를 여러 개 만들 수 있는데 이 때 생기는 문제</p>
<h3 id="store-하나">store 하나</h3>
<pre><code class="language-dart">void main() {
  runApp(
    ChangeNotifierProvider(
      create: (c) =&gt; Store1(),
      child: MaterialApp(
        home: MyApp(),
        theme: theme,
      ),
    ),
  );
}</code></pre>
<p>여러 <code>store</code>를 모두 <code>ChangeNotifierProvider</code>로 등록해줘야 함
이렇게 여러 <code>store</code>를 사용할 땐 <code>MultiProvider</code>로 기존의 <code>ChangeNotifierProvider</code>를 감싸줘야 한다.</p>
<h3 id="store-두개-이상">store 두개 이상</h3>
<pre><code class="language-dart">void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (c) =&gt; Store1()),
        ChangeNotifierProvider(create: (c) =&gt; Store2()),
      ],
      child: MaterialApp(
        home: MyApp(),
        theme: theme,
      ),
    ),
  );
}</code></pre>
<h3 id="get-요청으로-갖고온-데이터-활용하기">get 요청으로 갖고온 데이터 활용하기</h3>
<p>그럼 이번에는 get 요청으로 가져온 데이터를 <code>store</code> 속 <code>state</code>에 넣는 방법을 알아보자</p>
<p>다른 함수랑 작성, 사용법이 똑같은데 <code>async await</code>만 잘 넣어주면 된다.</p>
<pre><code class="language-dart">class Store1 extends ChangeNotifier {
  var profileImage = [];

  getData() async {
    var result = await http.get(Uri.parse(&#39;서버URL&#39;));
    var result2 = jsonDecode(result.body);
    profileImage = result2;
    notifyListeners();
}</code></pre>
<p><code>initState</code> 때 쓴다 치면
깨알복습 <code>initState</code> 쓰려면 위젯이 <code>stful</code>이어야 한다.</p>
<pre><code class="language-dart">void initState() {
  super.initState();
  Future.microtask(() {
    context.read&lt;Store1&gt;().getData();
  });
}</code></pre>
<p><code>context.read&lt;Store1&gt;().getData();</code> 만 쓰게 되면
잠시 뻘건 화면이 떴다가 정상적으로 동작하는 걸 볼 수 있는데
<code>Future.microtask</code> 같은 걸 써서
<code>MicroTask(비동기 작업 외 모든 동기 작업)</code>를 먼저 처리하도록 해야 한다.
<code>MicroTask</code>가 끝나면 나머지 <code>Task</code>들을 처리하는 순서다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] A RenderFlex overflowed by 60 pixels on the bottom.]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-A-RenderFlex-overflowed-by-60-pixels-on-the-bottom</link>
            <guid>https://velog.io/@coolgamja_/Flutter-A-RenderFlex-overflowed-by-60-pixels-on-the-bottom</guid>
            <pubDate>Mon, 28 Jul 2025 09:40:06 GMT</pubDate>
            <description><![CDATA[<p>UI가 화면 크기를 넘어갈 때 발생하는 에런 것 같다.
과속방지턱같은게 뜨는데 제법 봐주기 싫게 생겼다.</p>
<h2 id="해결-방법">해결 방법</h2>
<h3 id="image-등-위젯-크기-조정하기">Image 등 위젯 크기 조정하기</h3>
<p>오버플로 안나게 들어있는 위젯들을 쪼만하게 만들어주자</p>
<h3 id="singlechildscrollview로-감싸주기">SingleChildScrollView로 감싸주기</h3>
<pre><code class="language-dart">SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: Column(children: []),
)</code></pre>
<h3 id="listview로-감싸주기column">ListView로 감싸주기(Column)</h3>
<pre><code class="language-dart">ListView(
  return Column(children: []);
),</code></pre>
<h3 id="그-외">그 외</h3>
<ul>
<li><code>Expanded</code>로 감싸기(ListView)</li>
<li><code>SizedBox</code>로 감싸기(Container)</li>
</ul>
<br>
뜰 때마다 까먹어서 (내가)바로 찾을 수 있게 태그도 많이 넣어뒀다.]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Provider로 state 관리하기]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-Provider%EB%A1%9C-state-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@coolgamja_/Flutter-Provider%EB%A1%9C-state-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 25 Jul 2025 06:23:26 GMT</pubDate>
            <description><![CDATA[<p>A, B, C 위젯이 있고 A &gt; B &gt; C와 같은 구조일 때
A의 state를 C가 사용하려면 A → B → C와 같이 넘겨줘야함, 굉장한 번거로운!</p>
<p>그래서 Provider라는 별도의 공간을 만들어 state를 관리하면
굳이 넘겨넘겨주지 않고 비교적 자유롭게 state를 활용할 수 있게 된다.</p>
<h2 id="provider"><a href="https://pub.dev/packages/provider">Provider</a></h2>
<h3 id="0-설치--import">0. 설치 &amp; import</h3>
<pre><code class="language-dart">// main.dart
import &#39;package:provider/provider.dart&#39;;

// pubspec.yaml
dependencies:
  provider: ^6.1.5</code></pre>
<h3 id="1-store-생성">1. store 생성</h3>
<p>state를 보관할 보관함을 만들자. store라 부른다.</p>
<pre><code class="language-dart">class Store1 extends ChangeNotifier {
  var name = &#39;cool gamja&#39;;
}</code></pre>
<h3 id="2-store-원하는-위젯에-등록">2. store 원하는 위젯에 등록</h3>
<p>store 내의 state를 쓰고 싶은 위젯은 죄다 <code>ChangeNotifierProvider()</code>로 감싸야 하는데 대부분의 위젯이 써야한다면 <code>MaterialApp()</code> 자체를 감싸버리자.</p>
<pre><code class="language-dart">void main() {
  runApp(
    ChangeNotifierProvider(
      create: (c) =&gt; Store1(),
      child: MaterialApp(
        home: MyApp(),
        theme: theme,
        debugShowCheckedModeBanner: false,
        initialRoute: &#39;/&#39;,
      ),
    ),
  );
}</code></pre>
<p><code>MaterialApp</code>을 widget으로 감싼(전구) 다음 <code>ChangeNotifierProvider</code>로 바꾸고, <code>create</code> 문을 넣는다. 아까 만들어 둔 <code>Store1</code>와 파라미터 <code>c</code>가 필요하다.
이제 <code>MaterialApp</code>의 자식 위젯들은 <code>Store1</code>에 있는 모든 <code>state</code>를 쓸 수 있다.</p>
<h3 id="3-1-store-내-state-갖다-쓰기">3-1. store 내 state 갖다 쓰기</h3>
<p>그냥 냅다 <code>name</code> 이렇겐 못쓰고 <code>context.watch&lt;Store1&gt;().-</code> 요런 형식 지켜야;</p>
<pre><code class="language-dart">class Store1 extends ChangeNotifier {
  var name = &#39;cool gamja&#39;;
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(context.watch&lt;Store1&gt;().name)),
      body: Text(&#39;상세페이지&#39;),
    );
  }
}</code></pre>
<p>이제 모든 위젯에서 state를 직접 갖다 쓸 수 있게 됐다.
항상 쓸 필욘 없고 사이즈가 작은 경우 보내고/등록한뒤/쓰는 방식이 더 빠르다.</p>
<p><img src="https://velog.velcdn.com/images/coolgamja_/post/c57059f8-250a-44d2-998a-bb5024d5ba3b/image.png" alt=""></p>
<p>와!</p>
<h3 id="3-2-store-내-state-변경">3-2. store 내 state 변경</h3>
<p>버튼을 누르면 개명이 되도록 만들어보자.
부모에서 자식한테 함수를 전달했듯 store 안에 setState하는 함수를 짜두면 된다.</p>
<p>이 때 만든 함수는 <code>context.read&lt;Store1&gt;().-</code>
이런 형식으로 쓸 수 있다. state는 watch, setState는 read.</p>
<p>밖에서 직접 바꾸지 않는 이유는 탕비실에 비유할 수 있다.
맘대로 갖다쓰면 횡령이고 쓰려면 매뉴얼을 따라야하듯
바깥에서 직접 변수를 건드리면 버그 발생률이 높다</p>
<p>이 때 setState를 쓰는게 아니라 <code>notifyListeners</code>를 쓸건데
state 수정 후 재렌더링하도록 만든다.</p>
<pre><code class="language-dart">class Store1 extends ChangeNotifier {
  var name = &#39;cool gamja&#39;;
  changeName() {
    name = &#39;cool gogooma&#39;;
    notifyListeners();
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(context.watch&lt;Store1&gt;().name)),
      body: Column(
        children: [
          ElevatedButton(
            onPressed: () {
              context.read&lt;Store1&gt;().changeName();
            },
            child: Text(&#39;개명&#39;),
          ),
        ],
      ),
    );
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/coolgamja_/post/31bb357f-3e64-4a89-912b-2f372214be77/image.gif" alt=""></p>
<h2 id="정리">정리</h2>
<ol>
<li>store 생성
(ChangeNotifier)</li>
<li>store 등록
(ChangeNotifierProvider)</li>
<li>state 사용
(context.watch<Store1>().state명)
(context.read<Store1>().setState명 + notifyListeners)</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 페이지 전환 애니메이션 커스텀]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%A0%84%ED%99%98-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%BB%A4%EC%8A%A4%ED%85%80</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%A0%84%ED%99%98-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%BB%A4%EC%8A%A4%ED%85%80</guid>
            <pubDate>Thu, 24 Jul 2025 05:41:26 GMT</pubDate>
            <description><![CDATA[<h2 id="1-cupertinopageroute">1. CupertinoPageRoute</h2>
<pre><code class="language-dart">import &#39;package:flutter/cupertino.dart&#39;;

GestureDetector(
  child: Text(widget.data[i][&#39;user&#39;], style: bold),
  onTap: () {
    Navigator.push(
      context,
      CupertinoPageRoute(builder: (c) =&gt; Profile()),
    );
  },
),</code></pre>
<p><code>MaterialPageRoute</code> 말고 <code>CupertinoPageRoute</code>를 쓰면 페이지 노출 시 우측에서 좌측으로 밀리는 트랜지션을 사용할 수 있다.
<code>import</code>문 필수!</p>
<h2 id="2-pageroutebuilder">2. PageRouteBuilder</h2>
<pre><code class="language-dart">GestureDetector(
  child: Text(widget.data[i][&#39;user&#39;], style: bold),
  onTap: () {
    Navigator.push(
      context,
      PageRouteBuilder(
        pageBuilder: (c, a1, a2) =&gt; Profile(),
        transitionsBuilder: (c, a1, a2, child) =&gt;
          애니메이션위젯(),
      ),
    );
  },
),</code></pre>
<h3 id="transitionsbuilderc-a1-a2-child">transitionsBuilder(c, a1, a2, child)</h3>
<p><code>PageRouteBuilder</code> 쓸 땐 파라미터 세 개가 필요하다.
커스텀 애니메이션을 <code>transitionsBuilder</code>에 넣을 수 있는데
얘는 파라미터 네 개가 필요하다.</p>
<p>네 개 파라미터 중 c는 context고</p>
<p>a1이 젤 중요한데 animation object라고 한다.
애니메이션이 얼마나 진행됐는지 0에서 1사이 숫자로 표현해준다.
페이지 전환 시작시엔 0, 전환이 끝나면 1인 셈이다.</p>
<p>a2도 animation object인데 child 말고 기존 페이지의 진행도를 의미한다.
보통 child에 애니메이션을 주고 싶으니 쓸 일이 많지 않을듯</p>
<p>child는 애니메이션 줄 애고 위 코드같은 경우 Profile이 들어있는 격</p>
<h3 id="transitionsduration">transitionsDuration</h3>
<p>애니메이션 진행 속도를 지정할 수 있다.
예를 들어 <code>transitionsDuration: Duration(miliseconds: 500)</code>는
500밀리초 동안 애니메이션을 동작시키라는 의미다.</p>
<h2 id="애니메이션-위젯">애니메이션 위젯</h2>
<p>이름만 봐도 대충 뭐하는 위젯인지 알 수 있다.
이리저리 사용해보기</p>
<h3 id="fadetransition">FadeTransition</h3>
<p><code>FadeTransition(opacity: a1, child: child)</code></p>
<p>a1이 애니메이션 진행도이므로(0 -&gt; 0.1 -&gt; ... -&gt; 1)
opacity에 주면 a1과 함께 불투명도가 높아지며 Fade-In 효과를 만들어낸다.</p>
<h3 id="slidetransition">SlideTransition</h3>
<pre><code class="language-dart">PageRouteBuilder(
  pageBuilder: (c, a1, a2) =&gt; Profile(),
    transitionsBuilder: (c, a1, a2, child) =&gt;
      SlideTransition(
        position: Tween(
          begin: Offset(-1.0, 0.0),
          end: Offset(0.0, 0.0),
        ).animate(a1),
        child: child,
      ),
),</code></pre>
<p>여기선 <code>begin</code>이 중요한데 slide 애니메이션의 시작 위치를 정할 수 있다.
왼쪽이 x 좌표고 오른쪽이 y 좌표다.
위 코드의 경우 페이지가 좌측에서 우측으로 밀리고, <code>(1.0, 0,0)</code>이라면 그 반대다.
<code>(0.0, -1.0)</code>이면 위에서, <code>(1.0, -1.0)</code>이면 우측 상단 모서리에서 밀려온다.</p>
<h3 id="hero"><a href="https://youtu.be/Be9UH1kXFDw">Hero</a></h3>
<p>페이지에서 게시물 누르면 게시물 내용은 고대로 유지되고 크기만 쑤욱 커지는 그거
짬 날 때 한번 써보기.</p>
<h3 id="그-외">그 외</h3>
<ul>
<li>PositionedTransition</li>
<li>ScaleTransition</li>
<li>RotationTransition</li>
<li>SlideTransition</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] GestureDetector]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-GestureDetector</link>
            <guid>https://velog.io/@coolgamja_/Flutter-GestureDetector</guid>
            <pubDate>Thu, 24 Jul 2025 05:01:07 GMT</pubDate>
            <description><![CDATA[<h3 id="text-위젯에-인터렉션-넣기">Text 위젯에 인터렉션 넣기</h3>
<p>유저가 <code>Text()</code> 위젯을 눌렀을 때 상세페이지를 띄우고 싶을 수도 있음
근데 onPressed 같은 게 없으니 이 때 <code>GestureDetector()</code> 위젯으로 싸맨다.</p>
<pre><code class="language-dart">GestureDetector(child: Text(&#39;싸매 me&#39;), onTap: (){});</code></pre>
<p><code>onPressed</code> 말고 <code>onTap</code>을 쓸 수 있다.
길게 눌렀을 때 인터렉션 주려면 <code>onLongPress</code>
두 번 눌렀을 때 인터렉션을 주려면 <code>onDoubleTap</code>
핀치(확대 모션)할 때 주려면 <code>onScaleStart</code>
왼쪽으로 스와이프 했을 때 코드 실행하려면 <code>onHorizontalDragStart</code></p>
<p>이런식으로 특정 동작에 따라 코드 실행시킬 때 <code>GestureDetector()</code> 써보기.
상세페이지는 <code>Navigator.push()</code>로 구현해보자.</p>
<pre><code class="language-dart">onTap: () {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (c) =&gt; Text(&#39;상세페이지&#39;)),
  );
},</code></pre>
<h3 id="결과물">결과물</h3>
<p>우와 생긴거</p>
<p><img src="https://velog.velcdn.com/images/coolgamja_/post/1befb931-def8-4d09-81f7-113741d5b218/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] state 말고 shared preferences]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-state-%EB%A7%90%EA%B3%A0-shared-preferences</link>
            <guid>https://velog.io/@coolgamja_/Flutter-state-%EB%A7%90%EA%B3%A0-shared-preferences</guid>
            <pubDate>Wed, 23 Jul 2025 07:24:09 GMT</pubDate>
            <description><![CDATA[<p><code>state</code>에 저장한 데이터는 앱 재가동시 휘발된다.
데이터를 저장하기 위한 방법 중 <code>shared preferences</code> 사용법을 배워보자.</p>
<h2 id="0-데이터-보존-방법">0. 데이터 보존 방법</h2>
<ol>
<li>서버로 보내서 DB에 저장 - 중요한 데이터</li>
<li>폰 메모리카드에 저장 - 들 중요한 데이터
요게 <code>shared preferences</code>를 활용하는 방법이다.
유저가 직접 삭제하지 않는 이상 반영구적으로 남아있게 됨</li>
</ol>
<h2 id="1-shared-preferences">1. <a href="https://pub.dev/packages/shared_preferences">shared preferences</a></h2>
<h3 id="설치">설치</h3>
<pre><code class="language-dart">// pubspec.yaml
dependencies:
  shared_preferences: ^2.5.3

// main.dart
import &#39;dart:convert&#39;; // &lt;- jsonDecode 함수 쓸 때 필요
import &#39;package:shared_preferences/shared_preferences.dart&#39;;</code></pre>
<p>pub get 후에는 앱(emulator)을 종료하고 다시 시작해주자</p>
<h3 id="사용법">사용법</h3>
<h4 id="데이터-저장">데이터 저장</h4>
<p>저장할 땐 <code>storage.setString(&#39;데이터이름&#39;, 저장할데이터)</code>
그니까 <code>key</code>하고 <code>value</code> 형태로 쓰면 됨</p>
<pre><code class="language-dart">saveData() async {
  var storage = await SharedPreferences.getInstance();
  storage.setString(&#39;name&#39;, &#39;John&#39;);
}</code></pre>
<h4 id="데이터-출력">데이터 출력</h4>
<p>출력할 땐 <code>storage.getString(&#39;데이터이름&#39;)</code></p>
<pre><code class="language-dart">saveData() async {
  var storage = await SharedPreferences.getInstance();
  storage.setString(&#39;name&#39;, &#39;John&#39;);
  var result = storage.getString(&#39;name&#39;);
  print(result); // John
}</code></pre>
<h4 id="데이터-삭제">데이터 삭제</h4>
<p>삭제할 땐 <code>storage.remove(&#39;데이터이름&#39;)</code></p>
<h3 id="함수">함수</h3>
<h4 id="데이터-저장-함수">데이터 저장 함수</h4>
<ul>
<li><code>setString(&#39;name&#39;, &#39;John&#39;)</code></li>
<li><code>setBool(&#39;bool&#39;, true)</code></li>
<li><code>setInt(&#39;int&#39;, 1)</code></li>
<li><code>setDouble(&#39;double&#39;, 2.5)</code></li>
<li><code>setStringList(&#39;stringList&#39;, [&#39;John1&#39;, &#39;John2&#39;])</code></li>
</ul>
<p><code>map</code> 자료형은 <code>jsonEncode + setString</code> 조합 활용
<code>map</code>에 전부 따옴표 붙여서 <code>JSON</code>으로 가라쳐줌</p>
<pre><code class="language-dart">var storage = await SharedPreferences.getInstance();
var map = {&#39;count&#39;: 3};
storage.setString(&#39;map&#39;, jsonEncode(map));</code></pre>
<h4 id="데이터-호출-함수">데이터 호출 함수</h4>
<ul>
<li><code>getString(&#39;name&#39;)</code></li>
<li><code>getBool(&#39;bool&#39;)</code></li>
<li><code>getInt(&#39;int&#39;)</code></li>
<li><code>getDouble(&#39;double&#39;)</code></li>
<li><code>getStringList(&#39;stringList&#39;)</code></li>
</ul>
<p>아까 저장한 <code>map</code>을 일단 갖고와보자</p>
<pre><code class="language-dart">var result = storage.getString(&#39;map&#39;);
print(result); // {&#39;count&#39;: 3}</code></pre>
<p>근데 이거는 꺼내쓰기 어려운(<code>result[&#39;count&#39;]</code>: 에러) 형태라
<code>jsonDecode</code> 함수가 필요하다.</p>
<pre><code class="language-dart">print(jsonDecode(result)); // Error: The argument type &#39;String?&#39; can&#39;t be assigned to the parameter type &#39;String&#39; because &#39;String?&#39; is nullable and &#39;String&#39; isn&#39;t.</code></pre>
<p>그냥 <code>getString</code> 말고 <code>.get(&#39;key&#39;)</code>도 먹는데 꺼내보면 <code>Object?</code> 요런식의 타입으로 끄내짐. <code>.getString(&#39;key&#39;)</code>로 꺼내면 <code>String?</code> 이렇게 타입을 좀 더 명확하게 저장할 수 있다.</p>
<p>다시 돌아가서 <code>String</code>인지 <code>null</code>인지 확실히 않아 뜨는 에러를 해결하기 위해 <code>??</code> 써서 <code>null check</code> 해주기.</p>
<pre><code class="language-dart">var result = storage.getString(&#39;map&#39;) ?? &#39;없어&#39;;
print(jsonDecode(result)); // {count: 3}
print(jsonDecode(result)[&#39;count&#39;]); // 3</code></pre>
<h3 id="활용">활용</h3>
<ul>
<li>기존에 <code>state</code>로 관리했던 데이터를 <code>shared preferences</code>에 넣어두고,
앱을 켤 때 갖고오는 식으로 관리하면 <strong>로드 속도</strong>가 빨라지고 2 서버와 주고받는 <strong>데이터 양</strong>도 줄일 수 있다.</li>
<li>근데 <code>shared preferences</code>에는 문자만 저장 가능하므로 이미지를 저장하고 싶을 땐 network 이미지를 폰에 몰래 저장해주는 패키지 <a href="https://pub.dev/packages/cached_network_image">cached_network_image</a>를 써보자.
매번 다운받는 게 아니라 필요할 때 폰에서 가져와주니 더 빠르다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 포스팅 구현하기]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%ED%8F%AC%EC%8A%A4%ED%8C%85-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%ED%8F%AC%EC%8A%A4%ED%8C%85-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 22 Jul 2025 08:17:31 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/coolgamja_/post/7d69ed15-ac2d-4084-9968-1aff65a7b103/image.gif" alt=""></p>
<p>와!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 폰에서 이미지 갖다쓰기]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%ED%8F%B0%EC%97%90%EC%84%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EA%B0%96%EB%8B%A4%EC%93%B0%EA%B8%B0</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%ED%8F%B0%EC%97%90%EC%84%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EA%B0%96%EB%8B%A4%EC%93%B0%EA%B8%B0</guid>
            <pubDate>Tue, 22 Jul 2025 06:49:21 GMT</pubDate>
            <description><![CDATA[<h2 id="설정">설정</h2>
<h3 id="image_picker-설치"><a href="https://pub.dev/packages/image_picker">image_picker</a> 설치</h3>
<pre><code class="language-dart">dependencies:
  image_picker: ^1.1.2</code></pre>
<h3 id="iosrunnerinfoplist">ios/Runner/Info.plist</h3>
<pre><code class="language-dart">&lt;key&gt;NSPhotoLibraryUsageDescription&lt;/key&gt;
&lt;string&gt;사진첩점 쓸게요&lt;/string&gt;
&lt;key&gt;NSCameraUsageDescription&lt;/key&gt;
&lt;string&gt;카메라점 쓸게요&lt;/string&gt;
&lt;key&gt;NSMicrophoneUsageDescription&lt;/key&gt;
&lt;string&gt;마이크점 쓸게요&lt;/string&gt;</code></pre>
<p>이런 걸 <code>&lt;dict&gt;</code> 밑에 넣는다.
사용자에게 허락 팝업 띄울 때 보이는 문구다.</p>
<h3 id="maindart">main.dart</h3>
<pre><code>import &#39;package:image_picker/image_picker.dart&#39;;
import &#39;dart:io&#39;;</code></pre><h2 id="활용">활용</h2>
<p>이미지 업로드 버튼을 누른 사용자에게
접근 권한 요청 팝업을 띄우고 갖다쓴다 정도의 시나리오로 가보자</p>
<p>그전에 안드로이드 미리보기를 하면 카메라로 사진을 찍을수가 있음
녹색사람이 까불까불하는데 냅다 찍어서 갤러리에 사진 저장해두기</p>
<h3 id="일단-기본-사용법">일단 기본 사용법</h3>
<p>사진 말고 카메라 띄우려면 <code>ImageSource.camera</code>
이미지 말고 비디오 고르려면 <code>picker.pickVideo()</code>
여러 이미지 고르려면 <code>picker.pickMultiImage()</code></p>
<pre><code class="language-dart">IconButton(
  onPressed: () async {
    var picker = ImagePicker();
    var image = await picker.pickImage(source: ImageSource.gallery);
  },
  icon: Icon(Icons.add_box_outlined, size: 35),
),</code></pre>
<p>이렇게 하면 고른 이미지가 <code>image</code> 변수에 저장되고
이걸 자유롭게 갖다 쓰기 위해 <code>state</code>(<code>var userImage;</code>) 하나 생성</p>
<pre><code class="language-dart">setState() {userImage = File(image.path)}</code></pre>
<p>요때 주의할 점 사용자가 이미지를 고르지 않았을 수 있으니 <code>null</code> 체크하기</p>
<h3 id="파일-형식으로-이미지-띄우는-법">파일 형식으로 이미지 띄우는 법</h3>
<p><code>Image.asset</code>은 asset 폴더에 있는 이미지 띄울 때
<code>Image.network</code>는 넷상 이미지 띄울 때
<code>Image.file</code>은 파일 경로로 이미지 띄울 때 사용</p>
<h2 id="참고">참고</h2>
<h3 id="photofilters"><a href="https://pub.dev/packages/photofilters/versions">photofilters</a></h3>
<p>인스타 보면 게시물 올릴 때 필터 넣은거 보여주는 그거 만들 때 활용</p>
<h3 id=""></h3>
<h3 id="노란-과속방지턱같은거">노란 과속방지턱같은거</h3>
<p>레이아웃 깨질 때 보이는거</p>
<p><img src="https://velog.velcdn.com/images/coolgamja_/post/ee4effef-9d89-42b8-bda8-32c49cb6efcb/image.png" alt=""></p>
<p><code>resizeToAvoidBottomInset</code> 써보거나
위젯을 아예 새 페이지로 분리해보거나
이미지 크기를 조절해보거나
스크롤바 넣어서 해결해보기</p>
<h3 id="전체-예시-코드">전체 예시 코드</h3>
<pre><code class="language-dart">IconButton(
  onPressed: () async {
    var picker = ImagePicker();
    var image = await picker.pickImage(source: ImageSource.gallery);
    if (image != null) {
      setState(() {
        userImage = File(image.path);
      });
    }

    Navigator.push(
      context,
      MaterialPageRoute(builder: (c) =&gt; Upload(userImage: userImage)),
    );
  icon: Icon(Icons.add_box_outlined, size: 35),
),</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] routes 사용법]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-routes-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@coolgamja_/Flutter-routes-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Mon, 21 Jul 2025 08:56:40 GMT</pubDate>
            <description><![CDATA[<h2 id="이렇게">이렇게</h2>
<p>페이지가 많은 경우 Tab, Nivigator 말고 아래와 같이 <code>route</code>를 활용할 수 있다.</p>
<pre><code class="language-dart">void main() {
  runApp(
    MaterialApp(
      initialRoute: &#39;/&#39;,
      routes: {
        &#39;/&#39;: (c) =&gt; Text(&#39;초기 페이지&#39;),
        &#39;/detail&#39;: (c) =&gt; Text(&#39;다음 페이지&#39;)
      },
  );
}</code></pre>
<h3 id="initialroute">initialRoute</h3>
<p>앱 로드시 이동할 <code>route</code> 지정</p>
<h3 id="routes">routes</h3>
<p><code>route</code>별 보여줄 페이지(위젯) 지정</p>
<h2 id="navigatorpushnamedcontext-route">Navigator.pushNamed(context, route)</h2>
<p><code>Navigator.pushNamed(context, &#39;/detail&#39;)</code>
이거 쓰면 유저들이 버튼 눌렀을 때 다른 라우트로 이동시킬 수 있음</p>
<h2 id="주의">주의</h2>
<p>맨 처음부터 <code>route</code> 나누는 작업은 불필요할 것 같고 페이지 많아지거나 복잡한 앱일 경우 거때 나눠주면 좋을듯 state 이런거 끼면 굉장히 복잡해질 가능성 농hoo</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Navigator로 페이지 이동 구현하기, MaterialPageRoute 사용법]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-Navigator%EB%A1%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%9D%B4%EB%8F%99-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-MaterialPageRoute-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@coolgamja_/Flutter-Navigator%EB%A1%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%9D%B4%EB%8F%99-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-MaterialPageRoute-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Mon, 21 Jul 2025 08:16:23 GMT</pubDate>
            <description><![CDATA[<h2 id="navigator">Navigator</h2>
<h3 id="특징">특징</h3>
<ul>
<li>페이지 이동 시 새로운 페이지가 기존 페이지를 대체하는 웹사이트와 달리 모바일앱에서는 새로운 페이지가 기존 페이지 위에 덮이는 구조</li>
<li>탭과 달리 Stack으로 페이지가 관리되므로 뒤로가기 시 페이지가 걷히는 식</li>
</ul>
<h3 id="사용법">사용법</h3>
<p><code>React</code>의 <code>useRouter</code>랑 비슷하게 <code>.push</code>를 사용한다.</p>
<pre><code class="language-dart">IconButton(
  onPressed: () {Navigator.push(context, route)},
  icon: Icon(Icons.add_box_outlined, size: 35),
),</code></pre>
<ul>
<li>이 때 context에는 MaterialApp 정보가 포함되어야 함</li>
<li>두 번째 파라미터는 route인데 아래와 같이 작성가능</li>
<li><code>return</code> 뒤, 그러니까 <code>Text(&#39;New Page&#39;)</code> 부분이 새로운 페이지로 뜨는거</li>
<li>길어지면 커스텀 위젯으로 빼는 게 방법 ~</li>
</ul>
<pre><code class="language-dart">MaterialPageRoute(
  builder: (c) {
    return Text(&#39;New Page&#39;);
  },
),</code></pre>
<h3 id="자란">자란</h3>
<p><img src="https://velog.velcdn.com/images/coolgamja_/post/f5b81c0f-557d-4f0a-87f3-800b29025821/image.gif" alt=""></p>
<h3 id="닫기-버튼">닫기 버튼</h3>
<p>새로 열린 페이지를 닫고 싶을 수 있으니 버튼 하나 만들어보자</p>
<pre><code class="language-dart">IconButton(
  onPressed: () {
    Navigator.pop(context);
  },
  icon: Icon(Icons.close),
),</code></pre>
<p>이 때 <code>Navigator.pop</code>에 들어가는 <code>context</code>는
<code>Navigator.push</code>와 마찬가지로 <code>MaterialApp</code> 정보를 포함해야 함</p>
<h2 id="참고-return-생략하기">(참고) return 생략하기</h2>
<p>위의 <code>MaterialPageRoute</code>의 <code>builder</code>에서와 같이 함수 안에 <code>return</code>문 하나밖에 없다면 아래와 같이 화살표 함수로 더 간단히 작성할 수 있다.</p>
<pre><code class="language-dart">MaterialPageRoute(builder: (c) =&gt; Text(&#39;New Page&#39;)),</code></pre>
<p><code>Arrow Function</code> 인 잉글리시</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 서버와 데이터 주고 받기]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%EC%84%9C%EB%B2%84%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A3%BC%EA%B3%A0-%EB%B0%9B%EA%B8%B0</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%EC%84%9C%EB%B2%84%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A3%BC%EA%B3%A0-%EB%B0%9B%EA%B8%B0</guid>
            <pubDate>Thu, 17 Jul 2025 17:11:00 GMT</pubDate>
            <description><![CDATA[<h2 id="서버">서버</h2>
<ul>
<li>method(GET/POST), url 주면서 데이터 달라하면 주는 프로그램</li>
<li>url은 서버 개발한 넘한테 묻/달라하기</li>
<li>서버 개발은 nodejs나 firebase 참고</li>
</ul>
<h3 id="get">GET</h3>
<p>데이터 읽고 싶을 때</p>
<h3 id="post">POST</h3>
<p>데이터 보내고 싶을 때</p>
<h2 id="테스트">테스트</h2>
<p>강의 상 임시 서버로 GET 요청을 날려보자</p>
<h3 id="패키지-설치">패키지 설치</h3>
<ul>
<li><code>pubspec.yaml</code> &gt; <code>dependencies</code> 밑에 <code>http: ^버전</code></li>
<li>pub get (전구)</li>
</ul>
<h3 id="androidmanifestxml">AndroidManifest.xml</h3>
<p>android &gt; app &gt; src &gt; main &gt; AndroidManifest.xml <code>&lt;application</code> 앞에
<code>&lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&gt;</code> 작성</p>
<h3 id="maindart">main.dart</h3>
<p>맨 위에 아래 두 줄 추가</p>
<pre><code class="language-dart">import &#39;package:http/http.dart&#39; as http;
import &#39;dart:convert&#39;;</code></pre>
<h3 id="get-요청-보내기">GET 요청 보내기</h3>
<ul>
<li>MyApp 위젯이 로드될 때 GET 요청하도록 <code>initState(){}</code> 생성</li>
<li>GET 요청은 시간이 걸리므로 넘어가지 않도록  <code>await</code> 필요</li>
<li>근데 initState에 <code>async</code> 못 붙이니까 GET 함수 따로 빼기</li>
</ul>
<pre><code class="language-dart">getData() async {
  var result = await http.get(
    Uri.parse(&#39;서버URL&#39;),
  );
  print(result.body);
}

@override
void initState() {
  super.initState();
  getData();
}</code></pre>
<h3 id="jsondecode">jsonDecode()</h3>
<ul>
<li>큰따옴표로 묶어서 문자처럼 사기친 상태</li>
<li>jsondecode로 가공 편하게 변환 ex) <code>print(jsonDecode(result.body));</code></li>
</ul>
<h2 id="예외-처리">예외 처리</h2>
<h3 id="1-null-data">1. null data</h3>
<h4 id="에러-메세지">에러 메세지</h4>
<pre><code>NoSuchMethodError: &#39;[]&#39;
Dynamic call of null.
Receiver: null
Arguments: [0]</code></pre><h4 id="원인">원인</h4>
<ol>
<li>data가 아직 로딩되지 않은 상태 (null/빈 리스트)에서 data[i]에 접근해서</li>
<li>initState에서 비동기 함수 getData()가 실행중일 때, build()가 먼저 호출</li>
</ol>
<h4 id="해결">해결</h4>
<p>→ <code>data == null</code> 또는 <code>data.isEmpty</code> 체크</p>
<pre><code class="language-dart">return data == null
  ? CircularProgressIndicator()
  : ListView.builder(</code></pre>
<p>혹은</p>
<pre><code class="language-dart">if (data.isNotEmpty) {
  return ListView.builder(
  // (생략)
} else {
  return CircularProgressIndicator()</code></pre>
<h3 id="2-isdisposed">2. !isDisposed</h3>
<h4 id="에러-메세지-1">에러 메세지</h4>
<pre><code>Assertion failed: !isDisposed
&quot;Trying to render a disposed EngineFlutterView.&quot;</code></pre><h4 id="원인-1">원인</h4>
<p>동기 작업이 완료되었을 때 위젯이 이미 dispose(unmount)된 경우
위젯이 unmount(dispose) 된 후에도 setState()가 호출되면 문제가 됨</p>
<pre><code class="language-dart">void loadData() async {
  var result = await http.get(Uri.parse(&#39;서버URL&#39;));
  setState(() {  // ❗ 요때 위젯이 dispose되었을 수 있음
    data = jsonDecode(result.body);
  });
}</code></pre>
<h4 id="해결-1">해결</h4>
<pre><code class="language-dart">getData() async {
  var result = await http.get(Uri.parse(&#39;서버URL&#39;));
  if (!mounted) return;
  setState(() {
    data = jsonDecode(result.body);
  });
}</code></pre>
<h3 id="3-서버-다운--요청-경로-문제-등">3. 서버 다운 / 요청 경로 문제 등</h3>
<pre><code class="language-dart">getData() async {
  var result = await http.get(Uri.parse(&#39;서버URL&#39;));
  if (result.statusCode == 200) { 요청 성공 시 실행 코드 }
  else { 실패 시 실행 코드 }
}</code></pre>
<h2 id="그-외">그 외</h2>
<h3 id="dio"><a href="https://pub.dev/packages/dio">Dio</a></h3>
<p>조금 더 짧은 코드로 GET 요청 가능한 패키지</p>
<h3 id="futurebuilder"><a href="https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html">FutureBuilder()</a></h3>
<p>Future()를 뱉는 애들을 넣을 수 있는데 데이터가 자꾸 추가되면 사용하기 불편함</p>
<p>ex) <code>body: [FutureBuilder(future: data, builder: (){}), ...],</code></p>
<h3 id="list-map">list, map</h3>
<p>map 안에 map, list 안에 list 등 모든 자료형을 넣기 가능</p>
<h4 id="list">list</h4>
<ul>
<li>형태: [자료1, 자료2...]</li>
<li>읽기: list이름[0]</li>
<li>결과: 자료1</li>
</ul>
<h4 id="map">map</h4>
<ul>
<li>형태: {&#39;이름1&#39;: 자료1, &#39;이름2&#39;: 자료2...}</li>
<li>읽기: map이름[&#39;이름1&#39;]</li>
<li>결과: 자료1</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 자꾸 까먹는 이미지 위젯 사용법]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%EC%9E%90%EA%BE%B8-%EA%B9%8C%EB%A8%B9%EB%8A%94-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%9C%84%EC%A0%AF-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%EC%9E%90%EA%BE%B8-%EA%B9%8C%EB%A8%B9%EB%8A%94-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%9C%84%EC%A0%AF-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Thu, 17 Jul 2025 16:12:28 GMT</pubDate>
            <description><![CDATA[<h3 id="imageasset경로">Image.asset(&#39;경로&#39;)</h3>
<ul>
<li>프로젝트 최상단에 assets/ 폴더 만들고 이미지 파일 담기</li>
<li>pubspec.yaml &gt; <code>flutter: assets:</code> &gt; 하단에 <code>- assets/</code> 추가</li>
<li>ex) <code>Image.asset(&#39;assets/coolgam.png&#39;)</code></li>
</ul>
<h3 id="imagenetwork웹이미지주소">Image.network(&#39;웹이미지주소&#39;)</h3>
<p>ex) <code>Image.network(&#39;https://github.com/u0empty/image/1.png&#39;)</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 인스타그램 홈탭 레이아웃]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%99%88%ED%83%AD-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%99%88%ED%83%AD-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83</guid>
            <pubDate>Wed, 16 Jul 2025 14:52:27 GMT</pubDate>
            <description><![CDATA[<h3 id="생김새">생김새</h3>
<p><img src="https://velog.velcdn.com/images/coolgamja_/post/e4d29f60-9665-448b-a4b0-e1f325e1d423/image.png" alt=""></p>
<p>정말 못생겼다</p>
<h3 id="코드body">코드(body)</h3>
<pre><code class="language-dart">body: [
  ListView.builder(
    itemCount: 3,
    itemBuilder: (context, i) {
      return Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Image.asset(
            &#39;assets/icecream.png&#39;,
          ),
          Padding(
            padding: const EdgeInsets.all(20),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  children: [
                    Text(&#39;좋아요 &#39;, style: bold),
                    Text(&#39;100&#39;, style: bold),
                  ],
                ),
                Text(&#39;글쓴이&#39;),
                Text(&#39;글내용&#39;),
              ],
            ),
          ),
        ],
      );
    },
  ),
  Text(&#39;샵&#39;),
][tab],</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 탭으로 페이지 분리하기]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%ED%83%AD%EC%9C%BC%EB%A1%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%B6%84%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%ED%83%AD%EC%9C%BC%EB%A1%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%B6%84%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 16 Jul 2025 07:28:52 GMT</pubDate>
            <description><![CDATA[<p>Navigator, Router 말고 Tab으로 페이지를 분리해보자
TabBar 위젯을 갖다써도 되지만 동적 UI는 처음이니 직접 만들어보기</p>
<h2 id="동적-ui-만들기">동적 UI 만들기</h2>
<ol>
<li>state에 UI(오늘은 tab)의 현재 상태 저장</li>
<li>state에 따라 UI를 어떻게 보일지 작성</li>
<li>유저가 state를 쉽게 조작하도록 만들기</li>
</ol>
<h3 id="1-state에-tab의-현재-상태-저장">1. state에 tab의 현재 상태 저장</h3>
<ul>
<li>기존 위젯이 stless 였다면 stful로 변환하기</li>
<li>state 생성 ex) <code>var tab = 0;</code></li>
</ul>
<h3 id="2-state에-따라-어떤-tab을-노출시킬지-작성">2. state에 따라 어떤 tab을 노출시킬지 작성</h3>
<ul>
<li>if문을 써도 되지만 body를 List로 짜면 편함</li>
<li><code>body: [Text(&#39;홈&#39;), Text(&#39;샵&#39;)][tab]</code></li>
</ul>
<h3 id="3-유저가-state를-쉽게-조작하도록-만들기">3. 유저가 state를 쉽게 조작하도록 만들기</h3>
<ul>
<li>bottomNavigationBar의 구조가 아래와 같다면</li>
</ul>
<h4 id="기존-bottomnavigationbar">기존 bottomNavigationBar</h4>
<pre><code class="language-dart">bottomNavigationBar: BottomNavigationBar(
  items: [
    BottomNavigationBarItem(
      icon: Icon(Icons.home_outlined),
      label: &#39;홈&#39;,
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.shopping_bag_outlined),
      label: &#39;샵&#39;,
    ),
  ],
),</code></pre>
<ul>
<li>onTab을 추가하여 tab 값을 변경하자</li>
<li>onTab에는 파라미터 하나를 넣게 되어 있는데 탭 값과 같음</li>
</ul>
<h4 id="ontab이-있는-bottomnavigationbar">onTab이 있는 bottomNavigationBar</h4>
<pre><code class="language-dart">bottomNavigationBar: BottomNavigationBar(
  onTap: (i) {
    setState(() {
      tab = i;
    });
  },
  items: [
    BottomNavigationBarItem(
      icon: Icon(Icons.home_outlined),
      label: &#39;홈&#39;,
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.shopping_bag_outlined),
      label: &#39;샵&#39;,
    ),
  ],
),</code></pre>
<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/coolgamja_/post/e7be5ac4-ad29-44c5-a818-e642bc709c50/image.gif" alt=""></p>
<p>TabBar 위젯 말고 PageView 같은 위젯도 사용해보자.
흔히 말하는 Carousel 개발 시 자주 쓰인다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 자식한테 원하는 Theme 적용하기]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%A4%91%EA%B0%84%EC%97%90-ThemeData-%EB%81%BC%EC%9B%8C%EB%84%A3%EA%B8%B0</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%A4%91%EA%B0%84%EC%97%90-ThemeData-%EB%81%BC%EC%9B%8C%EB%84%A3%EA%B8%B0</guid>
            <pubDate>Tue, 15 Jul 2025 15:34:49 GMT</pubDate>
            <description><![CDATA[<p>중간에 어떤 위젯의 자식들은 새로운 Theme을 따르면 좋겠다 싶을 땐
위젯을 <code>Theme()</code>으로 감싸고(전구 활용) <code>data: ThemeData(원하는 Theme)</code> 작성</p>
<h3 id="구조">구조</h3>
<pre><code class="language-dart">void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(
        textTheme: TextTheme(bodyMedium: TextStyle(color: Colors.blue)), // 전체 텍스트 색상: 파랑
      ),
      home: Scaffold(
        body: Center(child: Text(&#39;파란색 텍스트&#39;)), // 별도 style 없음 → 파랑
      ),
    ),
  );
}</code></pre>
<h3 id="비교">비교</h3>
<pre><code class="language-dart">void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(
        textTheme: TextTheme(bodyMedium: TextStyle(color: Colors.blue)), // 기본 파랑
      ),
      home: Scaffold(
        body: Theme(
          data: ThemeData(
            textTheme: TextTheme(bodyMedium: TextStyle(color: Colors.red)), // 오버라이드 빨강
          ),
          child: Center(child: Text(&#39;빨간색 텍스트&#39;)), // Theme 위젯 영향 → 빨강
        ),
      ),
    ),
  );
}</code></pre>
<h3 id="원하는-theme-가져오기">원하는 Theme 가져오기</h3>
<p><code>style: Theme.of(context).textTheme.bodyMedium</code>
→ <code>ThemeData() &gt; textTheme &gt; bodyMedium</code> Theme만 적용하라</p>
<p>요런식으로 원하는 ThemeData 안의 내용을 불러올 수 있다.
바꿔말하면 여러 스타일을 정의해두고 <code>Theme.of()</code>로 갖다 쓰기 가능</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 긴 코드 파일(변수)로 빼기]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%EA%B8%B4-%EC%BD%94%EB%93%9C-%ED%8C%8C%EC%9D%BC-%EB%B3%80%EC%88%98-%EB%A1%9C-%EB%B9%BC%EA%B8%B0</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%EA%B8%B4-%EC%BD%94%EB%93%9C-%ED%8C%8C%EC%9D%BC-%EB%B3%80%EC%88%98-%EB%A1%9C-%EB%B9%BC%EA%B8%B0</guid>
            <pubDate>Tue, 15 Jul 2025 15:12:15 GMT</pubDate>
            <description><![CDATA[<h3 id="maindart">main.dart</h3>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

void main() {
  runApp(
    MaterialApp(
      home: MyApp(),
      theme: ThemeData(
        appBarTheme: AppBarTheme(
          elevation: 1,
          titleTextStyle: TextStyle(fontWeight: FontWeight.bold, fontSize: 25),
        ),
      ),
      debugShowCheckedModeBanner: false,
    ),
  );
}

class MyApp extends StatelessWidget {
(생략)</code></pre>
<p>여기서 <code>ThemeData(~)</code>를 잘라 lib 폴더 안에 새롭게 파일 하나를 만들고 붙인다.
만든 파일은 아무렇게나 작명 ex) <code>style.dart</code></p>
<h3 id="분리할-파일">분리할 파일</h3>
<p>에 옮긴 <code>ThemeData(~)</code>는 변수로 선언해주고
맨 끝에 세미콜론(<code>;</code>)을 붙인 뒤
<code>main.dart</code>에 있던 <code>import</code> 문을 갖다 붙인다.
기본 위젯들을 쓰기 위함이다.</p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

var theme = ThemeData(
  appBarTheme: AppBarTheme(
    elevation: 1,
    titleTextStyle: TextStyle(fontWeight: FontWeight.bold, fontSize: 25),
  ),
);</code></pre>
<h3 id="마무리">마무리</h3>
<p>정의한 변수명을 <code>main.dart</code>에서 ThemeData가 있던 곳에 작성하고,
해당 파일의 경로를 import 한다.
나는 자동완성 기능을 사용했는데
<code>import &#39;style.dart&#39;;</code>도
<code>import &#39;./style.dart&#39;;</code>도 된다.</p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;
import &#39;package:instagram/style.dart&#39;;

void main() {
  runApp(
    MaterialApp(home: MyApp(), theme: theme, debugShowCheckedModeBanner: false),
  );
}</code></pre>
<p><code>ThemeData()</code> 뿐만 아니라
다른 모든 긴, 복잡한, 분리 필요한 코드도 요렇게 파일로 빼내어 관리할 수 있다.</p>
<h3 id="import-시-변수-중복-문제-피하기">import 시 변수 중복 문제 피하기</h3>
<ol>
<li><code>as</code> 키워드로 파일 재정의<pre><code class="language-dart">import &#39;./style.dart as style&#39;;
</code></pre>
</li>
</ol>
<p>(생략)
    MaterialApp(home: MyApp(), theme: style.theme),</p>
<p>```</p>
<ol start="2">
<li>한 파일 내에 여러 변수가 정의된 경우, 다른 파일에서의 사용 막기
→ 변수/함수/클래스명 앞에 언더바(<code>_</code>) 붙이기
ex) <code>var _byeonsoo = ..;</code> </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] DEBUG 배너 제거]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-DEBUG-%EB%B0%B0%EB%84%88-%EC%A0%9C%EA%B1%B0</link>
            <guid>https://velog.io/@coolgamja_/Flutter-DEBUG-%EB%B0%B0%EB%84%88-%EC%A0%9C%EA%B1%B0</guid>
            <pubDate>Mon, 14 Jul 2025 15:56:33 GMT</pubDate>
            <description><![CDATA[<h2 id="maindart">main.dart</h2>
<p><code>MaterialApp</code>에 <strong>debugShowCheckedModeBanner: false</strong>를 추가하자</p>
<pre><code class="language-dart">MaterialApp(
  debugShowCheckedModeBanner: false,
  ...
)</code></pre>
<h2 id="자잔">자잔</h2>
<p><img src="https://velog.velcdn.com/images/coolgamja_/post/482165e9-b871-4059-ae4a-b0cdf14a1d60/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/coolgamja_/post/dd875817-43e1-4614-9290-56cb3de3952e/image.png" alt=""></p>
<h2 id="debug-배너의-역할">DEBUG 배너의 역할</h2>
<ul>
<li>앱이 Debug 모드(개발 중 실행)로 실행 중임을 시각적으로 알림</li>
<li>릴리스 빌드(배포용)가 아니라는 것을 명확히 구분하기 위함</li>
<li>실수로 Debug 앱을 사용자에게 배포하는 것을 방지하기 위한 개발자 도구</li>
</ul>
<h2 id="flutter-실행-모드-비교">Flutter 실행 모드 비교</h2>
<table>
<thead>
<tr>
<th>모드</th>
<th>특징</th>
<th>용도</th>
<th>디버깅 가능 여부</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Debug</strong></td>
<td>- 코드 hot reload 가능<br>- assert 작동<br>- &quot;DEBUG&quot; 배너 표시</td>
<td>개발 중 기능 구현 및 테스트</td>
<td>가능</td>
</tr>
<tr>
<td><strong>Profile</strong></td>
<td>- 성능 분석 가능<br>- 로그 및 CPU 사용량 등 모니터링<br>- 디버깅 제한</td>
<td>성능 측정, 배포 전 최적화 단계</td>
<td>제한적 가능</td>
</tr>
<tr>
<td><strong>Release</strong></td>
<td>- 최적화된 바이너리 생성<br>- 디버깅 기능 제거<br>- 배너 없음</td>
<td>실제 배포용 (플레이/앱스토어)</td>
<td>불가능</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 새 프로젝트 생성 및 스타일 분리]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%EC%83%88-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%8A%A4%ED%83%80%EC%9D%BC-%EB%B6%84%EB%A6%AC</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%EC%83%88-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%8A%A4%ED%83%80%EC%9D%BC-%EB%B6%84%EB%A6%AC</guid>
            <pubDate>Mon, 14 Jul 2025 15:49:41 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-초기-세팅">프로젝트 초기 세팅</h2>
<h3 id="프로젝트-생성">프로젝트 생성</h3>
<ul>
<li>File &gt; New &gt; New Flutter Project</li>
<li>Flutter SDK Path 확인</li>
<li>프로젝트명 작명 &gt; Create</li>
</ul>
<h3 id="lint-끄기">lint 끄기</h3>
<p><code>analysis_options.yaml</code> 파일 내 <code>rules:</code> 밑에 코드 추가</p>
<pre><code class="language-dart">rules:
  prefer_typing_uninitialized_variables: false
  prefer_const_constructors_in_immutables: false
  prefer_const_constructors: false
  avoid_print: false
  prefer_const_literals_to_create_immutables: false</code></pre>
<p><code>const</code> 부분은 재랜더링을 줄임, 앱 발행 전 true 시도해보기</p>
<h3 id="maindart">main.dart</h3>
<p>죄다 지우고 stless 위젯 대충 만들어서 시작
MaterialApp은 미리 빼두는게 편할듯</p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

void main() {
  runApp(MaterialApp(home: MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold();
  }
}</code></pre>
<h2 id="스타일-분리-themedata">스타일 분리: Themedata()</h2>
<h3 id="사용법">사용법</h3>
<pre><code class="language-dart">void main() {
  runApp(
    MaterialApp(
      home: MyApp(),
      theme: ThemeData(
        iconTheme: IconThemeData(color: Colors.blue),
        appBarTheme: AppBarTheme(color: Colors.grey),
      ),
    ),
  );
}</code></pre>
<h3 id="특징">특징</h3>
<ul>
<li>위젯 본인과 가장 가까운 스타일을 먼저 적용</li>
<li>복잡한 위젯(규칙X, 찾아보며 습득)은 <strong>복잡한위젯Theme()</strong> 에 넣어야 먹을 때가 있음
ex) <code>IconThemeData(color: Colors.blue)</code>이 있어도 <code>appBar: AppBar(actions: [Icon(Icons.star)]),</code> 속의 star는 blue가 아님</li>
</ul>
<h3 id="themedata-디자인-반영안됨">ThemeData 디자인 반영안됨</h3>
<h4 id="기존-코드">기존 코드</h4>
<pre><code class="language-dart">theme: ThemeData(
  iconTheme: IconThemeData(color: Colors.amber),
  appBarTheme: AppBarTheme(color: Colors.grey),
),</code></pre>
<h4 id="생김새">생김새<img src="https://velog.velcdn.com/images/coolgamja_/post/5a5585fa-8cbb-4284-b523-f3606834199a/image.png" alt=""></h4>
<h4 id="변경-코드">변경 코드</h4>
<pre><code class="language-dart">theme: ThemeData(
  iconTheme: IconThemeData(color: Colors.amber),
  appBarTheme: AppBarTheme(
    color: Colors.grey,
    actionsIconTheme: IconThemeData(color: Colors.amber),
  ),
),</code></pre>
<h4 id="생김새-1">생김새<img src="https://velog.velcdn.com/images/coolgamja_/post/f28fb7cc-eac8-47cb-b79f-24e0938fc3cf/image.png" alt=""></h4>
<h3 id="textthemedata-활용">TextThemeData 활용</h3>
<ul>
<li>Text()는 bodyText2를 사용하는 등 내부적으로 정의된 값 존재</li>
<li>전체 Text 설정이라면 ThemeData 안에 넣어도 되지만 <code>var text1 = TextStyle(); Text(&#39;&#39;, style: text1)</code> 처럼 변수로 빼서 쓰는 게 나을지도</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 위젯 정리]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%EC%9C%84%EC%A0%AF-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%EC%9C%84%EC%A0%AF-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 10 Jul 2025 07:24:49 GMT</pubDate>
            <description><![CDATA[<h2 id="앱-스타일-위젯">앱 스타일 위젯</h2>
<ul>
<li>MaterialApp(): 구글 기본앱 느낌, 커스텀도 이걸로하고 후에 구글물 짜기</li>
<li>Curpentino(): 앱스토어 느낌</li>
</ul>
<h2 id="기본-위젯">기본 위젯</h2>
<ul>
<li>Icon(Icons.-)</li>
<li>Image.asset(&#39;path&#39;)</li>
<li>Text(&#39;&#39;, style: TextStyle(..))</li>
<li>CircleAvatar(radius: 30,
backgroundImage: AssetImage(&#39;assets/이미지.확장자&#39;)</li>
<li>TextButton(child: Text(&#39;버튼&#39;), onPressed: (){} )</li>
<li>ElevatedButton(child: Text(&#39;버튼&#39;), onPressed: (){} )</li>
<li>IconButton(icon: Icon(), onPressed: (){} )</li>
<li>floatingActionButton:
  FloatingActionButton(child: Text(&#39;버튼&#39;), onPressed: (){})</li>
</ul>
<h3 id="textfield">TextField()</h3>
<p>글자 입력란</p>
<h4 id="아이콘-넣기">아이콘 넣기</h4>
<pre><code class="language-dart">TextField(
  decoration: InputDecoration(
    icon: Icon(Icons.star), // prefixIcon, suffixIcon..
  ),
),</code></pre>
<h4 id="테두리-색-채우기">테두리, 색 채우기</h4>
<pre><code class="language-dart">TextField(
  decoration: InputDecoration(
    icon: Icon(Icons.abc),
    enabledBorder: OutlineInputBorder(
      borderSide: BorderSide(color: Colors.lightBlue, width: 2.0),
      borderRadius: BorderRadius.circular(30),
    ),
    focusedBorder: OutlineInputBorder(
      borderSide: BorderSide(color: Colors.blueAccent, width: 2.0),
      borderRadius: BorderRadius.circular(30),
    ),
    border: OutlineInputBorder(),

    filled: true,
    fillColor: Colors.blue.shade50,
  ),
),</code></pre>
<h4 id="placeholder-">placeholder ?</h4>
<pre><code>TextField(
  decoration: InputDecoration(
    hintText: &#39;hintText&#39;,
    helperText: &#39;helperText&#39;,
    labelText: &#39;labelText&#39;,
    counterText: &#39;counterText&#39;
  ),
),</code></pre><h2 id="커스텀-위젯">커스텀 위젯</h2>
<p>재사용 잦은 UI나 큰 페이지 단위로 커스텀 위젯화</p>
<pre><code class="language-dart">MaterialApp(
  home: Scaffold(
    appBar: AppBar(),
    body: SizedBox( child: Custom() ),
  )
);

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

  @override
  Widget build(BuildContext context) {
    return 따로 뺄 위젯()
  }
}</code></pre>
<h2 id="레이아웃-위젯">레이아웃 위젯</h2>
<ul>
<li>Row(mainAxisAlignment: .., children: [])</li>
<li>Column(mainAxisAlignment: .., children: [])</li>
<li>ListView(children: [])
SNS 피드 등 긴 목록이 필요할 때 사용
무한 스크롤, 메모리 절약 등 유용함</li>
<li>Scaffold(
appBar: AppBar(title: ..),
body: ..,
bottomNavigationBar: BottomAppBar(..))</li>
<li>AppBar(
title : Text(&#39;제목&#39;),
leading : Icon(Icons.앱바 최좌측 아이콘),
actions : [ Icon(Icons.앱바 최우측 아이콘들), Icon(Icons.-) ])</li>
</ul>
<h3 id="listtile">ListTile()</h3>
<p>좌측 그림 + 우측 Text 레이아웃</p>
<pre><code class="language-dart">ListTile(
    leading: Image.asset(&#39;assets/coolgam.png&#39;),
    title: Text(&#39;멋감&#39;),
)</code></pre>
<h3 id="listviewbuilder">ListView.builder()</h3>
<p>목록 동적 생성</p>
<pre><code class="language-dart">ListView.builder(
    itemCount: list.length(),
    itemBuilder: (context, i) {
        return ListTile(
            leading: Image.asset(&#39;assets/coolgam.png&#39;),
            title: Text(list[i]),
        )
    }
)</code></pre>
<h3 id="flexible">Flexible()</h3>
<p>여러 박스 배치 시 너비/높이를 고정값 말고 비율로 주고 싶을 때</p>
<pre><code class="language-dart">Row(
    children: [
        Flexible(flex: 1, child: Container(color: Colors.amber)),
        Flexible(flex: 2, child: Container(color: Colors.blue)),
    ]
)</code></pre>
<p>→ 각 1 : 2만큼 차지</p>
<h3 id="expanded">Expanded()</h3>
<p>여러 박스 배치 시 하나의 박스만 남은 가로폭 꽉 차게 만들기</p>
<pre><code class="language-dart">Row(
    children: [
        Expanded(flex: 1, child: Container(color: Colors.amber)),
        Container(width: 50, child: Container(color: Colors.blue)),
    ]
)</code></pre>
<h2 id="박스-위젯">박스 위젯</h2>
<ul>
<li>SizedBox(): width, height만 필요한 박스면 이게 가벼우니 이거 쓰기</li>
<li>Container(width: 10, height: 10, color: Colors.amber)
color를 줘야 눈에 보임
width, height가 안먹는 이유: 위치를 안잡아줘서
Center(child: Container(..)) 등</li>
<li>Container(width: double.infinity): 박스폭 가로로 꽉차게</li>
<li>Container(margin: ..): 바깥 여백
근데 이제 <code>EdgeInsets.all(10)이나</code>.fromLTRB(10, 20, 30, 40)` 같이 드럽게 줘야함</li>
<li>Container(padding: ..): 안쪽 여백
Row, Column 말고 Container에만 여백 줄 수 있음
Row에 여백 주려면 Container로(를) 감싸기
padding을 위한 Padding() 위젯도 존재</li>
<li>Container(decoration: BoxDecoration(..))
color, boxShadow, borderRadius 등 중요치 않은 박스 스타일 지정</li>
<li>(복습) Center(child: Container(..)): 중앙정렬</li>
<li>Align(alignment: Alignment..., child: Container(..))
Alignment 뒤에 bottomLeft 등 넣어 박스 정렬</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 앱 발행하기(Build)]]></title>
            <link>https://velog.io/@coolgamja_/Flutter-%EC%95%B1-%EB%B0%9C%ED%96%89%ED%95%98%EA%B8%B0Build</link>
            <guid>https://velog.io/@coolgamja_/Flutter-%EC%95%B1-%EB%B0%9C%ED%96%89%ED%95%98%EA%B8%B0Build</guid>
            <pubDate>Wed, 09 Jul 2025 15:19:48 GMT</pubDate>
            <description><![CDATA[<h2 id="📱앱-발행하기">📱앱 발행하기</h2>
<h3 id="bundle-id-생성">Bundle ID 생성</h3>
<ul>
<li><code>android/app/build.gradle</code> 파일에 있는
<code>applicationId = &quot;com.example.contact_app&quot;</code> 이 기존 Bundle ID</li>
<li>프로젝트 생성 시 자동 생성, <code>com.회사명.앱이름</code> 으로 지어주는데 변경 가능</li>
<li>View &gt; Tool Windows &gt; Terminal
<code>dart pub global activate rename</code> 입력</li>
<li><code>rename setAppName --targets ios,android --value &quot;com.회사명.앱이름&quot;</code> 입력</li>
<li>추후 ios 앱 발행 시 ID에 언더바(<code>_</code>)가 포함되면 문제될 수 있음</li>
</ul>
<h3 id="apk-파일-발행">.apk 파일 발행</h3>
<ul>
<li>File &gt; Project Structure</li>
<li>SDK 버전 선택 &gt; OK</li>
<li>Build &gt; Flutter &gt; Build APK</li>
<li>생성된 .apk 파일을 폰으로 옮기면 바로 설치 가능</li>
<li>말고 플레이스토어에 올리려면 아래 과정 따라가기</li>
</ul>
<h3 id="key-파일-생성">key 파일 생성</h3>
<ul>
<li>터미널에 <code>flutter doctor -v</code> 입력</li>
<li><code>Java binary at:</code> 뒤의 주소 중 <code>bin</code>까지 복사</li>
<li>경로 중 띄어쓰기 포함되어 있는 곳은 큰 따옴표로 묶어두기</li>
<li>key 파일 저장할 폴더 하나 만들기
폴더 안의 내용물이 전부 삭제되므로 반드시 새로운 폴더 생성</li>
<li>아래 폼에 맞게 입력 후 터미널에 입력<pre><code>// 폼
복사해둔경로\keytool -genkey -v -keystore key파일넣을폴더경로\upload-keystore.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias upload
</code></pre></li>
</ul>
<p>// 예시
C:&quot;Program Files&quot;\Android&quot;Android Studio1&quot;\jbr\bin\keytool -genkey -v -keystore C:\flutter_key\upload-keystore.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias upload </p>
<pre><code>
- key 비번 입력 후 폴더 경로와 함께 잘 기억해뒀다가
- 현재 프로젝트의 android 폴더 안에 `key.properties` 생성
- 아래 폼에 맞게 입력하고 저장(원(won) 기호 말고 / 사용)

```dart
// 폼
storePassword=입력한비번1
keyPassword=입력한비번2
keyAlias=upload
storeFile=key파일경로/upload-keystore.jks

// 예시
storePassword=123456
keyPassword=123456
keyAlias=upload
storeFile=C:/flutter_key/upload-keystore.jks</code></pre><ul>
<li>이번엔 android/app/build.gradle 파일 내
<code>android {</code> 바로 전에 아래 코드 네 줄 붙여넣기</li>
</ul>
<pre><code class="language-dart">def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file(&#39;key.properties&#39;)
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android { ..</code></pre>
<ul>
<li><code>android {}</code> 안쪽, <code>buildTypes {</code> 바로 전에 아래 코드 여덟 줄 붙여넣기</li>
</ul>
<pre><code class="language-dart">signingConfigs {
    release {
        keyAlias keystoreProperties[&#39;keyAlias&#39;]
        keyPassword keystoreProperties[&#39;keyPassword&#39;]
        storeFile keystoreProperties[&#39;storeFile&#39;] ? file(keystoreProperties[&#39;storeFile&#39;]) : null
        storePassword keystoreProperties[&#39;storePassword&#39;]
    }
}</code></pre>
<ul>
<li><code>buildTypes {</code> 안에 <code>debug</code> -&gt; <code>release</code>로 수정<pre><code class="language-dart">buildTypes {
  release {
      signingConfig signingConfigs.release
  }
}</code></pre>
</li>
</ul>
<h3 id="aab-파일-발행">.aab 파일 발행</h3>
<ul>
<li>Build &gt; Flutter &gt; Build App Bundle</li>
<li>이걸 구글 플레이스토어에 등록하면 되는데
이 때 개발자 계정(생성 시 25달러 결제) 필요</li>
<li>우선 비공개로 올려서 앱 테스트 가능</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>