Flutterと云ふものを触った話を語らん

#はじめに
江坂プロクラでは「狂人」と名高いハロウィンでマグロの仮装してた1年次の人間です。Slack名は「神」です。[以前書いてた記事](https://qiita.com/Nemesis/items/e886a36407767ace8a75)
13日目の記事書きました。今日は13日の金曜日です。~~私が厚切りジェイソンだ~~
今回は10月あたりからFlutter触ってアプリ開発の練習がてらリマインダー作ってた話をします。
12月15日にGDG神戸で発表するので発表する点の整理も兼ねて記事書いていきたいと思います。
見て欲しいところとか自慢したいところはコード貼ってくつもり

###Flutterを選んだ理由
・Flutterであれば動いている言語が違うiOSとAndroidの両方で動かせるから別々で書く必要がない
・シンプルでいい感じのデザインが元から用意されている
・興味が湧いた

###環境
APIはFlutterで、言語はDartで、エディタはIntelliJです。開発環境はiOSです。それと作業用にYoutube見てました。

#できたもんの紹介
まだまだ途中ですが軽く紹介します。

###全体の感じ

<img width="427" alt="スクリーンショット 2019-12-03 14.52.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278363/bba48e72-4481-7895-9d67-1e4a4fabb1ef.png">
<img width="430" alt="スクリーンショット 2019-12-03 14.59.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278363/9c28923b-fb8e-09d5-7d65-3da63328d762.png">
<img width="414" alt="スクリーンショット 2019-12-06 10.13.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278363/96da1077-15c7-8a10-5279-e86ada2a8c04.png">


UIはこんな感じです。特に詳しい指定しなくてもいい感じの出してくれるのがFlutterのいいところ。まああとあと変わるかもしれないけど
実装してないところもあるけど実装してるところはそれぞれ紹介します。

###予定の追加画面

<img width="427" alt="ぺ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278363/722c1f07-610a-0b66-c4db-88315892050c.png">

①予定の名前入力欄
ここに予定の名前を入力します。

②予定の備考入力欄
ここに予定の備考を入力します。

③時間指定ボタン
ここで時間を指定すると、その指定した時間に通知を送ってくれます。

④繰り返し通知ボタン
これを「なし」以外のどれかにしておくと、例えば「1日後」であれば最初に指定された時間の1日後にもう一度、といったように決まった周期で繰り返して通知してくれます。

```dart
child:Column(
                  children: <Widget>[
                    Text(
                      '繰り返し通知',
                      style: TextStyle(fontSize: 20,fontWeight: FontWeight.bold),
                    ),
                    RadioListTile(
                      activeColor: Colors.green,
                      title: Text(
                        'なし',
                        style: TextStyle(fontSize: 15,fontWeight: FontWeight.bold),
                      ),
                      value: "none",
                      groupValue: _type,
                      onChanged: repeatNotice,
                    ),
                    RadioListTile(
                      activeColor: Colors.green,
                      title: Text(
                        '1日後',
                        style: TextStyle(fontSize: 15,fontWeight: FontWeight.bold),
                      ),
                      value: "oneDayLater",
                      groupValue: _type,
                      onChanged: repeatNotice,
                    ),
                    RadioListTile(
                      activeColor: Colors.green,
                      title: Text(
                        '1週間後',
                        style: TextStyle(fontSize: 15,fontWeight: FontWeight.bold),
                      ),
                      value: "oneWeekLater",
                      groupValue: _type,
                      onChanged: repeatNotice,
                    ),
                    RadioListTile(
                      activeColor: Colors.green,
                      title: Text(
                        '1ヶ月後',
                        style: TextStyle(fontSize: 15,fontWeight: FontWeight.bold),
                      ),
                      value: "oneMonthLater",
                      groupValue: _type,
                      onChanged: repeatNotice,
                    ),
                  ],
                ),
```

```dart
void repeatNotice(String e)=>setState(() {
    _type = e;//選択されたボタンのvalueの値を設定する
  });
```

⑤ブックマークボタン
詳しくは後述しますが、ブックマークされた状態で予定を追加します。

⑥追加ボタン
ここを押すと、予定を追加できます。追加するときに、入力された情報をローカルストレージに保存したのち、まとめて一個のデータにしてリストに追加します。

```dart
floatingActionButton:FloatingActionButton(
        onPressed: () {
          var random = new math.Random(); //ここから
          var number = random.nextInt(1000000000).toString();
          var supplement = new math.Random();
          if (number.length < 9) {
            for (int i = 0; i <= 9 - number.length; i++) {
              number = number + supplement.nextInt(10).toString();
            }
          } //ここまでの処理についてはリストから削除する要素を特定するためのキーがかぶるとエラーが出るために９桁のIDを作ってそれを回避するための処理。
          // 一番最初に作られた乱数が９桁以下だと自動的に９桁にされる
          _addItem(myController.text, formatter.format(_myDateTime), number, _description.text);
          myController.clear();
          _description.clear();
        },//ここで予定のデータを追加するクラスを呼び出す
        backgroundColor:Colors.black54,
        child: Icon(Icons.add,color:Colors.white,),
      ),
```

```dart
void _addItem(String planName, String time, String number, String description) {
    if (planName != "") {
      setState(() {
        if (description=="") {
          description = "なし";
        }
        items.add({"number": number, "planName": planName, "time": time, "description": description,"bookMark":_onBookMark});//ここでデータをまとめてリストに追加してます。
        savePlanData(planName, time, number, description,_onBookMark,);//ここでローカルストレージにデータ保存してます。
        planTimer(time,planName,number,_type);//ここで繰り返し通知に指定があった時の処理のクラスを呼び出してます。
        _type="none";
        _bookMarkSwitch=false;
        _scaffoldKey.currentState.showSnackBar(
            SnackBar(
              content: Text('予定を追加しました',
              style: TextStyle(color: Colors.white, fontSize: 15,fontWeight: FontWeight.bold)),
              backgroundColor: Colors.black54,
            )
        );
        planNameFocusNode.unfocus();
        descriptionFocusNode.unfocus();
      });
    }
    else {
      _scaffoldKey.currentState.showSnackBar(
          SnackBar(content: Text('エラー:予定名が空白です',
              style: TextStyle(color: Colors.red, fontSize: 15,fontWeight: FontWeight.bold)),
              backgroundColor: Colors.black54
          )
      );
      planNameFocusNode.unfocus();
      descriptionFocusNode.unfocus();
    }
  }
```

```dart
planTimer(String time, String planName,String id,String repeater) {//通知のコード
    var nowTime;
    timer = Timer.periodic(const Duration(seconds: 1), (timer) {//タイマー使って一秒ごとで実行
      nowTime = new DateTime.now();
      for(int i=0;i<items.length;i++){
        if(items[i]["number"]==id) {//予定一覧の中に同じID、つまりその予定があれば実行、なかったらタイマーを止める
          var item = items[i];
          if (time == formatter.format(nowTime)) {
            if(repeater=="none"){
              item["planName"]=item["planName"]+"(実行済み)";
              CommonClass().save();//予定を変更後、データをセーブ
            }
            _onNotification(planName,id,repeater); //通知を送るクラスへ
            timer.cancel();
          }
        }else{
          timer.cancel();
        }
      }
    });
  }
```

```dart
Future _onNotification(String planName,String id,String repeater) async {
    var _repeat;
    await flutterLocalNotificationsPlugin.schedule(
        0, '予定の時間です。', '$planName', DateTime.now().add(Duration(seconds: 1)),
        platformChannelSpecifics, payload: 'item id 2'
    );//通知を送る
    switch(repeater){//もし繰り返し通知が何かしら指定されていたら実行
      case "oneDayLater":
        _repeat=DateTime.now().add(Duration(days:1));//現在の時間の１日後を指定してもう一度上記の通知のクラスへ。あとも一緒
        planTimer(formatter.format(_repeat),planName,id,repeater);
        break;

      case "oneWeekLater":
        _repeat=DateTime.now().add(Duration(days:7));
        planTimer(formatter.format(_repeat),planName,id,repeater);
        break;

      case "oneMonthLater":
        _repeat=DateTime.now().add(Duration(days:30));
        planTimer(formatter.format(_repeat),planName,id,repeater);
        break;

      default:
        break;
    }
  }
```

追加する瞬間に予定ごとにIDを割り振ってます。これは予定の確認画面で予定を削除する時その予定を特定するためのキーがかぶるとエラーが出るので予定の名前や時刻なんかをキーとするとそのエラーが起きかねないので9桁のIDを割り振ることでそのIDをキーにすることで回避してます。THE★力技
ローカルストレージ保存は便利だけど結構型が決まっててちょうど良さそうなのがStringList型のやつしかなくてDynamic型が保存できねぇからわざわざデータ分解して保存してる
同じタイミングでデータを分解してそれぞれのリストに保存してるしロードする時も同じタイミングでリストから取ってきて合体させるから今現在データのズレは起きてないけど起きた時が怖すぎるしもっといいやり方あったら教えてください

###予定の確認画面

<img width="423" alt="スクリーンショット 2019-12-04 11.00.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278363/3d81ddee-c760-49f5-7ea9-6bea9f9c47f6.png">

①予定
予定の追加画面で追加された予定がここに表示されます。
タップすることでブックマークすることができます。ブックマークされた予定は画像のように左端に緑色のマークが出てきます。予定の確認画面の⑤ブックマークボタンはオンにしておくとこのブックマークがされた状態で予定が追加される、というものです。また、スワイプすることで予定を削除できます。ブックマークされた予定は削除できません。

```dart
onTap:(){//ブックマークの判定。Cardに予定を表示して、タップされた時に発動
                      setState(() {
                        if(item["bookMark"]==""){
                          item["bookMark"]="bookMark";
                          bookMarkItems.add(items[index]);//ブックマークリストに追加
                          _scaffoldKey.currentState.showSnackBar(
                              SnackBar(content: Text('予定:${item["planName"]} をブックマークしました', style: TextStyle(color: Colors.white, fontSize: 15,fontWeight: FontWeight.bold,)), backgroundColor: Colors.black54)
                          );
                        }else{
                          item["bookMark"]="";
                          bookMarkItems.remove(items[index]);//ブックマークリストから削除
                          _scaffoldKey.currentState.showSnackBar(
                              SnackBar(content: Text('予定:${item["planName"]} のブックマークを外しました', style: TextStyle(color: Colors.white, fontSize: 15,fontWeight: FontWeight.bold,)), backgroundColor: Colors.black54)
                          );
                        }
                        CommonClass().save();//ローカルストレージに保存
                      });
                    },
```

②予定編集ボタン
押すとメニューが開き、メニューから予定の情報のそれぞれを編集できます。
<img width="431" alt="スクリーンショット 2019-12-04 11.19.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278363/1dc265a3-ddcc-7ab6-a220-b6b36692cd52.png">
<img width="414" alt="スクリーンショット 2019-12-04 11.19.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278363/a7517153-0bc2-d4e4-b8d0-5dbf24053cfb.png">

③ブックマーク確認ボタン
ブックマークした予定のみ、ここで確認することができます。
<img width="431" alt="スクリーンショット 2019-12-04 11.28.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278363/15755868-c529-6985-53b5-83d9687203b6.png">
この画面では、ブックマークされているのは上の予定と下の予定です。すると…
<img width="430" alt="スクリーンショット 2019-12-04 11.31.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278363/5f1af76b-36b4-dc84-1dd4-8b17ee9c6a1b.png">
こうなります。ブックマークした予定のみを取得してきてここで表示してくれます。

###ヘルプ

こっちは後回しにしてたので特に何もないです。アプリについての説明書いたくらい
<img width="425" alt="スクリーンショット 2019-12-05 11.29.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278363/ba9103e8-16c5-e41f-ac04-b1ae3bb8de55.png">

#終わりに

習い事で通ってるプログラミングスクールのDiscordチャンネルで「Flutter存外に楽しい」って呟いたらなんやかんやあってGDG神戸で30分枠もらって発表することになりました。~~Discordで呟いただけなのに~~
元々はスマホアプリ開発→販売までの流れをある程度知るために練習がてら始めたFlutterだったけど本当に楽しいからみんなも是非触って見てね