🗂️

Firebaseの値を次のページに渡す

2022/12/24に公開

詳細ページを作って仕組みを学ぶ

Firebaseから取得したデータをもとに、モデルの情報を表示する詳細ページを作る方法ってどうやって作るのか考えていました。
Flutter大学の過去の動画を見たことがあれば、リストとコンストラクターが必要なのがわかっていたのですが、私、リストあんまり使ったことなかったのでわかっていませんでした😅

こんなアプリを作りました

完成品のソースコード
https://github.com/sakurakotubaki/DetailPage

  • 必要な要件
    • Dartの基本的な知識
      • Listで型に指定したクラスのプロパティを取得
      • Firebaseの情報をStreamで取得
      • onTapで表示されているリストをタップすると画面遷移する設定
      • 詳細ページでコンストラクターを定義して、前のページの値を受け取る
      • Text()でクラスのプロパティを表示する

今回やったのは以前書いた記事に機能を追加しただけですね。
参考にした過去の記事

https://zenn.dev/joo_hashi/articles/18ff323bc60563
https://zenn.dev/joo_hashi/articles/2de8f1144184ad

今回はRiverpodとFreezedを使用しました。
Riverpodを使ったのは、状態管理ではGoogleが推奨することにしたからというのもありますが、情報が多く以前から使用していたので選択。
Freezdを使用したのは、自分で、toJSONやコンストラクターを定義しなくても自動生成してくれるからです。
今回短いコードで作れたのは、packageのおかげですね。
以前は長いコード書いて、自分でも何書いてんのか分かりませんでした。

今回使用したpackageを記載
Freezedは設定が多いので、さくしんさんの記事が参考になると思います。
私もいつも参考にしております。
https://zenn.dev/sakusin/articles/b19e9a2c3829e0

pubspec.yaml
name: freezed_app
description: A new Flutter project.

# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1

environment:
  sdk: '>=2.18.0 <3.0.0'

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  firebase_core: ^2.4.0
  cloud_firestore: ^4.2.0
  flutter_riverpod: ^2.1.1
  json_annotation: ^4.7.0
  freezed_annotation: ^2.2.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.0
  json_serializable: ^6.5.4
  freezed: ^2.3.2
  build_runner: ^2.3.3

# For information on the generic Dart part of this file, see the
# following ui: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

アプリのディレクトリはこのようにしました。

lib
├── firebase_options.dart
├── main.dart
├── model
│   ├── user.dart
│   ├── user.freezed.dart
│   └── user.g.dart
├── provider
│   └── provider.dart
└── ui
    └── detail.dart

モデルのクラスを定義
リストの型として使います。Firebaseの値はクラスとリストがなくても表示できますが、他のページに値を渡すときには、クラスとリストは必要です!
user.dart以外はコマンドで自動生成されるので、こちらのファイルだけ書きます。

model/user.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'user.freezed.dart';
part 'user.g.dart';

// Userモデルを定義

class User with _$User {
  const factory User({
    required String name,
    required String email,
    required String address,
    required int personalNumber,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

クラスを定義したら、以下のコマンドを実行してファイルを自動生成します。

flutter pub run build_runner build

Firebaseのデータを取得するファイルを作成します。
StreamProviderで全ての値を取得して、リストにUserクラスを型として指定します。
mapで、新しいリストを生成します。
JavaScriptだとmapメソッドは新しい配列を生成するメソッドで役割は似ています。

mapについて参考になるサイト

provider/provider.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../model/user.dart';

/// リストでUserクラスを型に使いプロパティを使えるようにする.
final usersStreamProvider = StreamProvider<List<User>>((ref) {
  // FireStoreの全てのデータを取得.
  final collection = FirebaseFirestore.instance.collection('users');
  // データ(Map型)を取得.
  final stream = collection.snapshots().map(
        // CollectionのデータからUserクラスを生成する.
        (e) => e.docs.map((e) => User.fromJson(e.data())).toList(),
      );
  return stream;
});

Listを使うアプリのページ
main.dartにリストから取得した値を表示するコードを書きました。
ListView.builderで、先ほど作ったmapを使っているStreamProviderから取得したデータがこちらに表示されます。
ListView.builderに、onTapを追加して、画面遷移の設定を追加すると、表示されているリストの値をタップしたときに詳細画面へ移動することができます。

main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_app/provider/provider.dart';
import 'package:freezed_app/ui/detail.dart';

import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(ProviderScope(child: const MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.cyan,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    // StreamProviderを読み取る(取得できる型は `AsyncValue<T>`)
    final users = ref.watch(usersStreamProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('個人情報アプリ'),
      ),
      // AsyncValue は `.when` を使ってハンドリングする
      body: users.when(
          // 処理中は `loading` で指定したWidgetが表示される
          loading: () => const CircularProgressIndicator(),
          // エラーが発生した場合に表示されるWidgetを指定
          error: (error, stack) => Text('Error: $error'),
          // 取得した `users` が `data` で使用できる
          data: (users) {
            return ListView.builder(
                itemCount: users.length, // リストの数をカウントする
                itemBuilder: (context, index) {
                  // 値とindexを渡す
                  final user = users[index]; // クラスのプロパティを使うための変数
                  return ListTile(
                    title: Text(user.name), // クラスのnameプロパティを表示
                    onTap: () {
                      // リストをタップすると詳細画面へ遷移する設定
                      Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) => Detail(
                                    user: users[index],
                                  )));
                    },
                  );
                });
          }),
    );
  }
}

詳細画面を作成
こちらが詳細画面のページで、前のページからリストのindexの値を受け取ると、0番目なら0の詳細内容が、1番目なら1の詳細内容を表示します。
やっていることは、変数にクラスを保存して、クラスのプロパティを使っているだけ。

ui/detail.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../model/user.dart';

class Detail extends ConsumerWidget {
  // ListView.builderから値を受け取れるように、コンストラクターを定義する
  const Detail({super.key, required this.user});
  // 受け取った値を保存する変数を作る
  final User user;

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: Text('詳細ページ'),
      ),
      body: Column(
        children: [
          SizedBox(height: 20),
          // 受け取った値を保存した変数を使って、Userクラスのプロパティを表示できるようにする
          Container(
            padding: const EdgeInsets.only(left: 20),
            alignment: Alignment.topLeft,
            child: Row(
              children: [
                Text('名前: '),
                Text(
                  '${user.name}',
                  style: TextStyle(fontSize: 20),
                ),
              ],
            ),
          ),
          Container(
            padding: const EdgeInsets.only(top: 10, left: 20),
            alignment: Alignment.topLeft,
            child: Row(
              children: [
                Text('メールアドレス: '),
                Text(
                  '${user.email}',
                  style: TextStyle(fontSize: 20),
                ),
              ],
            ),
          ),
          Container(
            padding: const EdgeInsets.only(top: 10, left: 20),
            alignment: Alignment.topLeft,
            child: Row(
              children: [
                Text('住所: '),
                Text(
                  '${user.address}',
                  style: TextStyle(fontSize: 20),
                ),
              ],
            ),
          ),
          Container(
            padding: const EdgeInsets.only(top: 10, left: 20),
            alignment: Alignment.topLeft,
            child: Row(
              children: [
                Text('個人番号: '),
                Text(
                  '${user.personalNumber}',
                  style: TextStyle(fontSize: 20),
                ),
              ],
            ),
          ),
          const SizedBox(height: 50),
          Container(
            alignment: Alignment.topLeft,
            width: 250,
            decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
            child: Text("""
                    これは${user.name}さんの\n
                    個人情報が記載された\n
                    詳細ページです。
                    """, style: TextStyle(fontSize: 15)),
          )
        ],
      ),
    );
  }
}

やってみた感想

個人情報を表示するアプリを作ってみて、リスト、コンストラクター、mapへの理解が深まった気がします。
実はなんとなく使ってる人が多かったりする😅

Discussion