LoginSignup
6
6

More than 1 year has passed since last update.

Flutterだけでサービスを完結させたい、欲張りな僕みたいなあなたへ。

Last updated at Posted at 2021-12-07

こんにちは、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.完成形

完成形は以下のような感じです。
「ユーザーがアプリに登録したとき、その情報を管理画面にも表示する」というユースケースを想定しています。

スクリーンショット 2021-12-07 17.32.17.png

3.はじめよう

3-1.Flutterプロジェクトの作成

まずは、Flutterプロジェクトを作成しましょう。
スクリーンショット 2021-12-07 14.25.32.png

ここで、ターミナルで
flutter run -d chrome
と打つことで、chromeでrunすることができます。

スクリーンショット 2021-12-07 14.28.05.png
↑いつものカウントアプリがchromeで表示されました。

3-2.Firebaseプロジェクトの作成

次に、Firebaseを繋げましょう。
スクリーンショット 2021-12-07 14.51.32.png

アプリを追加から、

スクリーンショット 2021-12-07 14.54.46.png
ウェブを選択します。
スクリーンショット 2021-12-07 14.56.46.png

アプリの名前だけ登録し、後のプロセスはスキップしてコンソール画面に戻ります。
スクリーンショット 2021-12-07 17.34.42.png

コンソール画面の左側にある歯車の設定にいきます。

スクリーンショット 2021-12-07 15.26.48.png

webプロジェクトのCDNを選択してプロジェクトのweb/index.htmlに設定します。
スクリーンショット 2021-12-07 17.49.47.png

その後、
pubspec.yamlファイルに
FirebaseCoreCloudFirestoreのパッケージを追加すれば完了です。(Pub getも忘れずに)

3-3.WEB画面にデータを表示する

今回、ユーザーがアプリに情報を登録したと仮定して、事前にCloudFirestoreにデータを3つ作っておきます。
スクリーンショット 2021-12-07 17.23.17.png

これらを管理画面(WEB)に表示していきます!

今回、ファイルを、Domain, View, ViewModel, Repositoryに分けます。
状態管理には簡易的にriverpodを導入します。(今回のプロジェクトで入れる必要性はないと思いますが慣れているのでいれました)

※注意:2021年12月7日時点ですが、riverpodを導入するとビルドできないという問題がありました。channelをstableではなく、masterにすれば動きました。
https://github.com/rrousselGit/river_pod/issues/889

モデルを管理するDomain層(アプリ側でユーザーが登録した名前とEmailアドレス)

app_user.dart
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層

management_repository.dart
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

management_view_model.dart
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

management_view.dart
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の処理をして、ビルドすると、、、
スクリーンショット 2021-12-07 17.28.58.png
CloudFirestoreの情報をリストの形で取得することができました!
デザインを整えれば管理画面っぽくなると思います。

4. 終わりに

Flutter for Webで、管理画面を構築してみました。
Flutter1つで、iOSもAndroidもWebも開発できるのは魅力的ですね。
まだまだ細かいところは実装できない・実装しづらい部分もあるみたいですが、徐々に改善されるのではないかと思っています。

多くの人が使うプロダクトを作りたいなあ。
今年もあと20日ほどですが、良い年末をお過ごしください。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6