NCMBのFlutter SDKを使ってカメラメモアプリを作る（その3：写真とメモを表示する）

NCMBでは公式SDKとしてSwift/Objective-C/Java/Unity/JavaScript SDKを用意しています。また、それ以外にもコミュニティSDKとして、非公式ながらFlutter/React Native/Google Apps Script/C#/Ruby/Python/PHPなど幅広い言語向けにSDKが開発されています。

今回はコミュニティSDKの一つ、Flutter SDKを使ってカメラメモアプリを作ってみます。[前回は写真を選択して、メモデータと一緒に保存するまでの流れを解説しました](https://qiita.com/goofmint/items/a18e14d8962d36869cc6)。今回は保存したデータの取得と表示までを解説します。

## 完成版のコード

作成したデモアプリのコードは[NCMBMania/flutter_camera_memo: FlutterとNCMBによるカメラメモアプリです](https://github.com/NCMBMania/flutter_camera_memo)にアップロードしてあります。

## 一覧画面について

一覧画面は `ListPage` と `_ListPageState` を使っています。`ListPage` は `_ListPageState` を読み込んでいるだけです。

```dart
class ListPage extends StatefulWidget {
  const ListPage({
    Key? key,
  }) : super(key: key);
  @override
  State<ListPage> createState() => _ListPageState();
}
```

## 一覧画面の構築

![](https://camo.qiitausercontent.com/af9e7ea9508e5badffdb39c1b747c290074d4560/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3139373032362f64356138373339342d623761652d373333382d663861652d6463376235333838333565652e706e67)

`_ListPageState` で使っている変数は次の通りです。

```dart
// 一覧画面
class _ListPageState extends State<ListPage> {
  // メモ一覧が入る配列
  var _memos = [];
```

### 初期化

初期化時にNCMBのデータストアからメモを取得します。データを取得する際には `NCMBQuery` を利用します。そして `fetchAll` にてデータを取得します。

```dart
@override
void initState() {
	super.initState();
	getMemos();
}

// 登録されているメモを取得する
Future<void> getMemos() async {
	// 検索対象のクラス
	var query = NCMBQuery('Memo');
	// 取得件数
	query.limit(100);
	// 検索実行
	var ary = await query.fetchAll();
	// 結果をセット
	setState(() {
		_memos = ary;
	});
}
```

### 画面構築

メモの一覧を取得していれば、それを `ListView` で一覧表示します。1件ずつのデータは `ListItem` （後述）にて描画します。

```dart
@override
Widget build(BuildContext context) {
	return ListView.builder(
		itemBuilder: (BuildContext context, int index) {
			var memo = _memos[index];
			return Container(
					decoration: const BoxDecoration(
						border: Border(
							bottom: BorderSide(color: Colors.black38),
						),
					),
					child: ListItem(memo));
		},
		itemCount: _memos.length,
	);
}
```

## 1件ずつの表示

一覧で1件ずつの表示を行うのは `ListItem` と `_ListItemState` です。 `ListItem` は受け取ったメモデータを保持しています。

```dart
class ListItem extends StatefulWidget {
  final NCMBObject memo;
  const ListItem(this.memo, {Key? key}) : super(key: key);

  @override
  State<ListItem> createState() => _ListItemState();
}
```

### 画面構築

`_ListItemState` は以下の変数を保持しています。

```dart
// 一覧時の行データ
class _ListItemState extends State<ListItem> {
  // 画像データが入るバイト列
  Uint8List? _image;
```

### 初期化

`_ListItemState` の初期化時に `getPhoto` を使ってNCMBのファイルストアから写真データをダウンロードします。写真データのダウンロードは `NCMBFile.download` にて可能です。

```dart
@override
void initState() {
	super.initState();
	getPhoto();
}

// 写真をNCMBから取得する
Future<void> getPhoto() async {
	// ダウンロード
	var file = await NCMBFile.download(widget.memo.getString('fileName'));
	// セット（dataにデータが入っている）
	setState(() {
		_image = file.data as Uint8List;
	});
}
```

### 画面描画

![](https://camo.qiitausercontent.com/7a22c1c0578829486539d48290afcd6f487b28e0/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3139373032362f34393733623638392d333735632d343030642d613564322d3234373039623839393661372e706e67)

画面描画は `ListTile` にて行っています。最初はアイコンを表示し、画像がダウンロードできれば差し替えています。 `viewDate` は日付をフォーマットして返却する関数です。

```dart
// 画面描画
@override
Widget build(BuildContext context) {
	return ListTile(
		leading: _image != null
				? Image.memory(
						_image!,
						width: 50,
					)
				: const Icon(
						Icons.photo,
						color: Colors.blue,
						size: 50,
					),
		title: Text(viewDate(widget.memo.getDateTime('createDate'))),
		subtitle: Text(
			widget.memo.getString('text', defaultValue: ''),
			overflow: TextOverflow.ellipsis,
			maxLines: 2,
		),
		onTap: () {
			Navigator.push(context, MaterialPageRoute(builder: (_) {
				return ImagePage(_image!);
			}));
		},
	);
}

// 日付はフォーマットして表示
String viewDate(DateTime date) {
	var format = DateFormat('MM月dd日 HH:mm');
	return format.format(date);
}
```

## 一覧をタップした時の処理

一覧をタップした際には `ImagePage` を呼び出しています。

```dart
// 抜粋
Navigator.push(context, MaterialPageRoute(builder: (_) {
	return ImagePage(_image!);
}));
```

ImagePageは受け取った画像データを全体表示するだけのウィジェットです。後はスワイプ操作で一覧画面に戻れるようにしています。

```dart
// 一覧をタップ時に画像を拡大表示する
class ImagePage extends StatelessWidget {
  // 画像データ
  final Uint8List _image;
  const ImagePage(this._image, {Key? key}) : super(key: key);

  // 描画用
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        child: Center(
          child: Hero(
            tag: 'imageHero',
            child: Image.memory(_image),
          ),
        ),
        onPanUpdate: (details) {
          // Swiping in right direction.
          if (details.delta.dx > 0) {
            Navigator.pop(context);
          }
        },
      ),
    );
  }
}
```

![](https://camo.qiitausercontent.com/0e6dd88bc8463206f12b98fe3d71a4630fc651ae/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3139373032362f32356334316130362d343839392d316663342d356233632d6566613239346138353236382e706e67)

## まとめ

今回までの流れでメモデータと写真をNCMBへアップロードし、さらに一覧で表示するまでの流れが完成です。写真データはアプリでもよく扱うデータだと思うので、この方法を参考にしてください。

NCMBでは他にもプッシュ通知であったり、IDやパスワードを使った認証、サーバサイドにコードを配置するスクリプト機能があります。ぜひご利用ください。

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