消えちゃうメモで使われている小技紹介

---
# :book: Flutterの記事を整理し本にしました :book:
https://zenn.dev/kazutxt/books/flutter_practice_introduction

* <font color="red"><b>本稿の記事を含む様々な記事を体系的に整理し本にまとめました</b></font>
* 今後はこちらを最新化するため、<font color="red"><b>最新情報はこちらをご確認くださ</b>い</font>
* 10万文字を超える超大作になっています(笑)

---

# はじめに
## 概要
* 消えちゃうメモアプリを作ったので、その詳細を紹介します。
* 買い物とかのメモを残すのですが、うまく行ってもうまくいかなくても消すメモってあるなっとおもって、時限式メモのアプリを作りました。


## 対象読者
* これからFlutterでアプリを作ってみたいと思っている方
* Flutterの実アプリでどんな技術が使われているか知りたい方

## 関連記事
* [Flutter系の記事のまとめ](https://qiita.com/kazutxt/items/524acb1c134015682ccf)

# ダウンロード先
* [GooglePlay](https://play.google.com/store/apps/details?id=com.kazutxt.disappearing_taskmemo)
* [AppStore](https://apps.apple.com/us/app/%E6%B6%88%E3%81%88%E3%81%A1%E3%82%83%E3%81%86%E3%83%A1%E3%83%A2/id1555927986)


# 本編
## 全体像
* まずどんなアプリかを簡単に紹介し、その後使われている技術を紹介します。
* 実際にアプリをダウンロードして、動作と下記の解説を対比させながらご覧頂くと分かりやすいかと思います。 

## アプリ紹介
このアプリは、時限式のメモを残すことができます。
時間はいくつかの選択肢から選ぶことができ、削除や時間のリセットも可能です。

![pic1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/9bc979c5-4635-3bab-d1a9-24e4eeb8056e.jpeg)
![pic2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/8456314a-1610-d4bd-e6a8-9c9046c616ef.jpeg)

## 使われている機能
## Markdownを使う

![pic3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/1dcdbb34-ceee-a1cc-849e-bb40e50cf98d.jpeg)

#### ソース
```yaml:pubspec.yaml
dependencies:
  flutter_markdown: ^0.4.4
````


``` memo.dart
Widget build(BuildContext context) {
    return Scaffold(
      appBar: /* 中略 */
      body: SingleChildScrollView(
          // スクロールできるようにしておくのと、パッディングをいれてみやすくしておく
          physics: AlwaysScrollableScrollPhysics(),
          child: Container(
              child: Padding(
                  padding: const EdgeInsets.all(10.0),
                  child: MarkdownBody(data: markdownSource)))),
    );
  }

final String markdownSource = '''
# Markdownでいろいろ
/* 中略 */
''';
```

## プルダウンを行う

![pic4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/8c8e1c99-26e0-0fa5-2dca-e6560028c488.jpeg)


```memo.dart
DropdownButton<String>(
                            value: _selectDuration,
                            onChanged: _handleChange,
                            items: TimeToLive.values.map((v) {
                              return DropdownMenuItem<String>(
                                value: v.name,
                                child: Text(v.name),
                              );
                            }).toList(),
                          )
```

``` TimeToLive.dart
enum TimeToLive {
  TEN_MINUTES,
  ONE_HOUR,
  ONE_DAY,
  ONE_WEEK,
  ONE_MONTH,
  ONE_YEAR,
}
extension TimeToLiveExt on TimeToLive {
  String get name {
    switch (this) {
      case TimeToLive.TEN_MINUTES:
        return '10分';
        break;
      case TimeToLive.ONE_HOUR:
        return '1時間';
        break;
      case TimeToLive.ONE_DAY:
        return '1日';
        break;
      case TimeToLive.ONE_WEEK:
        return '1週';
        break;
      case TimeToLive.ONE_MONTH:
        return '1ヶ月';
        break;
      case TimeToLive.ONE_YEAR:
        return '1年';
        break;
    }
    return "1日";
  }
```

## 複数行の入力をする/フォーカスをコントロールする

![pic5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/63464888-357d-eba1-b3c4-729d96ae5a2d.jpeg)


```memo.dart
TextFormField(
                            //initialValue: "",
                            enabled: true,
                            focusNode: _focusNode,
                            maxLines: null,
                            keyboardType: TextInputType.multiline,
                            controller: _textEditingController,
                            decoration: InputDecoration(
                              hintText: "メモ",
                            ),
                            onChanged: (String value) {
                              memo = value;
                            })),
```
keyboardType:にTextInputType.multilineを設定するEnterが改行になります。
※通常はEnterは確定になります

forcusNode:にForcusNode型を与えると、フォーカスをコントロールできます。
_forcusNode.unforcus();でフォーカスを外せます

controllerにTextEditingController型を与えると、細かいコントロールができます。
別箇所で入力された値を取得したり、clearで入力をリセットしたりできます。


## キー入力時にキーボード入力エリア部分にあるWidgetを底上げをする

![pic6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/19cdbc57-034a-4fb1-beb7-ebd1932e8823.jpeg)


``` memo.dart 
Widget build{
 return Scaffold(
   resizeToAvoidBottomInset: false //
   body: SingleChildScrollView( 
     reverse: true,
     child: /*中略*/
   ),
 );
}
```
resizeAboidBottomInsetはキー入力のときに、底上げされたときにtrue(デフォルト)だと重ならないようにする。falseだとそのまま表示するようになります。

SingleChildScrollView要素がはみ出したときにスクロールを行うWidgetになります。通常は要素がはみ出すたびに下にスクロールするようになるのですが、今回はreverseをtrueにして、はみ出すたびに上にスクロールして行くようにしています。



## ダークモードを実装する

![pic7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/658b284a-d1e4-219b-9c9f-e4b5483928b6.jpeg)


```yaml:pubspec.yaml
provider: ^"4.3.3#
``` 

* 切り替え用のクラスを準備します

``` memo.dart
// ライトモードとダークモードを切り替えるためのクラス
import 'package:flutter/material.dart';
class AppTheme extends ChangeNotifier {
  AppTheme() {
    _isDark = false;
  }
  bool get isDark => _isDark;
  bool _isDark;
  ThemeData buildTheme() {
    if (isDark) {
      return ThemeData(
          primarySwatch: Colors.green,
          visualDensity: VisualDensity.adaptivePlatformDensity,
          brightness: Brightness.dark);
    } else {
      return ThemeData(
          primarySwatch: Colors.green,
          visualDensity: VisualDensity.adaptivePlatformDensity,
          brightness: Brightness.light);
    }
  }
  void setMode(isDark) {
    _isDark = isDark;
    notifyListeners();
  }
  void changeMode() {
    _isDark = !_isDark;
    notifyListeners();
  }
}
```

* main.dartで切り替えを反映できるようにしておきます

``` main.dart
void main() {
  //ダークテーマへの切り替えをするためにProviderを使う
  runApp(
    ChangeNotifierProvider(
      create: (_) => AppTheme(),
      child: MyApp(),
    ),
  );
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: Provider.of<AppTheme>(context).buildTheme(),
      home: MyHomePage(title: 'タイトル'),
    );
  }
}
```

* 利用する場所

```dart
SwitchListTile(
          title: Text('テーマ'),
          subtitle: Text("ライト/ダーク"),
          value: fl.darkFlag,
          onChanged: (bool value) {
            setState(() {
	     　// スイッチを切り替えるために状態変化
              fl.darkFlag = value;
            });
	    // ライトテーマ/ダークテーマの切り替え
            Provider.of<AppTheme>(context, listen: false).changeMode();
            Save.saveFlag(fl); //状態を保存する
          },
          secondary: Icon(FontAwesomeIcons.glasses, color: Colors.teal),
        )
```
---

# 補足
## 備考
* 今回は、Providerとcontextは少しむずかしいので触れません。気になる方は調べてみると理解が深まるかと思います。
* ソースコードは可能な限り主要な要素を残すように記載しておりますが、単純コピーでは動作しないものが多いと思いますので、ご自身のコードに合わせてご活用ください
* 自分も勉強とTry&Errorをしながらなので、もっと良い実装方法があるかもしれません。
    * 一例として見ていただけると幸いです

## 参考文献
* [Flutter公式サイト](https://flutter.dev/)
* [アプリの公式サイト](https://smart-simple-tools.kazutxt.com/)