こんにちは、Flutterエンジニアとしてお仕事をしているyamoriです。
もともと自分は「自分で作ったサービスを世に出したい!」という思いからFlutterを始めました。
プロダクトを作っているうちに、作ること自体も楽しくなってしまい、この記事を書いています。
##1.背景
Flutterでサービスを開発するときに、純粋なCtoCのサービスでない限り、サービス提供側のアクション(たとえばお知らせ画面とかユーザー管理画面とか)が必要になることが多いと思います。
管理画面は、大抵の場合スマホアプリではなくWEBで開発することになると思うのですが、WEBだけReactなど他のフレームワークを作ると学習することが増えてしまいます・・・(それはそれで勉強になるのですが)
この記事は、Flutterの思想である「1つのコードベースから、美しいモバイル、ウェブ、デスクトップ、組み込みアプリケーションを構築、テスト、デプロイする」
Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase.
を活かすべく、管理画面もFlutterで作ってしまおうという試みです。
(引用元:https://flutter.dev/)
##2.完成形
完成形は以下のような感じです。
「ユーザーがアプリに登録したとき、その情報を管理画面にも表示する」というユースケースを想定しています。
##3.はじめよう
###3-1.Flutterプロジェクトの作成
まずは、Flutterプロジェクトを作成しましょう。
ここで、ターミナルで
flutter run -d chrome
と打つことで、chromeでrunすることができます。
###3-2.Firebaseプロジェクトの作成
次に、Firebaseを繋げましょう。
アプリを追加から、
アプリの名前だけ登録し、後のプロセスはスキップしてコンソール画面に戻ります。
コンソール画面の左側にある歯車の設定にいきます。
webプロジェクトのCDNを選択してプロジェクトのweb/index.htmlに設定します。
その後、
pubspec.yamlファイルに
FirebaseCoreとCloudFirestoreのパッケージを追加すれば完了です。(Pub getも忘れずに)
###3-3.WEB画面にデータを表示する
今回、ユーザーがアプリに情報を登録したと仮定して、事前にCloudFirestoreにデータを3つ作っておきます。
これらを管理画面(WEB)に表示していきます!
今回、ファイルを、Domain, View, ViewModel, Repositoryに分けます。
状態管理には簡易的にriverpodを導入します。(今回のプロジェクトで入れる必要性はないと思いますが慣れているのでいれました)
※注意:2021年12月7日時点ですが、riverpodを導入するとビルドできないという問題がありました。channelをstableではなく、masterにすれば動きました。
https://github.com/rrousselGit/river_pod/issues/889
モデルを管理するDomain層(アプリ側でユーザーが登録した名前とEmailアドレス)
import 'package:cloud_firestore/cloud_firestore.dart';
class AppUser {
AppUser({
this.name,
this.email,
});
factory AppUser.fromDocumentSnapshot(DocumentSnapshot snap) {
final Map<String, dynamic> data = snap.data()! as Map<String, dynamic>;
return AppUser(
name: data['name'],
email: data['email'],
);
}
final String? name;
final String? email;
}
Firebaseとのやりとりを行うRepository層
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:sample_management_app/app_user.dart';
class ManagementRepository {
final _db = FirebaseFirestore.instance;
final _collectionPath = 'users';
Future<List<AppUser>> fetchUserList() async {
try {
final snapshot = await _db.collection(_collectionPath).get();
return snapshot.docs.map((e) => AppUser.fromDocumentSnapshot(e)).toList();
} catch (e) {
rethrow;
}
}
}
RepositoryとViewを繋ぐロジックを担当するViewModel
import 'package:flutter/cupertino.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:sample_management_app/management_repository.dart';
import 'app_user.dart';
final userListModel = ChangeNotifierProvider(
(ref) => ManagementViewModel()..init(),
);
class ManagementViewModel extends ChangeNotifier {
final ManagementRepository _repository = ManagementRepository();
List<AppUser> userList = [];
Future<void> init() async {
userList = await _repository.fetchUserList();
notifyListeners();
}
}
WEB画面に描画するView
import 'package:flutter/material.dart';
import 'management_view_model.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class ManagementView extends HookConsumerWidget {
const ManagementView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final vm = ref.watch(userListModel);
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
itemCount: vm.userList.length,
itemBuilder: (_, index) {
final user = vm.userList[index];
return ListTile(
title: Text(user.name!),
trailing: Text(user.email!),
);
},
),
);
}
}
main.dartにRiverpodやFirebaseのinitializeの処理をして、ビルドすると、、、
CloudFirestoreの情報をリストの形で取得することができました!
デザインを整えれば管理画面っぽくなると思います。
##4. 終わりに
Flutter for Webで、管理画面を構築してみました。
Flutter1つで、iOSもAndroidもWebも開発できるのは魅力的ですね。
まだまだ細かいところは実装できない・実装しづらい部分もあるみたいですが、徐々に改善されるのではないかと思っています。
多くの人が使うプロダクトを作りたいなあ。
今年もあと20日ほどですが、良い年末をお過ごしください。