Flutter でリストベースのアプリをサクッと作る

[Rapido](https://pub.dartlang.org/packages/rapido) と云う Flutter 用の RAD フレームワークを利用して、リストベースのアプリ（メモ帳、タスク管理等）を簡単に作る方法を紹介します。Android Studio + Flutter の開発環境が整っている前提です。

#### まずは準備から

Android Studio で Flutter の新規プロジェクト (Flutter Application を選択) を作ります。

プロジェクトツリーから *pubspec.yaml* を開いて dependencies に以下を追加。

```yaml
dependencies:
  rapido: ^0.0.12
```
エディタ右上の `Packages get` をクリックするとインストールされます。下のようなエラーが出たら先に `Flutter update` を実行してからやり直します。

```bash
Because rapido >=0.0.11 depends on google_maps_flutter >=0.0.3 which requires Flutter SDK version >=0.11.9 <2.0.0, rapido >=0.0.11 is forbidden.
So, because flutter_rapido_app depends on rapido ^0.0.11, version solving failed.
```

#### サンプルを動かしてみる
*Main.dart* の中身を以下のコードでごっそり入れ替えます。サンプルと言っても基本となるテンプレのようなものなので、ここから始めるのがよいです。

```dart
import 'package:flutter/material.dart';
import 'package:rapido/documents.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Rapido Demo',
      home: RapidoExample(title: 'Rapdio'),
    );
  }
}

class RapidoExample extends StatefulWidget {
  RapidoExample({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _RapidoExampleState createState() => _RapidoExampleState();
}

class _RapidoExampleState extends State<RapidoExample> {
  final DocumentList documentList = DocumentList(
    "task list",
    labels: {"開始日": "date", "タイトル": "task", "優先度": "pri count"},
  );

  @override
  Widget build(BuildContext context) {
    return DocumentListScaffold(
      documentList,
      title: "タスク",
    );
  }
}

```

コードはこれだけですが実行するとこんな感じのアプリが起動します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/188470/5955c6d7-9d51-d974-d024-b46e488797d8.png" width="30%" /> <img src="https://qiita-image-store.s3.amazonaws.com/0/188470/44f170d3-f803-2e10-41c3-5ce27192adc5.png" width="30%" /> <img src="https://qiita-image-store.s3.amazonaws.com/0/188470/bb8b16a6-d68c-bb14-7021-eededb42f769.png" width="30%" />
機能もタスク追加、保存、編集、ソート、削除とあり、すでに簡単なタスク管理アプリの体を成しています。

ここから自分の用途・好みにあったアプリに書き換えていきます。カスタマイズ対象は管理するデータと見た目です。

#### タスク管理するデータのカスタマイズ

Rapido でのデータの追加はとても簡単でデータベース周りの変更や、モデル、UI を作ったりする必要はなく DocumentList の `labels` にコロン区切りの文字列のペアを追加するだけです。試しに`"ノート": "note"` と追加して実行すると

```dart
  final DocumentList documentList = DocumentList(
    "task list",
    labels: {"開始日": "date", "タイトル": "task", "優先度": "pri count", "ノート": "note"},
  );
```
<img src="https://qiita-image-store.s3.amazonaws.com/0/188470/df18abe4-a1cd-0a0d-af99-784b90e82de5.png" width="30%" /> <img src="https://qiita-image-store.s3.amazonaws.com/0/188470/33beea85-9fc8-a0ea-d97c-440cd355eb15.png" width="30%" />
一番下にちゃんと「ノート」が追加されています。これだけのコード変更で UIコンポーネント の追加のみならず、データの永続化処理も追加されています。

この文字列のペア`"ノート": "note"` は左側が UI 上での表示に、そして右側がとても重要で、どう内部で処理されるかを決める手がかりになります。「開始日」 のように `"date"` で終わるものは Date 型、「優先度」 のように `"count"` で終わるものは数値型、その他は文字列として扱われます。今回は使いませんが `"map_point"` とすると Google Maps を使ったロケーションピッカーを組み込むことも可能です（Android では完動しません）。

右側のスクショでレイアウトが崩れているのは、デフォルトではすべてのデータを表示しようとするので、ノート部分が入り切らないからです。こう云う場合は DocumentListScaffold に `titleKeys` で必要なものだけを指定して表示を減らすことができます。

```dart
  @override
  Widget build(BuildContext context) {
    return DocumentListScaffold(documentList,
        title: "Tasks", titleKeys: ["date", "task", "pri count"]);
  }
```
ついでにノートも複数行表示可能な `subtitleKey` を使って表示できるようにします。

```dart
  @override
  Widget build(BuildContext context) {
    return DocumentListScaffold(
      documentList,
      title: "Tasks",
      titleKeys: ["date", "task", "pri count"],
      subtitleKey: "note",
      ...
    );
  }
```
<img src="https://qiita-image-store.s3.amazonaws.com/0/188470/a05ae6e3-1750-4e83-ce38-a9a0c1c201da.png" width="30%" />

崩れなしに表示されるようになりました。

#### 見た目のカスタマイズ

ここまでは Rapido が提供するデフォルトのレンダリングを利用しましたが、ある程度自由にカスタマイズができないと不便です。見た目を少し変更してみます（デモ用なのでデザイン性は無視）。

DocumentListScaffold の `decoration` を使って背景に色を付けてみます。

```dart
      decoration: BoxDecoration(color: Colors.orange),
```
これで背景がオレンジになります（ボタンを押しての部分は一番下のコードを見てください）。

<img src="https://qiita-image-store.s3.amazonaws.com/0/188470/bdbb870d-96f6-2e94-4599-a0739f7475f1.png" width="30%" />

もちろん、背景には画像も指定することができますが今回は簡潔さを優先します（画像も数行増えるくらいで出来ます）。

一覧の各タスクの表示も変更してみます。DocumentListScaffold の `customItemBuilder` を利用します（※customItemBuilder を使う場合は titleKeys 及び subtitleKey は無視されます）。

簡単な例としてタスク情報を Card 内に並べるだけの変更を行います。

```dart
  @override
  Widget build(BuildContext context) {
    return DocumentListScaffold(
      documentList,
      customItemBuilder: _customBuilder,
      ...
    );
  }

  Widget _customBuilder(int index, Document doc, BuildContext context) {
    return Card(
      child: Column(
        children: <Widget>[
          Text(documentList[index]["task"],
              style: Theme.of(context).textTheme.title),
          Text(documentList[index]["date"].toString()),
          Text(documentList[index]["pri count"].toString()),
          Text(documentList[index]["note"])
        ],
      ),
      color: Colors.blueAccent,
    );
  }
```
<img src="https://qiita-image-store.s3.amazonaws.com/0/188470/8f083962-84f5-a59b-a36d-404ad7d20af8.png" width="30%" />
各データを縦に並べて、タイトルのテキストサイズを少し大きくしただけですが簡単に見た目の変更ができました。

メニューが消えているので追加します。下の一行を Column の children に追加するだけです。

```dart
DocumentActionsButton(documentList, index: index)
```
が、愚直にこのレイアウトで Text たちの下に追加するとカードの中央一番下にメニューが出てくるので少しいじります。

```dart
  Widget _customBuilder(int index, Document doc, BuildContext context) {
    return Card(
      child: Row(
        children: <Widget>[
          Expanded(
            child: Column(
              children: <Widget>[
                Text(documentList[index]["task"],
                    style: Theme.of(context).textTheme.title),
                Text(documentList[index]["date"].toString()),
                Text(documentList[index]["pri count"].toString()),
                Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text(documentList[index]["note"]),
                )
              ],
            ),
          ),
          DocumentActionsButton(documentList, index: index)
        ],
      ),
      color: Colors.blueAccent,
    );
  }
```
<img src="https://qiita-image-store.s3.amazonaws.com/0/188470/cab907cb-e066-b3ea-c18f-aa04b2999c33.png" width="30%"/>

優先度によってカードの色を変更したりするコードもついで追加してこんな感じに（コードは一番下にあります）。
<img src="https://qiita-image-store.s3.amazonaws.com/0/188470/d23a4433-ccc2-4bc6-ca71-5a6766a74144.png" width="30%" />
無理やり見た目を変えたので出来上がりがちょっとあれですが、Rapido を使うと簡単にこの類のアプリが作れそうだと云うことは伝わったのではないでしょうか。下にあるのがこのアプリの全コードです。半分以上が見た目のカスタマイズで、基本部分は DocumentList と DocumentListScaffold を数行実装するだけで Rapido が善きに処理してくれていることが分かると思います。

```dart
import 'package:flutter/material.dart';
import 'package:rapido/documents.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Rapido Demo',
      home: RapidoExample(title: 'Rapdio'),
    );
  }
}

class RapidoExample extends StatefulWidget {
  RapidoExample({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _RapidoExampleState createState() => _RapidoExampleState();
}

class _RapidoExampleState extends State<RapidoExample> {
  final DocumentList documentList = DocumentList(
    "task list",
    labels: {"開始日": "date", "タイトル": "task", "優先度": "pri count", "ノート": "note"},
  );

  @override
  Widget build(BuildContext context) {
    return DocumentListScaffold(
      documentList,
      title: "タスク",
      titleKeys: ["date", "task", "pri count"],
      subtitleKey: "note",
      customItemBuilder: _customBuilder,
      emptyListWidget: Center(
        child: Text(
          "ボタンを押してタスクを追加",
          textAlign: TextAlign.center,
        ),
      ),
      decoration: BoxDecoration(color: Colors.orange),
    );
  }

  Widget _customBuilder(int index, Document doc, BuildContext context) {
    return Card(
      child: Row(
        children: <Widget>[
          Padding(
            padding: EdgeInsets.all(8.0),
            child: Text(documentList[index]["pri count"].toString(),
                style: Theme.of(context).textTheme.display1),
          ),
          Expanded(
            child: Column(
              children: <Widget>[
                Text(documentList[index]["task"],
                    style: Theme.of(context).textTheme.title),
                Text(documentList[index]["date"].toString()),
                Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text(documentList[index]["note"]),
                )
              ],
            ),
          ),
          DocumentActionsButton(documentList, index: index)
        ],
      ),
      color: _calculateColor(documentList[index]["pri count"]),
    );
  }

  Color _calculateColor(int priority) {
    if (priority < 3) {
      return Colors.red;
    } else if (priority < 7) {
      return Colors.yellow;
    }
    return Colors.green;
  }
}
```

#### 終わりに
Rapido を使って本格的な商用アプリを作ることはないと思いますが、個人アプリでなら問題なく利用出来ると思います。また、Flutter を始めたばかりの人は好きなデータを追加した上で裏の処理を気にせず、完動するアプリを使いながらレイアウトの組み方を学ぶのにも良いのではと思います（寧ろこっちの用途でお勧め）。

[Flutter Meetup Tokyo](https://flutter-jp.connpass.com/) と云う Flutter の勉強会を不定期（１～１ヶ月半置きくらい）で開催しています。Flutter に興味がある方は一度足を運んでみてください。


#### 参考資料
https://rapido-mobile.github.io/tutorials/introduction.html
https://www.youtube.com/channel/UCeoRpyhpNJmiMuAEJ4WRljg/featured