Zapp!でニフクラ mobile backendのデモ（Todoアプリ）を体験できます

[Zapp!](https://zapp.run/)はFlutterアプリをクラウド環境で開発できるサービスです。DartPadと違って、任意のFlutter/Dartパッケージをインストールできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f2a436a4-679b-4a04-4a8b-bc938152fe96.png)


そんなZapp!を使って、ニフクラ mobile backendのFlutter SDKを利用したデモアプリであるTodoアプリを体験できるようにしました。

## アプリの概要

アプリは2画面で作られています。

### Todo一覧画面

![0123 zt006jut106.zapp.page - 0922143642.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/a0fc372c-bc0b-eaf6-c571-428473341461.jpeg)


NCMBのデータストアからTodoを一覧取得、画面に表示します。

### Todo編集画面

![0123 zt006jut106.zapp.page - 0922143645.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/ea1232ad-9232-c70f-450c-ff36311899b2.jpeg)


一覧画面でタップされたTodoを編集する画面です。新規登録でも同じ画面を使っています。

## アプリの機能

データストアのCRUD操作（データの取得、新規作成、更新、削除）が体験できます。タスクのスワイプで削除が出ます。

![0123 zt006jut106.zapp.page - 0922143647.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/85132070-cfc6-0a44-339e-8a06876ce51f.jpeg)

データは自由に操作してもらって構いません。

## アプリのURL

Todoアプリは下記のURLで試せます。

https://zt006jut106.zapp.page/#/

## アプリのコード

アプリのコードは下記になります。

https://zapp.run/embed/zt006jut106?theme=dark&lazy=true&split=50

Zapp!は誰でも編集できるような気がするので、こちらに予備としてコードを全文残しておきます。

```dart
import 'package:flutter/material.dart';
import 'package:ncmb/ncmb.dart';

void main() {
  // NCMBを初期化
  NCMB('9170ffcb91da1bbe0eff808a967e12ce081ae9e3262ad3e5c3cac0d9e54ad941', '9e5014cd2d76a73b4596deffdc6ec4028cfc1373529325f8e71b7a6ed553157d');
  // 最初に表示するWidget
  runApp(MyTodoApp());
}

class MyTodoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 右上に表示される"debug"ラベルを消す
      debugShowCheckedModeBanner: false,
      // アプリ名
      title: 'タスクアプリ',
      theme: ThemeData(
        // テーマカラー
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      // リスト一覧画面を表示
      home: TodoListPage(),
    );
  }
}

// タスク一覧画面用Widget
class TodoListPage extends StatefulWidget {
  @override
  _TodoListPageState createState() => _TodoListPageState();
}

// タスク一覧画面用ステート
class _TodoListPageState extends State<TodoListPage> {
  // Todoリストのデータ（初期値は空）
  List<NCMBObject> todoList = [];

  // 初期データをセットアップする処理
  @override
  void initState() {
    super.initState();
    getAllTask();
  }

  // NCMBから既存のTodoリストを取得する処理
  void getAllTask() async {
    // Todoクラス（DBで言うテーブル相当）を検索するクエリーオブジェクト
    var query = NCMBQuery('Todo');
    // データを取得
    var items = await query.fetchAll();
    // データを適用
    setState(() {
      todoList = items
        .map((item) => item as NCMBObject)
        .toList();
    });
  }

  // 画面構築
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 画面上部に表示するAppBar
      appBar: AppBar(
        title: Text('タスク一覧'),
        // タスク追加用のアイコンを設置
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () async {
              // 新しいTodoを取得
              final NCMBObject item = await Navigator.of(context).push(
                MaterialPageRoute(builder: (context) {
                  // タスクの追加、編集を行う画面への遷移
                  return TodoPage(
                    // 初期値としてNCMBObjectを渡す
                    todo: NCMBObject('Todo'),
                  );
                }),
              );
              // レスポンスがあれば、リストに追加
              // キャンセルされた場合は null が来る
              if (item != null) {
                setState(() {
                  todoList.add(item);
                });
              }
            },
          ),
        ],
      ),
      // データを元にListViewを作成
      body: ListView.builder(
        itemCount: todoList.length,
        itemBuilder: (context, index) {
          final item = todoList[index];
          // スワイプで削除する機能
          return Dismissible(
            key: Key(item.get('objectId') as String),
            child: Card(
              child: ListTile(
                title: Text(item.get('body') as String),
                onTap: () async {
                  // タップした際には編集画面に遷移する
                  final NCMBObject obj = await Navigator.of(context).push(
                    MaterialPageRoute(builder: (context) {
                      return TodoPage(
                        todo: item,
                      );
                    }),
                  );
                  // 編集後のデータがあれば、リストを更新する
                  if (obj != null) {
                    setState(() {
                      todoList[index] = obj;
                    });
                  }
                },
              ),
            ),
            direction: DismissDirection.endToStart,
            // スワイプした際に表示する削除ラベル
            background: new Container(
                padding: EdgeInsets.only(right: 20.0),
                color: Colors.red.shade500,
                child: new Align(
                  alignment: Alignment.centerRight,
                  child: new Text('削除',
                      textAlign: TextAlign.right,
                      style: new TextStyle(color: Colors.white)),
                )),
            // スワイプした際に処理
            onDismissed: (direction) {
              // スワイプされた要素をデータから削除する
              setState(() {
                todoList[index].delete();
                todoList.removeAt(index);
              });
              // Snackbarを表示する
              ScaffoldMessenger.of(context)
                  .showSnackBar(SnackBar(content: Text('削除しました')));
            },
          );
        },
      ),
    );
  }
}

// Todoを受け取るステートフルウィジェット
class TodoPage extends StatefulWidget {
  TodoPage({
    Key? key,
    required this.todo,
  }) : super(key: key);

  final NCMBObject todo;

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

// Todoの追加、または更新を行うウィジェット
class _TodoPageState extends State<TodoPage> {
  // テキスト入力用
  String? _text;
  @override
  Widget build(BuildContext context) {
    // 画面表示用のラベル
    final label = widget.todo.get('objectId') != null ? 'リスト更新' : 'リスト追加';
    return Scaffold(
      // 画面上部に表示するAppBar
      appBar: AppBar(
        title: Text(label),
        actions: <Widget>[
          // 新規保存、更新用のボタン
          IconButton(
            icon: Icon(Icons.save),
            onPressed: () async {
              // 保存処理
              widget.todo.set('body', _text!);
              await widget.todo.save();
              // 前の画面に戻る
              Navigator.of(context).pop(widget.todo);
            },
          ),
        ],
      ),
      body: Container(
        // 余白を付ける
        padding: EdgeInsets.all(64),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const SizedBox(height: 8),
            // テキスト入力
            TextFormField(
              // 初期値の設定。デフォルト値は空文字
              initialValue: (_text = widget.todo.getString('body', defaultValue: "")),
              onChanged: (String value) {
                _text = value;
              },
            ),
          ],
        ),
      ),
    );
  }
}
```

## コードの解説

このTodoアプリは[【ハンズオン資料】NCMBのFlutter SDKを使ってTodoアプリを作る - Qiita](https://qiita.com/goofmint/items/67ccaabf89921dc3eb1d)のコードを利用しています。Flutter 3.0.5に合わせて多少の修正が入っています。

## まとめ

FlutterアプリでNCMBを使う際の参考として、ぜひご覧ください。データストアの基本的な使い方が分かるはずです。

[mBaaSでサーバー開発不要！ | ニフクラ mobile backend](https://mbaas.nifcloud.com/)