#はじめに
Flutter + FirebaseのアプリでProviderを使うためにはどうすればいいのか?
この記事では上記の疑問を、ほんの少しでも理解するための1つの参考例としてみていただければと思います。。
かなり我流な気もするので「これが正しい書き方だ!」という方がいれば、コメントください!
###追記
このやり方でいくと値の更新がリアルタイムで画面に反映されませんでした。。。
もはやどうしたらいいのかわかりません!!!助けてください!!!!
###追記 その2
かなり無理矢理ですが、なんとか更新できました。。。
#前提
バージョンは下記の通り
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
provider: ^4.3.3
firebase_core: "0.7.0"
firebase_auth: "^0.20.1"
cloud_firestore: "^0.16.0+1"
この記事ではFirebaseとの連携部分は省略します
#全体像
まずユーザーのプロフィールページをイメージしてください。
ドキュメントusers/${uid}
が保持する
・displayName
・email
・introText
①これらの値をProvider
で監視!
②displayName変更のように「プロフィール情報」が変更されたときにnotifyListeners()
で変更を知らせる
③変更の通知を受け取ってConsumerで囲ってある「プロフィール情報」のStatelessWidgetが変わる!
(状態はProvider
から取得できるため、Widgetで保持する必要が無くなった。そのため、StatelessWidgetを使う。)
#フォルダ構成
ざっくりとこんな感じで。。。
lib/
├ models/user_model.dart
├ provider/user_provider.dart
├ screen/my_page_screen.dart
└ main.dart/
#一番困ったところ
ここがわかるとProvider と Firebaseの連携がとってもスムーズにいくかも。
Providerで監視するには「データモデル」みたいな型(データ構造を表すEntityとかいうやつかな?)を指定する必要があり、Firestoreから取得した値をそのままProviderに適応することはできないのです。。
私の場合は「型の変換」という部分が一番わけがわからず迷走していた部分でした
ここ重要なので覚えておきましょう!
#実装
長々と前置きを挟みましたが、ここから実際のコードを書きます。。
##user_model.dart
class UserModel {
String uid;
String email;
String displayName;
String photoURL;
String introText;
UserModel({
this.uid,
this.email,
this.displayName,
this.photoURL,
this.introText,
});
}
##user_provider.dart
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/user_model.dart';
class UserProvider with ChangeNotifier {
UserModel _userModels;
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future fetchUserData() async {
final userId = _firebaseAuth.currentUser.uid;
final docs = await _firestore.doc('users/${userId}').get();
final userModels = UserModel(
uid: userId,
displayName: docs.data()['displayName'],
email: docs.data()['email'],
photoURL: docs.data()['photoUrl'],
introText: docs.data()['introText'],
);
_userModels = userModels;
notifyListeners();
}
}
final docs = await _firestore.doc('users/${userId}').get();
ここでFirestoreから取得したDocumentSnapshot
(docsという変数に格納)をUserModel
に変換しないといけません。
変換している部分がここ
final userModels = UserModel(
uid: userId,
displayName: docs.data()['displayName'],
email: docs.data()['email'],
photoURL: docs.data()['photoUrl'],
introText: docs.data()['introText'],
);
ちなみに、今回はDocumentSnapshot
(ドキュメント1つ)を変換しましたが、コレクションで取得して変換する場合は下記を参考にmap
を使ってみてください(ちょっと古いですが・・)
##main.dart
状態を供給するには、アプリ全体をProviderでラップする必要があるのでmain.dart
で**「MaterialApp」を「MultiProvider」で囲う**
また、UserProvider()..fetchUserData()
で、createされると同時にfetchUserData()
が実行されます。
(「createされる」の意味があんまりわかっていないのですが、、誰か教えてください。。)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './screens/my_page_screen.dart';
import 'providers/user_provider.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// Create the initialization Future outside of `build`:
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _initialization,
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return Center(
child: Text('読み込みでエラー発生' + snapshot.error),
);
}
// Once complete, show your application
if (snapshot.connectionState == ConnectionState.done) {
return Home();
}
// Otherwise, show something whilst waiting for initialization to complete
return CircularProgressIndicator();
},
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => UserProvider()..fetchUserData(),
)
],
child: MaterialApp(
// 省略
),
);
}
}
##my_page_screen.dart
SizeConfig
については下記を参考に
Consumer
のmodelを使って、user_provider.dart
から値を受け取ります。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../config/size_config.dart';
import '../providers/user_provider.dart';
class MyPageScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Scaffold(
body: Container(
child: Padding(
padding: const EdgeInsets.only(left: 30, right: 30, top: 30),
child: Column(
children: <Widget>[
Consumer<UserProvider>(
builder: (context, model, child) {
final userData = model.userModels;
return Row(
children: <Widget>[
Container(
height: 10 * SizeConfig.blockSizeVertical,
width: 20 * SizeConfig.blockSizeHorizontal,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
fit: BoxFit.fill,
image: NetworkImage(
'https://lh5.googleusercontent.com/-rEw1ckfg8Sc/AAAAAAAAAAI/AAAAAAAAAAA/AMZuuckW028Ka5poorv9UE629d5mtR13CA/s96-c/photo.jpg'),
)),
),
SizedBox(
width: 5 * SizeConfig.blockSizeHorizontal,
),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
userData.displayName,
style: Theme.of(context).textTheme.headline6,
),
SizedBox(
height: 1 * SizeConfig.blockSizeVertical,
),
Text(userData.introText),
],
),
)
],
);
},
),
],
),
),
),
);
}
}
#問題点
データの取得が終わってないのにウィジェットが作成されるからか、Consumerで一瞬エラーが発生する。。。 pic.twitter.com/y8iJE1VsO9
— 高卒プログラマーげんと (@gento34165638) May 17, 2021
一瞬エラーが発生します。。。
どうすれば良いのでしょうか????!!
###追記
こちらにでエラーを回避できました!
#参考
https://fireship.io/lessons/advanced-flutter-firebase/