ifをネストさせた方が良いこともあった💡
疑問だがこれはどうだろうか?
DocumentSnapshot
でデータを取得したい。できるけど、分岐処理をつけないとエラーが発生したときにアプリがクラッシュする!
Flutter Fireというサイトにいい感じのエラー処理が書いてる。
🔥ドキュメントスナップショット
DocumentSnapshot は、クエリから、またはドキュメントに直接アクセスすることによって返されます。 データベースにドキュメントが存在しない場合でも、スナップショットは常に返されます。
ドキュメントが存在するかどうかを確認するには、exists プロパティを使用します。
FirebaseFirestore.instance
.collection('users')
.doc(userId)
.get()
.then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
print('Document exists on the database');
}
});
ドキュメントが存在する場合は、data メソッドを呼び出してそのデータを読み取ることができます。このメソッドは Map<String, Dynamic> を返し、ドキュメントが存在しない場合は null を返します。
FirebaseFirestore.instance
.collection('users')
.doc(userId)
.get()
.then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
print('Document data: ${documentSnapshot.data()}');
} else {
print('Document does not exist on the database');
}
});
riverpodのコードで書くとこんな感じになる。中途はんぱですいません🙇
今回重要なのは、if(変数.exists)を理解することと、ifをネストしないと、null checkができないので、Text Widgetのところで!
マークをつけないといけません!
body: worksState.when(
data: (works) {
// ifでerrorの場合の処理を書く
if (works.exists) {
// データがあれば、worksのデータを取得
final work = works.data();
if (work != null) {
return // Widgetを書く
}
} else {
// データがなけれが、存在しないことを表示
return const Text('works data is not exist');
}
return null;
},
error: (error, stackTrace) => Text('error: $error'),
loading: () => const IndicatorComponent(),
),
);
上記のコードを使いたいときは、こんな感じでFutureProvider
の処理を書きました。まずは、ロジックを書いたクラスを作っておく。
(keepAlive: true)
StopWatchController stopWatchController(StopWatchControllerRef ref) {
return StopWatchController(ref);
}
class StopWatchController {
StopWatchController(this.ref);
final Ref ref;
Future<DocumentSnapshot<Works>> getWorks() async {
try {
final docRef = await ref.read(worksConverterProvider).get();
return docRef;
} on Exception catch (e) {
logger.d('error: $e');
rethrow;
}
}
こんな感じで、Future<DocumentSnapshot<Works>>
型のプロバイダーを生成するメソッドを定義します。わかりにくくてすいません💦
これは実験で作ってるコードで、動きますが全部載せると仕事でも使ってるので、どうかなと思ったので、これぐらいしか今回は書いてないです。
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../../../application/controller/stop_watch/stop_watch_controller.dart';
import '../../../../application/helper/logger/logger.dart';
import '../../../entity/task/task_state.dart';
part 'works_future.g.dart';
Future<DocumentSnapshot<Works>> worksFuture(WorksFutureRef ref) async {
try {
final docRef = await ref.watch(stopWatchControllerProvider).getWorks();
return docRef;
} on Exception catch (e) {
throw Exception('error: $e');
} finally {
logger.d('works Future get🔌');
}
}
existsってなんなのか?
exists
という単語は、存在するという意味で、コードジャンプをして内部実装を見てみると、ゲッターのようです。メソッドってことですね。どんな役割があるかというと....
このスナップショットの参照を返します。
DocumentReference<T> 参照を取得します。
ソースに関するこのドキュメントに関するメタデータ、およびドキュメントにローカルがあるかどうか
SnapshotMetadata メタデータを取得します。
/// Returns
true
if the document exists.
bool get exists;
ドキュメントが存在する場合は
true
を返します。
bool get が存在します。
内部実装はこれ:
// Copyright 2020, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of cloud_firestore;
typedef FromFirestore<T> = T Function(
DocumentSnapshot<Map<String, dynamic>> snapshot,
SnapshotOptions? options,
);
typedef ToFirestore<T> = Map<String, Object?> Function(
T value,
SetOptions? options,
);
/// Options that configure how data is retrieved from a DocumentSnapshot
/// (e.g. the desired behavior for server timestamps that have not yet been set to their final value).
///
/// Currently unsupported by FlutterFire, but exposed to avoid breaking changes
/// in the future once this class is supported.
class SnapshotOptions {}
/// A [DocumentSnapshot] contains data read from a document in your [FirebaseFirestore]
/// database.
///
/// The data can be extracted with the data property or by using subscript
/// syntax to access a specific field.
abstract class DocumentSnapshot<T extends Object?> {
/// This document's given ID for this snapshot.
String get id;
/// Returns the reference of this snapshot.
DocumentReference<T> get reference;
/// Metadata about this document concerning its source and if it has local
/// modifications.
SnapshotMetadata get metadata;
/// Returns `true` if the document exists.
bool get exists;
/// Contains all the data of this document snapshot.
T? data();
/// {@template firestore.documentsnapshot.get}
/// Gets a nested field by [String] or [FieldPath] from this [DocumentSnapshot].
///
/// Data can be accessed by providing a dot-notated path or [FieldPath]
/// which recursively finds the specified data. If no data could be found
/// at the specified path, a [StateError] will be thrown.
/// {@endtemplate}
dynamic get(Object field);
/// {@macro firestore.documentsnapshot.get}
dynamic operator [](Object field);
}
class _JsonDocumentSnapshot implements DocumentSnapshot<Map<String, dynamic>> {
_JsonDocumentSnapshot(this._firestore, this._delegate) {
DocumentSnapshotPlatform.verify(_delegate);
}
final FirebaseFirestore _firestore;
final DocumentSnapshotPlatform _delegate;
String get id => _delegate.id;
late final DocumentReference<Map<String, dynamic>> reference =
_firestore.doc(_delegate.reference.path);
late final SnapshotMetadata metadata = SnapshotMetadata._(_delegate.metadata);
bool get exists => _delegate.exists;
Map<String, dynamic>? data() {
// TODO(rrousselGit): can we cache the result, to avoid deserializing it on every read?
return _CodecUtility.replaceDelegatesWithValueInMap(
_delegate.data(),
_firestore,
);
}
dynamic get(Object field) {
return _CodecUtility.valueDecode(_delegate.get(field), _firestore);
}
dynamic operator [](Object field) => get(field);
}
まとめ
今回やりたいことをまとめると...
- if(変数名.exists)で値が取得できているかエラーチェックをする.
-
!
使いたくないので、if (変数名 != null)のネストしたif文を使う.
こんな感じですね。リーダブルコードには、if文はネストさせるのはアンチパターンと書いてありますけど、必要な場面もある気がします。ネストしないとifの分岐処理書けないし、{}の中が変数の有効範囲(スコープ)なので、work
って値を渡せません!
最近こんなコードを書くことが多いのですが、riverpod
のコードへの理解よりもDartの基本的な文法への理解が必要だなと思いました。
あとは、Firebaseのデータ型とかオブジェクト思考ですね。良いコードを書こうと日々探究してますが中々うまくいかないです💦
Discussion