web-dev-qa-db-ja.com

Flutter-すべてのビューにユーザーデータを渡す方法

フラッターの世界とモバイルアプリの開発は初めてであり、アプリ全体でユーザーデータを渡す方法に苦労しています。

私はいくつかのことを試しましたが、どれも素晴らしいとは思えず、従うべきベストプラクティスのパターンがあると確信しています。

例が簡単になるため、認証にfirebaseを使用しています。現在、ログイン用の個別のルートがあります。ログインしたら、ほとんどのビューで、表示するアイテムの権限を確認したり、引き出しにユーザー情報を表示したりするためのユーザーモデルが必要です。

Firebaseにはawait firebaseAuth.currentUser();があります。ユーザーが必要になる可能性があるすべての場所でこれを呼び出すのがベストプラクティスですか?もしそうなら、この電話をかけるのに最適な場所はどこですか?

flutter codelab は、書き込みを許可する前にユーザーを認証する優れた例を示しています。ただし、ページで認証を確認してビルドするものを決定する必要がある場合、非同期呼び出しをbuildメソッドに入れることはできません。

initState

私が試した方法の1つは、initStateをオーバーライドし、ユーザーを取得するために呼び出しを開始することです。 futureが完了したら、setStateを呼び出してユーザーを更新します。

_    FirebaseUser user;

    @override
    void initState() {
      super.initState();
      _getUserDetail();
    }

  Future<Null> _getUserDetail() async {
    User currentUser = await firebaseAuth.currentUser();
    setState(() => user = currentUser);
  }
_

これはきちんと機能しますが、それを必要とするウィジェットごとに多くの儀式のようです。ユーザーなしで画面が読み込まれ、将来の完了時にユーザーと共に更新されるときにもフラッシュがあります。

ユーザーをコンストラクターに渡す

これも機能しますが、すべてのルート、ビュー、およびそれらにアクセスする必要がある可能性のある状態をユーザーに渡す多くの定型句です。また、変数を渡すことができないため、ルートを移行するときにpopAndPushNamedを実行することはできません。次のようなルートを変更する必要があります。

_Navigator.Push(context, new MaterialPageRoute(
    builder: (BuildContext context) => new MyPage(user),
));
_

継承ウィジェット

https://medium.com/@mehmetf_71205/inheriting-widgets-b7ac56dbbeb1

この記事では、InheritedWidgetを使用するための素敵なパターンを示しました。継承されたウィジェットをMaterialAppレベルに配置すると、認証状態が変更されたときに子が更新されません(間違っていると確信しています)。

_  FirebaseUser user;

  Future<Null> didChangeDependency() async {
    super.didChangeDependencies();
    User currentUser = await firebaseAuth.currentUser();
    setState(() => user = currentUser);
  }

  @override
  Widget build(BuildContext context) {
    return new UserContext(
      user,
      child: new MaterialApp(
        title: 'TC Stream',
        theme: new ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: new LoginView(title: 'TC Stream Login', analytics: analytics),
        routes: routes,
      ),
    );
  }
_

FutureBuilder

FutureBuilderも適切なオプションのように見えますが、各ルートで多くの作業が必要と思われます。以下の部分的な例では、_authenticateUser()はユーザーを取得し、完了時に状態を設定しています。

_  @override
  Widget build(BuildContext context) {
    return new FutureBuilder<FirebaseUser>(
      future: _authenticateUser(),
      builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return _buildProgressIndicator();
        }
        if (snapshot.connectionState == ConnectionState.done) {
          return _buildPage();
        }
      },
    );
  }
_

ベストプラクティスのパターンや、例に使用するリソースへのリンクに関するアドバイスをいただければ幸いです。

24
Aaron

継承されたウィジェットをさらに調査することをお勧めします。以下のコードは、非同期にデータを更新する際にそれらを使用する方法を示しています。

import 'Dart:convert';

import 'package:flutter/material.Dart';
import 'package:http/http.Dart' as http;

void main() {
  runApp(new MaterialApp(
      title: 'Inherited Widgets Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new Scaffold(
          appBar: new AppBar(
            title: new Text('Inherited Widget Example'),
          ),
          body: new NamePage())));
}

// Inherited widget for managing a name
class NameInheritedWidget extends InheritedWidget {
  const NameInheritedWidget({
    Key key,
    this.name,
    Widget child}) : super(key: key, child: child);

  final String name;

  @override
  bool updateShouldNotify(NameInheritedWidget old) {
    print('In updateShouldNotify');
    return name != old.name;
  }

  static NameInheritedWidget of(BuildContext context) {
    // You could also just directly return the name here
    // as there's only one field
    return context.inheritFromWidgetOfExactType(NameInheritedWidget);
  }
}

// Stateful widget for managing name data
class NamePage extends StatefulWidget {
  @override
  _NamePageState createState() => new _NamePageState();
}

// State for managing fetching name data over HTTP
class _NamePageState extends State<NamePage> {
  String name = 'Placeholder';

  // Fetch a name asynchonously over HTTP
  _get() async {
    var res = await http.get('https://jsonplaceholder.typicode.com/users');
    var name = JSON.decode(res.body)[0]['name'];
    setState(() => this.name = name); 
  }

  @override
  void initState() {
    super.initState();
    _get();
  }

  @override
  Widget build(BuildContext context) {
    return new NameInheritedWidget(
      name: name,
      child: const IntermediateWidget()
    );
  }
}

// Intermediate widget to show how inherited widgets
// can propagate changes down the widget tree
class IntermediateWidget extends StatelessWidget {
  // Using a const constructor makes the widget cacheable
  const IntermediateWidget();

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new Padding(
        padding: new EdgeInsets.all(10.0),
        child: const NameWidget()));
  }
}

class NameWidget extends StatelessWidget {
  const NameWidget();

  @override
  Widget build(BuildContext context) {
    final inheritedWidget = NameInheritedWidget.of(context);
    return new Text(
      inheritedWidget.name,
      style: Theme.of(context).textTheme.display1,
    );
  }
}
15
Matt S.

この問題のために別の問題にぶつかりました ここで確認できます だから、私が思いついた解決策は少しだらしなく、個別のインスタンスDartページを作成し、それをすべてのページにインポートしました。

 GoogleSignInAccount Guser = googleSignIn.currentUser;
 FirebaseUser Fuser;

ログイン時にユーザーをそこに保存し、すべてのStateWidgetがnullかどうかを確認しました

  Future<Null> _ensureLoggedIn() async {

if (Guser == null) Guser = await googleSignIn.signInSilently();
if (Fuser == null) {
  await googleSignIn.signIn();
  analytics.logLogin();
}
if (await auth.currentUser() == null) {
  GoogleSignInAuthentication credentials =
  await googleSignIn.currentUser.authentication;
  await auth.signInWithGoogle(
    idToken: credentials.idToken,
    accessToken: credentials.accessToken,
  );
}

これは、現在のアプリでクリーンアップした古いコードですが、現在そのコードは手元にありません。 nullユーザーをチェックアウトして再度ログインするだけです

私のアプリには3ページ以上あり、継承ウィジェットは非常に手間がかかったため、ほとんどのFirebaseインスタンスでもそれを行いました。

1
Prime