🕳️

Bad state: cannot get a field on a DocumentSnapshotPlatform which does

2023/04/21に公開

nullとは違うエラーが発生する?

Firestoreからデータを取得できれば、問題なくアプリは動作するが、Firestoreにデータが存在しなかったら、例外処理が発生してしまう!

ちゃんと、nullだったら、画像がないアバターのiconと情報が登録されてないことを伝える文字を表示するロジックを使ったのですが、新しく個人開発しているアプリでは、解決できませんでした🤔

前例があるけど、今回は違う?

過去の記事を見ると、Firestoreのフィールド名を間違えていたことが原因だったりして、発生するそうです。
例を出すと...

Text(data['image']);// これは間違い
Text(data['images']);// これは正しい。

こちらが問題のコード

何が原因なのかさっぱりわからない?

// ignore_for_file: unused_import, unnecessary_import, prefer_const_constructors, avoid_unnecessary_containers, unnecessary_null_comparison

import 'package:sugary_map/application/analytics_provider/analytics.dart';
import 'package:sugary_map/application/auth_provider/sign_in/sign_in.dart';
import 'package:sugary_map/application/store_provider/get_profile/get_profile_provider.dart';
import 'package:sugary_map/presentation/constant/privacy_const.dart';
import 'package:sugary_map/presentation/export/global_export.dart';
import 'package:sugary_map/presentation/router/auth_provider.dart';
import 'package:sugary_map/presentation/ui/component/global/custom_divider.dart';
import 'package:sugary_map/presentation/ui/page/auth_page/signin_page.dart';
import 'package:sugary_map/presentation/ui/page/user/navigation_page/mypage/mypage_list/accont_page.dart';
import 'package:sugary_map/presentation/ui/page/user/navigation_page/mypage/mypage_list/profile_test.dart';
import 'package:url_launcher/url_launcher.dart';

class MyPage extends ConsumerWidget {
  const MyPage({super.key});

  static const routeName = '/myPage';

  
  Widget build(BuildContext context, WidgetRef ref) {
    // ユーザーのログイン状態を監視する
    final authStateAsync = ref.watch(authProvider);
    final signInService = ref.read(signInProvider);
    final analyticsRef = ref.read(analyticsServiceProvider);
    // Todo: ProfileData
    final getUser = ref.watch(profileProvider);

    return authStateAsync.when(
        loading: () => const CircularProgressIndicator(),
        error: (err, stack) => Text('Error: $err'),
        data: (user) {
          // nullじゃなかったら、ログインしているWidgetを表示
          return user != null
              ? Scaffold(
                  backgroundColor: Colors.white,
                  appBar: AppBar(
                    title: const Text('マイページ'),
                  ),
                  body: Center(
                    child: Column(
                      children: <Widget>[
                        Padding(
                            padding: const EdgeInsets.only(
                                top: 20, left: 20, bottom: 20),
                            child: getUser.when(
                              loading: () => const CircularProgressIndicator(),
                              error: (err, stack) => Text('Error: $err'),
                              data: (data) {
                                return Row(
                                  children: [
                                    Container(
                                      clipBehavior: Clip.antiAlias,
                                      width: 80,
                                      height: 80,
                                      decoration: const BoxDecoration(
                                        // BoxShapeをcircleにしているので丸型になってほしい
                                        shape: BoxShape.circle,
                                        color: Colors.blue,
                                      ),
                                      // 正方形の画像を表示する
                                      // Containerは丸型なので丸くなってほしい
                                      child: data != null
                                          ? Image.network(data['imageUrl'])
                                          : null,
                                    ),
                                    const SizedBox(width: 20),
                                    Column(
                                      children: [
                                        data != null
                                            ? Text(data['name'])
                                            : const Text('プロフィールが登録されてません'),
                                        SizedBox(height: 20),
                                        Row(
                                          // ignore: prefer_const_literals_to_create_immutables
                                          children: [
                                            Text('称号'),
                                            SizedBox(width: 20),
                                            data != null
                                                ? Text(data['degree'])
                                                : const Text('プロフィールが登録されてません'),
                                          ],
                                        ),
                                      ],
                                    )
                                  ],
                                );
                              },
                            )),
                        Expanded(
                          child: ListView(
                            children: [
                              GestureDetector(
                                onTap: () {
                                  GoRouter.of(context)
                                      .goNamed(ProfileTest.rootName);
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('Profile Test'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('お気に入り'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('使い方'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {
                                  context
                                      .goNamed(UserAccountSettings.routeName);
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('アカウント設定'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {
                                  launchUrl(Uri.parse(privacyUrl));
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('プライバシーポリシー'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {
                                  launchUrl(Uri.parse(termsUrl));
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('利用規約'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('お問い合わせ'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () async {
                                  signInService.signOut();
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('ログアウト'),
                                ),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                  // nullだったらログインしていないWidgetを表示する
                )
              : Scaffold(
                  backgroundColor: Colors.white,
                  appBar: AppBar(
                    title: Text('マイページ'),
                  ),
                  body: Center(
                    child: Column(
                      children: <Widget>[
                        Padding(
                          padding: const EdgeInsets.only(
                              top: 20, left: 20, bottom: 20),
                          child: Row(
                            children: [
                              Container(
                                child: Icon(Icons.person),
                              ),
                              const SizedBox(width: 20),
                              Column(
                                // ignore: prefer_const_literals_to_create_immutables
                                children: [
                                  const Text('アカウントが登録されていません'),
                                  SizedBox(height: 20),
                                ],
                              )
                            ],
                          ),
                        ),
                        Expanded(
                          child: ListView(
                            children: [
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('お気に入り'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('使い方'),
                                ),
                              ),
                              CustomDivider(),
                              // GestureDetector(
                              //   onTap: () {
                              //     context
                              //         .goNamed(UserAccountSettings.routeName);
                              //   },
                              //   child: ListTile(
                              //     trailing: Icon(Icons.arrow_forward_ios),
                              //     title: Text('アカウント設定'),
                              //   ),
                              // ),
                              // CustomDivider(),
                              GestureDetector(
                                onTap: () {
                                  launchUrl(Uri.parse(privacyUrl));
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('プライバシーポリシー'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {
                                  launchUrl(Uri.parse(termsUrl));
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('利用規約'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('お問い合わせ'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () async {
                                  analyticsRef.logEvent();
                                  context.goNamed(SignInPage.routeName);
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('ログイン'),
                                ),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                );
        });
  }

対象方法を見つけた!

.dataに原因があった!
Firebaseの.dataとはどんなものか?

.data

ドキュメントスナップショットの使い方よくない?

実験して、分かったことは、 snapshotのデータをとってくる data[] これを使わない方がいいらしい?
.dataは、Map<String, dynamic>のデータ型になっている。
データ型の問題というより、データないんだけど取ってくるのが、例外処理の原因になったりする。
どうやって防止するかと言いますと、? ! をつける。他には、Riverpod使っていたら、.whenの()の中に、dataと書かない方がいい🤔
()の中には、valueとか、configとか、かぶらない名前を書きましょう。

例外処理を防止できたコード

// ignore_for_file: unused_import, unnecessary_import, prefer_const_constructors, avoid_unnecessary_containers, unnecessary_null_comparison

import 'package:sugary_map/application/analytics_provider/analytics.dart';
import 'package:sugary_map/application/auth_provider/sign_in/sign_in.dart';
import 'package:sugary_map/application/store_provider/get_profile/get_profile_provider.dart';
import 'package:sugary_map/presentation/constant/privacy_const.dart';
import 'package:sugary_map/presentation/export/global_export.dart';
import 'package:sugary_map/presentation/router/auth_provider.dart';
import 'package:sugary_map/presentation/ui/component/global/custom_divider.dart';
import 'package:sugary_map/presentation/ui/page/auth_page/signin_page.dart';
import 'package:sugary_map/presentation/ui/page/user/navigation_page/mypage/account/update_user.dart';
import 'package:sugary_map/presentation/ui/page/user/navigation_page/mypage/mypage_list/accont_page.dart';
import 'package:sugary_map/presentation/ui/page/user/navigation_page/mypage/mypage_list/profile_test.dart';
import 'package:url_launcher/url_launcher.dart';

class MyPage extends ConsumerWidget {
  const MyPage({super.key});

  static const routeName = '/myPage';

  
  Widget build(BuildContext context, WidgetRef ref) {
    // ユーザーのログイン状態を監視する
    final authStateAsync = ref.watch(authProvider);
    final signInService = ref.read(signInProvider);
    final analyticsRef = ref.read(analyticsServiceProvider);
    // Todo: ProfileData
    final getUser = ref.watch(profileProvider);

    return authStateAsync.when(
        loading: () => const CircularProgressIndicator(),
        error: (err, stack) => Text('Error: $err'),
        data: (user) {
          // nullじゃなかったら、ログインしているWidgetを表示
          return user != null
              ? Scaffold(
                  backgroundColor: Colors.white,
                  appBar: AppBar(
                    actions: [
                      Container(
                        margin: EdgeInsets.only(right: 30),
                        child: TextButton(
                            onPressed: () {},
                            child: const Text(
                              'プロフィールを編集',
                              style:
                                  TextStyle(color: Colors.black, fontSize: 17),
                            )),
                      ),
                    ],
                  ),
                  body: Center(
                    child: Column(
                      children: <Widget>[
                        Padding(
                            padding: const EdgeInsets.only(
                                top: 20, left: 20, bottom: 20),
                            child: getUser.when(
                              loading: () => const CircularProgressIndicator(),
                              error: (err, stack) => Text('Error: $err'),
                              data: (value) {
                                // data => valueに変更。名前の衝突が起きる?
                                /// [静的解析が必要]
                                final data = value.data();
                                return Row(
                                  children: [
                                    Container(
                                      clipBehavior: Clip.antiAlias,
                                      width: 80,
                                      height: 80,
                                      decoration: const BoxDecoration(
                                        // BoxShapeをcircleにしているので丸型になってほしい
                                        shape: BoxShape.circle,
                                        color: Colors.blue,
                                      ),
                                      // 正方形の画像を表示する
                                      // Containerは丸型なので丸くなってほしい
                                      /// [data?[]を使う]
                                      child: data?['imageUrl'] != null

                                          /// [data![]を使う]
                                          ? Image.network(data!['imageUrl'])
                                          : null,
                                    ),
                                    const SizedBox(width: 20),
                                    Column(
                                      children: [
                                        data?['name'] != null
                                            ? Text(data!['name'])
                                            : const Text('プロフィールが登録されてません'),
                                        SizedBox(height: 20),
                                        Row(
                                          // ignore: prefer_const_literals_to_create_immutables
                                          children: [
                                            Text('称号'),
                                            SizedBox(width: 20),
                                            data?['degree'] != null
                                                ? Text(data!['degree'])
                                                : const Text('称号が登録されてません'),
                                          ],
                                        ),
                                      ],
                                    )
                                  ],
                                );
                              },
                            )),
                        Expanded(
                          child: ListView(
                            children: [
                              GestureDetector(
                                onTap: () {
                                  // GoRouter.of(context)
                                  // .goNamed(ProfileTest.rootName);
                                  GoRouter.of(context)
                                      .goNamed(UpdateUser.routeName);
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('Profile Test'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('お気に入り'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('使い方'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {
                                  context
                                      .goNamed(UserAccountSettings.routeName);
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('アカウント設定'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {
                                  launchUrl(Uri.parse(privacyUrl));
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('プライバシーポリシー'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {
                                  launchUrl(Uri.parse(termsUrl));
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('利用規約'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('お問い合わせ'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () async {
                                  signInService.signOut();
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('ログアウト'),
                                ),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                  // nullだったらログインしていないWidgetを表示する
                )
              : Scaffold(
                  backgroundColor: Colors.white,
                  appBar: AppBar(
                    title: Text('マイページ'),
                  ),
                  body: Center(
                    child: Column(
                      children: <Widget>[
                        Padding(
                          padding: const EdgeInsets.only(
                              top: 20, left: 20, bottom: 20),
                          child: Row(
                            children: [
                              Container(
                                child: Icon(Icons.person),
                              ),
                              const SizedBox(width: 20),
                              Column(
                                // ignore: prefer_const_literals_to_create_immutables
                                children: [
                                  const Text('アカウントが登録されていません'),
                                  SizedBox(height: 20),
                                ],
                              )
                            ],
                          ),
                        ),
                        Expanded(
                          child: ListView(
                            children: [
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('お気に入り'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('使い方'),
                                ),
                              ),
                              CustomDivider(),
                              // GestureDetector(
                              //   onTap: () {
                              //     context
                              //         .goNamed(UserAccountSettings.routeName);
                              //   },
                              //   child: ListTile(
                              //     trailing: Icon(Icons.arrow_forward_ios),
                              //     title: Text('アカウント設定'),
                              //   ),
                              // ),
                              // CustomDivider(),
                              GestureDetector(
                                onTap: () {
                                  launchUrl(Uri.parse(privacyUrl));
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('プライバシーポリシー'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {
                                  launchUrl(Uri.parse(termsUrl));
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('利用規約'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () {},
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('お問い合わせ'),
                                ),
                              ),
                              CustomDivider(),
                              GestureDetector(
                                onTap: () async {
                                  analyticsRef.logEvent();
                                  context.goNamed(SignInPage.routeName);
                                },
                                child: ListTile(
                                  trailing: Icon(Icons.arrow_forward_ios),
                                  title: Text('ログイン'),
                                ),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                );
        });
  }
}

ビルドが無事にできた。後でコードを色々修正しないといけませんが、例外処理は防げた。

最後に

同じエラーが出て苦しんでいる人の解決策になるかも知れないと思いまして、一つの例として技術記事を書きました。誰かのお役に立てると嬉しいです。

Discussion