#はじめに
https://qiita.com/kokogento/items/76da17fb51806fbb7ff1
一応上記の続きということになります。
※しかしながら、大幅にコードを変えているのであまり続き感はありません。。。
動作はこんな感じ
かなり無理矢理な方法ですが、なんとか実現したい動作は実装できた??!😅 pic.twitter.com/YUz0gvFicm
— 高卒プログラマーげんと (@gento34165638) May 23, 2021
かなり無理やり実装した感があるので、「このやり方がおすすめ!という人は是非教えて下さいませ
#前提
バージョンは下記の通り
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との連携部分は省略します
#フォルダ構成
ざっくりとこんな感じで。。。
lib/
├ main.dart
├ models
├ └ user_model.dart
├ providers
│ └ user_provider.dart
├ widgets
│ └ mypage_view.dart
└ screens
├ mypage_screen.dart
└ mypage_edit_screen.dart
#一番困ったところ
このやり方でいくと値の更新がリアルタイムで画面に反映されませんでした。。。
よくわかりませんが、下記動画を参考にとにかく根本から作り替えてみることにしました。
時として、今まで作り上げたコードを全て捨て去る時も重要かと。。
#実装
極力省けるところは省きます!
##user_provider.dart
import 'package:cloud_firestore/cloud_firestore.dart';
class UserModel {
UserModel(DocumentSnapshot doc) {
uid = doc.id;
email = doc.data()['email'];
displayName = doc.data()['displayName'];
photoURL = doc.data()['photoURL'];
introText = doc.data()['introText'];
}
String uid;
String email;
String displayName;
String photoURL;
String introText;
}
UserModel
を「DocumentSnapshot」という型にしておきます。
##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 user;
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();
this.user = UserModel(docs);
notifyListeners();
}
Future updateUserData(UserModel editedUserData) async {
final doc = _firestore.doc('users/${editedUserData.uid}');
await doc.update({
'displayName': editedUserData.displayName,
'email': editedUserData.email,
'photoURL': editedUserData.photoURL,
'introText': editedUserData.introText,
});
notifyListeners();
}
}
##mypage_view.dart
userのデータを表示するだけのwidget。
これを後記するmypage_screen.dart
で呼び出す。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../config/size_config.dart';
import '../../providers/user_provider.dart';
class MyPageView extends StatelessWidget {
@override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Consumer<UserProvider>(builder: (context, model, child) {
final userData = model.user;
return userData == null
? Container(
child: CircularProgressIndicator(),
)
: 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:96-c/photo.jpg'),
)),
),
SizedBox(
width: 5 * SizeConfig.blockSizeHorizontal,
),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
userData.displayName != null
? userData.displayName
: 'No Name',
style: Theme.of(context).textTheme.headline6,
),
SizedBox(
height: 1 * SizeConfig.blockSizeVertical,
),
Text(userData.introText != null
? userData.introText
: 'No introduction'),
],
),
)
],
);
});
}
}
##mypage_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../config/size_config.dart';
import '../../providers/user_provider.dart';
import '../../widgets/mypage/mypage_view.dart';
import './mypage_edit_screen.dart';
class MyPageScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Scaffold(
appBar: AppBar(
title: Text('My Page'),
actions: <Widget>[
Consumer<UserProvider>(
builder: (context, model, child) {
final user = model.user;
return IconButton(
icon: Icon(
Icons.edit_sharp,
),
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyPageEditScreen(
user: user,
),
fullscreenDialog: true,
));
model.fetchUserData();
},
);
},
)
],
),
body: Container(
child: Padding(
padding: const EdgeInsets.only(left: 30, right: 30, top: 30),
child: Column(
children: <Widget>[
MyPageView(),
// 省略
AppBar
にアイコンを設置して、マイページの編集画面へと遷移させる。
その時、現在ログインしてるユーザーをMyPageEditScreen
に渡す。
//「現在ログインしてるユーザー」とはここで取得してる部分
final user = model.user;
で、model.user
がどこから来るかというと、user_provider.dart
から。
UserModel user;
// 省略
Future fetchUserData() async {
final userId = _firebaseAuth.currentUser.uid;
final docs = await _firestore.doc('users/${userId}').get();
this.user = UserModel(docs);
notifyListeners();
}
##mypage_edit_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../models/user_model.dart';
import '../../config/size_config.dart';
import '../../providers/user_provider.dart';
class MyPageEditScreen extends StatelessWidget {
// mypage_screen.dartから受け取ったuser
MyPageEditScreen({this.user});
final UserModel user;
String _displayName;
String _email;
String _photoURL;
String _introText;
final _form = GlobalKey<FormState>();
Future _saveProfile(UserProvider model, BuildContext ctx) async {
_form.currentState.save();
// 「user」の情報を無理やり上書き
user.displayName = _displayName;
user.email = _email;
user.photoURL = _photoURL;
user.introText = _introText;
await model.updateUserData(user);
// 省略
}
@override
Widget build(BuildContext context) {
final nameEditingController = TextEditingController();
final introEditingController = TextEditingController();
// 初期値を入れる。エラー処理をしていないのでnullならエラー出る。。。
nameEditingController.text = user.displayName;
introEditingController.text = user.introText;
// 保存した時に空っぽのまま更新されてしまうので、現在のuserの値を入れておく
_email = user.email;
_photoURL = user.photoURL;
return Consumer<UserProvider>(builder: (context, model, child) {
final userData = user;
return Scaffold(
appBar: AppBar(
title: Text('Edit Profile'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.check_sharp),
onPressed: () {
_saveProfile(model, context);
},
)
],
),
body: Container(
child: Padding(
padding: const EdgeInsets.only(left: 30, right: 30, top: 30),
child: Column(
children: <Widget>[
userData == null
? Container(
child: CircularProgressIndicator(),
)
: Form(
key: _form,
child: Row(
children: <Widget>[
// 省略
SizedBox(
width: 5 * SizeConfig.blockSizeHorizontal,
),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
controller: nameEditingController,
style:
Theme.of(context).textTheme.headline6,
textInputAction: TextInputAction.done,
onSaved: (value) {
_displayName = value;
},
),
SizedBox(
height: 1 * SizeConfig.blockSizeVertical,
),
TextFormField(
controller: introEditingController,
textInputAction: TextInputAction.done,
keyboardType: TextInputType.multiline,
maxLines: null,
onSaved: (value) {
_introText = value;
},
)
],
),
),
],
),
),
],
),
),
),
);
});
}
}
model.updateUserData
で渡さないといけないのはDocumentSnapshot
であるUserModel
なので、
_editedUserData = UserModel(
displayName: _editedUserData.displayName,
email: user.email,
photoURL: user.photoURL,
introText: _editedUserData.introText,
);
model.updateUserData(_editedUserData);
みたいな形にすることができなかった。。(私が無理やっただけで、やり方によってはできるのかな?)
更新すると何故かemailとかが空っぽになってしまうので、無理やりだが
user.displayName = _displayName;
user.email = _email;
user.photoURL = _photoURL;
user.introText = _introText;
await model.updateUserData(user);
みたいな形にした。。。
##main.dart
// 省略
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => UserProvider()..fetchUserData(),
),
// 省略
#最後に
参考にした動画の通りChangeNotifierProvider
を使うのか、StreamProvider
を使うのか、どっちを使えばいいのか今一わかりません!
今回はかなり無理やり実現しちゃった感がすごいので、、、どなたか同じような挙動を実現できるお勧めの方法があれば教えて下さい