Flutterを初めてnull-safetyに苦しめられている話

## 必死に調べた時間が無駄になったオチ
(追記)
VSCodeのクイックフィックス使おうね
\# VSCodeあまり使わないからこんな機能あるの知らなったの…

以下は自分の調べた内容を無駄にするのも嫌だったので残します。

## Flutterを初めた
色々あってFlutterを触ってみることになった。
スマホアプリ開発は全くの未経験のため、AWSサービスを頼って色々やろうと思った

[あつらえ向きのガイドがあった](https://aws.amazon.com/jp/getting-started/hands-on/build-flutter-app-amplify/module-one/)
流石はAWS

### が、問題発生
サンプルコードをそのままコピペしてるとエラーが発生する
![qiita_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312174/4fc0468d-073c-8beb-5729-59c357c8e23f.png)

```:エラー1
The parameter 'key' can't have a value of 'null' because of its type, but the implicit default value is 'null'.
Try adding either an explicit non-'null' default value or the 'required' modifier.
```

![qiita_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312174/fd227a3c-232b-1a5e-c8f2-751cfd976a04.png)

```:エラー2
The property 'authFlowStatus' can't be unconditionally accessed because the receiver can be 'null'.
Try making the access conditional (using '?.') or adding a null check to the target ('!')
```

### 原因はFlutter/Dartのアップデート
色々調べてみたところnull-safetyが原因だと分かった
[Flutter2の登場](https://developers.googleblog.com/2021/03/announcing-flutter-2.html)

AWSのサンプルに限らず現時点(2021/06/08)で表に出ている多くのFlutterやってみた系の記事のコードや、書籍の記載内容なども含めてこの手のエラーが発生する。
\# 恐らくFlutter1.X時代に記載された記事が多いからだと思われる。

### で、結局どうすればエラー解決すんの？
それを調べていたら下記にたどり着いた[
Flutter2のDart Null Safetyを既存のプロジェクトに導入する](https://zuma-lab.com/posts/flutter-dart-sound-null-safety-replace)

> エラー修正方法 2 required キーワードを設定する
> エラー修正方法 3 Optional 型でラップをする

#### エラー1は上記記事の記載そのままで解決する。どちらかでOKだ

```Dart:login_page.dart
  final VoidCallback shouldShowSignUp;
  LoginPage({required Key key,required this.shouldShowSignUp}) : super(key: key);
```
```Dart:login_page.dart
  final VoidCallback? shouldShowSignUp;
  LoginPage({Key? key, this.shouldShowSignUp}) : super(key: key);
```
Optional型にするとnullチェックのif文を作成しないとnullエラーが発生する可能性はあるが、過去に触ってきた言語でnullチェック文を書いてきた人間からするとこちらの方が取っつきやすいのではないだろうか。

#### エラー2は上記の方法では解決できない。
、ここでエラーを吐いている原因は`stream:`オプションで指定したストリームから取得されるsnapshotのパラメータである。
\# コード全体は原文のAWSページを見て頂きたい。

```Dart:main.dart
home: StreamBuilder<AuthState>(
    // 2
    stream: _authService.authStateController.stream,
    builder: (context, snapshot) {
      // 3
      if (snapshot.hasData) {
        return Navigator(
          pages: [
            // 4
            // Show Login Page
            if (snapshot.data.authFlowStatus == AuthFlowStatus.login)
              MaterialPage(child: LoginPage()),

            // 5
            // Show Sign Up Page
            if (snapshot.data.authFlowStatus == AuthFlowStatus.signUp)
              MaterialPage(child: SignUpPage())
          ],
          onPopPage: (route, result) => route.didPop(result),
        );
      } else {
        // 6
        return Container(
          alignment: Alignment.center,
          child: CircularProgressIndicator(),
        );
      }
    }),
```


StreamControllerのstreamはnullableである
> stream → Stream<T>?
The asynchronous computation to which this builder is currently connected, possibly null. When changed, the current summary is updated using afterDisconnected, if the previous stream was not null, followed by afterConnected, if the new stream is not null.

そのためそこから取得されるsnapshotもnullableなのだと思われる。
\# Flutterの公式リファレンスを読んでも細かい記載が見当たらなかったためあくまで推定、有識者の見解ができればほしい

##### パラメータがnullableでif文に乗せれないなら、変数に入れてしまえばいいじゃない
変数に入れようにも該当のif文がある場所はpagesのList内のため変数定義はできない。
ならどうするか？MaterialPageを返す関数を定義すればよいのである

```Dart:main.dart
  MaterialPage snapshotPage(snapshot){
    AuthFlowStatus status = snapshot.data.authFlowStatus;
    if (status == AuthFlowStatus.login)
      return MaterialPage(
        child: LoginPage()
      );
    if (status  == AuthFlowStatus.signUp)
      return MaterialPage(
        child: SignUpPage()
      );
    // 何も返さない場合は関数がエラーになるため、空ページを返す
    return MaterialPage(child: Scaffold());
  }

...

home: StreamBuilder<AuthState>(
        // 2
        stream: _authService.authStateController.stream,
        builder: (context, snapshot) {
          // 3
          if (snapshot.hasData) {
            return Navigator(
              pages: [
                snapshotPage(snapshot)
              ],
              onPopPage: (route, result) => route.didPop(result),
            );
          }

...
```

\# と思っていたのだが、なぜか関数に引数として渡した後ならif文にそのまま載せてもエラーにはならない
~~これはDart/Flutter初学者である私には原因がわからない。~~
(2022/11追記) コードに記載している関数は引数の型を省略しているので分かりにくいが、システム的には下記のように認識されている。
要はnullを許容するdynamic?型ではなく、nullを許容できないdynamic型として認識されているため、nullを考慮することなくコーディングできている訳だ
```Dart:
  MaterialPage snapshotPage(dynamic snapshot){...}
```

```Dart:main.dart
  MaterialPage snapshotPage(snapshot){
    if (snapshot.data.authFlowStatus == AuthFlowStatus.login)
      return MaterialPage(
        child: LoginPage()
      );
    if (snapshot.data.authFlowStatus == AuthFlowStatus.signUp)
      return MaterialPage(
        child: SignUpPage()
      );
    // 何も返さない場合は関数がエラーになるため、空ページを返す
    return MaterialPage(child: Scaffold());
  }
```

### なにはともあれ解決
エラーが解決し、サンプルのアプリも動くことが確認できた。

![qiita_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312174/91b96481-fecf-f2c3-abf3-56b955b6d112.png)
![qiita_4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312174/17df653b-cabd-d5d3-714c-a9e066a9c49b.png)