Flutter - 使用提供程序包侦听 Firebase 用户的用户配置文件(Firestore)中的数据更改

Posted

技术标签:

【中文标题】Flutter - 使用提供程序包侦听 Firebase 用户的用户配置文件(Firestore)中的数据更改【英文标题】:Flutter - Listening for data changes in user profile (Firestore) of Firebase user using provider package 【发布时间】:2021-02-08 21:42:09 【问题描述】:

现在我成功地对我的 Firebase 用户的 auth-changes 做出反应,将其映射到我自己的自定义用户类

import 'package:mypckg/models/user.dart' as local;
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:mypckg/services/database.dart';

class AuthService 
  final auth.FirebaseAuth _auth = auth.FirebaseAuth.instance;

  // create user obj based on firebase user
  Future<local.User> _userFromFirebaseUser(auth.User user) async 
    return user != null
        ? local.User(/* ... init user properties ... */)
        : null;
  

  // auth change user stream
  Stream<local.User> get user 
    return _auth.authStateChanges().asyncMap(_userFromFirebaseUser);
  

...

在 Cloud Firestore 中,我存储了 Firebase 用户未涵盖的该用户的其他值,例如

ma​​in.dart 中,提供程序设置为在本地用户登录或注销时为我的应用程序提供 (authStateChanges)。我的想法是订阅另一个流,它将监听 Cloud Firestore 中“用户”文档的更改。

class MyPckg extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    final i18n = I18n.delegate;
    //AuthService().signInAnon();
    return MultiProvider(
      providers: [
        StreamProvider<User>(
          create: (_) => AuthService().user,
        ),
        /* my idea to subscribe to another stream which will listen to changes on user details in Firestore */
        StreamProvider<User>(
          create: (_) => AuthService().customUser,
        ),
      ],
      child: DynamicTheme(
        defaultBrightness: Brightness.light,
        data: (brightness) 

...

我有一个配置文件视图,用户可以在其中编辑这些值,例如语言环境,它被正确写入 Firestore

Future<void> updateUserLanguage(String language) async 
    return await usersCollection.doc(uid).update(
      'language': language,
    );
  

但视图不会重建,因为当前流仅对 authStateChanges 做出反应。

有没有人有一个工作示例如何设置来自 Firestore 集合中用户的链接,我的应用程序将在那里监听所做的更改?我的 customUser 方法应该是什么样子?

谢谢!

【问题讨论】:

你卡在哪里了?更具体地说,您面临什么错误? 好吧,我不确定一般如何设计结构。我是否必须在我的视图中使用 FutureBuilder 来显示和更改自定义用户值,还是应该从提供者对用户对象进行简单的初始化就足够了?喜欢dart final User user = Provider.of&lt;User&gt;(context); 我有确切的问题。你有没有想过解决方案? @Ocrimops 不,很遗憾没有。 我现在正在处理同样的问题,medium.com/flutter-community/… 似乎接近我们想要实现的目标。当我弄清楚时会发布答案。 【参考方案1】:

我决定使用 StreamProvider 将登录状态中的所有内容包装起来:

class Wrapper extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    final user = Provider.of<User>(context);
    if (user == null) 
      return Authenticate();
     else 
      return StreamProvider<Profile>.value(
        value: DatabaseService(uid: user.uid).profile,
        initialData: Profile(userName: '0', userRole: '0'),
        child: HomeScreen(),
      );
    
  

db 服务接受 UID 并返回快照:

class DatabaseService 
  final String uid;
  DatabaseService(
    this.uid,
  );

static CollectionReference _profileCollection =
      FirebaseFirestore.instance.collection('profiles');

  Stream<Profile> get profile 
    return _profileCollection.doc(uid).snapshots().map(_profileFromSnapshot);
  

  Profile _profileFromSnapshot(DocumentSnapshot snapshot) 
    return Profile(
      userName: snapshot.data()['userName'] ?? '',
      userRole: snapshot.data()['role'] ?? '',
    );
  

最后,我通过消费者在任何需要的地方使用数据:

class HomeScreen extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Container(
        child: Center(
          child: Consumer<Profile>(builder: (
            context,
            profile,
            child,
          ) 
            return Text(profile.userName)
...

edit:我制作了一个关于这种技术的视频。链接:https://youtu.be/mAH9YT_y6ec

【讨论】:

嘿,如果你的代码是开源的,你能发布一个链接吗?谢谢! 对不起,这是一个私人回购,你不明白哪一部分? 您好,抱歉回复晚了。我做的和你完全一样。但是当新用户登录时我的流仍然没有更新并且仍然显示来自已注销用户的旧数据。这是我的代码link 供参考。谢谢。 如果我得到了心理周期,我会发布一个关于这个的视频系列。这似乎是一个普遍的问题。加上 Nav2.0,但那是另一回事了。【参考方案2】:

对于那些也在努力使用这种方法的人,这是我的解决方案(感谢所有引导我采用这种方法的贡献者):

我在 MyClass 中设置了一个方法“setAppUser”,可以从任何孩子调用。对此很重要

static _MyClassState? of(BuildContext context) =>
          context.findAncestorStateOfType<_MyClassState>();

所以这里是 main.dart(只有必要的部分):

class MyClass extends StatefulWidget 
  @override
  _MyClassState createState() => _MyClassState();
  static _MyClassState? of(BuildContext context) =>
      context.findAncestorStateOfType<_MyClassState>();


class _MyClassState extends State<MyClass> 
  User appUser = User();

  set setAppUser(User user) => 
        setState(() 
          appUser = user;
        )
      ;

  @override
  Widget build(BuildContext context) 
  ...
    return MultiProvider(
        providers: [
          StreamProvider<UserLogin>(
            initialData: UserLogin(signedIn: false),
            create: (_) => AuthService().userAuthStateChanges,
            catchError: (_, error) => UserLogin(signedIn: false),
          ),
          StreamProvider<User>(
            initialData: appUser,
            create: (_) => AuthService().userData,
            catchError: (_, error) => appUser,
          ),
        ],
        child: MaterialApp(
        ...

在我的登录表单中,我在收到新的登录用户后调用 main.dart 的“setAppUser”方法。现在小部件被重建,新的 appUser 设置为我的提供者。

  ...
  await _auth
    .signInWithEmailAndPassword(
    textControllerEmail.text,
    textControllerPassword.text)
      .then((result) 
        if (result.id != "") 
          MyClass.of(context)!.setAppUser = result;
          ...

【讨论】:

以上是关于Flutter - 使用提供程序包侦听 Firebase 用户的用户配置文件(Firestore)中的数据更改的主要内容,如果未能解决你的问题,请参考以下文章

使用带有提供程序包的“模块”的 Flutter 应用程序架构

使用 nativescript-plugin-firebase 添加带有查询的事件侦听器

如何在 Dart 2 中限制 TextEditingController 侦听器事件 - Flutter

为啥 Flutter 导入提供程序包不起作用?

Flutter:如何暂停和重新启动“Stream”侦听器?

flutter-MethodChannel的后台侦听器