【Flutter】FirebaseのAuthでEmail&パスワード認証を実装する(前半)

先週初めてFirebaseを使ってみたので備忘録的に残しておきます。

フォームでEmailとパスワードを入力し、Firebaseとの接続を確認できたところをゴールとします。

## 参考になると思う方
- Flutterの環境構築ができてる

## とりあえずHello World
```dart:main.dart
import 'package:flutter/material.dart';
import 'login_page.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  
  @override
    Widget build(BuildContext context) {

      return new MaterialApp(
        title: 'Flutter auth',
        theme: new ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: new LoginPage(),
      );
    }
}
```

`login_page.dart`ファイルを作成し、こちらにAuthの具体的なコードを書いていきます。

```dart:login_page.dart
import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {

  @override
  State<StatefulWidget> createState() => new _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Flutter auth'),
      ),
      body: new Container(
        child: new Text('Hello World')
      ),
    );
  }
}
```
`home: new LoginPage()` homeにlogin_page.dartファイルのLoginPageクラスを返してるということですね。

とりあえずこんな感じ。

<img width="356" alt="スクリーンショット 2020-02-26 11.27.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487891/9f950750-ea61-84a3-af51-fd6a562fb15a.png">

## フォームの作成
bodyのContainerに変更を加えていきます。

```dart:login_page.dart
class _LoginPageState extends State<LoginPage> {
  
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Flutter auth'),
      ),
      body: new Container(
        //追加
        child: new Form(
          child: new Column(
            children: <Widget>[
              new TextFormField(
                decoration: new InputDecoration(labelText: 'Email'),
              ),
              new TextFormField(
                decoration: new InputDecoration(labelText: 'Password'),
              ),
            ],
          )
        )
      ),
    );
  }
```

ここでchildとchildrenの違いについて説明しておきます。

>child takes a single widget
child: Text('foo')

>children takes a list of widgets
children: <Widget>[Text('foo'), Text('bar')]

>参考: https://stackoverflow.com/questions/54053909/what-is-the-difference-between-child-and-children-property-in-flutter

childは一つのWidgetのみをとり、childrenはWidgetをリストとしていくつもとることができると。

今回の例でもForm Widgetの中のchildにColumn Widgetを入れ、そのプロパティにchildrenを使用することで`TextFormField`として二つのFormをリストとして表示しています。

<img width="358" alt="スクリーンショット 2020-02-26 12.17.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487891/3afa1416-7487-e963-f756-f247277d4034.png">

## ボタンの作成
```dart:login_page.dart
class _LoginPageState extends State<LoginPage> {

  //追加
  void validateAndSave() {

  }
  
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Flutter auth'),
      ),
      body: new Container(
　　　　　//追加
        padding: EdgeInsets.all(16.0),
        child: new Form(
          child: new Column(
            //追加
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              new TextFormField(
                decoration: new InputDecoration(labelText: 'Email'),
              ),
              new TextFormField(
                decoration: new InputDecoration(labelText: 'Password'),
              ),
              //追加
              new RaisedButton(
                child: new Text('Login', style: new TextStyle(fontSize: 20.0)),
                onPressed: validateAndSave,
              ),
            ],
          )
        )
      ),
    );
  }
}
```
とりあえずボタンを作ります。

```dart
new RaisedButton(
                child: new Text('Login', style: new TextStyle(fontSize: 20.0)),
                onPressed: validateAndSave,
```

`onPressed`でボタンを押した時の処理を実行します。

処理を行う`validateAndSave`関数はあとで記述します。

あとはイケてないところを少し修正します。

```dart
padding: EdgeInsets.all(16.0),
```
これでスペースを整え、

```dart
 crossAxisAlignment: CrossAxisAlignment.stretch,
```
こちらでボタンを画面いっぱいに引き延ばします。

>| 種別 | 内容 |
|:--|:--|
| baseline | テキストのベースラインを揃えるように配置される |
| center | 中央寄せになる |
| start | Columnなら左寄せ（厳密にはTextDirectionによって開始位置が決まる） |
| Rowなら上寄せになる(厳密にはVerticalDirectionによって開始位置が決まる) |
| end | Columnなら右寄せ（厳密にはTextDirectionによって開始位置が決まる） |
| Rowなら上寄せになる(厳密にはVerticalDirectionによって開始位置が決まる) |
| **streach** | ***子要素の幅また高さを埋めるように配置する*** |
参考:https://qiita.com/kaleidot725/items/35f6940e594bdf073eb8#crossaxisalignment

crossAxisAlignmentについてはこちらに詳しく書いてあるので参考にしてみてください。

<img width="347" alt="スクリーンショット 2020-02-26 12.42.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487891/dde381b0-a052-9e42-4faa-dd075963f4e4.png">

UIはいい感じになりましたね。

## loginフォームにvalidationをかけ、saveする
```dart:login_page.dart
body: new Container(
        padding: EdgeInsets.all(16.0),
        child: new Form(
          //追加
          key: formKey,
          child: new Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              new TextFormField(
                decoration: new InputDecoration(labelText: 'Email'),
                //追加
                validator: (value) => value.isEmpty ? 'Email can\'t be empty' : null,
                onSaved: (value) => _email = value,
              ),
              new TextFormField(
                decoration: new InputDecoration(labelText: 'Password'),
                //追加
                obscureText: true,
                validator: (value) => value.isEmpty ? 'Password can\'t be empty' : null,
                onSaved: (value) => _password = value,
              ),
              new RaisedButton(
                child: new Text('Login', style: new TextStyle(fontSize: 20.0)),
                onPressed: validateAndSave,
              ),
            ],
          )
        )
      ),
```
keyについてなのですが、少し難しく、まだ浅い理解に留まっています:confounded:

>Controls how one widget replaces another widget in the tree.

>In addition, using a GlobalKey as the widget's key allows the element to be moved around the tree (changing parent) without losing state. When a new widget is found (its key and type do not match a previous widget in the same location), but there was a widget with that same global key elsewhere in the tree in the previous frame, then that widget's element is moved to the new location.

>Flutter公式: https://api.flutter.dev/flutter/widgets/Widget/key.html

ツリー内にあるWidgetを他のWidgetに入れ替えるのをコントロールすると。

さらに、GlobalKeyをWidgetのkeyとして使用すると状態を失うことなく、Elementをツリー内で移動できると(親を変える)。

ああ〜なるほどね...（わからん）

一応良さげな記事を見つけたので置いておきます。これはFlutterというフレームワークそのものの理解が必要そうです。

動画をみてもわからなかった... 
>Keys! What are they good for?: https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d

```dart
validator: (value) => value.isEmpty ? 'Email can\'t be empty' : null,
onSaved: (value) => _email = value,
```
formが空だった場合にはnullと`Email can't be empty`というテキストを返し、入力された場合はセーブします。

パスワードも同じように実装しますが、打った文字が隠れるように下記のコードを記述します。

```dart
obscureText: true,
```

次にボタンを押した時の処理を加えていきます。

```dart:login_page.dart
class _LoginPageState extends State<LoginPage> {
  
  final formKey = new GlobalKey<FormState>();

  String _email;
  String _password; 

  void validateAndSave() {
    final form = formKey.currentState;
    if(form.validate()) {
      form.save();
      print('Form is valid. Email: $_email, password: $_password');
    } else {
      print('Form is Invalid. Email: $_email, password: $_password');
    }
  }
```

上からformKeyと_email、_passwordを定義します。

定数の前に_(アンダースコア)がついていることに気づくと思うのですが、これはdartの言語使用で、そのライブラリ内だけ参照できるように制限をかけるものらしいです。

<img width="338" alt="スクリーンショット 2020-02-26 13.18.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487891/fca8d4a0-06dd-f51c-b566-214e945346ad.png">

空欄のままボタンを押すと上の写真のようにDEBUG CONSOLEでは`flutter: Form is Invalid. Email: null, password: null`と表示されたでしょうか？

そのあとEmailとPasswordに「test」と入力してボタンを押すと`flutter: Form is valid. Email: test, password: test`
Form is validしたことを確認できると思います:relaxed:

あとはFirebaseと接続してデータベースに反映させるだけです!!

## Firebaseのライブラリを追加
このサイトに飛んで最新バージョンの`firebase_auth`をインストールします。
>dart package: https://pub.dev/packages/firebase_auth#-installing-tab-

```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  firebase_auth: ^0.15.4

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
```

セーブすると自動的に`flutter pub get`が走ると思います。

```dart:main.dart
import 'package:firebase_auth/firebase_auth.dart';
```

ここからはFirebaseの設定に入るのですが、長くなってきたので二つの記事に分けます。

ではこちらの記事で:wave:
>【Flutter】FirebaseのAuthでメアド&パスワード認証を実装する(後半)
https://qiita.com/iketeruhiyoko/items/7d0718bc6210ed545913

## 参考
>Flutter & Firebase Auth 02 - Create, validate and save a login form + Firebase iOS auth integration
https://www.youtube.com/watch?v=BNOUtPSN-kA