web-dev-qa-db-ja.com

アプリのロード時にデータをプルするようにFlutterウィジェットのシーケンスを修正

アプリの読み込み時にローカルストレージからデータを読み取ろうとすると、フラッターの問題が発生します。

現在のユーザーの認証情報を保持する継承されたウィジェットがあります。アプリが読み込まれたら、ローカルストレージを調べてセッショントークンを探します。セッショントークンが存在する場合、継承されたウィジェットをこの情報で更新します。

画面はダイナミックです。ユーザーが認証されていることがわかっている場合は、要求された画面に移動し、それ以外の場合は、登録画面に移動します。

私が遭遇している問題は、継承されたウィジェットに依存するウィジェット(My routerウィジェット)のinitState()メソッドから継承されたウィジェットの状態を更新できないことです

アプリが継承されたウィジェットを読み込んで更新するときに、ローカルストレージから読み取るにはどうすればよいですか?

アプリ実行時のエラー:

flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building _InheritedAuthContainer:
flutter: inheritFromWidgetOfExactType(_InheritedAuthContainer) or inheritFromElement() was called before
flutter: RootState.initState() completed.
flutter: When an inherited widget changes, for example if the value of Theme.of() changes, its dependent
flutter: widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor
flutter: or an initState() method, then the rebuilt dependent widget will not reflect the changes in the
flutter: inherited widget.
flutter: Typically references to inherited widgets should occur in widget build() methods. Alternatively,
flutter: initialization based on inherited widgets can be placed in the didChangeDependencies method, which
flutter: is called after initState and whenever the dependencies change thereafter.

ルーターウィジェット(ルート)

class Root extends StatefulWidget {
  @override
  State createState() => RootState();
}

class RootState extends State<Root> {
  static Map<String, Widget> routeTable = {Constants.HOME: Home()};
  bool loaded = false;
  bool authenticated = false;

  @override
  void initState() {
    super.initState();
    if (!loaded) {
      AuthContainerState data = AuthContainer.of(context);
      data.isAuthenticated().then((authenticated) {
        setState(() {
          authenticated = authenticated;
          loaded = true;
        });
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        initialRoute: '/',
        onGenerateRoute: (routeSettings) {
          WidgetBuilder screen;
          if (loaded) {
            if (authenticated) {
              screen = (context) => SafeArea(
                  child: Material(
                      type: MaterialType.transparency,
                      child: routeTable[routeSettings.name]));
            } else {
              screen = (conext) => SafeArea(
                  child: Material(
                      type: MaterialType.transparency, child: Register()));
            }
          } else {
            screen = (context) => new Container();
          }
          return new MaterialPageRoute(
            builder: screen,
            settings: routeSettings,
          );
        });
  }
}

Authをチェックし、自分自身を更新してルーターウィジェットの再レンダリングをトリガーする、継承されたウィジェットメソッド

Future<bool> isAuthenticated() async {
    if (user == null) {
      final storage = new FlutterSecureStorage();
      List results = await Future.wait([
        storage.read(key: 'idToken'),
        storage.read(key: 'accessToken'), 
        storage.read(key: 'refreshToken'),
        storage.read(key: 'firstName'),
        storage.read(key: 'lastName'),
        storage.read(key: 'email')
      ]);
      if (results != null && results[0] != null && results[1] != null && results[2] != null) {
        //triggers a set state on this widget
        updateUserInfo(
          identityToken: results[0], 
          accessToken: results[1], 
          refreshToken: results[2],
          firstName: results[3],
          lastName: results[4],
          email: results[5]
        );
      }
    }
    return user != null && (JWT.isActive(user.identityToken) || JWT.isActive(user.refreshToken));
  }

メイン

void main() => runApp(
  EnvironmentContainer(
    baseUrl: DEV_API_BASE_URL,
    child: AuthContainer(
      child: Root()
    )
  )
);

アプリの読み込み時にローカルストレージを確認し、この情報を保持する継承されたウィジェットを更新する正しい方法は何ですか?

10
TemporaryFix

実際、InheritedWidgetメソッドからinitStateにアクセスすることはできません。代わりに、didChangeDependenciesからアクセスしてみてください。

例:

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  if (!loaded) {
    AuthContainerState data = AuthContainer.of(context);
    data.isAuthenticated().then((authenticated) {
      setState(() {
        authenticated = authenticated;
        loaded = true;
      });
    });
  }
}

もう1つの方法は、initStateを使用してSchedulerBindingでデータフェッチをスケジュールすることです。あなたはドキュメントを見つけることができます ここ

SchedulerBinding.instance.addPostFrameCallback((_) {
  // your login goes here
});

注:didChangeDependenciesは、親InheritedWidgetの状態または依存関係が変更されるたびに呼び出されることに注意してください。ドキュメントをご覧ください here

お役に立てれば!

9
Hemanth Raj

@ hemanth-rajの答えは正しいですが、実際にはこれとは少し異なる方法を推奨します。データなしでAuthContainerを作成する代わりに、ウィジェットを作成してデータを直接渡す前に、実際にユーザーセッションの読み込みを行うことができます。この例では scoped_model plugin を使用して、継承されたウィジェットのボイラープレートを抽象化します(継承されたウィジェットを手動で作成することを強くお勧めします!)。

Future startUp() async {
  UserModel userModel = await loadUser();
  runApp(
    ScopedModel<UserModel>(
      model: userModel,
      child: ....
    ),
  );
}

void main() {
  startup();
}

これは多かれ少なかれ私のアプリで行うことであり、私はそれで問題がありませんでした(ただし、loadUserが失敗する可能性がある場合は、おそらくエラー処理を行う必要があります)。

これにより、userStateコードがよりクリーンになります=)。

そして参考までに、私のUserModelで行ったことはbool get loggedIn => ...これは、ユーザーがログインしているかどうかを示すためにそこに必要な情報を認識しています。そうすれば、それを個別に追跡する必要はありませんが、モデルの外部から通知するためのすてきな単純な方法が得られます。

3
rmtmckenzie

this の例として、次のようにします。

void addPostFrameCallback(FrameCallback callback) {
  // Login logic code 
}

このフレームの終わりにコールバックをスケジュールします。

新しいフレームを要求しません。

このコールバックは、永続的なフレームコールバックの直後(メインレンダリングパイプラインがフラッシュされたとき)に、フレーム中に実行されます。フレームが進行中で、フレーム後のコールバックがまだ実行されていない場合でも、登録されたコールバックはフレーム中に実行されます。それ以外の場合、登録されたコールバックは次のフレームで実行されます。

コールバックは、追加された順序で実行されます。

フレーム後のコールバックは登録解除できません。彼らは正確に一度呼ばれます。

0
Hank Moody