#はじめに
この記事は**Provider**を使って、Formとは別のファイルにあるボタンを押して「保存」する方法をざっくりと解説します。
#外部ファイルで保存の処理を実行したい!!
これは一体どう言うことか?
下記の画面を見ながら説明する!
##ファイルの構造
ファイルの構造は以下👇のようなイメージ
└ form_screen.dart
├ form_widget.dart
└ save_button.dart
・form_widget.dart
が「作品の見所」から①、②、③の部分であり、ユーザーが入力をするFormの部分
・save_button.dart
がFloatingActionButtonの部分で、このボタンを押すとユーザーがform_widget.dart
で入力した内容が保存される
##入力された値を保持するには?
入力した値を保持するには、以下のように_form.currentState.save()
というような実装内容にしないといけませんよね
final _form = GlobalKey<FormState>();
String _userId;
String _password;
// 省略
body: Form(
key: _form, // 追加
child: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'id'),
onSaved: (value) {
_userId = value;
},
),
TextFormField(
decoration: InputDecoration(labelText: 'password'),
onSaved: (value) {
_password = value;
},
),
TextButton(
child: Text('Save'),
onPressed: () {
_form.currentState.save(); // これが重要!!!
print(_userId);
print(_password);
},
),
しかし今回はFormと保存するボタンが別のファイルにあります!!
さてどうしましょう?
と言うのが、今回の肝になる内容です。
色々とやり方はあるとは思いますが、今回はProviderを使って実現しようと思います。
#Providerを使って実装
・ProviderとFirebaseの導入は省略します。
・実際のソースは映画のAPIを使っており、今回の記事の内容とは関係のないソースが一部あります。適当に省略しているので、謎のコードがあっても気にしないでください。あくまで「参考」という事で!
##ファイルの構造
あくまでイメージです。実際のソースでは差異があります。
lib/
├ main.dart
├ models
├ └ mypage_movie_model.dart
├ providers
│ └ mypage_movie_provider.dart
├ widgets
├ save_movie_text.dart
└ mypage_movie_form.dart
##mypage_movie_model.dart
Providerの中でFormの入力データを扱うために、データの型を定義します。
import 'package:cloud_firestore/cloud_firestore.dart';
class MyPageMovieModel {
MyPageMovieModel(DocumentSnapshot doc) {
movieId = doc.id;
title = doc.data()['title'];
pointText1 = doc.data()['pointText1'];
pointText2 = doc.data()['pointText2'];
pointText3 = doc.data()['pointText3'];
}
String movieId;
String title;
String pointText1;
String pointText2;
String pointText3;
}
##mypage_movie_provider.dart
Formの値を保持 + Firestoreに値を保存する処理を書きます。
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/mypage_movie_model.dart';
class MyPageMovieProvider with ChangeNotifier {
MyPageMovieModel myPageMovie;
String point1Text = '';
String point2Text = '';
String point3Text = '';
final uid = FirebaseAuth.instance.currentUser.uid;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
// Formに入ってる内容をFirestoreに保存する
Future editMyMovies(String movieId) async {
final movieRef = _firestore.doc('users/${uid}/movies/${movieId}');
await movieRef.update({
'id': int.parse(movieId),
'point1': point1Text,
'point2': point2Text,
'point3': point3Text,
});
}
}
update
メソッドを使っていますが、保存と同じ意味です。
point1Text
という値が、Providerを使うことによって「Formがあるファイル」と「保存ボタンがあるファイル」で共通化されるような、そんな認識かと!
##save_movie_text.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'mypage_movie_form.dart';
import '../../providers/mypage_movie_provider.dart';
class SaveMovieText extends StatelessWidget {
final int id;
SaveMovieText(this.id);
@override
Widget build(BuildContext context) {
return Consumer<MyPageMovieProvider>(builder: (context, model, child) {
return FloatingActionButton(
onPressed: () async {
print('保存' + id.toString());
await model.editMyMovies(id.toString());
// FloatingActionButtonを押したら、Formに入力されてる内容が出力される!
print(model.point1Text);
print(model.point2Text);
print(model.point3Text);
},
child: const Icon(Icons.save),
backgroundColor: Theme.of(context).primaryColor,
);
});
}
}
model.point1Text
というのが、mypage_movie_provider.dart
で定義してある「point1Text」の事!
##mypage_movie_form.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../common_UI/space_box.dart';
import '../../providers/mypage_movie_provider.dart';
class MyPageMovieForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<MyPageMovieProvider>(builder: (context, model, child) {
return Form(
child: ListView(
padding: EdgeInsets.only(top: 10),
shrinkWrap: true,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
shape: BoxShape.circle),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('1'),
],
),
),
SpaceBox.width(10),
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: '見所1'),
keyboardType: TextInputType.multiline,
maxLines: null,
textInputAction: TextInputAction.newline,
onChanged: (value) {
// ここでmypage_movie_provider.dartのpoint1Textに、入力した内容を反映してる
model.point1Text = value;
},
),
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
shape: BoxShape.circle),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('2'),
],
),
),
SpaceBox.width(10),
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: '見所2'),
keyboardType: TextInputType.multiline,
maxLines: null,
textInputAction: TextInputAction.newline,
onChanged: (value) {
model.point2Text = value;
},
),
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
shape: BoxShape.circle),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('3'),
],
),
),
SpaceBox.width(10),
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: '見所3'),
keyboardType: TextInputType.multiline,
maxLines: null,
textInputAction: TextInputAction.newline,
onChanged: (value) {
model.point3Text = value;
},
),
),
],
),
],
),
);
});
}
}
model.point1Text
というのが、mypage_movie_provider.dart
で定義してある「point1Text」の事!
##main.dart
import 'package:provider/provider.dart';
import './providers/mypage_movie_provider.dart';
// 省略
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => MyPageMovieProvider(),
),
],
child: MaterialApp(
// 省略
あんまりよくわかってませんが、MultiProvider
のchildにMaterialApp
を置くことで、全て?がProviderの監視対象になるようです
(この認識が間違ってた教えて下さい)
#最後に
これで
・Providerを使ってFormに入力された値を保持
・Formとは別のファイルにあるボタンから保存を実行
ができました!
最初は色々なファイルに無理やり値を渡して試してみましたが、結局うまくいかず。。。。
Providerを使えば良い感じに実現できました。