<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Flutter 일기</title>
        <link>https://velog.io/</link>
        <description>안 되면 되게 하라</description>
        <lastBuildDate>Fri, 06 Jan 2023 03:31:34 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Flutter 일기</title>
            <url>https://images.velog.io/images/sharveka_11/profile/4f92d265-b99e-4b36-8044-269351de71c5/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Flutter 일기. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sharveka_11" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Flutter #58 - package : another_flushbar]]></title>
            <link>https://velog.io/@sharveka_11/package-anotherflushbar</link>
            <guid>https://velog.io/@sharveka_11/package-anotherflushbar</guid>
            <pubDate>Fri, 06 Jan 2023 03:31:34 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기 58번째
참고 : <a href="https://pub.dev/packages/another_flushbar/example">https://pub.dev/packages/another_flushbar/example</a>
<br><br>
Firebase Auth로 이메일 로그인을 구현하면, 이메일 형식이 잘못되었거나 비밀번호 자릿수가 모자라면 알아서 에러 사항을 보내준다. <a href="https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseAuthException">FirebaseAuthException</a>에서 보내주는데, 사용자에게 에러 사항을 띄워주도록 <strong>화면 윗부분에 알림창</strong> 같은 걸 만들고 싶었다. </p>
<p>40번 일기에 썼던 SnackBar를 쓰려니 이건 화면 아래에서만 올라오고 위에서는 안되더라... 그래서 다른 걸 찾아봤더니 flushbar 라는 게 있었다.
<br><br><br><br><br><br></p>
<h1 id="another_flushbar">another_flushbar</h1>
<p>이건 내장 위젯이 아니라 package라서, 우선 yaml 파일에 추가를 해주자. Firebase Auth 와 같이 테스트할거니까, Firebase package도 추가해준다.</p>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/653b770e-04fa-4c07-97c2-c994a28a7e11/image.png" alt=""></p>
<p>오늘은 코드가 좀 길다. 하지만 중요한 부분은 FlushBar 부분! <strong>flushbar 사용 부분은 맨 아래의 ElevatedButton의 onPressed 이다.</strong></p>
<pre><code class="language-dart">import &#39;package:another_flushbar/flushbar.dart&#39;;
import &#39;package:firebase_auth/firebase_auth.dart&#39;;
import &#39;package:firebase_core/firebase_core.dart&#39;;
import &#39;package:flutter/material.dart&#39;;
import &#39;firebase_options.dart&#39;;

Future&lt;void&gt; main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const Root());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: FlushBarApp(),
      ),
    );
  }
}

class FlushBarApp extends StatefulWidget {
  const FlushBarApp({super.key});

  @override
  State&lt;FlushBarApp&gt; createState() =&gt; _FlushBarAppState();
}

class _FlushBarAppState extends State&lt;FlushBarApp&gt; {
  @override
  Widget build(BuildContext context) {
    final auth = FirebaseAuth.instance;
    String email = &#39;&#39;;
    String password = &#39;&#39;;
    const inputdeco = InputDecoration(
      hintText: &#39;Enter your email&#39;,
      contentPadding: EdgeInsets.symmetric(horizontal: 16.0),
    );
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        TextField(
          decoration: inputdeco,
          onChanged: (value) {
            email = value;
          },
        ),
        TextField(
          decoration: inputdeco.copyWith(hintText: &#39;Enter your password&#39;),
          obscureText: true,
          onChanged: (value) {
            password = value;
          },
        ),
        ElevatedButton(
          onPressed: () async {
            try {
              await auth.createUserWithEmailAndPassword(
                  email: email, password: password);
            } on FirebaseAuthException catch (err) {
              Flushbar(
                title: &quot;Error&quot;,
                message: err.message.toString(),
                flushbarPosition: FlushbarPosition.TOP,    // TOP, BOTTOM 2가지 있다.
                duration: const Duration(seconds: 3),    // flushbar 화면에 머무는 시간
                icon: Icon(
                  Icons.info_outline,
                  size: 28.0,
                  color: Colors.blue[300],
                ),    //맨 왼쪽에 뜨는 아이콘
                backgroundColor: Colors.black,        //flushbar 배경 색
              ).show(context);    // 보여줘
            }
          },
          child: const Text(&#39;Login&#39;),
        ),
      ],
    );
  }
}</code></pre>
<p>우선 아래와 같이 써주면 Firebase가 보내주는 에러 메시지에 바로 접근할 수 있다.</p>
<pre><code class="language-dart">on FirebaseAuthException catch(err){
    ...
    message : err.message &lt;-  exception을 따로 설정하지 않으면 .message라는 속성은 없다.
}</code></pre>
<p><br><br><br>
실행 화면을 바로 보자.</p>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/4ab276dc-ed29-405e-aea5-8204682ef22e/image.gif" alt=""></p>
<p><br><br><br><br><br><br><br></p>
<h3 id="titletext-messagetext">titleText, messageText</h3>
<p>title, message 글자 크기가 너무 작게 느껴진다면, titleText, messageText에 Text위젯을 넣어서 style 을 따로 설정하면 된다. 이 때 title, message 속성을 그대로 두면 titleText, messageText 속성이 우선하게 된다.</p>
<pre><code class="language-dart">titleText: Text(
  &#39;Error&#39;,
  style: TextStyle(fontSize: 20, color: Colors.white),
),
messageText: Text(
  err.message.toString(),
  style: TextStyle(fontSize: 16, color: Colors.white),
),</code></pre>
<p>title, message 에 글자를 바로 설정하면 아래처럼 되는데,
<img src="https://velog.velcdn.com/images/sharveka_11/post/a728dc69-5539-4b32-9522-dd3e5ec0f484/image.png" alt="">
titleText, messageText를 통해 Text 위젯에서 style 속성을 설정한 것은 이렇게 달라지게 된다.<img src="https://velog.velcdn.com/images/sharveka_11/post/884f712f-8406-44bd-b4aa-c5aa674c5364/image.png" alt="">
<br><br><br><br><br><br><br></p>
<h3 id="flushbarstyle---floating-grounded">flushbarStyle - FLOATING, GROUNDED</h3>
<p>flushbarStyle 은 알림창이 화면 끝부분에 붙어있는 것처럼 나오느냐, 둥둥 떠서 나오느냐 를 설정한다. 기본값은 FlushbarStyle.FLOATING이다.</p>
<p>FLOATING
<img src="https://velog.velcdn.com/images/sharveka_11/post/e0d63379-31c8-482f-82af-30dd1a4f257f/image.png" alt=""></p>
<p>GROUNDED
<img src="https://velog.velcdn.com/images/sharveka_11/post/366fc40b-6a32-4de7-8ff8-687e44dfa106/image.png" alt="">
<br><br><br>
이외에도 속성이 여러가지인데, 오늘은 꼭 알아야 될 것만 우선 알아보았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter #57 - This app is using a deprecated version of the Android embedding.]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-57-This-app-is-using-a-deprecated-version-of-the-Android-embedding</link>
            <guid>https://velog.io/@sharveka_11/Flutter-57-This-app-is-using-a-deprecated-version-of-the-Android-embedding</guid>
            <pubDate>Thu, 29 Dec 2022 16:31:40 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기 57번째
참고 : <a href="https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects">https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects</a>
<a href="https://syrang.tistory.com/17">https://syrang.tistory.com/17</a>
<a href="https://github.com/flutter/flutter/issues/28787">https://github.com/flutter/flutter/issues/28787</a>
주의! 이 일기는 문제 해결에 초점을 맞춘 일기입니다. 설명은 할 수 없어요...
<br><br><br><br><br><br><br><br></p>
<h1 id="1-deprecated-version-of-the-android-embedding">1. Deprecated version of the Android embedding.</h1>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/e65984c7-f2dc-4cc3-a703-0ba326a7c456/image.png" alt=""></p>
<p>Udemy에서 Flutter 기초 강의를 듣고 있는데, 2019년 강의라 버전 문제가 빈번히 발생한다.
특히 Android emulator에서 실행이 안되는데, 반대로 iOS simulator 실행은 잘 됨.
Take a look at the docs for migrating an app : <a href="https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects">https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects</a>
여기를 보라고 하니 한번 가보자.</p>
<p><br><br></p>
<blockquote>
<p><strong>1. If you don&#39;t have any of your own added code to _android/app/src/main/java/[your/package/name]/MainActivity.java _- remove the body of your MainActivity.java and change the FlutterActivity import. The new FlutterActivity no longer requires manually registering your plugins. It will now perform the registration automatically when the underlaying FlutterEngine is created.</strong></p>
</blockquote>
<p>-&gt; MainActivity.java 파일에 따로 추가한 코드가 없다면, body부분 지워버리고 import 수정하라.</p>
<p>android/app/src/main/java/[your/package/name]/MainActivity.java 파일을 찾아서</p>
<pre><code class="language-java">package [my_project_name];    // 각자 만든 프로젝트 이름으로 설정됨.

import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
  }
}</code></pre>
<p>위 코드를 아래와 같이 변경해준다.</p>
<pre><code class="language-java">package [my_project_name];    // 이건 그대로 두면 됨.

import io.flutter.embedding.android.FlutterActivity;

public class MainActivity extends FlutterActivity {

}</code></pre>
<p>이런 파일은 꼭꼭 숨어있는데, VS Code에서는 Comman+P(iOS) , Control+P(Windows) 를 누르고 MainActivity.java를 치면 바로 찾을 수 있다. Android Studio를 쓴다면 창 오른쪽 위에 Search 아이콘을 누르고 찾으면 된다.
<br><br><br><br><br><br><br></p>
<blockquote>
<p><strong>2. <em>Open android/app/src/main/AndroidManifest.xml</em>. Replace the reference to FlutterApplication in the application tag with ${applicationName}. Add a new meta-data tag under application.</strong></p>
</blockquote>
<p>-&gt; android:name 부분을 변경, meta-data tag를 application tag내부에 새로 삽입</p>
<p>android/app/src/main/AndroidManifest.xml 파일에 가서 </p>
<pre><code class="language-xml">&lt;application
    android:name=&quot;io.flutter.app.FlutterApplication&quot; -&gt; &quot;${applicationName}&quot;
 요렇게 수정하고

&lt;meta-data
    android:name=&quot;flutterEmbedding&quot;
    android:value=&quot;2&quot; /&gt;
application 태그 내부에 meta-data 태그 추가
</code></pre>
<p>결론적으로, </p>
<pre><code class="language-xml">&lt;application
        android:name=&quot;io.flutter.app.FlutterApplication&quot;
             ...
        &gt;
        &lt;activity ...
            &gt;
            &lt;meta-data
                android:name=&quot;io.flutter.app.android.SplashScreenUntilFirstFrame&quot;
                android:value=&quot;true&quot; /&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot;/&gt;
                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot;/&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;</code></pre>
<p>위 코드에서 아래와 같이 변경</p>
<pre><code class="language-xml">    &lt;application
        android:name=&quot;${applicationName}&quot;    
        &gt; &lt;!-- 요 윗줄 수정 --&gt;
        &lt;activity ...
            &gt;
            &lt;meta-data
                android:name=&quot;io.flutter.app.android.SplashScreenUntilFirstFrame&quot;
                android:value=&quot;true&quot; /&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot;/&gt;
                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot;/&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
      &lt;!-- 아래의 meta-data 구문 추가 --&gt;
        &lt;meta-data
            android:name=&quot;flutterEmbedding&quot;
            android:value=&quot;2&quot; /&gt;
    &lt;/application&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/b56dbb70-d409-4397-bb32-70ccfb9ad614/image.png" alt=""></p>
<p>에러가 싹 사라졌다 ^^</p>
<p><br><br><br><br><br><br><br><br><br><br><br></p>
<h1 id="2-android-studio---emulator-실행">2. Android Studio - Emulator 실행</h1>
<p>혹시나 해서 Android Studio 열고 emulator 상에서 실행하니 또 안됨.</p>
<h3 id="1-찾아보니-android-gradle-플러그인-버전이-문제라고-하네">1. 찾아보니 Android Gradle 플러그인 버전이 문제라고 하네</h3>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/9a2bb107-8da1-4f6f-b9bc-14757cd2a603/image.png" alt=""></p>
<p><a href="https://syrang.tistory.com/17">https://syrang.tistory.com/17</a></p>
<pre><code class="language-dart">buildscript {
    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath &#39;com.android.tools.build:gradle:4.2.0&#39;
    }
}
</code></pre>
<p>android/build.gradle에 가서 3.7.1 이던 버전을 4.2.0 으로 수정
<br><br><br><br><br><br><br></p>
<h3 id="2-gradle-버전을-수정해-달라고-했다">2. Gradle 버전을 수정해 달라고 했다.</h3>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/0f3b5a04-20f6-4fe8-9d34-e50733699c9a/image.png" alt="">
gradle-wrapper.properties 파일에 가서 </p>
<pre><code class="language-java">distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
-&gt; 변경 후
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip</code></pre>
<p><br><br><br><br><br><br></p>
<h3 id="3-또-에러-compilesdkversion을-수정해달라고-한다">3. 또 에러. compileSdkVersion을 수정해달라고 한다.</h3>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/168fc28d-4510-48f9-b5e5-e70fcaace36c/image.png" alt="">
app 수준 build.gradle 파일에 가서</p>
<pre><code class="language-java">android {
    compileSdkVersion 29-&gt;31 변경</code></pre>
<p><br><br><br><br><br></p>
<h3 id="4-또또-에러-뭔지-알지도-못하겠다">4. 또또 에러. 뭔지 알지도 못하겠다.</h3>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/7e01c73a-d93a-4dec-ba63-2424d9d91286/image.png" alt=""></p>
<p>Warning: 예상치 않은 요소... 이 줄을 복사해서 구글에 뒤졌는데, StackOverFlow의 한 답변에서 Android SDK를 말끔히 삭제하고 다시 설치하라고 하더라. </p>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/75340bf1-90e7-4995-8353-bab95e11b90a/image.png" alt="">
SDK Manager에 들어가서 우선
<img src="https://velog.velcdn.com/images/sharveka_11/post/9116a3e2-0c43-47a6-8ae8-bf29def8a141/image.png" alt="">
혹시 모르니 Build Tools 업데이트 가능한 것들은 싹 업데이트를 해주었다.</p>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/26b4903d-8fb5-4d16-925c-5edf3c35f6fc/image.png" alt="">
OK</p>
<p>그리고 SDK Platforms에서 원래 쓰던 32 버전을 uninstall 한 후 다시 설치
체크 상자를 누르다보면 X 표시가 뜨는데, Apply하면 uninstall 됨.
<img src="https://velog.velcdn.com/images/sharveka_11/post/91cf7d2c-7a3c-469d-b063-e1efe6f86db4/image.png" alt=""></p>
<p>그래도 안됨
<br><br><br><br><br><br><br></p>
<h3 id="5-살려주세요">5. 살려주세요...</h3>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/9d13f7e1-0bfe-4d20-bce7-747b92b45720/image.png" alt=""></p>
<p>A failure occured while executing.... 이 줄을 복사해서 검색했더니 못 알아들을 결과만 나오길래, 
Failed to generate v1 signature 로 다시 검색.</p>
<p><a href="https://github.com/flutter/flutter/issues/28787">https://github.com/flutter/flutter/issues/28787</a> 여기를 보고
~/.android/debug.keystore 까지 지움</p>
<p>또 안됨
저장 용량이 부족하대나 뭐래나
Emulator 하나 더 만들어서 실행
<br><br><br><br><br><br></p>
<h3 id="6-엉엉-성공">6. 엉엉 성공</h3>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/7071aa6a-99cc-4fd3-96fe-7e8693f1e4e3/image.png" alt=""></p>
<p><br><br>
코드 수정하면서 일기 쓰다가 실행이 도저히 안되서 중간에 한번 싹 날려먹고 처음부터 다시 했다.
앞으로는 그냥 새 프로젝트를 만들어서 코드를 잘 복사해보면 어떨까 싶다....</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter #56 - Slider]]></title>
            <link>https://velog.io/@sharveka_11/Slider</link>
            <guid>https://velog.io/@sharveka_11/Slider</guid>
            <pubDate>Sat, 24 Dec 2022 07:00:08 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기 56번째
참고 : <a href="https://api.flutter.dev/flutter/material/Slider-class.html">https://api.flutter.dev/flutter/material/Slider-class.html</a>
<a href="https://api.flutter.dev/flutter/material/RangeSlider-class.html">https://api.flutter.dev/flutter/material/RangeSlider-class.html</a>
<br><br><br><br><br><br></p>
<h1 id="slider">Slider</h1>
<p>특정 범위 내에서 값을 선택할 때 사용하는 위젯으로, 굉장히 자주 사용된다. 기본적인 형태는 아래와 같이 생겼다.
<img src="https://velog.velcdn.com/images/sharveka_11/post/9320cbac-4b3c-48e6-8b58-bb481f070be0/image.png" alt=""></p>
<pre><code class="language-dart">  const Slider({
    super.key,
    required this.value,
    required this.onChanged,
    this.onChangeStart,
    this.onChangeEnd,
    this.min = 0.0,
    this.max = 1.0,
    this.divisions,
    this.label,
    this.activeColor,
    this.inactiveColor,
    this.thumbColor,
    this.mouseCursor,
    this.semanticFormatterCallback,
    this.focusNode,
    this.autofocus = false,
  })</code></pre>
<p>생성자는 이렇게 생겼는데 <strong>value, onChanged</strong> property는 꼭 넣어줘야한다.
Slider위젯의 상태가 변경될때마다 onChanged property에 설정된 함수를 호출하고, 변경된 value에 맞게 위젯을 다시 빌드하게 된다. 그래서 애초에 Slider위젯은 StatefulWidget을 상속받는 형태이다.
<br><br><br><br><br><br></p>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<p>flutter.dev에서 가장 간단한 예시를 가져다가 실행해보았다.</p>
<pre><code class="language-dart">class SliderExample extends StatefulWidget {
  const SliderExample({super.key});

  @override
  State&lt;SliderExample&gt; createState() =&gt; _SliderExampleState();
}

class _SliderExampleState extends State&lt;SliderExample&gt; {
  double _currentSliderValue = 20;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text(&#39;Slider&#39;)),
      body: Slider(
        value: _currentSliderValue,
        max: 50,
        label: _currentSliderValue.toString(),
        onChanged: (double value) {
          setState(() {
            _currentSliderValue = value;
            print(_currentSliderValue);
          });
        },
      ),
    );
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/85abd6d9-6f88-4ed1-ba02-60d3834676ae/image.gif" alt=""></p>
<p>onChanged에 설정한 setState 함수 내부에서, _currentSliderValue를 계속 변경해주고 있는 것을 알 수 있다.
min 기본값이 0이기 때문에, min 값을 위젯에 따로 설정해주지 않아도 0으로 되어있다. 
label 이라는 property도 있는데 화면에서는 나타나지 않았다. 왜일까?
<br><br><br><br></p>
<h2 id="division-label-property">division, label property</h2>
<p>아래의 두 줄을 Slider위젯 안에 넣어보자.</p>
<pre><code class="language-dart">divisions: 5,
label: _currentSliderValue.round().toString(),</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/4ead201d-85f4-45ee-84d4-43f431638060/image.gif" alt="">
<strong>divisions</strong> 인자를 넣어서 구간을 나눠주면 잘 나타나는 것을 알 수 있는데, <strong>SliderThemeData</strong> 설정에 가보면</p>
<pre><code class="language-dart">return SliderThemeData(
    ...
    showValueIndicator: ShowValueIndicator.onlyForDiscrete,
);</code></pre>
<p>Discrete, 불연속적인 값에 대해서만 valueIndicator가 나타나도록 설정되어 있다. <strong>ShowValueIndicator.always</strong> 또는 <strong>ShowValueIndicator.onlyForContinuous</strong>로 바꿔주면 연속적인 범위 내에서도 잘 동작한다.</p>
<pre><code class="language-dart">body: SliderTheme(
        data: const SliderThemeData(showValueIndicator: ShowValueIndicator.always),
        child: Slider(
          value: _currentSliderValue,
          max: 50,
          // divisions: 5,
          label: _currentSliderValue.round().toString(),
          onChanged: (double value) {
            setState(() {
              _currentSliderValue = value;
            });
          },
        ),
      ),</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/dc3bdf3e-56e0-468a-ace4-01725bf8f957/image.gif" alt=""></p>
<p><br><br><br><br><br><br><br></p>
<h1 id="slidertheme">SliderTheme</h1>
<p>Slider위젯의 property를 다시 살펴보면, 이런 걸 갖고 있다.</p>
<pre><code class="language-dart">Slider(
    activeColor: Colors.blue,
    inactiveColor: Colors.green,
    thumbColor: Colors.red,
)</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/5793e1d0-fea6-4502-8ad2-130808b4d413/image.png" alt="">
저 동그라미인 thumb를 뭐라고 해석해야 되나...? 아무튼 저렇게 색상을 바꿀 수 있는데, Slider위젯 내부에서 설정하려면 속성이 한정적이다. Slider위젯을 좀 더 예쁘게 꾸미려면, <strong>SliderThemeData</strong> 라는 것이 필요하다. ThemeData.sliderTheme에서 설정하거나, SliderTheme위젯으로 Slider위젯 전체를 감싸주면 된다. </p>
<pre><code class="language-dart">return MaterialApp(
    theme: ThemeData(
        sliderTheme: SliderThemeData(inactiveTrackColor: Colors.green), ...),
      home: const SliderExample(),
);</code></pre>
<p>이렇게 MaterialApp에서 설정해줘도 되는데, 나는 Slider위젯을 감싸는 형태로 적어보겠다. 어차피 SliderThemeData를 쓴다는 점은 똑같다.</p>
<p><br><br><br><br><br></p>
<h2 id="코드-예시로-알아보자-1">코드 예시로 알아보자</h2>
<pre><code class="language-dart">body: SliderTheme(
  data: SliderTheme.of(context).copyWith(
    activeTrackColor: Color(0xFF4CAF50),
    inactiveTrackColor: Color(0xFF757575),
    thumbColor: Color(0xFFCDDC39),
    thumbShape: RoundSliderThumbShape(enabledThumbRadius: 20.0),
    //
    overlayColor: Color(0xFFCDDC39).withOpacity(0.2),
    overlayShape: RoundSliderOverlayShape(overlayRadius: 40.0),
    showValueIndicator: ShowValueIndicator.onlyForContinuous,
    valueIndicatorShape: PaddleSliderValueIndicatorShape(),
    // valueIndicatorShape 의 기본값은 RectangularSliderValueIndicatorShape 인 듯
  ),
  child: Slider(
... 이전 코드와 같음
),</code></pre>
<p>copyWith 메소드는 객체가 원래 갖는 속성들, 그러니까 필드 설정값을 그대로 복사해다가 일부만 새로운 값으로 변경할 때 쓴다. 사실 저기서 </p>
<pre><code class="language-dart">  1. 이렇게 쓰던지
  data: SliderTheme.of(context).copyWith(
  2. 아님 이렇게 쓰던지
  data: SliderThemeData(</code></pre>
<p>실행화면 상으로는 차이가 없더라. 하지만 copyWith 이걸 많이들 사용하는 것 같다. </p>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/65cb197e-8d62-4475-a7e3-c9d5b8109bf6/image.gif" alt=""></p>
<p>overlay는 위처럼 thumb 주변으로 나타나는 좀 더 큰 원 부분이다. showValueIndicator는 위에서 division과 함께 설명했고, PaddleSliderValueIndicatorShape를 적용했더니 저렇게 원 모양으로 나타났다. 설정 안하면 네모 모양이다.</p>
<p><br><br><br><br><br><br><br>
Slider위젯은 사실상 값이 1개인 Slider인데, 아래 그림처럼 1개 값이 아닌 범위 값을 선택하는 Slider를 원한다면 <strong>RangeSlider</strong>를 쓰면 된다.
<img src="https://velog.velcdn.com/images/sharveka_11/post/cce2c58a-afbb-42f1-bb07-13ba5d4cec69/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter #55 - CircleAvatar]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-55-CircleAvatar</link>
            <guid>https://velog.io/@sharveka_11/Flutter-55-CircleAvatar</guid>
            <pubDate>Thu, 08 Dec 2022 09:26:48 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기 55번째 
참고 1 : <a href="https://api.flutter.dev/flutter/material/CircleAvatar-class.html">https://api.flutter.dev/flutter/material/CircleAvatar-class.html</a>
참고 2 : <a href="https://api.flutter.dev/flutter/painting/ImageProvider-class.html">https://api.flutter.dev/flutter/painting/ImageProvider-class.html</a></p>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/639cb877-ba8f-41e1-a600-4d122869fc48/image.png" alt=""></p>
<p>Google에서는 User Profile을 띄울 때, 설정한 사진이 없으면 이렇게 사용자 이름의 첫 글자를 대문자로 띄워준다. 한글 이름이 다 뜨는 경우도 물론 있긴 하더라. </p>
<p>아무튼 저런 동그란 위젯을 쓸 경우가 빈번히 발생하는데, 이럴 때 유용한 것이 <strong>CircleAvatar</strong>이다.
<br><br><br><br><br><br></p>
<h1 id="circleavatar">CircleAvatar</h1>
<p>CircleAvatar의 생성자는 이렇게 생겼다.</p>
<pre><code class="language-dart">  const CircleAvatar({
    super.key,
    this.child,
    this.backgroundColor,
    this.backgroundImage,
    this.foregroundImage,
    this.onBackgroundImageError,
    this.onForegroundImageError,
    this.foregroundColor,
    this.radius,
    this.minRadius,
    this.maxRadius,
  })</code></pre>
<p>그 동안 여기저기 보고 다닌 결과 <strong>child, backgroundColor, backgroundImage, radius</strong> 이 네가지 속성을 많이 쓰는 것 같았다. </p>
<p>지금까지 Flutter위젯은 주로 child에 띄울 것을 설정하기에, 여기서도 그런줄 알았더니 아니었다.</p>
<pre><code class="language-dart">final Widget? child;

Typically a [Text] widget. 
If the [CircleAvatar] is to have an image,
use [backgroundImage] instead.</code></pre>
<p>child에는 보통 Text 위젯을 쓰고, CircleAvatar가 이미지를 가져야 한다면 backgroundImage를 대신 쓰라고 한다.</p>
<p>우선 child, radius, backgroundColor 속성까지 설정해서 위젯을 하나 그려보았다.</p>
<pre><code class="language-dart">CircleAvatar(
   radius: 50,    // CircleAvatar의 크기 결정
   child: Text(
      &#39;User&#39;,
      style: TextStyle(fontSize: 30),
   ),
   backgroundColor: Colors.black,
)</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/879671db-3c29-4889-8bd4-c851eca65e27/image.png" alt=""></p>
<p>이제 backgroundImage 도 설정해보자. 여기서 한참 헤맸다.
<br><br><br><br><br><br><br></p>
<h1 id="backgroundimage---imageprovider">backgroundImage - ImageProvider</h1>
<pre><code class="language-dart">{ImageProvider&lt;Object&gt;? backgroundImage}</code></pre>
<p>backgroundImage 속성은 ImageProvider 타입의 데이터를 받는다. 그런데 <strong>ImageProvider 는 Abstract class이기 때문에, 이를 구현한 다른 클래스의 객체를 넣어주어야 한다.</strong> </p>
<p><strong>Abstract class(추상 클래스)는 이 자체의 인스턴스를 생성할 수 없다는 특징이 있다.</strong> </p>
<pre><code class="language-dart"> CircleAvatar(
     backgroundImage: ImageProvider(),
),</code></pre>
<p>CircleAvatar의 backgroundImage속성에 ImageProvider타입 데이터가 들어간다고 해서, ImageProvider를 적어보면 
<img src="https://velog.velcdn.com/images/sharveka_11/post/eafd0350-30f4-4bf8-94ab-11f8f794045b/image.png" alt="">&#39;<strong>추상 클래스는 인스턴스화 안 된다</strong>&#39;고 다른 거 넣어달라고 한다.
<br><br><br>
<strong>ImageProvider 클래스를 상속받아 구현한 자식 클래스의 객체를 사용해야 한다. FileImage(File), MemoryImage(Uint8List), NetworkImage(Url), AssetImage(Asset) 등의 클래스가 ImageProvider를 구현한 자식 클래스이다.</strong> 나는 이번에 Asset에 User Image를 jpg 파일로 하나 넣어두었기 때문에, AssetImage를 backgroundImage에 넣어보도록 하겠다.</p>
<pre><code class="language-dart">CircleAvatar(
  radius: 50,
  backgroundImage: AssetImage(&#39;images/user_image.jpg&#39;),
),</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/f9a4f1f8-67f5-4213-ada8-067061dabd4d/image.png" alt="">
backgroundColor속성을 적용해도 backgroundImage가 있으면 나타나지 않는다. 다만 child에 Text위젯을 설정했다면, 아래처럼 이미지와 겹쳐서 뜨게 된다.
<img src="https://velog.velcdn.com/images/sharveka_11/post/f6ac2f62-4aeb-4210-8e0f-ec53ce35a523/image.png" alt=""></p>
<p><br><br><br><br><br><br><br></p>
<h1 id="abstract-class">Abstract class</h1>
<p>Implementation(구현)은 예전에 C++과 자바를 배울 때 접한 적이 있는데, Abstract class나 Interface에 적용되는 개념이다. 
<a href="https://aileen93.tistory.com/107">Ailyn님의 블로그</a>에 차이점이 깔끔하게 설명되어 있었다. 내가 나중에 까먹으면 가서 봐야지.</p>
<p>추상 클래스의 경우 다른 클래스에서 이를 상속받아서 구현해야, 추상 클래스의 요소들을 사용할 수 있다. 추상 클래스 내부의 (abstract 키워드가 붙지 않은) 일반 메소드는 상속 시 바로 사용할 수 있는데, <strong>abstract로 선언된 method는 반드시 자식 클래스에서 구현해야한다. 이걸 overriding이라고 함!</strong> <a href="https://programmingnote.tistory.com/29">참고 - Overloading, Overriding</a></p>
<p>클래스 생성 후 인스턴스도 못 만들면 뭔 소용이 있을까? 
만약 클래스를 여러 개 만들어야 하는데, 뭔가 비슷한 함수를 구현해야 할 일이 있다면? -&gt; abstract method의 경우 함수 인자와 반환값, 함수명 선언만 하고 본문은 없는 형태로 만들어놓을 수 있으니, 이 메소드를 상속받아 각각의 클래스에서 구현할 수 있다.</p>
<p><strong>상속 관계에 있는 클래스의 경우, 자식 클래스의 인스턴스(객체)는 부모 클래스 자료형인 것처럼 사용할 수 있다. 즉, NetworkImage나 AssetImage를 ImageProvider타입의 데이터가 필요한 곳에 넣어서 쓸 수 있다는 말이다.</strong> </p>
<p>다만 반대는 성립하지 않는다. 부모 클래스 객체를 자식 클래스 자료형으로는 못 쓴다. ImageProvider클래스가 추상 클래스가 아니더라도, ImageProvider의 인스턴스를 NetworkImage, AssetImage자료형으로는 사용할 수 없다. <a href="https://wikidocs.net/280">점프 투 자바 : IS-A관계</a> 여기에 잘 나와 있다.</p>
<p><br><br><br><br><br></p>
<h2 id="-번외---상속-클래스-간의-메소드-살펴보기">* 번외 - 상속 클래스 간의 메소드 살펴보기</h2>
<p>재미로 NetworkImage 클래스를 한번 살펴보자. 자세히는 말고 간단히만...
<a href="https://api.flutter.dev/flutter/painting/NetworkImage-class.html">https://api.flutter.dev/flutter/painting/NetworkImage-class.html</a> 여기에 가보면
<img src="https://velog.velcdn.com/images/sharveka_11/post/4e9364f4-689b-428b-bef0-df63fb46e07e/image.png" alt=""></p>
<p>요런 상속관계를 갖는다고 한다. </p>
<p><br><br>우선 Object class설명 페이지에 가보면
<code>The base class for all Dart objects except null.</code> 
이렇게 적혀 있다. </p>
<p>Object 클래스는 아래와 같이 2가지의 메소드를 갖고 있다.</p>
<pre><code class="language-dart">noSuchMethod(Invocation invocation) → dynamic
Invoked when a non-existent method or property is accessed.

toString() → String
A string representation of this object.</code></pre>
<p><br><br>
그 다음으로 ImageProvider는 어떤 메소드를 갖고 있는지 보자.</p>
<pre><code class="language-dart">createStream(ImageConfiguration configuration) → ImageStream
Called by resolve to create the ImageStream it returns.
@protected

evict({ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty}) → Future&lt;bool&gt;
Evicts an entry from the image cache.

load(T key, DecoderCallback decode) → ImageStreamCompleter
Converts a key into an ImageStreamCompleter, and begins fetching the image.
@Deprecated(&#39;Implement loadBuffer for faster image loading. &#39; &#39;This feature was deprecated after v2.13.0-1.0.pre.&#39;), @protected

loadBuffer(T key, DecoderBufferCallback decode) → ImageStreamCompleter
Converts a key into an ImageStreamCompleter, and begins fetching the image.
@protected

noSuchMethod(Invocation invocation) → dynamic
Invoked when a non-existent method or property is accessed.
inherited

obtainCacheStatus({required ImageConfiguration configuration, ImageErrorListener? handleError}) → Future&lt;ImageCacheStatus?&gt;
Returns the cache location for the key that this ImageProvider creates.

obtainKey(ImageConfiguration configuration) → Future&lt;T&gt;
Converts an ImageProvider&#39;s settings plus an ImageConfiguration to a key that describes the precise image to load.

resolve(ImageConfiguration configuration) → ImageStream
Resolves this image provider using the given configuration, returning an ImageStream.
@nonVirtual

resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) → void
Called by resolve with the key returned by obtainKey.
@protected

toString() → String
A string representation of this object.
override</code></pre>
<p>noSuchMethod 와 toString 메소드 아래에는 override라는 키워드가 적혀 있다. Object클래스의 메소드를 상속받아 함수 본문을 변경한 것이다.
<br><br>
이제 NetworkImage 클래스의 메소드를 한번 보자.</p>
<pre><code class="language-dart">createStream(ImageConfiguration configuration) → ImageStream
Called by resolve to create the ImageStream it returns.
@protected, inherited

evict({ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty}) → Future&lt;bool&gt;
Evicts an entry from the image cache.
inherited

load(NetworkImage key, DecoderCallback decode) → ImageStreamCompleter
Converts a key into an ImageStreamCompleter, and begins fetching the image.
override

loadBuffer(NetworkImage key, DecoderBufferCallback decode) → ImageStreamCompleter
Converts a key into an ImageStreamCompleter, and begins fetching the image.
override

noSuchMethod(Invocation invocation) → dynamic
Invoked when a non-existent method or property is accessed.
inherited

obtainCacheStatus({required ImageConfiguration configuration, ImageErrorListener? handleError}) → Future&lt;ImageCacheStatus?&gt;
Returns the cache location for the key that this ImageProvider creates.
inherited

obtainKey(ImageConfiguration configuration) → Future&lt;NetworkImage&gt;
Converts an ImageProvider&#39;s settings plus an ImageConfiguration to a key that describes the precise image to load.
inherited

resolve(ImageConfiguration configuration) → ImageStream
Resolves this image provider using the given configuration, returning an ImageStream.
@nonVirtual, inherited

resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, NetworkImage key, ImageErrorListener handleError) → void
Called by resolve with the key returned by obtainKey.
@protected, inherited

toString() → String
A string representation of this object.
inherited
</code></pre>
<p>뭔가 키워드가 많아졌다. override라고 표시된 메소드는 부모 클래스로부터 상속받아 재정의한 메소드이고, inherited 라고 표시된 메소드는 부모 클래스의 메소드를 그대로 받은 것이라 보면 된다. 
공식 문서에 들어가서, NetworkImage 메소드 중 inherited 키워드가 있는 메소드를 클릭하면 ImageProvider의 메소드로 연결이 된다.</p>
<p><br><br><br><br><br><br></p>
<h2 id="번외-2---networkimage">번외 2 - NetworkImage</h2>
<p>NetworkImage 메소드를 찾으면서 발견했는데, NetworkImage도 abstract class였다...! 근데 어떻게 생성이 된 걸까? </p>
<p>그래서 Stackoverflow에 처음으로 질문을 해봤다. 잘 안되는 영어 쓰기로 겨우겨우 질문을 했는데, 누군가 내 질문을 잘 알아듣고 거의 바로 답변을 해주었다^^ 신기해
<a href="https://stackoverflow.com/questions/74719232/can-i-create-an-instance-of-abstract-class">stackoverflow 질문 답변</a> 에 따르면, </p>
<pre><code class="language-dart">const factory NetworkImage(
    String url, 
    { double scale, Map&lt;String, String&gt;? headers 
}) = network_image.NetworkImage;</code></pre>
<p>실제로는 _network_image_io.dart의 factory생성자를 호출하는 형태라, 추상 클래스 생성자가 아니라고 했다. 그리고 해당 파일로 가보니</p>
<pre><code class="language-dart">/// The dart:io implementation of [image_provider.NetworkImage].
@immutable
class NetworkImage extends image_provider.ImageProvider&lt;image_provider.NetworkImage&gt; 
 implements image_provider.NetworkImage {
  /// Creates an object that fetches the image at the given URL.
  ///
  /// The arguments [url] and [scale] must not be null.
  const NetworkImage(this.url, { this.scale = 1.0, this.headers })
    : assert(url != null),
      assert(scale != null);
...
}</code></pre>
<p>정말 NetworkImage를 구현한 클래스가 또 있었다. </p>
<p><br><br><br><br><br><br>CircleAvatar 위젯 간단히 쓰고 끝날 줄 알았는데, 거의 최장의 일기가 된 거 같다. factory 생성자도 제대로 공부해야하게 생겼네...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter #54 - BoxFit]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-54-BoxFit</link>
            <guid>https://velog.io/@sharveka_11/Flutter-54-BoxFit</guid>
            <pubDate>Sun, 04 Dec 2022 12:33:00 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기 54번째
참고 : <a href="https://api.flutter.dev/flutter/painting/BoxFit.html">https://api.flutter.dev/flutter/painting/BoxFit.html</a>
<br><br><br><br><br>
난 Flutter를 마구잡이로 배웠기 때문에, 기초를 한 번 다져볼 겸 Udemy의 강의를 구매했다. 역시 기초부터 해준다. App Icon 설정하는 방법을 이번에 처음 알게 되었으니...</p>
<p>Image 위젯 띄우는 방법을 가르쳐줬으니 이번엔 이미지를 다른 걸로 알아서 바꿔보라고 한다. Image 위젯에 띄울 그림은 쉽게 바꿨는데, 왜인지 애가 너무 작다. </p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(&#39;I Am Plant&#39;),
          backgroundColor: Colors.blueGrey[900],
        ),
        body: const Center(
          child: Image(
            height: 300,
            image: AssetImage(&#39;images/icons8_plant.png&#39;),
          ),
        ),
      ),
    ),
  );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/3ca6a3be-399f-43fa-aff1-3968a6083eed/image.png" alt=""></p>
<p><del>머쓱</del></p>
<p>내가 다운받은 png 파일의 아이콘 크기는 24 x 24 라서, 화면에 띄우니 아주아주 작게 나왔다. 좀 더 화면에 꽉 차게 나왔으면 좋겠는데...</p>
<p><br><br><br><br><br></p>
<h1 id="boxfit">BoxFit</h1>
<p>Image 위젯의 속성 중에 fit이라는 것이 있다.
fit 에는 <strong>BoxFit</strong> 타입이 들어가는데, 나는 최대한 크게 나왔으면 좋겠어서 아래와 같이 속성값을 변경해주었다. </p>
<pre><code class="language-dart">fit : BoxFit.cover</code></pre>
<p>BoxFit은 위젯이 차지할 수 있는 범위 내에서 적용되기 때문에, width | height 값을 같이 설정해야 크기를 변경할 수 있다.
<strong>width, height만 설정한다고 해서 위젯이 크게 나오는 게 아니다.</strong></p>
<pre><code class="language-dart">child: Image(
   height: 300,
   image: AssetImage(&#39;images/icons8_plant.png&#39;),
   fit: BoxFit.cover,
 ),
</code></pre>
<table>
<thead>
<tr>
<th>fit 속성 적용 안함</th>
<th>fit 속성 적용</th>
</tr>
</thead>
<tbody><tr>
<td><img width="250px" alt="Boxfit" src="https://velog.velcdn.com/images/sharveka_11/post/7064b185-b210-4854-93ff-780e62ccfbf4/image.png"></td>
<td><img width="250px" alt="Boxfit" src="https://velog.velcdn.com/images/sharveka_11/post/a5a66950-1dda-4319-a00b-4d08dc199f0a/image.png"></td>
</tr>
</tbody></table>
<p><br><br>
이외에도 다양한 BoxFit 값을 설정할 수 있는데, 
<a href="https://api.flutter.dev/flutter/painting/BoxFit.html">https://api.flutter.dev/flutter/painting/BoxFit.html</a> 
여기에 방문하면 그림 예시도 그려져 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter #53 - Immutable]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-53-Immutable</link>
            <guid>https://velog.io/@sharveka_11/Flutter-53-Immutable</guid>
            <pubDate>Tue, 15 Nov 2022 14:29:04 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기 53번째
참고 : <a href="https://dart.academy/immutable-data-patterns-in-dart-and-flutter/">Dart Academy - Immutable Data Patterns in Dart and Flutter</a>
<br><br><br><br></p>
<p>Flutter 개발을 하다 보면 <strong>Immutable, Mutable</strong>이라는 용어가 자주 등장한다. 사전적인 의미는 아래와 같다.</p>
<pre><code>mutable : 변할 수 있는
immutable : 변경할 수 없는, 불변의</code></pre><p>변경이 안 된다! 라고만 하면 &#39;아 변수의 값을 못 바꾸는 건가&#39; 하고 생각했는데, Immutable이라는 속성은 단순히 그런게 아니었다. 
<br><br><br><br></p>
<h1 id="immutable-data">Immutable Data</h1>
<p><a href="https://dart.academy/immutable-data-patterns-in-dart-and-flutter/">Dart Academy - Immutable Data Patterns in Dart and Flutter</a>
여기의 글을 한번 읽어보았다.</p>
<p>우선 String, Numbers, Boolean 타입의 값은 생성되고 나면 변경이 안 된다고 한다.</p>
<pre><code class="language-dart"> var str = &#39;Hello My Name is...&#39;;
 str = &#39;Hello&#39;;
 print(str);    // Console 출력 : Hello

 var booltype = true;
 booltype = false;
 print(booltype);    // Console 출력 : false</code></pre>
<p>변수 값이 변하니까 겉으로는 변경이 잘 되는 것처럼 보인다.</p>
<p>하지만 Dart Academy의 설명에 따르면, String 변수는 문자열 자체를 담는 것이 아니라, <strong>String 데이터가 있는 위치의 주소를 담는 참조 변수</strong>가 되는 것이라고 한다.</p>
<br>
새로운 문자열('Hello')의 메모리 주소를 기존의 변수(str)에 할당하는 것이다. 변수에 문자열 자체를 할당하는 것이 아니다. boolean타입 변수의 경우도 마찬가지이다.

<p>즉, <strong>데이터를 Immutable로 관리한다</strong>는 뜻. 이미 메모리에 할당된 데이터 값은 변함이 없고, 데이터를 가리킬 주소값(변수가 담을)은 변경이 가능하다.
<br><br><br><br><br><br><br><br></p>
<h2 id="그럼-변하기-전의-데이터는-어떻게-되는가">그럼 변하기 전의 데이터는 어떻게 되는가?</h2>
<p>원래의 문자열 자체는 변하진 않지만, 더 이상 이 데이터를 참조할 수 있는 변수가 없다. 메모리에 있긴 한데 접근할 방법이 없는 데이터가 되는 것... 이 경우 Dart의 <strong>Garbage Collector</strong>가 알아서 메모리를 정리해준다.</p>
<p>데이터 자체가 아닌 주소값으로 관리하기 때문에, 데이터를 안전하게 관리할 수 있고, 예상치 못한 데이터 변경 시도를 막기 위해 데이터를 복사하는(<a href="https://johngrib.github.io/wiki/defensive-copy/">기계인간 John Grib - Defensive copy</a>)등의 대응책을 쓰지 않아도 된다는 장점이 있다. Immutable이 아닐 경우 데이터를 가져오는 과정에서 데이터 변경 시도가 있을 수 있기 때문!
<br><br><br><br><br><br><br><br><br><br></p>
<h1 id="immutable-variable---final--const">Immutable variable - Final &amp; Const</h1>
<p>대부분 데이터는 일단 Immutable이라는 걸 알았는데, 변수를 Immutable로 관리하려면 어떻게 할까? Final, Const를 이용해보자.</p>
<blockquote>
<h4 id="1-final-const-변수는-초기화를-한-이후-변경할-수-없다는-공통점이-있다">1. final, const 변수는 초기화를 한 이후 변경할 수 없다는 공통점이 있다.</h4>
</blockquote>
<pre><code class="language-dart">final String strFinal = &#39;Hello&#39;;
const int intConst = 1;
strFinal = &#39;hello world&#39;;      // error1
intConst = 2;                    // error2</code></pre>
<p>dartpad에서 위의 코드를 입력해보면, </p>
<pre><code class="language-dart">error1 : The final variable &#39;strFinal&#39; can only be set once.
error1 : Constant variables can&#39;t be assigned a value.</code></pre>
<p>요렇게 된다.
<br/><br/><br/><br/><br/><br/></p>
<blockquote>
<h4 id="2-final-변수는-런타임에-const-변수는-컴파일-타임에-상태가-정해진다는-차이점이-있다">2. final 변수는 런타임에, const 변수는 컴파일 타임에 상태가 정해진다는 차이점이 있다.</h4>
</blockquote>
<pre><code class="language-dart">final String strFinal;
const int intConst;            // error
strFinal = &#39;hello world&#39;;    // 가능!</code></pre>
<pre><code class="language-dart">The constant &#39;intConst&#39; must be initialized.
Try adding an intialization to the declaration.
</code></pre>
<p>final 변수는 선언( final String strFinal )과 동시에 초기화( strFinal = &#39;---&#39; )할 필요는 없고, 나중에 해줘도 된다. Flutter 코드를 작성할 때 final로 선언하고, 이후에 다른 코드에서 초기화를 하는 경우는 꽤 빈번하다.</p>
<p>하지만 const로 선언해 constant, 즉 상수가 되면 선언과 초기화를 반드시 힘께 해줘야 한다. final로 설정한 변수의 경우 런타임에 그 값을 알 수 있으면 되지만, <strong>Constant 값의 경우 컴파일 단계에서 값이 확정되어야 하기 때문에 선언과 동시에 초기화를 해주어야 한다.</strong>  </p>
<p>+) <a href="https://pc.net/helpcenter/answers/compile_time_vs_runtime">Compile time vs Run time</a> 
컴파일 타임 : 사람이 작성한 코드를 기계어로 번역 -&gt; 실행 가능한 프로그램이 되도록 만드는과정
런타임 : 컴파일이 된 프로그램, 즉 앱을 실행하는 과정.</p>
<p><br/><br/><br/>
다만 <a href="https://dart.dev/guides/language/language-tour#final-and-const">Dart.dev - guides</a>에서 final 객체는 변경이 안되지만 필드는 수정 가능하다고 한다. </p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/697b0b3f-2600-4a2a-b4f8-b19c776e8f68/image.png" alt=""></p>
</blockquote>
<p>이게 무슨 말인지... <a href="https://zoiworld.tistory.com/703">행복한 개발자님의 블로그</a>를 보고 알았다.</p>
<pre><code class="language-dart">final List&lt;int&gt; intList = [];
intList = [1];        // error
intList.add(1);        // 가능!</code></pre>
<p>위 코드를 Dart Pad에서 실행해보면, 두번째 줄에서만 에러가 난다.</p>
<pre><code class="language-dart">The final variable &#39;intList&#39; can only be set once.</code></pre>
<p>객체에 아예 다른 값을 설정하는 건 안되지만, 값을 수정하는 정도는 되는 모양이다. 하지만 const 로 선언하면 수정이고 뭐고 아무것도 안 됨!
<br/><br/><br/><br/><br/><br/></p>
<blockquote>
<h4 id="const-적용-안되는-대표적인-예시-2가지">const 적용 안되는 대표적인 예시 2가지</h4>
</blockquote>
<ol>
<li><p>DateTime.now()</p>
<pre><code class="language-dart">const DateTime nowTime = DateTime.now();    // error</code></pre>
<p>DateTime.now()의 경우 실행 시간에 그 값이 결정되기 때문에, const로 초기화할 수 없다. final은 가능!</p>
</li>
<li><p>Instance Variables (인스턴스 변수)
클래스가 있는 객체지향 프로그래밍에서, 클래스에 정의된 변수를 인스턴스 변수라고 한다.</p>
<pre><code class="language-dart">class Person{
 final String name;
 final int age;
}</code></pre>
<p>위 코드에서 변수 하나라도 const로 바뀌면 에러. 
const를 꼭 써야 한다면 앞에 static을 붙여 정적 변수로 만들고, 클래스 내부에서 미리 초기화를 해준다면 쓸 순 있다. 다만 해당 변수는 모든 객체에서 똑같은 값을 갖게 된다.</p>
<pre><code class="language-dart">static const int age = 10;</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter #52 - BuildContext]]></title>
            <link>https://velog.io/@sharveka_11/Flutter</link>
            <guid>https://velog.io/@sharveka_11/Flutter</guid>
            <pubDate>Fri, 11 Nov 2022 14:13:27 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기 52번째.
<br/><br/><br/><br/>
플러터 코드에서 BuildContext는 </p>
<pre><code class="language-dart">BuildContext context</code></pre>
<p>요런 형태로 자주 보인다. 그런데 BuildContext가 뭘까..? </p>
<pre><code class="language-dart">Navigator.of(context)
MediaQuery.of(context)</code></pre>
<p>Navigator 사용할 때 항상 써서 궁금했는데, 일기 쓰기를 오래 쉬는 바람에 쓰질 못했다. <a href="https://api.flutter.dev/flutter/widgets/BuildContext-class.html">flutter.dev</a>페이지와 <a href="https://www.youtube.com/watch?v=o-HpnWhI70U">유튜버 코딩셰프님의 강의</a>를 참고했다. 코딩셰프님 BuildContext강의는 2개!</p>
<p><br/><br/><br/><br><br></p>
<h1 id="buildcontext">BuildContext</h1>
<p>우선 flutter.dev의 설명에 따르면, <img src="https://velog.velcdn.com/images/sharveka_11/post/441c5905-6c0f-4b43-823c-924f1a58f63c/image.png" alt="">
<strong>위젯트리 내에서 위젯의 위치를 다루는 정보</strong> - 정도가 되겠다.</p>
<pre><code class="language-dart">print(context.widget); 
-&gt; context 있는 곳에서 해보면 위젯 이름이 뜬다.</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/3d0e3b7f-a9f8-4522-a459-d86300573914/image.png" alt="">위젯트리는 위 그림과 같은, 플러터 코드의 구조를 말한다. 플러터는 위젯을 층층이 쌓아 화면에 나타내는 구조이다.(이건 flutter create하면 기본으로 나오는 코드의 위젯 트리이다.)</p>
<p>플러터 아이콘이 왼쪽에 그려진 MyApp, MaterialApp...같은 아이들이 전부 위젯이다. 그리고 위젯트리를 보면 build함수로 위젯을 리턴하는 것을 알 수 있다. 옆에 보면 이런 것도 있죠. </p>
<pre><code>(BuildContext context...</code></pre><p>그럼 이제 위젯 빌드할 때 BuildContext가 쓰인단 건 알 수 있겠다. 공식 문서의 내용을 좀 더 읽어보자. 일부 문장을 떼어 왔다.
<br><br><br><br><br><br><br></p>
<h1 id="flutterdev의-설명을-읽어보자">Flutter.Dev의 설명을 읽어보자</h1>
<blockquote>
<h4 id="1-buildcontext-objects-are-passed-to-widgetbuilder-functions-such-as-statelesswidgetbuild">1. BuildContext objects are passed to WidgetBuilder functions (such as StatelessWidget.build).</h4>
</blockquote>
<p>-&gt; BuildContext객체는 WidgetBuilder 함수(StatelessWidget의 build 같은 거)로 전달된다. 
(아래 코드처럼 build함수는 인자로 BuildContext를 받는다.)</p>
<pre><code class="language-dart">@override
Widget build(BuildContext context) {
  return Scaffold(... </code></pre>
<p><br><br></p>
<blockquote>
<h4 id="2-each-widget-has-its-own-buildcontext-which-becomes-the-parent-of-the-widget-returned-by-the-statelesswidgetbuild-or-statebuild-function">2. Each widget has its own BuildContext, which becomes the parent of the widget returned by the StatelessWidget.build or State.build function.</h4>
</blockquote>
<p>-&gt; 각 위젯은 자신만의 BuildContext를 갖고 있는데, 이 BuildContext는 StatelessWidget.build 또는 State.build 함수(StatefulWidget의 build 함수)에 의해 리턴되는 위젯의 부모가 된다.</p>
<blockquote>
<h4 id="3-in-particular-this-means-that-within-a-build-method-the-build-context-of-the-widget-of-the-build-method-is-not-the-same-as-the-build-context-of-the-widgets-returned-by-that-build-method">3. In particular, this means that within a build method, the build context of the widget of the build method is not the same as the build context of the widgets returned by that build method.</h4>
</blockquote>
<p>-&gt; 2번 문단이 뭔 말이냐면, build함수를 갖고 있는 위젯의 build context는, 이 위젯의 build함수로 리턴한 위젯의 build context와 다르다는 말.</p>
<pre><code class="language-dart">class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(</code></pre>
<p>build함수를 갖고 있는 MyHomePage의 build context는, 리턴된 Scaffold위젯의 build context와는 다르다! 
즉, Widget build(BuildContext context) &lt;- 여기의 context는 아래 리턴되는 Scaffold의 context와는 달라요...!
<br></p>
<p>^^ 뭐래는지 대충 알겠지만 어렵다
<br><br><br><br><br><br><br><br><br></p>
<h1 id="코드-예시로-알아보자">코드 예시로 알아보자</h1>
<p><a href="https://api.flutter.dev/flutter/material/Scaffold/of.html">flutter.dev - material-scaffold-of method</a></p>
<pre><code class="language-dart">class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
  // here, Scaffold.of(context) returns null -&gt; 왜일까?

    return Scaffold(
        appBar: AppBar(title: Text(&#39;Demo&#39;)),
        // 여기 Builder는 왜 있을까?
        body: Builder(
          builder: (BuildContext context) {
            return TextButton(
              child: const Text(&#39;BUTTON&#39;),
              onPressed: () {
                Scaffold.of(context).showBottomSheet&lt;void&gt;(
                  (BuildContext context) {
                    return Container(
                      alignment: Alignment.center,
                      height: 200,
                      color: Colors.amber,
                      child: Center(
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          children: &lt;Widget&gt;[
                            const Text(&#39;BottomSheet&#39;),
                            ElevatedButton(
                              child: const Text(&#39;Close BottomSheet&#39;),
                              onPressed: () {
                                Navigator.pop(context);
...괄호가 너무 많아 생략
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/65b2e762-a480-43fa-9eac-8df84d37fd34/image.png" alt=""></p>
<p>위 코드를 실행한건데, Scaffold.of(context).showBottomSheet 가 있는 TextButton을 왜 Builder로 감싸주었을까?
<img src="https://velog.velcdn.com/images/sharveka_11/post/04d30a4f-2725-4d7c-98ff-cdcca638ea12/image.png" alt="">
이렇게 Builder위젯을 벗겨내고 실행하면 에러가 나기 때문.
Scaffold.of 메소드는 context를 이용해 가장 가까운 ScaffoldState 를 찾는데, ScaffoldState란 Scaffold의 State(상태)를 말한다. 
No Scaffold ancestor...! return Scaffold가 있는데도 왜 못찾을까?</p>
<p><strong>Scaffold를 리턴하는 build함수에 인자로 전달되는 context는 Scaffold의 것이 아니기 때문이야...</strong> Builder를 이용해 Scaffold의 context를 뱉어줄, Scaffold 아래에 위치하는 새 BuildContext를 만들어서 써야 한다. 그래서 예제 코드에는 Builder 위젯으로 감싸놓은 것! Builder.builder에서 context를 쓰기 때문이다.</p>
<pre><code class="language-dart">   body: Builder(
          builder: (BuildContext context) {</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/94d6b422-c983-4e5e-a7b7-12e8fa259be1/image.png" alt=""></p>
<p>print로 한번 찍어볼까?
<img src="https://velog.velcdn.com/images/sharveka_11/post/54d7a05a-a68d-4fd2-8b8a-e4aec80124f2/image.png" alt="">역시나 다른 아이였다.</p>
<p>Scaffold.of(context)에서 쓰인 context는 
MyHomePage의 build함수에 인자로 들어간 context가 아니라, 
Builder.builder에 전달된 context라는 것을 알 수 있다.</p>
<p>즉, Scaffold의 context는 Builder.builder에 전달된 context...! 
<a href="https://www.youtube.com/watch?v=-zxGPfjiQQA">코딩셰프님 강의</a>에서는 <strong>현재 주어진 context에서 위젯 트리를 거슬러 올라가면서 scaffold state를 찾는다</strong>고 했으니, scaffold아래에 있는 context만 있으면 되는 것 같다.<img src="https://velog.velcdn.com/images/sharveka_11/post/fcd84e82-ee84-440d-a693-9d1e81832c70/image.png" alt="">공식문서에도 &quot;under&quot; the Scaffold라고 나와있으니..!</p>
<p><br><br><br><br><br><br><br></p>
<h2 id="그럼-앞으로-scaffoldofcontext를-쓸-때는-builder를-꼭-써야하나">그럼 앞으로 Scaffold.of(context)를 쓸 때는 Builder를 꼭 써야하나?</h2>
<p>아니다. <a href="https://api.flutter.dev/flutter/material/Scaffold/of.html">flutter.dev - material-scaffold-of method</a>에서는 다른 방식을 추천함.</p>
<pre><code class="language-dart">// void main 생략

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;,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text(&#39;Demo&#39;),
        ),
        body: MyScaffoldBody(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Builder(
      builder: (BuildContext context) {
        return TextButton(
          child: const Text(&#39;BUTTON&#39;),
          onPressed: () {
            Scaffold.of(context).showBottomSheet&lt;void&gt;(
              (BuildContext context) {
                return Container(
                  alignment: Alignment.center,
                  height: 200,
                  color: Colors.amber,
                  child: Center(
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: &lt;Widget&gt;[
                        const Text(&#39;BottomSheet&#39;),
                        ElevatedButton(
                          child: const Text(&#39;Close BottomSheet&#39;),
                          onPressed: () {
                            Navigator.pop(context);
... 괄호 전부 생략
}
</code></pre>
<p>이렇게 Scaffold랑 Scaffold.of(context)쓰는 부분을 똑 떼주면 됨. Builder를 쓰는 건 Scaffold 안에서 바로 Scaffold.of(context)를 써야할 때! 이다. 
<strong>flutter.dev 에서는 그냥 똑 떼는 게 효율적이라고 하네.</strong></p>
<p><br><br><br><br><br><br>
일기 쓰는데 이 context가 쟤 건지 얘 건지 헷갈려서 미칠 뻔 했다.
코딩셰프님 유튭 강의 덕분에 핵심을 확실히 알 수 있어서 감사했다!</p>
<p>내 오후를 다 쓴 일기... 여기까지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter #51 - Cocoapods not installed.]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-Error-Cocoapods-not-installed</link>
            <guid>https://velog.io/@sharveka_11/Flutter-Error-Cocoapods-not-installed</guid>
            <pubDate>Tue, 08 Nov 2022 00:37:31 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기 51번째.
<br><br><br><br></p>
<blockquote>
<h1 id="cocoapods-not-installed">Cocoapods not installed.</h1>
</blockquote>
<p>Flutter 개발중에 자주 발목을 잡는 우리 Cocoapods... </p>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/18196943-5ef5-440b-8133-ba42b319fc6d/image.png" alt=""> Terminal에서는 이렇게 나오고, Debug console에는 </p>
<pre><code class="language-dart">Cocoapods not installed. Skipping pod install error</code></pre>
<p>라고 뜰 것이다.
설치가 안되었다고 해서 ios 폴더로 이동해 pod install만 치면 똑같은 에러가 그대로 나는 경우가 많다.<br><br><br><br></p>
<blockquote>
<h2 id="해결방법">해결방법</h2>
</blockquote>
<ol>
<li>Podfile.lock 삭제 후 다시 pod install </li>
</ol>
<ol start="2">
<li><p><strong>Android Studio - File - Invalidate caches - Restart</strong>
vs code가 주사용 에디터라면, vs code를 잠시 꺼놓고 안드로이드 스튜디오에 가서 이 작업을 해주세요.</p>
</li>
<li><p>cocoapods 재설치</p>
<pre><code>sudo gem uninstall cocoapods
sudo gem install cocoapods</code></pre></li>
</ol>
<p>보통은 2번으로 거의 해결이 되었지만, 1번이나 3번을 다 써야 해결될 때도 있었다.</p>
<p><a href="https://velog.io/@tmdgks2222/Flutter-CocoaPods-not-installed-Skipping-pod-install-error">cocoapods때문에 자주 찾아갔던 블로그</a>의 도움을 받았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter #50 -
Firebase google sign in - SHA1 지문 생성 (MAC m1)]]></title>
            <link>https://velog.io/@sharveka_11/Firebase-google-sign-in-SHA1-key-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@sharveka_11/Firebase-google-sign-in-SHA1-key-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Mon, 07 Nov 2022 14:14:32 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기 50번째.
<br><br><br></p>
<h1 id="firebase-google-sign-in">Firebase Google Sign-in</h1>
<p>Flutter 앱에서 사용자 인증을 위해 Firebase의 <strong>Google Sign-in</strong>을 사용할 경우, <strong>SHA1</strong>이라는 디지털 지문이 필요하다. <a href="https://developers.google.com/android/guides/client-auth?authuser=0&amp;hl=ko">SHA-1 cli 안내</a></p>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/56a93f6a-4f16-49a0-9f54-414fa441a37e/image.png" alt=""></p>
<p>그리고 나처럼 백지의 맥에서 SHA-1 얻으려고 하면, 당연히 안 된다.</p>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/522d064f-9e14-42f4-a60d-fa58393b7973/image.png" alt=""></p>
<p>JAVA가 없어서^^ JDK를 설치해야 한다. <br><br><br><br><br><br></p>
<h1 id="1-jdk-설치">1. JDK 설치</h1>
<p>JDK는 Java Development Kit의 약자인데, 자바 개발 키트, 즉 개발 도구들을 포함하고 있다. 
JDK를 찾다보면 JRE가 꼭 같이 나오는데, JRE는 Java Runtime Environment의 약자로, 자바로 만들어진 프로그램을 실행하는 역할을 한다. 개발을 위해 JDK 설치방법이 더 많이 검색되는 듯하다.</p>
<p>(참고 : <a href="https://coding-factory.tistory.com/826">코딩팩토리 블로그</a>, <a href="https://www.geeksforgeeks.org/differences-jdk-jre-jvm/">GeeksforGeeks - JDK, JRE, JVM</a>  )</p>
<p>Windows에는 Oracle JDK를 깔았는데, 공식 사이트 가서 zip파일 받아서 풀고 환경변수 설정하는 방식으로 설치했었다. 하지만 이번 mac에서는 <del>iterm2도 예쁘게 꾸며놨으니</del> homebrew를 이용해서 OpenJDK를 깔아보려고 한다. <a href="https://broccolies.com/2021/04/22/oracle-jdk%EC%99%80-open-jdk%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EB%B9%84%EA%B5%90/">Oracle JDK vs  OpenJDK</a></p>
<p><br><br><br></p>
<p><a href="https://adoptopenjdk.net">AdoptOpenJDK</a>  에 따르면.. 지금은 버전11이 LTS(Long Term Support)라고 하네</p>
<p><a href="https://github.com/AdoptOpenJDK/homebrew-openjdk">homebrew-openjdk github</a>    여기를 참고해서 설치했다. 근데 입력한 건 두 줄 밖에 없음.</p>
<pre><code>$ brew tap AdoptOpenJDK/openjdk
$ brew install --cask adoptopenjdk11
</code></pre><p><img src="https://velog.velcdn.com/images/sharveka_11/post/757d2b1f-0954-4d9c-9de7-ae4cf215e384/image.png" alt=""> 버전11의 openJDK 설치가 잘 되었다.</p>
<p>~/.bashrc나 ~/.zshrc 수정은 여러 개의 JDK버전을 깔았을 때, 특정 버전을 사용하려고 하면 수정해야 한다. 난 하나만 깔았으므로 패스. 깃헙에 추가해야 할 내용 나와있음!</p>
<p>+) 설치하다가 cask라는 명령어가 궁금해서 찾아보았다. 그냥 앱 설치를 cli로 쉽게 할 수 있도록 해주는 거라고 하는구만. <del>공식 깃헙 내용을 못알아들어서... 한글로 설명해준 블로그를 찾았다.</del>
<a href="https://findstar.pe.kr/2019/01/20/install-openjdk-by-homebrew/">homebrew --cask</a>
<br><br><br><br><br><br></p>
<h1 id="2-keystore-생성">2. Keystore 생성</h1>
<p>그리고 처음의 명령어를 다시 입력해 키를 구하려고 하면... 여전히 구해지지 않는다. <strong>키 저장소 파일이 없음.</strong>
<img src="https://velog.velcdn.com/images/sharveka_11/post/b3aaeba4-2a2d-44c6-bd9c-1087d5bbe948/image.png" alt=""></p>
<p>저기 키 저장소 파일이 존재하지 않음 - 옆으로 뜨는 경로</p>
<pre><code>/Users/사용자이름/.android/debug.keystore
이러한 경로를 가진 키 저장소가 있어야 한다.</code></pre><p><br><br><br>
구글에 뒤져보다가 keytool -genkey하면 된대서 따라 쳤다가, 경로를 제대로 설정하지 않아서 실패 <img src="https://velog.velcdn.com/images/sharveka_11/post/0f18bb79-c7b3-40e4-8e4a-ee49b568269f/image.png" alt="">home 경로에 알 수 없는 keystore하나 생성됨... 이건 지웠다. ㅠㅠ
(요렇게 정보를 자세하게 띄우려면 ls 말고 l 치면 다 뜬다.)
<br><br><br>
다시 찾아서 
<a href="https://gsgdvxhx.tistory.com/12">2019년에 똑같은 문제를 겪은 분이 있다</a>
<a href="https://stackoverflow.com/questions/8576732/there-is-no-debug-keystore-in-android-folder">그 분이 여기를 소개 : StackOverFlow - no debug keystore</a>
그리고 그 명령어를 그대로 넣어주었다. 어차피 출시용 아니구 디버그용이니까... 대충 설정하기로 하고, 명령어를 수정하지 않은 채로 그대로 넣었다. </p>
<pre><code class="language-dart">keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android \
-alias androiddebugkey -keypass android -dname &quot;CN=Android Debug,O=Android,C=US&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/sharveka_11/post/27894469-adf2-41b7-9342-9bc9befee711/image.png" alt=""><img src="https://velog.velcdn.com/images/sharveka_11/post/721450d8-12ea-42f7-ab07-a087546a0cdc/image.png" alt="">키 저장소 비밀번호 입력하라고 뜨는데, 명령어에 보면 -storepass android라고 설정한 걸 알 수 있음. 그대로 android 라고 치면 된다. 
keytool 명령어가 궁금하다면, <a href="https://www.lesstif.com/java/java-keytool-keystore-20775436.html">Java keytool 사용법</a> 여기에 잘 나와있었다.</p>
<p><strong>^^ 이제 저 SHA1 옆에 뜨는 00:00:.... 이걸 복사해서 Firebase에 갖다주면 끝.</strong>
<br><br><br></p>
<p>Mac에서의 JDK설치와 Keystore생성을 한 번에 다룬 글을 찾질 못해서... 써 보았다.
<del>왜 다들 JDK를 설치해 놓은거요? 나만 JDK 없었냐고...?</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter #49 - Your project requires a newer version of the Kotlin Gradle plugin.]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-Error-1-Your-project-requires-a-newer-version-of-the-Kotlin-Gradle-plugin</link>
            <guid>https://velog.io/@sharveka_11/Flutter-Error-1-Your-project-requires-a-newer-version-of-the-Kotlin-Gradle-plugin</guid>
            <pubDate>Wed, 06 Apr 2022 06:16:59 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기 49번째.
<br><br><br><br></p>
<blockquote>
<h1 id="your-project-requires-a-newer-version-of-the-kotlin-gradle-plugin">Your project requires a newer version of the Kotlin Gradle plugin.</h1>
</blockquote>
<p><img src="https://velog.velcdn.com/cloudflare/sharveka_11/0a70f821-269e-4a73-b9ad-c441d8b72fbf/image.png" alt=""></p>
<p>위 에러에 나오는 
<a href="https://kotlinlang.org/docs/gradle.html#plugin-and-versions">https://kotlinlang.org/docs/gradle.html#plugin-and-versions</a>
사이트에 들어가면 latest version이 나온다. 현재 시점은 1.6.20</p>
<p>+) 22.12.28 기준 위 사이트를 들어갔더니 그냥 맨둥맨둥한 페이지가 나왔다.
<a href="https://kotlinlang.org/docs/releases.html#release-details">https://kotlinlang.org/docs/releases.html#release-details</a></p>
<p>/android/build.gradle 파일 내부의</p>
<pre><code class="language-dart">buildscript {
    ext.kotlin_version = &#39;1.3.50&#39;
}</code></pre>
<p>해당 부분을 1.6.20으로 수정</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - #48. CircularProgressIndicator]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-48.-CircularProgressIndicator</link>
            <guid>https://velog.io/@sharveka_11/Flutter-48.-CircularProgressIndicator</guid>
            <pubDate>Sun, 27 Feb 2022 07:28:10 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기
참고 : <a href="https://api.flutter.dev/flutter/material/CircularProgressIndicator-class.html">Flutter.dev - material - CircularProgressIndicator</a>
<br><br><br><br><br><br></p>
<blockquote>
</blockquote>
<h1 id="circularprogressindicator">CircularProgressIndicator</h1>
<p>앱 진행상황을 나타낼 때 가장 많이 사용되는 위젯. 원형(Circular)과 선형(Linear) 2가지 Indicator 를 주로 쓴다. 사용법은 거의 동일하다시피 하다. 저번 FutureBuilder 일기에서 한번 사용해본 적이 있는데, 데이터가 오는 중이라는 표시를 CircularProgressIndicator로 하였다. 
오늘은 CircularProgressIndicator의 속성을 알아보도록 하겠다. 
<br><br><br>
우선 CircularProgressIndicator를 표시할 때, 두 가지 형태로 나뉜다.</p>
<h3 id="1-determinate">1. Determinate.</h3>
<p>특정한 값을 갖는 것인데, CircularProgressIndicator의 value 속성을 활용하는 것이다. 0.0에서 1.0사이의 값을 value에 설정한다. </p>
<p>특정 값으로 고정해도 되긴 한데, 고정해버리면 위젯 자체도 움직이지 않는다. 아래는 value 값을 0.6으로 준 상태이다. 저기서 상태가 전혀 변하지 않는다.
<img src="https://images.velog.io/images/sharveka_11/post/d39bb0ae-f08a-41da-8038-64a09051de78/image.png" alt=""></p>
<p>따라서, 0에서 특정 값까지의 진행률(변화)을 나타내주려면 value에 뭔가 변화하는 값을 주어야한다. Controller가 필요한 부분... </p>
<br>

<h3 id="2-indeterminate">2. Indeterminate.</h3>
<p>value값을 특정하지 않아서, 진행률이 얼마나 되는지 표시하지 않는다. 아직 진행중... 만 표시하는 것이다. 
<img src="https://images.velog.io/images/sharveka_11/post/aee619bc-b4ed-4c5c-896e-846636f53f41/ezgif.com-gif-maker.gif" alt="">
아직 데이터 안왔어요~ 만 표시하고 싶으면 </p>
<pre><code class="language-dart">CircularProgressIndicator()</code></pre>
<p>이렇게 위젯 표시하면 사실상 끝이다.
<br><br><br><br><br><br><br></p>
<blockquote>
</blockquote>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<h3 id="3-indeterminate-코드">3. Indeterminate 코드</h3>
<p>먼저 indeterminate 형식부터 만들어보았다.</p>
<pre><code class="language-dart"> @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          width: 300,
          height: 300,
          child: Stack(
            fit: StackFit.expand,
            children: const [
              CircularProgressIndicator(
                strokeWidth: 10,
                // backgroundColor: Colors.black,
                // color: Colors.green,
              ),
              Center(
                  child: Text(
                &#39;My Progress Indicator&#39;,
                style: TextStyle(fontSize: 20),
              )),
            ],
          ),
        ),
      ),
    );
  }</code></pre>
<p>위의 코드에서 주석친 부분을 해제하고 실행하면 아래와 같다.</p>
<img width="300px" alt="afterPress" src="https://images.velog.io/images/sharveka_11/post/259bdc1b-fc69-4483-8434-11f2f84c0b1e/ezgif.com-gif-maker%20(1).gif">

<p><del>img 태그의 활용법을 이제 알다니...</del>
위젯 내부의 속성을 조금씩 건드려보면, 다음과 같다.</p>
<table>
<thead>
<tr>
<th>backgroundColor 해제</th>
<th>backgroundColor, color 둘다 해제</th>
</tr>
</thead>
<tbody><tr>
<td><img width="250px" alt="afterPress" src="https://images.velog.io/images/sharveka_11/post/fe5f9f23-0d82-4646-b2de-a8d0ae92af53/image.png"></td>
<td><img width="257px" heigh="300px" alt="beforePress" src="https://images.velog.io/images/sharveka_11/post/a154879e-458f-4c51-a00a-cc9cae590dcd/image.png"></td>
</tr>
</tbody></table>
<h3 id="4-determinate-코드">4. Determinate 코드</h3>
<pre><code class="language-dart">class _MyStatefulWidget extends State&lt;MyStatefulWidget&gt; with TickerProviderStateMixin {

  late AnimationController controller;
  late Animation&lt;Color?&gt; _colorTween;
  @override
  void initState() {
    controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 10),
    )..addListener(() {
        setState(() {});
      });

    _colorTween = controller.drive(ColorTween(begin: Colors.yellow, end: Colors.blue));
    controller.forward();
    super.initState();
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          width: 300,
          height: 300,
          child: Stack(
            fit: StackFit.expand,
            children: [
              CircularProgressIndicator(
                strokeWidth: 10,
                value: controller.value,
                valueColor: _colorTween,
              ),
              Center(
                child: Text(
                  (controller.value * 100).toInt().toString(),
                  style: TextStyle(fontSize: 30),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}</code></pre>
<p>이번에는 value 값을 AnimationController로 설정하고, 색도 value 에 맞게 변화하도록 ColorTween을 만들어보았다. 이 코드는 공식 페이지의 예제를 가져다가 변형한 것이다.</p>
<pre><code class="language-dart">  AnimationController({
    double? value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    required TickerProvider vsync,
  })</code></pre>
<p>AnimationController를 AnimatedBuilder 일기 쓸 때 본 적 있지만, 사용법을 까먹었다. 다시 보면 lowerBound, upperBound가 각각 0과 1로 설정된 것을 알 수 있다. Controller의 value는 0부터 1까지 변하는 것이다. 얼마 동안? duraion에 설정된 시간 동안. </p>
<p>그렇다면 여기의 코드는, 10초 동안 value는 0에서 1까지로 변한다.
Stateful 위젯이므로, addListener에 setState를 추가해 주었다. </p>
<p>addListener method는 애니메이션의 value 속성이 변할 때마다 호출되는 것이다. value는 AnimationController가 실행되는 10초 동안 계속 변하므로, 10초 동안 setState()가 계속 실행된다는 말이다. 
AnimationController의 value를 CircularProgressIndicator.value에 설정하였으므로, CircularProgressIndicator도 setState에 의해 변하게 된다. </p>
<p>_colorTween은 CircularProgressIndicator의 value값이 변함에 따라 색도 변하게 하려고 만들었다. </p>
<p>실행결과는 아래와 같다.
<img width="300px" alt="afterPress" src="https://images.velog.io/images/sharveka_11/post/009f44ae-76e4-4887-b589-4c09fbb6fd62/ezgif.com-gif-maker%20(2).gif"></p>
<p>그 동안 CircularProgressIndicator는 사용할 일이 정말 많았다. LinearProgressIndicator 도 사용법이 거의 비슷하므로, CircularProgressIndicator에 대해서만 다루고 넘어가려고 한다. </p>
<p><br><br><br><br>
오늘의 일기는 여기까지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - #47. AnimatedDefaultTextStyle]]></title>
            <link>https://velog.io/@sharveka_11/AnimatedDefaultTextStyle</link>
            <guid>https://velog.io/@sharveka_11/AnimatedDefaultTextStyle</guid>
            <pubDate>Thu, 24 Feb 2022 09:14:57 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기
참고 : <a href="https://api.flutter.dev/flutter/widgets/AnimatedDefaultTextStyle-class.html">Flutter.dev - material - AnimatedDefaultTextStyle</a>
<br><br><br><br><br><br><br></p>
<blockquote>
</blockquote>
<h1 id="animateddefaulttextstyle">AnimatedDefaultTextStyle</h1>
<p>요즘은 애니메이션 위젯을 많이 보는데, 얼마 전에 썼던 DefaultTextStyle의 Animated 버전이 있길래 가져와보았다. 
공식 사이트에 예시 gif만 나와있고 코드가 없어... 그냥 그 gif랑 똑같이 생긴 걸 만들어보았다. </p>
<p>DefaultTextStyle 과 마찬가지로 자<strong>손 Text 위젯들에 동일하게 style을 적용</strong>하는 클래스이고, animation 효과를 줄 수 있다. <strong>duration, child, style</strong> 이 세가지 속성이 필수이다.</p>
<p>만약 좀 더 정교힌 애니메이션을 만들고 싶다면 DefaultTextStyleTransition 을 쓸 것을 추천한다. 이건 AnimationController를 사용해서 애니메이션을 관리할 수 있는 클래스인 모양이다.</p>
<blockquote>
</blockquote>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<p>매우매우 간단한 코드를 만들어보았다.</p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyStatelessWidget(),
    );
  }
}

class MyStatelessWidget extends StatefulWidget {
  const MyStatelessWidget({Key? key}) : super(key: key);

  @override
  State&lt;MyStatelessWidget&gt; createState() =&gt; _MyStatelessWidgetState();
}

class _MyStatelessWidgetState extends State&lt;MyStatelessWidget&gt; {
  bool isBlue = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text(&#39;AnimatedDefaultTextStyle&#39;),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              AnimatedDefaultTextStyle(
                style: TextStyle(
                  fontSize: 60,
                  color: isBlue ? Colors.blue : Colors.red,
                  fontWeight: isBlue ? FontWeight.bold : FontWeight.w300,
                ),
                duration: const Duration(milliseconds: 600),
                curve: Curves.elasticInOut,
                child: Column(
                  children: const [
                    Text(&#39;Flutter&#39;),Text(&#39;Color&#39;),Text(&#39;Size&#39;),
                  ],
                ),
              ),
              ElevatedButton(
                onPressed: () {
                  setState(() {
                    isBlue = !isBlue;
                  });
                },
                child: const Text(
                  &#39;Change Color&#39;,
                  style: TextStyle(fontSize: 20),
                ),
              ),
            ],
          ),
        ));
  }
}
</code></pre>
<p>코드에서 예측할 수 있듯 ElevatedButton을 누르면 isBlue의 값이 변하고, 이에 따라서 Text위젯들의 색과 굵기가 변한다.</p>
<p>실행화면은 이렇다.
<img src="https://images.velog.io/images/sharveka_11/post/5715c0fa-e5ee-4c51-be07-03c58c2bd2a5/ezgif.com-gif-maker%20(1).gif" alt=""></p>
<p><br><br><br>
더 이상 설명할 게 없다.
<br><br><br>
플러터 공식 유튜브의 오늘의 위젯은 100개를 넘겼다. 유튜브에 영상으로 올라오지 않은 것도 많으니, 엄청난 숫자일 것이다.
내 위젯일기는 이제 50개를 향해 달려가는데, 자주 사용하는 위젯들은 어느 정도 다뤄본 거 같기도 하다. 그런거 치곤 공부할 때마다 매번 새로운 위젯들이 나오긴 하지만...
이제 슬 package들도 일기로 써봐야 할 것 같다. 원래는 package 잘 쓸 줄 몰라서 일기로 안 썼는데, 어차피 일기인거! 내 마음대로 써야지 
<br>
오늘의 일기는 여기까지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - #46. AppBar]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-46.-AppBar</link>
            <guid>https://velog.io/@sharveka_11/Flutter-46.-AppBar</guid>
            <pubDate>Wed, 23 Feb 2022 12:59:14 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기
참고 1 : <a href="https://api.flutter.dev/flutter/material/AppBar-class.html">Flutter.dev - material - AppBar</a></p>
<p><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h1 id="appbar">AppBar</h1>
<p>오늘은 가장 많이 쓰는 위젯 중 하나인 AppBar를 다뤄보겠다.
제일 기본적인 건데 맨날 보는거라 일기로 쓸 생각을 못했다.</p>
<p>그래도 AppBar에 매번 글자만 넣을 수는 없으니, 이걸 어떻게 꾸밀 수 있는지 알아보도록 하자.</p>
<p>Flutter.dev 에 있는 나온 예시 코드에, 지금까지 배웠던 것들이 몇개 들어가 있길래 가져다가 몇 개만 추가해서 실행하였다.</p>
<p><img src="https://images.velog.io/images/sharveka_11/post/a5908b95-581f-4a3e-a15a-823fdaf8ab31/image.png" alt="">
우선 AppBar의 영역은 위와 같은데, 오늘은 leading, title, actions에 대해서 다뤄보겠다.
AppBar는 고정된 높이값을 갖는다. 스크롤을 내렸을 때 높이가 변하는(AppBar가 사라지는) 위젯을 찾는다면, 저번에 다뤄본 SliverAppBar를 쓰면 된다.
<br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyStatelessWidget(),
    );
  }
}

class MyStatelessWidget extends StatelessWidget {
  const MyStatelessWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&#39;AppBar Demo&#39;),
        elevation: 10,
        leading: const BackButton(),

        // backgroundColor: Colors.black54,
        actions: &lt;Widget&gt;[
          IconButton(
            icon: const Icon(Icons.add_alert),
            tooltip: &#39;Show Snackbar&#39;,
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text(&#39;This is a snackbar&#39;)));
            },
          ),
          IconButton(
            icon: const Icon(Icons.navigate_next),
            tooltip: &#39;Go to the next page&#39;,
            onPressed: () {
              Navigator.push(context, MaterialPageRoute&lt;void&gt;(
                builder: (BuildContext context) {
                  return Scaffold(
                    appBar: AppBar(
                      title: const Text(&#39;Next page&#39;),
                    ),
                    body: const Center(
                      child: Text(
                        &#39;This is the next page&#39;,
                        style: TextStyle(fontSize: 24),
                      ),
                    ),
                  );
                },
              ));
            },
          ),
        ],
      ),
      body: const Center(
        child: Text(
          &#39;This is the home page&#39;,
          style: TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}
</code></pre>
<p>우선 property 하나하나 뜯어보자.</p>
<h3 id="1-title-elevation-leading">1. title, elevation, leading</h3>
<pre><code class="language-dart">title: const Text(&#39;AppBar Demo&#39;),
elevation: 10,
leading: const BackButton(),</code></pre>
<p>title 에는 보통 위와 같이 Text 위젯을 많이 쓴다.</p>
<p>elevation은 그냥 AppBar아래의 그림자이다. 기본값은 4인데, 건드리는 경우는 많진 않다. AppBar바로 아래에서 body위젯이 시작되거나 한다면 elevation에 0을 줘서 없애버리는 경우는 가끔 있다. </p>
<table>
<thead>
<tr>
<th>elevation : 0</th>
<th>4</th>
<th>10</th>
</tr>
</thead>
<tbody><tr>
<td><img width="250px" alt="afterPress" src="https://images.velog.io/images/sharveka_11/post/b0a6c9b7-fa31-47f2-a047-14dd3d7fb23c/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202022-02-23%20%EC%98%A4%ED%9B%84%209_44_41.png"></td>
<td><img width="257px" alt="beforePress" src="https://images.velog.io/images/sharveka_11/post/7aaa44a7-7fd1-4f92-a2d5-b7931ba93ffe/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202022-02-23%20%EC%98%A4%ED%9B%84%209_44_08.png"></td>
<td><img width="250px" alt="afterPress" src="https://images.velog.io/images/sharveka_11/post/3d444f45-6d74-47eb-98c7-931f66460cf5/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202022-02-23%20%EC%98%A4%ED%9B%84%209_45_51.png"></td>
</tr>
</tbody></table>
<p>leading은 자주 활용될 만하다. title 왼쪽에 나오는 부분이고, IconButton을 넣든 아무 기능 없는 Icon을 넣든 상관없다. 여기서는 BackButton 위젯으로 한번 넣어보았다.</p>
<p><br><br></p>
<h3 id="2-actions">2. actions</h3>
<pre><code class="language-dart">actions: &lt;Widget&gt;[
          IconButton(
            icon: const Icon(Icons.add_alert),
            tooltip: &#39;Show Snackbar&#39;,
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text(&#39;This is a snackbar&#39;)));
            },
          ),
          IconButton(
            icon: const Icon(Icons.navigate_next),
            tooltip: &#39;Go to the next page&#39;,
            onPressed: () {
              Navigator.push(context, MaterialPageRoute&lt;void&gt;(
                builder: (BuildContext context) {
                  return Scaffold(
                    appBar: AppBar(
                      title: const Text(&#39;Next page&#39;),
                    ),
                    body: const Center(
                      child: Text(
                        &#39;This is the next page&#39;,
                        style: TextStyle(fontSize: 24),
                      ),
                ...
        ],</code></pre>
<p>사실 AppBar에서 눈여겨봐야 할 속성은 actions 이다. AppBar 오른쪽 끝으로 Icon을 나열하는 경우가 많기 때문이다. 특히 사용자 정보나 장바구니 같은 아이콘들이 대부분 저기 있지 않은가? 예를 들어서, CircleAvatar를 IconButton으로 감싸버리면 사용자 사진을 띄움과 동시에 IconButton의 기능을 할 수도 있다. </p>
<br>

<p>이처럼 여러가지로 활용할 수 있는 자리인데, 여기서는 SnackBar를 띄우는 것과 Navigation 기능을 수행하는 버튼이 사용되었다. 
showSnackBar는 이전의 40번 일기에서 사용해본 적이 있고, 두 번째 IconButton은 복잡하게 생긴 감이 있는데, 이전에 배웠던 Navigator를 사용한 것이다. 그때는 아래와 같은 코드를 썼다.</p>
<pre><code class="language-dart">Navigator.of(context)
              .push(MaterialPageRoute(builder: (context) =&gt; SecondPage()));</code></pre>
<p>SecondPage 위젯을 따로 만들어둔 후 호출하지 않고, builder 안에다가 위젯을 바로 써버린 형태이다. </p>
<p><br><br><br>
<strong>마지막으로 실행화면은 아래와 같다.</strong>
<img src="https://images.velog.io/images/sharveka_11/post/9f7a599f-5d70-40e2-8195-7dfea4d2bc16/ezgif.com-gif-maker.gif" alt=""></p>
<p><br><br><br>
오늘의 일기는 여기까지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - #45. BottomNavigationBar]]></title>
            <link>https://velog.io/@sharveka_11/BottomNavigationBar</link>
            <guid>https://velog.io/@sharveka_11/BottomNavigationBar</guid>
            <pubDate>Thu, 27 Jan 2022 02:56:59 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기
참고 1 : <a href="https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html">Flutter.dev - material - BottomNavigationBar class</a></p>
<p><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h1 id="bottomnavigationbar">BottomNavigationBar</h1>
<p>이름에서 보이는 그대로, <strong>화면 하단에 Navigation 기능</strong>을 하는 영역이다.
휴대폰에서 앱 쓸 때 정말 자주보는 기능이다. 클론 코딩으로 많이 하는 당근마켓이나, 쿠팡 등의 앱을 보자. 화면 하단에 홈 탭이나 검색 탭, 사용자 정보 탭 등이 있으며 해당 탭을 누르면 다른 페이지로 이동한다. </p>
<p>BottomNavigationBar를 쓸 때 required(필수로 넣어야 하는) 속성은 items 하나 뿐이다. List&lt; BottomNavigationBarItem&gt; 형태의 배열을 넣어주면 된다. 
대괄호 안에 BottomNavigationBarItem를 여러 개 넣어도 되고, List.generate등의 List builder를 써도 된다. 여기서는 여러 개를 각각 넣는 형태로 쓰겠다. (공식 사이트가 각각 넣었으므로...)</p>
<p>BottomNavigationBar에서는 최소한 탭이 2개 이상이어야 한다. 탭이 몇개인가에 따라 type속성이 변하는데, 3개까지는 fixed, 4개 이상부터는 shifting으로 기능하는 것이 기본이다. type에 대해서는 코드 설명과 함께 알아보자. </p>
<p><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<p>오늘은 간만에 공식 사이트의 예제를 가져와서 그대로 돌렸다. </p>
<h4 id="1-우선은-탭이-세-개인-경우부터-보자-탭이-4개-미만일-경우-bottomnavigationbar의-type속성-기본값은-bottomnavigationbartypefixed이다">1. 우선은 탭이 세 개인 경우부터 보자. 탭이 4개 미만일 경우 BottomNavigationBar의 type속성 기본값은 BottomNavigationBarType.fixed이다.</h4>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyStatefulWidget(),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State&lt;MyStatefulWidget&gt; createState() =&gt; _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State&lt;MyStatefulWidget&gt; {
// 탭을 이동할 때 쓸 변수!
  int _selectedIndex = 0;

  static const TextStyle optionStyle =
      TextStyle(fontSize: 40, fontWeight: FontWeight.bold);
  static const List&lt;Widget&gt; _widgetOptions = &lt;Widget&gt;[
    Text(
      &#39;Index 0: Home&#39;,
      style: optionStyle,
    ),
    Text(
      &#39;Index 1: Business&#39;,
      style: optionStyle,
    ),
    Text(
      &#39;Index 2: School&#39;,
      style: optionStyle,
    ),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&#39;BottomNavigationBar Sample&#39;),
      ),
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const &lt;BottomNavigationBarItem&gt;[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: &#39;Home&#39;,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            label: &#39;Business&#39;,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.school),
            label: &#39;School&#39;,
          ),
        ],
        currentIndex: _selectedIndex,
        onTap: _onItemTapped,
        // 아랫줄을 쓰지 않아도 탭이 4개 미만인 경우 기본으로 설정된다.
        // type: BottomNavigationBarType.fixed,

        // selectedItemColor: Colors.amber[800],
        // unselectedItemColor: Colors.blue,
        // backgroundColor: Colors.black,
      ),
    );
  }
}</code></pre>
<p>맨 아래 주석을 단 3줄은 색상 적용 부분인데, 실행 화면을 보면 바로 이해할 것이다.</p>
<table>
<thead>
<tr>
<th>주석의 색상 미적용</th>
<th>주석 색상 적용</th>
</tr>
</thead>
<tbody><tr>
<td><img width="250px" alt="100" src="https://images.velog.io/images/sharveka_11/post/bc67ac39-adc0-44f6-9e51-f941b2ce0255/ezgif.com-gif-maker.gif"></td>
<td><img width="250px" alt="300" src="https://images.velog.io/images/sharveka_11/post/223dcf53-d5db-4374-a952-ba0ea953977d/ezgif.com-gif-maker%20(1).gif"></td>
</tr>
</tbody></table>
<p>위에서 알 수 있듯 selectedItemColor 는 탭을 선택했을 때의 색상인데, 설정하지 않으면 ColorScheme.primary 색상(Colors.blue)이 적용된다. backgroundColor도 설정 안하면 흰색(opaque white)!</p>
<p>BottomNavigationBarItem을 하나하나 나열한 형태이며, icon, label 속성은 각각 탭에 보여지는 아이콘과 문구이다. 
탭을 이동하려면 currendIndex 속성을 바꿔야 선택한 탭으로 이동한다. 
여기서는 _selectedIndex라는 변수를 선언하여 index의 변화를 저장하고, 이 값을 currentIndex에서 쓴다. (탭의 인덱스는 맨 왼쪽부터 0,1,2,3...)</p>
<p>_selectedIndex는 onTap속성에서 변경해준다. 코드 상에서는 함수를 따로 썼던데, 아래와 같이 속성에다 바로 함수를 작성해도 된다. </p>
<pre><code class="language-dart">onTap: (int index) {
  setState(() {
     _selectedIndex = index;
  });
},</code></pre>
<br/>
이제 BottomNavigationBar.type 속성 테스트해야 하니, 탭을 4개로 만들어보자.
<br/><br/><br/><br/>

<h4 id="2-탭이-4개-이상일-경우-bottomnavigationbar의-type속성-기본값은-bottomnavigationbartypeshifting이다">2. 탭이 4개 이상일 경우 BottomNavigationBar의 type속성 기본값은 BottomNavigationBarType.shifting이다.</h4>
<p>위의 코드에서 settings 탭만 추가했다. 실행화면을 바로 보자.</p>
<table>
<thead>
<tr>
<th>주석의 색상 미적용</th>
<th>주석 색상 적용</th>
</tr>
</thead>
<tbody><tr>
<td><img width="250px" alt="100" src="https://images.velog.io/images/sharveka_11/post/b9021d4e-59fe-49c4-a070-a2a4b74ea871/ezgif.com-gif-maker%20(2).gif"></td>
<td><img width="250px" alt="300" src="https://images.velog.io/images/sharveka_11/post/75e7442b-4597-4f6e-a88d-6fda75a2b18b/ezgif.com-gif-maker%20(3).gif"></td>
</tr>
</tbody></table>
<p>색상을 아무것도 적용 안하면 탭이 안보인다. shifting이 적용될 경우 selectedItemColor를 설정하지 않으면 흰색으로 설정되며, backgroundColor또한 흰색이 된다. 
색상을 설정하면, 탭한 부분이 살짝 올라오는 것이 보인다. type이  shifting! 움직이기 때문이다. 이게 싫으면 type속성을 fixed로 설정해주면 된다.
<br/><br/>
backgrounColor는 BarItem별로 따로 설정할 수는 있다. 다만 type이 shifting일 경우에는 BottomNavigationBar수준에서 backgroundColor를 설정했는데 안먹는다;; 첫번째 BarItem에만 backgroundColor를 검정색으로 설정해주었더니 전체 Bar에 적용되는 것을 확인했다 (왜지?)</p>
<p>fixed일때는 BottomNavigationBar수준에서 backgroundColor를 설정하면 잘 적용된다. 이대로 해보면 아래와 같다.
<img src="https://images.velog.io/images/sharveka_11/post/05d1e170-3a61-4999-8900-7bf5d6538c95/ezgif.com-gif-maker%20(4).gif" alt=""></p>
<p><br/><br/><br/><br/>
지금은 Text위젯만 띄우는 것으로 했는데, onTap에 Navigator를 쓰면 다른 페이지도 띄울 수 있다! 
그리고 bottomNavigationBar에는 BottomNavigationBar 말고도 TabBar라는 것도 쓸 수 있다. 체감상 BottomNavigationBar를 더 많이 쓰는 것 같긴 한데, TabBar 요건 다음에.... 
<br/><br/><br/><br/>
오늘의 일기는 여기까지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - #44. DefaultTextStyle]]></title>
            <link>https://velog.io/@sharveka_11/DefaultTextStyle</link>
            <guid>https://velog.io/@sharveka_11/DefaultTextStyle</guid>
            <pubDate>Wed, 29 Dec 2021 06:01:48 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기
참고 1 : <a href="https://api.flutter.dev/flutter/widgets/DefaultTextStyle/DefaultTextStyle.html">https://api.flutter.dev/flutter/widgets/DefaultTextStyle/DefaultTextStyle.html</a>
<br/><br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h1 id="defaulttextstyle">DefaultTextStyle</h1>
<p>DefaultTextStyle. 플러터 튜토리얼 유튜브에서 볼 때, 가끔 등장했던 클래스이다. 자식으로 두는 텍스트 위젯들의 스타일을 결정한다. <strong>자식으로 오는 텍스트 위젯에 스타일이 명시되어 있지 않으면, 일괄적으로 적용한다. 자식 위젯에 스타일이 따로 적용되어 있다면, 그것이 DefaultTextStyle의 속성보다 우선적으로 적용된다.</strong></p>
<p>텍스트 위젯을 여러 개 써야할 때 스타일을 일일이 적용하지 않고, DefaultTextStyle 클래스를 활용해 몽땅 적용할 수 있다.</p>
<p>DefaultTextStyle은 안에 자손들 많이 넣을 때 쓰면 편한거라서, child 인자에 Text위젯 하나만 덜렁 써놓으면 사용하는 의미가 없다. Row나 Column을 먼저 넣어서 children 에 Text 위젯들을 몰아넣고, 한번에 스타일을 적용하는 것이다. </p>
<p>물론 모든 텍스트 위젯의 스타일이 다르다면? 안 사용하는게 낫다. <strong>근데 텍스트가 한 10개 되는데 그 중에 한두 개만 스타일을 다르게(또는 추가로) 적용한다? 이럴 때 사용하는 것!</strong></p>
<p>사용법도 어렵지 않으니 바로 코드로 넘어가보자.
<br/><br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<p>오늘 건 너무 간단해서 내가 대충 만들었다. 
필수 인자(argument)는 child, style 이다. 이것만 써 넣어도 돌아간다. </p>
<p>나머지 인자는 Text 위젯과 거의 비슷한 거 같은데, TextOverflow와 maxLines 정도는 Text 위젯에 쓸 때도 많으니 알아두는 것이 편할 것이다. </p>
<p>(그러고보니 Text 위젯은 자주 쓰는데도 인자들에 대해 공부해 본적이 없구만... 다음에 일기로 써봐야겠다.)</p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: &#39;Flutter Demo&#39;,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&#39;DefaultTextStyle&#39;),
      ),
      // 요기
      body: DefaultTextStyle(
          // 스타일 적용
          style: Theme.of(context).textTheme.headline1!,
        // 스타일을 적용할 자손들
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: const [
            Text(&#39;hello&#39;),
            Text(&#39;World&#39;),
            Text(
              &#39;hi&#39;,
              style: TextStyle(color: Colors.red),
            ),
            Text(
              &#39;how are you?&#39;,
              style: TextStyle(fontSize: 30),
            ),
            Text(&quot;I&#39;m fine&quot;),
          ],
        ),
      ),
    );
  }
}
</code></pre>
<p><img src="https://images.velog.io/images/sharveka_11/post/7b7154e3-bbb1-48e9-9eaa-b6d5eb382454/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202021-12-29%20%EC%98%A4%ED%9B%84%202_51_46.png" alt=""></p>
<p>코드에서는 세 번째, 네 번째 텍스트에는 따로 스타일을 적용했다. 그랬더니 이 두개만 스타일이 다르고, 나머지는 headline1 스타일이 그대로 적용되었다. 
<br/><br/><br/>
공부하다가 한번씩 발견하는데, 늘 정확한 사용법을 모르는 채로 따라해서 오늘은  일기에 적어보았다. 
오늘의 일기는 여기까지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - #43. Navigator2]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-43.-Navigator2</link>
            <guid>https://velog.io/@sharveka_11/Flutter-43.-Navigator2</guid>
            <pubDate>Mon, 18 Oct 2021 08:02:31 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기
참고1 : <a href="https://api.flutter.dev/flutter/widgets/Navigator-class.html">https://api.flutter.dev/flutter/widgets/Navigator-class.html</a>
참고2 : <a href="https://medium.com/flutter-community/flutter-push-pop-push-1bb718b13c31">https://medium.com/flutter-community/flutter-push-pop-push-1bb718b13c31</a>
참고3 : <a href="https://origogi.github.io/flutter/flutter-push-pop-push/">https://origogi.github.io/flutter/flutter-push-pop-push/</a> (참고2를 번역해서 포스팅한 오리고기씨의 블로그)
<br/><br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h1 id="navigator">Navigator</h1>
<p>지난 일기에 이어서 Navigator 를 이어가보자. <del>(독서여행 떠났다가 한달만에 쓰려니 어색하네)</del> 
Navigator는 Stack구조를 활용해 화면 전환을 하는 클래스이다. 저번 일기에서는 MaterialPageRoute를 이용했는데, 이번에는 route를 활용해 페이지들을 나열한 후, 라우트 이름으로 페이지 전환을 해보자.</p>
<p>우선 라우트는 목적지 네트워크로 가는 이동 경로를 말한다. 코드에서는 이러한 경로에 이름을 붙여 사용할 것이다. 그냥 첫번째 페이지, 두번째 페이지에 이름을 붙여 사용하는 것이라고 보면 되겠다. 코드는 저번 일기에서 사용한 것을 조금만 변형해 사용할 것이고, 왕창 바뀌는 건 아니니 바로 코드를 보자.</p>
<p><br/><br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<p>이번에는 다른 기능들도 추가했기 때문에, 페이지가 하나 늘어나서 코드가 좀 길다. [코드-설명] 식이다. 전체 코드는 다 이어붙이면 실행 가능하다.</p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: &#39;SubPage Example&#39;,
      theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity),
      initialRoute: &#39;/&#39;,
      routes: {
        &#39;/&#39;: (context) =&gt; FirstPage(),
        &#39;/second&#39;: (context) =&gt; SecondPage(),
        &#39;/third&#39;: (context) =&gt; ThirdPage(),
      },
    );
  }
}</code></pre>
<ol>
<li>우선 여기까지만 먼저 보면, initialRoute는 맨 처음 보여질 페이지이다. 그리고 routes에는 각각의 route 이름과 어떤 페이지를 연결할 것인지 설정한다. 매우 정직한 이름으로 설정해주었다. 아래 코드로 이어진다.</li>
</ol>
<hr>
<br/>

<pre><code class="language-dart">class FirstPage extends StatelessWidget {
  const FirstPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(&#39;Main Page&#39;),
      ),
      body: Container(
        child: Center(
          child: Text(
            &#39;첫 번째 페이지&#39;,
            style: TextStyle(fontSize: 30),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.arrow_forward),
        onPressed: () {
          // Navigator.of(context).push(
          //   MaterialPageRoute(
          //     builder: (context) =&gt; SecondPage(),
          //   ),
          // );
          Navigator.of(context).pushNamed(&#39;/second&#39;);
        },
      ),
    );
  }
}
</code></pre>
<ol start="2">
<li>named route 형식을 쓰니 코드가 훨씬 간단해졌다. /second라는 라우트 이름에 해당하는 페이지로 이동한다. </li>
</ol>
<hr>
<pre><code class="language-dart">class SecondPage extends StatelessWidget {
  const SecondPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(&#39;Second Page&#39;),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text(
            &#39;세번째 페이지로&#39;,
            style: TextStyle(fontSize: 20),
          ),
          onPressed: () {
            Navigator.of(context).pushReplacementNamed(&#39;/third&#39;);
          },
        ),
      ),
    );
  }
}

</code></pre>
<ol start="3">
<li>세번째 페이지로 가는 함수로 pushReplacementNamed 함수를 썼는데, 이 함수를 쓰게 되면 Stack에 있던 현재 페이지를, 함수 내부 인자로 들어있는 라우트 이름의 페이지로 대체하게 된다. 따라서 여기서는 SecondPage를 ThirdPage로 대체하게 되는 것이다. </li>
</ol>
<p>덮어쓰는 것이 아닌 &#39;대체&#39;라서, Stack에서 아예 사라져버리기 때문에 뒤로 가도 해당 페이지가 없다. </p>
<p>(저번 시간에는 named route를 쓰지 않았는데, 안쓰고 MaterialPageRoute를 쓰려면 Named가 안붙은 함수를 쓰면 된다.)</p>
<p>-&gt; 여기 실행화면을 보자
<img src="https://images.velog.io/images/sharveka_11/post/cd5e11ce-2be8-47e4-b9d2-9a56fd0b5ad5/ezgif.com-gif-maker.gif" alt=""></p>
<hr>
<p><br/><br/></p>
<pre><code class="language-dart">class ThirdPage extends StatelessWidget {
  const ThirdPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(&#39;Third Page&#39;),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text(
            &#39;첫번째 페이지로&#39;,
            style: TextStyle(fontSize: 20),
          ),
          onPressed: () {
            Navigator.of(context)
                .pushNamedAndRemoveUntil(&#39;/&#39;,  ModalRoute.withName(&#39;/&#39;));
          },
        ),
      ),
    );
  }
}</code></pre>
<ol start="4">
<li>메인 페이지로 돌아가는 함수로는 pushNamedAndRemoveUntil 요것을 썼다. 일단 이 함수는 이렇게 생겼다.</li>
</ol>
<pre><code class="language-dart"> Future&lt;T?&gt; pushNamedAndRemoveUntil&lt;T extends Object?&gt;(
    String newRouteName,
    RoutePredicate predicate, {
    Object? arguments,
  }) {
    return pushAndRemoveUntil&lt;T&gt;(_routeNamed&lt;T&gt;(newRouteName, arguments: arguments)!, predicate);
  }</code></pre>
<br/>

<p>내가 쓴 것은 필수 인자인  newRouteName, predicate 부분을 넣은 것인데, newRouteName 에 해당하는 route로 가면서, predicate 부분에서 true를 반환할 때까지 이전 라우트들을 몽땅 지워버린다. </p>
<p>ㅜㅜ뭐라는지 모르겠어서 코드를 이것저것 바꿔봤다. 함수 사용법은 참고2의 페이지에서 소개하며, 참고3의 오리고기 개발자님이 번역을 해두신 문서가 있다. </p>
<pre><code class="language-dart">Navigator.of(context)
                .pushNamedAndRemoveUntil(&#39;/&#39;,  ModalRoute.withName(&#39;/&#39;));</code></pre>
<p>요기서 withName안의 인자를 바꿔보자. 
<br/><br/>
그리고 SecondPage 클래스에서 pushReplacementNamed -&gt; pushNamed 로 바꿔준다. (Stack에서 어느 페이지까지 지워버리는지 알기 위함이다.)</p>
<pre><code class="language-dart">onPressed: () {
            // Navigator.of(context).pushReplacementNamed(&#39;/third&#39;);
            Navigator.of(context).pushNamed(&#39;/third&#39;);
},</code></pre>
<p>실행화면을 보자.</p>
<table>
<thead>
<tr>
<th>withNamed(&#39;/&#39;)</th>
<th>/second</th>
</tr>
</thead>
<tbody><tr>
<td><img width="250px" alt="100" src="https://images.velog.io/images/sharveka_11/post/0340d280-eeb9-4621-b92f-f6105a238d1e/ezgif.com-gif-maker%20(1).gif"></td>
<td><img width="250px" alt="300" src="https://images.velog.io/images/sharveka_11/post/a9c605ec-cca8-4107-8a7c-96021e9fcce8/ezgif.com-gif-maker.gif"></td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>/third</th>
<th>(route) =&gt; false</th>
</tr>
</thead>
<tbody><tr>
<td><img width="250px" alt="300" src="https://images.velog.io/images/sharveka_11/post/9dec2cf7-86d5-4687-a73a-99be6a82f073/ezgif.com-gif-maker%20(1).gif"></td>
<td><img width="250px" alt="300" src="https://images.velog.io/images/sharveka_11/post/a0a4ede3-a5ef-4ae4-8713-54c38eae6a33/ezgif.com-gif-maker%20(2).gif"></td>
</tr>
</tbody></table>
<p>가장 첫번째 페이지로 이동하면서, Stack에 어떤 페이지가 남아있는지 확인해보았다.
먼저 withNamed(&#39;/&#39;) 라고 쓰게 되면, 첫번째 페이지만 남겨놓고 그 위에 쌓인 페이지들은 다 제거한다. 그리고 첫번째 페이지로 이동하므로 Stack에는 FirstPage가 두개 쌓여있는 형태이다. 
second, third도 유사하다. 해당 페이지 직전까지의 요소들을 Stack에서 다 빼버리고 새로 첫번째 페이지를 Stack에 쌓는 것이다.</p>
<pre><code class="language-dart">(Route&lt;dynamic&gt; route) =&gt; false</code></pre>
<p>위와 같이 쓰게 되면, 이전에 Stack에 쌓아둔 모든 경로가 제거된다. 그래서 맨 오른쪽의 gif파일을 보면, 세번째 페이지에서 첫번째 페이지로 이동하면 뒤로 가기 버튼이 없다. 갈 곳이 없기 때문이다.</p>
<p><br/><br/></p>
<blockquote>
</blockquote>
<h3 id="그런데-이런-기능들은-어디에서-쓰는가">그런데 이런 기능들은 어디에서 쓰는가?</h3>
<p>대표적인 예시는 로그인이다. 로그인이나 회원가입에 성공하면 회원가입 페이지로는 돌아갈 일이 없다. 그럴 경우 Stack에서 로그인 페이지를 빼주어 뒤로가기를 연타하는 상황에서도 로그인 페이지가 나타나지 않도록 해준다. </p>
<p>또는 로그아웃이나 회원탈퇴를 하면, 앱의 처음화면으로 돌아가는 것을 본 적이 있을 것이다. 이 경우 이전 경로를 깡그리 삭제하고 첫화면으로 돌아간다. 이러한 상황에서 주로 사용한다. </p>
<p><del>난 Flutter 배우기 전에는 앱개발 지식이 전무해서 처음에는 &#39;이거 왜써...?&#39; 라고 생각했다.</del>
<br/><br/><br/><br/><br/><br/>
저번 일기를 짧게 썼더니 이번 일기가 왕창 늘어나버렸네
오늘의 일기는 여기까지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - #42. Navigator1]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-41.-Navigator</link>
            <guid>https://velog.io/@sharveka_11/Flutter-41.-Navigator</guid>
            <pubDate>Mon, 13 Sep 2021 13:29:24 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기
참고1 : <a href="https://api.flutter.dev/flutter/widgets/Navigator-class.html">https://api.flutter.dev/flutter/widgets/Navigator-class.html</a>
참고2 : <a href="https://api.flutter.dev/flutter/material/MaterialPageRoute-class.html">https://api.flutter.dev/flutter/material/MaterialPageRoute-class.html</a>
<br/><br/><br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h1 id="navigator">Navigator</h1>
<p>Navigator는 Stack 구조를 이용해 페이지를 관리할 때 사용하는 클래스이다. </p>
<p>보통 앱을 실행하면 우선 홈 화면이 나온다. 이후 화면을 이리저리 이동하다가, 홈으로 돌아가고 싶을 때는 페이지를 이동한 수만큼 뒤로가기 버튼을 눌러야 한다. 휴대폰 쓰는 사람이면 모두 알고 있다. 그냥 이러한 페이지 이동에 Navigator를 쓰는 것이다. </p>
<p>Stack은 먼저 들어간 것이 나중에 나오는 선입후출 구조인데, 넣을 때는 push, 뺄 때는 pop이라는 함수를 사용하는 것이 일반적이다. 프로그래밍 언어들은 Stack 라이브러리를 제공하는데, push, pop은 공통적으로 있는 메소드(함수)이다.</p>
<p><img src="https://images.velog.io/images/sharveka_11/post/761f7cce-ba79-4344-b547-24602c4256ad/image.png" alt=""></p>
<p>Navigator의 기능은 위의 그림과 흡사하다.
1페이지에 있다가 다른 페이지로 이동하면, 1페이지 위에 2페이지를 쌓는 형태이다. 2페이지를 벗어나면(뒤로 가면) 스택에서 2페이지가 빠지고 1페이지로 되돌아간다.
<br/><br/><br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<p>가장 간단한 Navigator코드를 만들어보았다.</p>
<pre><code class="language-dart">
import &#39;package:flutter/material.dart&#39;;

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: &#39;SubPage Example&#39;,
      theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity),
      home: FirstPage(),
    );
  }
}

class FirstPage extends StatelessWidget {
  const FirstPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(&#39;Main Page&#39;),
      ),
      body: Container(
        child: Center(
          child: Text(
            &#39;첫 번째 페이지&#39;,
            style: TextStyle(fontSize: 30),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.arrow_forward),
        onPressed: () {
          Navigator.of(context)
              .push(MaterialPageRoute(builder: (context) =&gt; SecondPage()));
        },
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  const SecondPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(&#39;Second Page&#39;),
      ),
      body: Container(
        child: Center(
          child: ElevatedButton(
            child: Text(
              &#39;돌아가기&#39;,
              style: TextStyle(fontSize: 20),
            ),
            onPressed: () {
              Navigator.of(context).pop();
            },
          ),
        ),
      ),
    );
  }
}
</code></pre>
<p>위와 같이 일단 페이지를 2개 만들고, 왔다갔다만 할 수 있도록 해보았다. 첫번째 페이지에서는 push로 SecondPage로 이동하며, MaterialPageRoute 라는 클래스를 활용하는데, Stack과 같은 구조로 새 페이지로 이전페이지를 교체하는(덮어씌우는) 기능을 한다. </p>
<p>안드로이드에서는 새 페이지가 위로 올라오면서 선명해진다. iOS에서는 오른쪽에서 밀려 들어오는 형식이다. 페이지를 나갈때는 애니메이션 효과가 반대로 기능한다. </p>
<p><br/><br/> <br/>
MaterialPageRoute클래스를 활용할 때는 builder 속성을 필수로 넣어야 하는데, builder에 쓰는 함수는 아래처럼 생겼다.</p>
<pre><code class="language-dart">typedef WidgetBuilder = Widget Function(BuildContext context);</code></pre>
<p>여기 만든 코드에서는 SecondPage를 따로 만들었는데, 사실 builder 함수 리턴에서 바로 return Scaffold(...); 해줘도 된다. 하지만 그렇게 하는 경우는 거의 없음! 위젯을 리턴해야 하는데 위젯은 대부분 코드가 많으므로, StatelessWidget이나 StatefulWidget 클래스를 따로 만들어놓고 거기로 바로 보내는 위 코드와 같은 방법을 사용한다.</p>
<p>실행결과는 아래와 같다.
<img src="https://images.velog.io/images/sharveka_11/post/38939f93-cb22-4a11-b54e-f3792ccaf14c/ezgif.com-gif-maker.gif" alt=""></p>
<p>화면 위에 나오는 뒤로가기 버튼을 눌러도 동일하다. MaterialPageRoute보다는 routes를 한번에 설정하고, 형태가 약간 다른 함수를 사용하는 것이 조금 더 자주 사용되는 방법인 듯하다. 이 방법은 다음 일기에서 알아보자. </p>
<p><br/><br/><br/>
오늘의 일기는 여기까지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - #41. WillPopScope]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-41.-WillPopScope-dl4l89tv</link>
            <guid>https://velog.io/@sharveka_11/Flutter-41.-WillPopScope-dl4l89tv</guid>
            <pubDate>Thu, 02 Sep 2021 15:15:37 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기
참고 : <a href="https://api.flutter.dev/flutter/widgets/WillPopScope-class.html">https://api.flutter.dev/flutter/widgets/WillPopScope-class.html</a>
<br/><br/><br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h1 id="willpopscope">WillPopScope</h1>
<p>오늘 배워본 것은 WillPopScope</p>
<blockquote>
<p>Registers a callback to veto attempts by the user to dismiss the enclosing ModalRoute.</p>
</blockquote>
<p>ModalRoute 설명을 잘 못알아들어서 내부를 정확히 이해하진 못했다.ㅠㅠ 
간단히 말하면, <strong>WillPopScope로 감싼 위젯이 언제 사라지고, 사라지지 않는지 조절할 수 있다</strong> 정도?</p>
<p>코드를 보면 사용법 자체는 간단하다.</p>
<p>일단 WillPopScope 는 아래와 같이 생겼다.</p>
<pre><code class="language-dart">  const WillPopScope({
    Key? key,
    required this.child,
    required this.onWillPop,
  }) : assert(child != null),
       super(key: key);

///////////////////////////////////////////////////
final WillPopCallback? onWillPop;
typedef WillPopCallback = Future&lt;bool&gt; Function();</code></pre>
<p>onWillPop 을 꼭 넣어주어야 하는데, WillPopCallback에 설정된 함수는 boolean 값을 반환하는 함수이다. true 이면 Navigator.pop()의 기능을 한다. false면 pop 안하겠지! 
Navigator.pop() 은 가장 최근에 들어간 페이지를 뺀다고 생각하면 된다. 그냥 뒤로가기랑 똑같다.
<br/><br/><br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<p>공식페이지의 예제를 가져다가 돌려보았다. 
<del>늘 느끼는 건데 글자 좀 크게 해줬으면ㅠㅠ</del></p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

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

/// This is the main application widget.
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyStatefulWidget(),
    );
  }
}

/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State&lt;MyStatefulWidget&gt; createState() =&gt; _MyStatefulWidgetState();
}

/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State&lt;MyStatefulWidget&gt; {
  bool shouldPop = true;

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        return shouldPop;
      },
      child: Scaffold(
        appBar: AppBar(
          title: const Text(&#39;Flutter WillPopScope demo&#39;),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: &lt;Widget&gt;[
              ElevatedButton(
                child: const Text(
                  &#39;Push&#39;,
                  style: TextStyle(fontSize: 30),
                ),
                style: ElevatedButton.styleFrom(
                  padding: EdgeInsets.all(15.0),
                ),
                onPressed: () {
                  Navigator.of(context).push&lt;void&gt;(
                    MaterialPageRoute&lt;void&gt;(
                      builder: (BuildContext context) {
                        return const MyStatefulWidget();
                      },
                    ),
                  );
                },
              ),
              SizedBox(height: 20),
              ElevatedButton(
                child: Text(
                  &#39;shouldPop: $shouldPop&#39;,
                  style: TextStyle(fontSize: 30),
                ),
                style: ElevatedButton.styleFrom(
                  padding: EdgeInsets.all(15.0),
                ),
                onPressed: () {
                  setState(
                    () {
                      shouldPop = !shouldPop;
                    },
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}
</code></pre>
<p>onWillPop에 shouldPop 값을 반환하도록 함수를 설정해두었는데, 두번째 버튼을 누르면 shouldPop의 값이 바뀐다. 
true 이면 onWillPop의 반환값도 true가 되어서 Navigator.pop()을 수행할 수 있다. 반대로 false가 되면 이전 페이지로 돌아가지 못한다. 
아래 실행화면을 보자.</p>
<p><img src="https://images.velog.io/images/sharveka_11/post/ac2a388f-a49d-423d-8d7a-29da95fbc1fe/ezgif.com-gif-maker%20(2).gif" alt=""></p>
<p><br/><br/><br/><br/><br/><br/><br/><br/><br/>
오늘의 일기는 여기까지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - #40. SnackBar]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-40.-SnackBar</link>
            <guid>https://velog.io/@sharveka_11/Flutter-40.-SnackBar</guid>
            <pubDate>Tue, 31 Aug 2021 16:49:16 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기
참고1 : <a href="https://api.flutter.dev/flutter/material/SnackBar-class.html">https://api.flutter.dev/flutter/material/SnackBar-class.html</a>
참고2 : <a href="https://api.flutter.dev/flutter/material/SnackBarBehavior-class.html">https://api.flutter.dev/flutter/material/SnackBarBehavior-class.html</a>
<br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h1 id="snackbar">SnackBar</h1>
<p>오늘 배워볼 것은 SnackBar
<strong>화면 아래에 짧은 메시지를 보여줄 수 있고, 간단한 동작도 사용가능하다</strong>. 버튼을 넣어서 누르면 또다른 페이지로 간다던가 그런거!</p>
<p>나는 Getx에 입문할 때 거기서 SnackBar를 먼저 써봤는데, Getx 패키지에 있는 SnackBar는 화면 아래뿐만 아니라 위에서도 띄울 수 있고 꾸미기 기능이 좀 더 다양하다. Getx를 주로 쓰는 사람은 Get.snackbar() 를 쓰는 것을 추천한다. 하지만 나는야 기본에 충실한 개발인... 오늘은 ScaffoldMessenger에서 제공하는 SnackBar를 살펴보도록 한다. </p>
<p>*<em>+) *</em>Flutter 유튜브 채널에서 봤을 때는 
Scaffold.of(context).showSnackBar() 의 형태로 쓰던데, 요건 이제 안 쓴다! 영상 보고나서 넣어봤더니 deprecated(더 이상 사용 안함)라고 뜬다.</p>
<p><br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<p>공식 페이지에 나온 코드를 가져다가 뜯어보자.</p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

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

/// This is the main application widget.
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text(&#39;Test SnackBar&#39;)),
        body: const Center(
          child: MyStatelessWidget(),
        ),
      ),
    );
  }
}

/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
  const MyStatelessWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      child: ElevatedButton(
        child: const Text(
          &#39;Show Snackbar&#39;,
          style: TextStyle(fontSize: 30),
        ),
        onPressed: () {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Row(
                children: [
                  Icon(
                    Icons.thumb_up,
                    color: Colors.white,
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: const Text(
                      &#39;Awesome Snackbar!&#39;,
                      style: TextStyle(fontSize: 18),
                    ),
                  ),
                ],
              ),
              action: SnackBarAction(
                label: &#39;Action&#39;,
                onPressed: () {
                //Action 을 누르면 다른 페이지로 이동
                  Navigator.of(context).push(
                    MaterialPageRoute(builder: (context) =&gt; SecondPage()),
                  );
                },
              ),
              // SnackBar 유지 시간
              duration: const Duration(milliseconds: 1500),
              // SnackBar의 가로 길이
              width: 280.0,
              // Inner Padding. 값을 크게 주면  content가 차지하는 부분이 줄어들게 된다.
              padding: const EdgeInsets.symmetric(
                horizontal: 10.0,
              ),
              // 얘는 좀 있다 또 알아보자
              behavior: SnackBarBehavior.floating,
              // SnackBar의 모양 조절
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10.0),
              ),
            ),
          );
        },
      ),
    );
  }
}
</code></pre>
<p>우선 SnackBar는 showSnackBar 로 불러줘야 한다. 예전의 AlertDialog에서 showDialog를 썼던 것과 똑같다. ^^
onPressed 부분은 허전해서 그냥 넣어보았다. 앱바 외에는 아무것도 없는 페이지를 하나 만들어서, 거기로 이동하도록 하였다.</p>
<pre><code class="language-dart">class SecondPage extends StatelessWidget {
  const SecondPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(&quot;SecondPage&quot;),
      ),
    );
  }
}</code></pre>
<p>정말 아무것도 없다.</p>
<p>실행 화면을 확인해보자.
<img src="https://images.velog.io/images/sharveka_11/post/5e277245-da6b-4476-8282-aabaac6218f6/ezgif.com-gif-maker%20(1).gif" alt=""></p>
<p><br/><br/><br/><br/><br/><br/>
기능 자체는 간단해서 그렇게 어렵지 않다.
다만 SnackBarBehavior 는 당장 이해가 안되서 탐색을 좀 해봤다.</p>
<pre><code class="language-dart">behavior: SnackBarBehavior.floating,</code></pre>
<p>요 부분이다. 참고 2에 가면 설명을 볼 수 있다. </p>
<p><strong>우선 SnackBar 위치의 미세한 차이를 만든다. 
그리고 FloatingActionButton이나 BottomNavigationBar 를 포함하고 있다면? 이 때의 SnackBar의 위치를 어떻게 할 것인가! 를 결정하는 속성이다.</strong></p>
<p>FloatingActionButton과 BottomNavigationBar 둘 다 화면 하단에 뜨는 위젯이다. 요런 위젯들이 있고 behavior 속성을 변화시킬 때, SnackBar의 위치가 어떻게 달라지는지 알아보자. </p>
<p>FloatingActionButton은 저번에 쓴 것을 재탕하고, BottomNavigationBar는 테스트용 코드를 급하게 만들어보았다.</p>
<p><br/><br/><br/></p>
<blockquote>
</blockquote>
<h2 id="snackbarbehaviorfixed">SnackBarBehavior.fixed</h2>
<p><strong>SnackBarBehavior.fixed를 쓸 때는 width 속성과 같이 쓰면 안된다.</strong></p>
<p>화면 하단에 아무것도 없을 때는 화면 하단에 붙어서 나타나고, 이와 유사하게 BottomNavigationBar가 있을 때는 그 위에 찰싹 붙어서 나타난다.  반면 FloatingActionButton의 경우는 버튼을 위로 밀어올리고 그 밑에 나타난다. </p>
<table>
<thead>
<tr>
<th>아무것도 없어용</th>
<th>BottomNavigationBar</th>
<th>FloatingActionButton</th>
</tr>
</thead>
<tbody><tr>
<td><img width="250px" alt="afterPress" src="https://images.velog.io/images/sharveka_11/post/c9610361-ea25-4217-8c64-2165c9ea7b94/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202021-09-01%20%EC%98%A4%EC%A0%84%201_38_42.png"></td>
<td><img width="257px" alt="beforePress" src="https://images.velog.io/images/sharveka_11/post/ef6c3b5a-e7f7-469e-a9bb-6ab8030a110b/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202021-09-01%20%EC%98%A4%EC%A0%84%201_34_45.png"></td>
<td><img width="250px" alt="afterPress" src="https://images.velog.io/images/sharveka_11/post/351e439b-aad8-4cad-a633-331820498d92/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202021-09-01%20%EC%98%A4%EC%A0%84%201_32_37.png"></td>
</tr>
</tbody></table>
<p><br/><br/><br/></p>
<blockquote>
</blockquote>
<h2 id="snackbarbehaviorfloating">SnackBarBehavior.floating</h2>
<p>이름처럼 살짝 둥둥 떠서 나타난다. 그리고 elevation(그림자)도 볼 수 있는데, 이 코드에는 elevation 속성을 설정하지 않았다. 설정하지 않은 경우는 기본 값은 6이다.</p>
<table>
<thead>
<tr>
<th>아무것도 없어용</th>
<th>BottomNavigationBar</th>
<th>FloatingActionButton</th>
</tr>
</thead>
<tbody><tr>
<td><img width="250px" alt="afterPress" src="https://images.velog.io/images/sharveka_11/post/900ec0bd-2acb-4820-8936-0e06249e6e79/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202021-09-01%20%EC%98%A4%EC%A0%84%201_42_19.png"></td>
<td><img width="257px" alt="beforePress" src="https://images.velog.io/images/sharveka_11/post/88d559bd-d8bf-4839-82a9-310097ac81e9/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202021-09-01%20%EC%98%A4%EC%A0%84%201_31_17.png"></td>
<td><img width="250px" alt="afterPress" src="https://images.velog.io/images/sharveka_11/post/daff9ccd-bbdf-49fc-a9fa-74239c94bf25/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202021-09-01%20%EC%98%A4%EC%A0%84%201_42_58.png"></td>
</tr>
</tbody></table>
<p><br/><br/><br/><br/><br/>
위에서 사용한 예시 코드는 MyApp부분을 아래처럼 수정하고, SnackBar의 behavior 속성을 바꿔가며 사용하였다. width 속성은 아예 빼버렸다.</p>
<pre><code class="language-dart">class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text(&#39;Test SnackBar&#39;)),
        body: const Center(
          child: MyStatelessWidget(),
        ),
        floatingActionButton: FloatingActionButton.extended(
          onPressed: () {},
          label: const Text(&#39;좋아요&#39;),
          icon: const Icon(Icons.thumb_up),
          backgroundColor: Colors.pink,
        ),
        // bottomNavigationBar: BottomNavigationBar(
        //   items: [
        //     BottomNavigationBarItem(
        //         label: &#39;one&#39;, icon: Icon(Icons.looks_one, color: Colors.blue)),
        //     BottomNavigationBarItem(
        //         label: &#39;two&#39;, icon: Icon(Icons.looks_two, color: Colors.blue)),
        //   ],
        // ),
      ),
    );
  }
}</code></pre>
<p><br/><br/><br/><br/><br/>
오늘의 일기는 여기까지!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - #39. SingleChildScrollView]]></title>
            <link>https://velog.io/@sharveka_11/Flutter-39.-SingleChildScrollView</link>
            <guid>https://velog.io/@sharveka_11/Flutter-39.-SingleChildScrollView</guid>
            <pubDate>Fri, 27 Aug 2021 14:38:09 GMT</pubDate>
            <description><![CDATA[<p>Flutter 일기
참고 : <a href="https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html">https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html</a></p>
<p><br><br><br><br><br></p>
<blockquote>
</blockquote>
<h1 id="singlechildscrollview">SingleChildScrollView</h1>
<p>오늘 배울 것은 SingleChildScrollView.
말 그대로 스크롤 가능한 건데, 자식을 하나만 갖는다. 하나뿐인 자식이 너무 커서 화면에 다 안나오고 삐져나간다던지 할 때 스크롤을 해서 자식을 다 볼 수 있도록 해준다. </p>
<p>보통은 Row 나 Column 위젯과 자주 같이 쓴다. 나열하는 위젯이 많아져서 스크린에 다 나오지 못할 때 Row, Column을 SingleChildScrollView로 감싸주면 스크롤이 가능해지고, Rendering 에러가 나지 않게 된다. </p>
<p><br/><br/><br/><br/><br/><br/><br/></p>
<blockquote>
</blockquote>
<h2 id="코드-예시로-알아보자">코드 예시로 알아보자</h2>
<p>색을 입힌 컨테이너를 나열하는 간단한 예시를 만들어보았다.
우선 SingleChildScrollView가 없을 때 이다.</p>
<pre><code class="language-dart">
import &#39;package:flutter/material.dart&#39;;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyStatelessWidget(),
    );
  }
}

class MyStatelessWidget extends StatelessWidget {
  const MyStatelessWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(&#39;Test SingleChildScrollView&#39;),
      ),
      body: Column(
        children: [
          Container(
            // A fixed-height child.
            color: Colors.yellow.withOpacity(.5),
            height: 120.0,
            alignment: Alignment.center,
            child: const Text(&#39;Fixed Height Content&#39;),
          ),
          SizedBox(
            height: 50,
          ),
          Container(
            // Another fixed-height child.
            color: Colors.green.withOpacity(.5),
            height: 120.0,
            alignment: Alignment.center,
            child: const Text(&#39;Fixed Height Content&#39;),
          ),
          SizedBox(
            height: 50,
          ),
          Container(
            // A fixed-height child.
            color: Colors.yellow.withOpacity(.5),
            height: 120.0,
            alignment: Alignment.center,
            child: const Text(&#39;Fixed Height Content&#39;),
          ),
        ],
      ),
    );
  }
}</code></pre>
<p>Column 안에 컨테이너 3개만 넣어보았는데, 화면 방향이 세로일 때는 괜찮지만 가로일 때는 에러가 나게 된다.
<br/><br/></p>
<table>
<thead>
<tr>
<th>세로</th>
<th>가로</th>
</tr>
</thead>
<tbody><tr>
<td><img width="250px" alt="100" src="https://images.velog.io/images/sharveka_11/post/35f88618-218e-4bb9-bada-96728175bf42/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202021-08-27%20%EC%98%A4%ED%9B%84%2010_53_52.png"></td>
<td><img width="250px" alt="300" src="https://images.velog.io/images/sharveka_11/post/091bd571-0abd-4f61-be60-1c5b79c40a83/Android%20Emulator%20-%20Pixel_4_XL_API_28_5554%202021-08-27%20%EC%98%A4%ED%9B%84%2010_53_58.png"></td>
</tr>
</tbody></table>
<p><br/><br/><br/><br/>
Column만 넣으면 화면에 위젯이 다 나타나지 못할 때 에러가 난다. 이것을 피하기 위해 SingleChildScrollView로 Column을 감싸서, 스크롤이 가능하도록 하는 것이다.</p>
<pre><code class="language-dart">body: SingleChildScrollView(
        child: Column(
          children: [
          ...
          ],
        ),
     ),</code></pre>
<p><img src="https://images.velog.io/images/sharveka_11/post/e02392d8-d660-4f29-abd9-85e6d9852b85/ezgif.com-gif-maker.gif" alt="">
위와 같이 Column이나 Row 위젯 외에도 단 한개의 위젯이 크기가 너무 커서 화면에 안나타날 때 사용해도 된다. 물론 그럴 일이 잘 없어서 Column, Row 와 함께 쓰는 것이 대부분이다.</p>
<p>사실 위의 예제처럼 스크롤 리스트의 위젯들이 항상 스크린의 너비와 같다면, ListView를 쓰는 것이 더 낫다고 한다. 그런데 공부하면서는 SingleChildScrollView를 쓰는 경우를 더 자주 봤다. 왜 그런지 정확히는 모르겠는데, Column이나 Row를 새 위젯으로 감싸는게 더 편리해서 그런것 같...기도 하고</p>
<p><br/><br/><br/><br/><br/><br/>
암튼 오늘의 일기는 여기까지!</p>
]]></description>
        </item>
    </channel>
</rss>