🕳️
Bad state: cannot get a field on a DocumentSnapshotPlatform which does
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