<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>tobie_.log</title>
        <link>https://velog.io/</link>
        <description>Android, Flutter 앱 개발</description>
        <lastBuildDate>Tue, 19 Nov 2024 02:05:55 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>tobie_.log</title>
            <url>https://velog.velcdn.com/images/tobie_/profile/e6889fef-40b9-4a01-9781-c642472f0e62/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. tobie_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/tobie_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Flutter - CacheNetworkImage]]></title>
            <link>https://velog.io/@tobie_/Flutter-CacheNetworkImage</link>
            <guid>https://velog.io/@tobie_/Flutter-CacheNetworkImage</guid>
            <pubDate>Tue, 19 Nov 2024 02:05:55 GMT</pubDate>
            <description><![CDATA[<h1 id="cachenetworkimage란">CacheNetworkImage란?</h1>
<p>Flutter CacheNetworkImage는 Flutter에서 네트워크 이미지를 효율적으로 로딩하고 캐싱하기 위한 강력한 패키지입니다. 이 패키지를 사용하면 <strong>네트워크 요청 횟수를 줄이고 앱의 성능을 향상</strong>시킬 수 있습니다.</p>
<p>&lt;장점&gt;</p>
<ul>
<li>이미 로드된 이미지는 <strong>캐시에 저장</strong>되어 재사용되므로 불필요한 네트워크 요청을 줄일 수 있습니다.</li>
<li>캐시된 이미지는 로컬에서 빠르게 로딩되어 사용자 경험을 향상시킵니다.</li>
<li>이미지 로딩 중이나 실패 시 사용자에게 알려줄 수 있는 플레이스홀더 및 에러 위젯을 제공합니다.</li>
</ul>
<p>&lt;단점&gt;</p>
<ul>
<li>캐시 크기가 커지면 메모리 사용량이 증가할 수 있으며 캐시 관리에 대한 신경을 써야 합니다.</li>
<li>외부 패키지에 의존하기 때문에 프로젝트 크기가 약간 증가할 수 있습니다.</li>
</ul>
<h4 id="cachenetworkimage-예제">CacheNetworkImage 예제</h4>
<pre><code>import &#39;package:cached_network_image/cached_network_image.dart&#39;;
import &#39;package:flutter/material.dart&#39;;

void main() =&gt; runApp(MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: &#39;CachedNetworkImage Example&#39;,
      home: Scaffold(
        appBar: AppBar(
          title: const Text(&#39;CachedNetworkImage Example&#39;),
        ),
        body: Center(
          child: CachedNetworkImage(
            imageUrl: &#39;https://health.chosun.com/site/data/img_dir/2023/07/17/2023071701753_0.jpg&#39;,
            placeholder: (context, url) =&gt; const CircularProgressIndicator(),
            errorWidget: (context, url, error) =&gt; const Icon(Icons.error),
          ),
        ),
      ),
    );
  }
}
</code></pre><p>CachedNetworkImage 위젯을 사용하여 네트워크 이미지를 로딩하고 이미지 로딩 중에는 CircularProgressIndicator를 로딩 실패 시에는 Error 아이콘을 표시합니다.</p>
<img src="https://velog.velcdn.com/images/tobie_/post/fa8058c0-08fe-4a60-88ee-a3c9e1d8b582/image.gif" width="40%" height="n%">

<p>&lt;캐싱 메커니즘&gt;</p>
<ul>
<li>동일한 URL을 가진 이미지는 한 번만 다운로드되고 캐시에 저장됩니다.</li>
<li>가장 최근에 사용하지 않은 항목을 먼저 삭제하여 메모리 공간을 확보하는 방식</li>
<li>캐시된 이미지는 설정된 만료 시간이 지나면 자동으로 삭제됩니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - Hot Reload와 Hot Restart]]></title>
            <link>https://velog.io/@tobie_/Flutter-Hot-Reload%EC%99%80-Hot-Restart</link>
            <guid>https://velog.io/@tobie_/Flutter-Hot-Reload%EC%99%80-Hot-Restart</guid>
            <pubDate>Mon, 18 Nov 2024 07:26:37 GMT</pubDate>
            <description><![CDATA[<p>Flutter 개발 환경에서 빠르고 효율적인 개발을 가능하게 해주는 핵심 기능인 Hot Reload와 Hot Restart에 대해 깊이 있게 알아보겠습니다. 현재 개발할때 가장 많이 사용하고 도움을 많이 받고 있는 기능입니다.</p>
<h1 id="hot-reload란">Hot Reload란?</h1>
<p>Hot Reload는 Flutter 애플리케이션을 실행한 상태에서 코드를 변경하고 저장하면 앱이 다시 시작되지 않고 변경된 부분만 빠르게 업데이트되는 기능입니다. 앱의 상태를 유지하면서 UI, 비즈니스 로직 등을수정하고 결과를 확인할 수 있습니다.</p>
<ul>
<li>앱을 다시 시작하지 않고 변경 사항을 바로 확인할 수 있습니다.</li>
<li>반복적인 빌드 및 실행 시간을 줄여 개발 효율을 높입니다.</li>
<li>상태 유지를 통해 디버깅과 테스트를 용이하게 합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/tobie_/post/97ad1854-8972-455a-9ee3-e5bcc7db37cb/image.png" alt=""></p>
<ol>
<li><strong>flutter run</strong> 명령어를 실행하여 앱을 실행합니다.</li>
<li>에디터에서 코드를 수정하고 <strong>저장</strong>합니다.(⚡️번개모양)</li>
<li>변경된 내용이 자동으로 반영됩니다. (일반적으로 s 키를 누르거나, IDE의 Hot Reload 버튼을 클릭하여 수동으로 트리거할 수 있습니다.)</li>
</ol>
<h1 id="hot-restart란">Hot Restart란?</h1>
<p>Hot Restart는 Hot Reload와 유사하지만, 앱의 상태를 초기화하고 변경된 코드와 함께 다시 시작하는 기능입니다. 앱의 상태가 변경된 경우 Hot Reload 대신 <strong>Hot Restart</strong>를 사용하여 앱을 완전히 새로 시작해야 합니다.</p>
<p><img src="https://velog.velcdn.com/images/tobie_/post/f2ba7419-9d96-4e39-8e5b-c79b3a1d114d/image.png" alt=""></p>
<ul>
<li>앱 상태가 변경된 경우, 정확한 결과를 확인하기 위해 상태를 초기화합니다.</li>
<li>변경된 코드를 반영하여 새로운 위젯 트리를 생성합니다.</li>
</ul>
<ol>
<li>flutter run 명령어를 실행하여 앱을 실행합니다.</li>
<li>에디터에서 코드를 수정하고 저장합니다.(⚡️번개모양 옆 Flutter Hot Restart)</li>
<li>변경된 내용과 함께 앱이 다시 시작됩니다. (일반적으로 r 키를 누르거나, IDE의 Hot Restart 버튼을 클릭하여 수동으로 트리거할 수 있습니다.)</li>
</ol>
<p><img src="https://velog.velcdn.com/images/tobie_/post/a7ee0c26-bedf-4fc0-8620-6334f21c0026/image.png" alt=""></p>
<p>Hot Reload와 Hot Restart는 Flutter 개발 과정에서 필수적인 기능입니다. 각 기능의 특징을 이해하고 적절하게 활용하면 개발 생산성을 크게 향상시킬 수 있습니다.</p>
<blockquote>
<p>Tip)</p>
</blockquote>
<ul>
<li>에디터의 자동 저장 기능을 활성화하여 코드 변경 시마다 Hot Reload/Hot Restart가 자동으로 실행되도록 설정할 수 있습니다.</li>
<li>시뮬레이터 성능이 좋을수록 Hot Reload/Hot Restart의 반응 속도가 빨라집니다.</li>
<li>네트워크 요청이 포함된 코드 변경 시에는 Hot Restart를 사용하는 것이 좋습니다.</li>
</ul>
<p><a href="https://docs.flutter.dev/tools/hot-reload">Flutter-Hot reload 공식문서 </a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - TextField (필수위젯)]]></title>
            <link>https://velog.io/@tobie_/Flutter-TextField-%ED%95%84%EC%88%98%EC%9C%84%EC%A0%AF</link>
            <guid>https://velog.io/@tobie_/Flutter-TextField-%ED%95%84%EC%88%98%EC%9C%84%EC%A0%AF</guid>
            <pubDate>Sun, 10 Nov 2024 07:51:16 GMT</pubDate>
            <description><![CDATA[<h1 id="textfield란">TextField란?</h1>
<blockquote>
<p><a href="https://api.flutter.dev/flutter/material/TextField-class.html">https://api.flutter.dev/flutter/material/TextField-class.html</a>
공식 문서에는 &quot;텍스트 필드를 사용하면 사용자는 하드웨어 키보드나 화면 키보드를 사용하여 텍스트를 입력할 수 있습니다.&quot; 라고 적혀있습니다.
<img src="https://velog.velcdn.com/images/tobie_/post/7ff89440-472e-4484-951f-d25dd9526948/image.png" alt=""></p>
</blockquote>
<p>즉, 사용자로부터 텍스트 입력을 받을 수 있는 가장 기본적인 위젯으로 앱에서 검색창, 로그인 창 등 다양한 입력폼에서 활용되고 있습니다.
아래에서는 TextField의 기본적인 사용법부터 주요 속성, 디자인 커스터마이징 방법까지 알아보겠습니다.</p>
<p>기본예제로</p>
<pre><code>import &#39;package:flutter/material.dart&#39;;

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text(&#39;TextField Example&#39;)),
        body: const Padding(
          padding: EdgeInsets.all(16.0),
          child: TextField(
            decoration: InputDecoration(
              labelText: &#39;Enter your text&#39;,
              border: OutlineInputBorder(),
            ),
          ),
        ),
      ),
    );
  }
}</code></pre><img src="https://velog.velcdn.com/images/tobie_/post/94729575-5516-42bd-b2c2-c7beba1461be/image.png" width="40%" height="n%">
이 예제에서는 TextField 위젯을 사용해 단순한 텍스트 입력 창을 만들고 InputDecoration을 통해 텍스트 필드의 레이블과 테두리를 설정하여 기본적인 스타일을 구성했습니다.

<img src="https://velog.velcdn.com/images/tobie_/post/57625d5e-7c01-4382-a2cb-c0cdcbaf395b/image.png" width="40%" height="n%">
실제 코드 TextField 의 레퍼런스를 확인해보면 다양한 속성들이 있으며 디자인, 사용자 편의성을 향상시킬수 있는 옵션들이 있습니다. 오늘은 대표적인 속성들만 알아보겠습니다.



<h3 id="controller">controller</h3>
<pre><code>final TextEditingController _controller = TextEditingController();

TextField(
  controller: _controller,
);

// 버튼을 눌렀을 때 텍스트 초기화
ElevatedButton(
  onPressed: () {
    _controller.clear();
  },
  child: Text(&#39;Clear Text&#39;),
);
</code></pre><ul>
<li>TextEditingController는 TextField의 텍스트를 제어하고 관리할 수 있도록 하는 속성입니다. </li>
<li>텍스트를 읽거나 초기 값을 설정하고, 외부에서 TextField의 텍스트를 변경할 수도 있습니다.</li>
<li>controller를 사용하면 텍스트 입력 상태에 대한 실시간 모니터링이나 특정 버튼 클릭 시 텍스트를 초기화하는 기능 등을 구현할 수 있습니다.</li>
</ul>
<h3 id="decoration">decoration</h3>
<pre><code>TextField(
  decoration: InputDecoration(
    labelText: &#39;Username&#39;,
    hintText: &#39;Enter your username&#39;,
    icon: Icon(Icons.person),
    prefixIcon: Icon(Icons.account_circle),
    suffixIcon: Icon(Icons.check_circle),
    border: OutlineInputBorder(),
    errorText: &#39;Invalid username&#39;,
  ),
);</code></pre><p>InputDecoration을 통해 TextField의 스타일과 디자인을 정의합니다.
<img src="https://velog.velcdn.com/images/tobie_/post/5b87450b-916c-4da7-8067-57610c3645b7/image.png" width="70%" height="n%"></p>
<ul>
<li>labelText: 필드의 제목을 표시합니다. 사용자가 입력을 시작하면 위로 이동하여 필드의 제목(&#39;Username&#39; 부분) 역할을 합니다.</li>
<li>hintText: 입력 전 필드 안에 예시 텍스트를 표시하여 사용자가 입력할 내용을 유추할 수 있도록 돕습니다.(&#39;Enter your username&#39;)</li>
<li>icon: 필드 외부 왼쪽에 표시되는 아이콘입니다. 일반적으로 필드의 전체적인 기능을 나타냅니다.(&#39;텍스트 필드 좌측 아이콘&#39;)</li>
<li>prefixIcon: 필드 내부 왼쪽에 표시되는 아이콘입니다. </li>
<li>suffixIcon: 필드 내부 오른쪽에 표시되는 아이콘으로 입력 검증 결과나 기타 정보를 표시할 때 유용합니다.</li>
<li>border: 필드의 테두리 스타일을 정의합니다. 이 예제에서는 기본 외곽선이 적용됩니다.</li>
<li>errorText: 필드에 오류가 발생했을 때 아래쪽에 표시되는 오류 메시지입니다. </li>
</ul>
<h3 id="keyboardtype">keyboardType</h3>
<p>keyboardType은 사용자가 어떤 종류의 입력을 할지를 결정하는 속성입니다.
이메일 주소를 입력할 때는 TextInputType.emailAddress, 숫자 입력 필드에는 TextInputType.number를 설정하여 사용자가 더 편리하게 입력할 수 있도록 합니다.
<img src="https://velog.velcdn.com/images/tobie_/post/419439b5-f081-4443-a60d-210a41949c7e/image.png" alt=""></p>
<ul>
<li>지원되는 키보드 타입에는 텍스트, 멀티라인, 전화번호, 이메일, URL 등이 있습니다.</li>
</ul>
<h3 id="obscuretext">obscureText</h3>
<ul>
<li>obscureText를 true로 설정하면 입력된 텍스트가 마스킹 처리되어 비밀번호와 같은 민감한 정보를 입력하는 필드에서 사용됩니다.</li>
<li>TextField가 텍스트 대신 원형 또는 별표 기호를 표시하게 되어 비밀번호 입력 필드 구현 시 유용합니다.</li>
</ul>
<pre><code>TextField(
  obscureText: true,
);</code></pre><img src="https://velog.velcdn.com/images/tobie_/post/99e97acd-c539-4392-a402-f9a0c004e450/image.png" width="70%" height="n%">
obscureText값을 true로 설정하면 위 사진과 같이 텍스트필드에 입력을 하게 되면 마스킹 처리된것을 확인 할 수 있습니다.


<h3 id="maxlength">maxLength</h3>
<ul>
<li>최대 입력 가능 글자 수를 제한하는 속성으로 기본적으로 텍스트 필드 아래에 제한된 글자 수가 표시됩니다.</li>
<li>너무 많은 글자 수가 입력되는 것을 방지할 때 유용하며 제한이 초과되면 더 이상 텍스트를 입력할 수 없습니다.</li>
</ul>
<pre><code>TextField(
  maxLength: 10,
);</code></pre><p><img src="https://velog.velcdn.com/images/tobie_/post/395c42ac-80fa-4e3d-85ca-1aaeadb90d34/image.png" alt=""></p>
<ul>
<li>int값으로 글자수를 제한합니다.</li>
</ul>
<h3 id="onchanged">onChanged</h3>
<ul>
<li>사용자가 텍스트를 입력하거나 삭제할 때마다 호출되는 콜백 함수로 TextField의 내용을 실시간으로 처리할 수 있습니다.</li>
<li>검색 필드나 실시간 입력 검증 등에 활용할 수 있습니다.</li>
</ul>
<pre><code>TextField(
  onChanged: (text) {
    print(&quot;Current text: $text&quot;);
  },
);</code></pre><p><img src="https://velog.velcdn.com/images/tobie_/post/80f5c797-2af9-47d7-a801-27d549294b02/image.png" alt=""></p>
<ul>
<li><p>onChanged는 TextField 내의 입력 변화를 실시간으로 추적하여 즉각적인 반응을 필요로 하는 다양한 UI와 로직을 구현하는 데 중요한 역할을 합니다.</p>
</li>
<li><p>ValueChanged<T>는 Flutter에서 사용되는 콜백 함수 타입 중 하나로 입력된 값을 변경할 때 호출하는 콜백 함수의 형식을 정의하는 타입입니다. 이 타입은 단일 인수(T)를 받고 반환값이 없는 함수 형태를 나타냅니다.</p>
</li>
<li><p>예를 들어, ValueChanged<String>은 String 타입의 변화를 처리하는 함수 타입을 의미합니다. 이 콜백 함수는 입력을 받을 때마다 실행되며 이를 통해 입력된 값을 기반으로 UI를 즉시 업데이트하거나 데이터 변경을 처리할 수 있게 해 줍니다.</p>
</li>
</ul>
<h3 id="onsubmitted">onSubmitted</h3>
<ul>
<li>사용자가 텍스트 입력을 완료하고 키보드의 완료 버튼을 눌렀을 때 호출되는 콜백 함수입니다.<ul>
<li>사용자 입력이 완료된 이후에 처리할 작업이 있을 때 사용되며, 검색이나 입력 제출 처리에 적합합니다.<pre><code>TextField(
onSubmitted: (text) {
print(&quot;Submitted text: $text&quot;);
},
);</code></pre><img src="https://velog.velcdn.com/images/tobie_/post/5f6af966-f3cd-4dad-872f-71ac0fec5988/image.png" width="50%" height="50%"></li>
<li>우측 하단에 done 버튼을 클릭하면 onSubmitted의 text의 마지막으로 입력된 값까지 출력하게 된다.</li>
</ul>
</li>
</ul>
<h3 id="style">style</h3>
<ul>
<li>입력 텍스트의 글꼴, 크기, 색상 등을 설정할 수 있는 TextStyle 속성입니다.<ul>
<li>입력 텍스트를 눈에 띄게 하거나 특정 스타일을 적용해야 할 때 유용합니다.</li>
</ul>
</li>
</ul>
<blockquote>
<p> 이 글에서는 TextField의 주요 속성들과 예제들을 통해 간략적으로 다뤄보았습니다. 이러한 속성들을 이해하고 활용한다면 사용자 경험을 개선하고 보다 효율적이고 입력 폼을 설계하는 데 큰 도움이 될 것입니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - Widget Tree (위젯트리)]]></title>
            <link>https://velog.io/@tobie_/Flutter-Widget-Tree-%EC%9C%84%EC%A0%AF%ED%8A%B8%EB%A6%AC</link>
            <guid>https://velog.io/@tobie_/Flutter-Widget-Tree-%EC%9C%84%EC%A0%AF%ED%8A%B8%EB%A6%AC</guid>
            <pubDate>Fri, 08 Nov 2024 05:47:25 GMT</pubDate>
            <description><![CDATA[<h1 id="widget-tree란">Widget Tree란?</h1>
<ul>
<li>UI의 구조와 구성 방식을 나타내며, Flutter 앱에서 UI를 구성하는 가장 기본적인 개념입니다.</li>
<li>각 UI 요소는 Flutter에서 모두 위젯으로 표현되고 이러한 위젯들이 계층 구조로 모여 앱의 화면을 형성합니다. </li>
<li>위젯 트리는 부모 위젯이 자식 위젯을 포함하는 방식으로 구성되어 있어 트리 모양의 계층 구조를 가지게 됩니다.</li>
</ul>
<p>Flutter의 위젯 트리는 두 가지 유형의 트리로 구성됩니다.</p>
<blockquote>
<p>Widget Tree: 이 트리는 UI의 선언적 구조를 정의하며 각 위젯이 어떤 요소를 포함하는지 정의합니다.
Element Tree: 실제로 위젯이 화면에 렌더링되는 순간 생성되며, 위젯의 상태와 생명 주기를 관리합니다.</p>
</blockquote>
<p>간단한 Flutter 예제를 통해 위젯 트리를 알아볼까요?</p>
<pre><code>import &#39;package:flutter/material.dart&#39;;

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(&#39;Flutter Widget Tree Example&#39;),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(&#39;Hello, Flutter!&#39;),
              ElevatedButton(
                onPressed: () {},
                child: Text(&#39;Click me&#39;),
              ),
            ],
          ),
        ),
      ),
    );
  }
}</code></pre><p>위 코드를 간략하게 해석하면</p>
<ul>
<li><p>MaterialApp: 앱의 전체 설정을 관리하는 최상위 위젯입니다. 화면 구조와 테마, 라우팅 등을 설정할 수 있습니다.</p>
</li>
<li><p>Scaffold: 앱의 기본 레이아웃 구조로 AppBar와 Body를 포함합니다. 이외에 floatingactionbutton, bottomnavigationbar 등 다양한 arguments 이 있습니다.</p>
</li>
<li><p>AppBar: 상단에 표시되는 앱의 타이틀 바입니다. 여기에는 title로 텍스트 위젯을 설정하여 앱 이름을 표시합니다.</p>
</li>
<li><p>Center: 화면의 중앙에 자식 위젯을 배치하는 역할을 합니다.</p>
</li>
<li><p>Column: 자식 위젯들을 세로로 나열할 수 있는 위젯입니다. 여기서는 Text와 ElevatedButton 위젯이 포함되어 있습니다. 자주 사용되는 위젯으로 앞으로 더 자세히 다룰 예정입니다.</p>
</li>
<li><p>Text: 간단한 텍스트를 화면에 표시합니다.</p>
</li>
<li><p>ElevatedButton: 클릭 가능한 버튼을 나타내며, onPressed 콜백을 통해 버튼 클릭 시의 동작을 설정할 수 있습니다.</p>
</li>
</ul>
<blockquote>
</blockquote>
<p>위젯 트리의 계층을 깊이 구성하다 보면 위젯의 중첩이 심화될 수 있으므로, 위젯을 적절히 분리하고 함수나 클래스 형태로 리팩토링하여 가독성을 높이는 것이 중요합니다.</p>
<p>✓ flutter에서 위젯 트리를 잘 활용하면 효율적이고 유지보수하기 쉬운 UI를 만들 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[flutter - go_router 패키지]]></title>
            <link>https://velog.io/@tobie_/flutter-gorouter-%ED%8C%A8%ED%82%A4%EC%A7%80</link>
            <guid>https://velog.io/@tobie_/flutter-gorouter-%ED%8C%A8%ED%82%A4%EC%A7%80</guid>
            <pubDate>Sun, 25 Aug 2024 04:28:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Flutter에서 네비게이션을 관리하는 방법에는 여러 가지가 있습니다. 그중에서 go_router 패키지는 선언적이고 직관적인 라우팅을 제공하며 이번 포스팅에서는 go_router를 사용하여 Flutter 애플리케이션의 네비게이션을 관리하는 방법을 살펴보겠습니다.</p>
</blockquote>
<h3 id="go_router란">go_router란?</h3>
<p>go_router는 Flutter 애플리케이션에서 라우팅을 단순화하는 데 사용되는 패키지입니다. 이 패키지는 Flutter의 기본 네비게이션 방법보다 더 선언적이고, 특히 복잡한 네비게이션 로직을 간결하게 작성할 수 있도록 돕습니다.</p>
<h3 id="go_router-설치">go_router 설치</h3>
<p><a href="https://pub.dev/packages/go_router">https://pub.dev/packages/go_router</a></p>
<blockquote>
<p>flutter pub add go_router</p>
</blockquote>
<blockquote>
<p>import &#39;package:go_router/go_router.dart&#39;;</p>
</blockquote>
<pre><code>class RoutePath {
    static const String main = &#39;/main&#39;;
    static const String splash = &#39;/splash&#39;;
}</code></pre><p>route_path.dart 휴먼에러를 줄이기 위해 클래스로 관리합니다.</p>
<pre><code>final GoRouter _router = GoRouter(
    routes: [
      GoRoute(
        path: &#39;/&#39;,
        builder: (context, state) =&gt; HomePage(),
      ),
      GoRoute(
        path: &#39;/splash&#39;,
        builder: (context, state) =&gt; Splash(),
      ),
    ],
  );</code></pre><p>routes.dart 파일로 routes를 관리합니다.</p>
<pre><code>
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
        routerConfig: routers,
        ..
    );
  }
}</code></pre><p>MaterialApp.router를 사용하여 Flutter 애플리케이션에 라우터를 설정합니다.</p>
<pre><code>class SplashPage extends StatefulWidget {
  const SplashPage({super.key});

  @override
  State&lt;SplashPage&gt; createState() =&gt; _SplashPageState();
}

class _SplashPageState extends State&lt;SplashPage&gt; {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&#39;Splash Page&#39;),
      ),

      body:  Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(&#39;Splash Page&#39;),
            TextButton(
                onPressed: ()  {
                  context.go(RoutePath.main);
            },
                child: const Text(&#39;Go to Main Page -&gt;&#39;)),
          ],
        ),
      ),
    );
  }
}</code></pre><pre><code>class MainPage extends StatefulWidget {
  const MainPage({super.key});

  @override
  State&lt;MainPage&gt; createState() =&gt; _MainPageState();
}

class _MainPageState extends State&lt;MainPage&gt; {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&#39;Main Page&#39;),
      ),

      body: const Center(
        child: Text(&#39;Main Page&#39;),
      ),
    );
  }
}</code></pre><p>Splash, Main 화면을 간단하게 만들어줍니다.Splash 화면에는 메인화면으로 이동할 수 있는 버튼을 추가 해줍니다. Splash의 context.go(RoutePath.main) go_router를 사용하면 context를 통해 쉽게 다른 경로로 이동할 수 있습니다.</p>
<p><img src="" alt="">
<img src="https://velog.velcdn.com/images/tobie_/post/76b41d05-b04d-4235-a5cb-9c62a24b2854/image.gif" width="50%" height="n%"></p>
<h3 id="결론">결론</h3>
<blockquote>
<p>go_router는 Flutter에서 복잡한 네비게이션 구조를 관리하는 데 매우 유용한 패키지입니다. 선언적인 라우팅 방법과 간단한 설정으로 다양한 네비게이션 요구사항을 쉽게 구현할 수 있습니다. 프로젝트의 필요에 맞게 go_router를 활용해 보세요.</p>
</blockquote>
<p>위 내용은 Flutter 프로젝트에서 go_router를 사용하여 네비게이션을 관리하는 방법을 소개했습니다. 프로젝트에 따라 다양한 기능을 더할 수 있으니, 필요에 맞게 커스터마이징해보세요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - 로딩화면, Shimmer Effect]]></title>
            <link>https://velog.io/@tobie_/Flutter-%EB%A1%9C%EB%94%A9%ED%99%94%EB%A9%B4-Shimmer-Effect</link>
            <guid>https://velog.io/@tobie_/Flutter-%EB%A1%9C%EB%94%A9%ED%99%94%EB%A9%B4-Shimmer-Effect</guid>
            <pubDate>Wed, 21 Aug 2024 10:03:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>모바일 애플리케이션에서 데이터를 로딩하는 동안 사용자가 아무런 피드백 없이 기다리게 하는 것은 좋은 사용자 경험을 제공하는 데 있어 큰 걸림돌이 될 수 있습니다. 이때 로딩 상태를 시각적으로 표현해주는 flutter_shimmer 패키지를 사용하면 로딩 중임을 직관적으로 알릴 수 있습니다.</p>
</blockquote>
<img src="https://velog.velcdn.com/images/tobie_/post/7b169d0f-07a0-4690-941e-8b7ce7f1efab/image.png" width="70%" height="n%">

<p>많이들 사용하는 인스타, 페이스북, 스레드 등 앱을 실행시키면 첫 화면으로 볼 수 있는 화면들이다. 
기존의 CircularProgressIndicator는 단순한 로딩 상태를 나타내지만, 앱화면을 미리 직관적으로 볼 수 있는 장점이 있습니다.</p>
<h3 id="shimmer-package-적용방법">shimmer package 적용방법</h3>
<p><a href="https://pub.dev/packages/shimmer/versions">https://pub.dev/packages/shimmer/versions</a></p>
<blockquote>
<p> $ flutter pub add shimmer</p>
</blockquote>
<blockquote>
<p>import &#39;package:shimmer/shimmer.dart&#39;;</p>
</blockquote>
<h3 id="예제">예제</h3>
<pre><code>       child: Shimmer.fromColors(
              baseColor: Colors.grey[300]!,
              highlightColor: Colors.grey[100]!,
              child: ListView.builder(
                itemCount: 6,
                itemBuilder: (_, __) =&gt; Padding(
                  padding: const EdgeInsets.symmetric(vertical: 8.0),
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Container(
                        width: 80.0,
                        height: 80.0,
                        color: Colors.white,
                      ),
                      const Padding(
                        padding: EdgeInsets.symmetric(horizontal: 8.0),
                      ),
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Container(
                              width: double.infinity,
                              height: 8.0,
                              color: Colors.white,
                            ),
                            const Padding(
                              padding: EdgeInsets.symmetric(vertical: 2.0),
                            ),
                            Container(
                              width: double.infinity,
                              height: 8.0,
                              color: Colors.white,
                            ),
                            const Padding(
                              padding: EdgeInsets.symmetric(vertical: 2.0),
                            ),
                            Container(
                              width: 40.0,
                              height: 8.0,
                              color: Colors.white,
                            ),
                          ],
                        ),
                      )
                    ],
                  ),
                ),
              ),
            ),</code></pre><blockquote>
<ul>
<li>Shimmer.fromColors: 이 위젯은 flutter_shimmer 패키지에서 제공하는 기본적인 기능입니다. baseColor와 highlightColor를 설정하여 애니메이션의 색상 변화를 지정할 수 있습니다. 이 예제에서는 회색 계열의 색상을 사용하여 부드러운 반짝임 효과를 적용하였습니다.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Container: 반짝이는 애니메이션을 보여주기 위해, 데이터가 로딩될 영역에 빈 컨테이너를 사용합니다. 로딩이 완료되면 실제 데이터로 이 컨테이너를 대체하게 됩니다.</li>
</ul>
</blockquote>
<img src="https://velog.velcdn.com/images/tobie_/post/bdc226a5-a6ae-4b0a-abc8-b6eeda7a662c/image.gif" width="50%" height="n%">

<h3 id="마무리">마무리</h3>
<p>flutter_shimmer 패키지는 Flutter 애플리케이션에서 로딩 상태를 시각적으로 개선할 수 있는 강력한 도구입니다. 사용자는 데이터가 로드되고 있음을 직관적으로 알 수 있으며, 로딩 애니메이션을 통해 보다 매끄러운 사용자 경험을 제공할 수 있습니다. 위의 예제와 같이 간단한 코드로 손쉽게 애니메이션을 추가해보세요. 앱의 완성도가 한층 더 높아질 것입니다.</p>
<p>이제 flutter_shimmer 패키지를 활용하여 로딩 상태에서도 사용자에게 즐거운 경험을 선사해보세요!!</p>
<p>/// 전체 소스 코드</p>
<pre><code>import &#39;package:flutter/material.dart&#39;;
import &#39;package:shimmer/shimmer.dart&#39;;

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: &#39;Flutter Demo&#39;,
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home:  MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text(&#39;Shimmer Example&#39;)),
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Shimmer.fromColors(
              baseColor: Colors.grey[300]!,
              highlightColor: Colors.grey[100]!,
              child: ListView.builder(
                itemCount: 6,
                itemBuilder: (_, __) =&gt; Padding(
                  padding: const EdgeInsets.symmetric(vertical: 8.0),
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Container(
                        width: 80.0,
                        height: 80.0,
                        color: Colors.white,
                      ),
                      const Padding(
                        padding: EdgeInsets.symmetric(horizontal: 8.0),
                      ),
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Container(
                              width: double.infinity,
                              height: 8.0,
                              color: Colors.white,
                            ),
                            const Padding(
                              padding: EdgeInsets.symmetric(vertical: 2.0),
                            ),
                            Container(
                              width: double.infinity,
                              height: 8.0,
                              color: Colors.white,
                            ),
                            const Padding(
                              padding: EdgeInsets.symmetric(vertical: 2.0),
                            ),
                            Container(
                              width: 40.0,
                              height: 8.0,
                              color: Colors.white,
                            ),
                          ],
                        ),
                      )
                    ],
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - Progress Indicator]]></title>
            <link>https://velog.io/@tobie_/Flutter-Progress-Indicator</link>
            <guid>https://velog.io/@tobie_/Flutter-Progress-Indicator</guid>
            <pubDate>Tue, 20 Aug 2024 02:56:50 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가며">들어가며</h1>
<blockquote>
<p>애플리케이션을 개발할 때, 사용자가 기다려야 하는 작업이 있을 때 진행 상황을 시각적으로 보여주는 것이 매우 중요합니다. 이때 Progress Indicator를 사용하면 사용자에게 현재 작업이 진행 중임을 알리고, 그 상태를 쉽게 파악할 수 있게 해줍니다.</p>
</blockquote>
<blockquote>
<p> 이번 포스팅에서는 Flutter에서 제공하는 여러 가지 Progress Indicator의 종류, 사용 방법, 각 파라미터에 대한 설명과 함께 예제 코드도 소개해드리겠습니다. 또한, Progress Indicator를 어떤 상황에서 사용하는 것이 적절한지에 대해서도 알아보겠습니다.</p>
</blockquote>
<h3 id="flutter에서-제공하는-progress-indicator의-종류">Flutter에서 제공하는 Progress Indicator의 종류</h3>
<p>1.CircularProgressIndicator </p>
<ul>
<li>작업의 전체 진행률을 알 수 없는 경우 사용합니다. 서버로부터 데이터를 불러올때 얼마나 오래 걸릴지 모를 때 사용합니다.</li>
</ul>
<p>2.LinearProgressIndicator </p>
<ul>
<li>작업의 전체 진행률을 알 수 있는 경우 사용됩니다. 파일 다운로드가 70% 완료된 경우 이를 시각적으로 표시 할 수 있습니다.</li>
</ul>
<h3 id="circularprogressindicator-예제">CircularProgressIndicator 예제</h3>
<pre><code>  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const Center(
        child: CircularProgressIndicator(
          color: Colors.grey,
          backgroundColor: Colors.blue,
          strokeWidth: 5,
        ),
      ),
    );
  }</code></pre><blockquote>
<ul>
<li>value: 진행률을 나타내는 값으로, 0.0에서 1.0 사이의 값을 설정합니다. 설정하지 않으면 비정량적 모드로 동작합니다.</li>
</ul>
</blockquote>
<ul>
<li>backgroundColor: 진행되지 않은 부분의 색상을 지정할 수 있습니다.</li>
<li>valueColor: 진행된 부분의 색상을 지정할 수 있습니다.</li>
<li>strokeWidth: 원형 선의 두께를 지정할 수 있습니다.</li>
</ul>
<img src="https://velog.velcdn.com/images/tobie_/post/6ebcb699-ed87-4450-9027-f660c6a3e4d7/image.gif" width="50%" height="50%">






<h3 id="linearprogressindicator-예제">LinearProgressIndicator 예제</h3>
<pre><code>  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: const Center(
        child: LinearProgressIndicator(
          value: 0.7, // 70% 진행률
          color: Colors.red,
          backgroundColor: Colors.blue,
        ),
      ),
    );
  }</code></pre><img src="https://velog.velcdn.com/images/tobie_/post/62e04f1e-b886-4b3a-84d2-0ed123455edc/image.png" width="50%" height="50%">

<blockquote>
<ul>
<li>value: 진행률을 나타내는 값으로, 0.0은 0%를, 1.0은 100%를 의미합니다. 설정하지 않으면 비정량적 모드로 동작합니다.</li>
</ul>
</blockquote>
<ul>
<li>backgroundColor: 진행되지 않은 부분의 색상을 지정할 수 있습니다.</li>
<li>valueColor: 진행된 부분의 색상을 지정할 수 있습니다.</li>
</ul>
<h3 id="응용">응용</h3>
<pre><code>if (isLoading) {
      return Platform.isAndroid
          ? Center(
              child: SizedBox(
                  height: indicatorSize,
                  width: indicatorSize,
                  child: CircularProgressIndicator(
                    strokeWidth: 3,
                    color: AppColors.primaryColor,
                  )))
          :  Center(
              child: SizedBox(
                  height: indicatorSize,
                  width: indicatorSize,
                  child: const CupertinoActivityIndicator(radius: 15)),
            );
    } else if (isError) {</code></pre><blockquote>
<p>isLoading이 true일 때, Android에서는 CircularProgressIndicator를, iOS에서는 CupertinoActivityIndicator를 중앙에 표시합니다. Android와 iOS에서 각기 다른 스타일의 로딩 인디케이터를 보여주는 것입니다.</p>
</blockquote>
<h3 id="정리하며">정리하며</h3>
<ul>
<li>파일 업로드/다운로드
사용자가 파일을 업로드하거나 다운로드할 때 진행 상황을 시각적으로 표시해줍니다.</li>
<li>데이터 로딩
서버에서 데이터를 가져오는 동안 로딩 상태를 표시하여 사용자가 기다리고 있음을 인식시킵니다.</li>
<li>복잡한 계산 작업
계산 작업이 오래 걸릴 경우, 작업이 진행 중임을 알려주기 위해 사용됩니다.</li>
<li>사용자 피드백 제공
비정량적 Progress Indicator를 사용하여 사용자가 어떤 작업이 백그라운드에서 처리 중임을 알 수 있게 해줍니다.</li>
</ul>
<blockquote>
<p>개인적으로 프로젝트 만들떄 주로 사용하는 애니메이션 로딩 패키지입니다.  </p>
</blockquote>
<ul>
<li><a href="https://pub.dev/packages/flutter_spinkit">flutter_spinkit</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>