Flutter Providerを使ってFormの値を保持し、外部ファイルで保存する処理を実行（Firestoreを使用）

#はじめに
この記事は**[Provider](https://pub.dev/packages/provider)**を使って、Formとは別のファイルにあるボタンを押して「保存」する方法をざっくりと解説します。

#外部ファイルで保存の処理を実行したい！！
これは一体どう言うことか？
下記の画面を見ながら説明する！
![スクリーンショット 2021-06-28 23.36.09.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167220/01346214-7c3b-b574-4c00-73ea3d2c1865.jpeg)
##ファイルの構造
ファイルの構造は以下👇のようなイメージ

```console
　└ form_screen.dart
     ├ form_widget.dart
     └ save_button.dart 
```

・`form_widget.dart`が「作品の見所」から①、②、③の部分であり、ユーザーが入力をする**Form**の部分

・`save_button.dart`が**FloatingActionButton**の部分で、このボタンを押すとユーザーが`form_widget.dart`で入力した内容が保存される

https://api.flutter.dev/flutter/material/FloatingActionButton-class.html

##入力された値を保持するには？
**入力した値を保持するには、以下のように`_form.currentState.save()`というような実装内容にしないといけませんよね**

```dart
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の導入は省略します。:bow:

・実際のソースは映画の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の入力データを扱うために、データの型を定義します。

```dart
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に値を保存する処理を書きます。

```dart
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

```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

```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

```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の監視対象になるようです
（この認識が間違ってた教えて下さい:sweat:）

#最後に
これで
・Providerを使ってFormに入力された値を保持
・Formとは別のファイルにあるボタンから保存を実行

ができました！

最初は色々なファイルに無理やり値を渡して試してみましたが、結局うまくいかず。。。。
Providerを使えば良い感じに実現できました。

#参考
https://github.com/kboyflutteruniv/todo_flutter_sample