22
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-06-08

必死に調べた時間が無駄になったオチ

(追記)
VSCodeのクイックフィックス使おうね
# VSCodeあまり使わないからこんな機能あるの知らなったの…

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

Flutterを初めた

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

あつらえ向きのガイドがあった
流石はAWS

が、問題発生

サンプルコードをそのままコピペしてるとエラーが発生する
qiita_1.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

エラー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の登場

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

で、結局どうすればエラー解決すんの?

それを調べていたら下記にたどり着いた
Flutter2のDart Null Safetyを既存のプロジェクトに導入する

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

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

login_page.dart
  final VoidCallback shouldShowSignUp;
  LoginPage({required Key key,required this.shouldShowSignUp}) : super(key: key);
login_page.dart
  final VoidCallback? shouldShowSignUp;
  LoginPage({Key? key, this.shouldShowSignUp}) : super(key: key);

Optional型にするとnullチェックのif文を作成しないとnullエラーが発生する可能性はあるが、過去に触ってきた言語でnullチェック文を書いてきた人間からするとこちらの方が取っつきやすいのではないだろうか。

エラー2は上記の方法では解決できない。

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

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?
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を返す関数を定義すればよいのである

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を考慮することなくコーディングできている訳だ

  MaterialPage snapshotPage(dynamic snapshot){...}
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
qiita_4.PNG

22
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?