【问题标题】:StreamProvider with RiverPod not working (try to migrate from Provider)带有 RiverPod 的 StreamProvider 不工作(尝试从 Provider 迁移)
【发布时间】:2021-01-05 22:25:13
【问题描述】:

我试图通过将简单的 FireStore auth Provider 示例迁移到 RiverPod 来了解 RiverPod。

这是我的身份验证服务:

import 'package:firebase_auth/firebase_auth.dart';

class AuthenticationService {
  final FirebaseAuth _firebaseAuth;
  AuthenticationService(this._firebaseAuth);

  // with StreamProvider we listen to these changes
  Stream<User> get authStateChanges => _firebaseAuth.authStateChanges();

  Future<String> signIn({String email, String password}) async {
    try {
      await _firebaseAuth.signInWithEmailAndPassword(
          email: email, password: password);
      return 'Signed in';
    } on FirebaseAuthException catch (e) {
      return e.message;
    }
  }

  Future<String> signUp({String email, String password}) async {
    try {
      await _firebaseAuth.createUserWithEmailAndPassword(
          email: email, password: password);
      return 'Signed up ';
    } on FirebaseAuthException catch (e) {
      return e.message;
    }
  }

  Future<void> signOut() async {
    await _firebaseAuth.signOut();
  }
}

在 main.dart 中,我创建了 2 个提供程序,因此我可以使用该服务并监听 AuthenticationService 中的属性

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:meditatie_app/authentication_service.dart';
import 'package:meditatie_app/home_page.dart';
import 'package:meditatie_app/signin_page.dart';
import 'package:provider/provider.dart';

Future<void> main() async {
  // initalize Firebase and before that we need to initialize the widgets.
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        // Normal provider to serve the AuthenticationService in the widgettree
        // so the login form can use this provider to use .singIn()
        Provider<AuthenticationService>(
          create: (_) => AuthenticationService(FirebaseAuth.instance),
        ),
        // also a StreamProvider that serves the AuthenticationSerivce authStateChanges
        // this stream is updated by the FirebaseAuth package when users signin or out
        // this provider use context.read<AuthenticationService> to find the
        // provider dived here above
        StreamProvider(
          create: (context) =>
              context.read<AuthenticationService>().authStateChanges,
        )
      ],
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: AuthenticationWrapper(),
      ),
    );
  }
}

class AuthenticationWrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final firebaseUser = context.watch<User>();
    if (firebaseUser != null) {
      return HomePage();
    }
    return SignInPage();
  }
}

这里是 SingIn 页面:

import 'package:flutter/material.dart';
import 'package:meditatie_app/authentication_service.dart';
import 'package:provider/provider.dart';

class SignInPage extends StatelessWidget {
  final TextEditingController emailController = TextEditingController();
  final TextEditingController passwordController = TextEditingController();

...
          RaisedButton(
            onPressed: () {
              // Sign in code
              context.read<AuthenticationService>().signIn(
                    email: emailController.text.trim(),
                    password: passwordController.text.trim(),
                  );
            },
...

这适用于普通 Provider,但我无法让它与 RiverPod 一起使用

我所做的是:

这些提供者我在 providers.dart 中全局化了

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/all.dart';

import 'authentication_service.dart';

final authenticationSeriviceProvider =
    Provider((ref) => AuthenticationService(FirebaseAuth.instance));
final authStateChangeProvider = StreamProvider.autoDispose<User>((ref) {
  return ref
      .watch(authenticationSeriviceProvider)
      .authStateChanges;
});

这是正确的吗? authStateChangeProvider 正在使用 authenticationServiceProvider

什么时候使用它:

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:meditatie_app/home_page.dart';
import 'package:meditatie_app/signin_page.dart';
import 'package:flutter_riverpod/all.dart';
import 'providers.dart';

Future<void> main() async {
  // initialize Firebase and before that we need to initialize the widgets.
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(
    // riverpod needs at toplevel a Provider container
    // for storing state of different providers.
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: AuthenticationWrapper(),
    );
  }
}

// Riverpods ConsumerWidget
// which can consume a provider
// rebuild if the value of the provider changes
class AuthenticationWrapper extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final firebaseUser = watch(authStateChangeProvider);
    if (firebaseUser != null) {
      return HomePage();
    }
    return SignInPage();
  }
}

我的“firebaseUser”不再是用户,而是 AsyncValue

当我将其更改为:

class AuthenticationWrapper extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final User firebaseUser = watch(authStateChangeProvider).data?.value;
    if (firebaseUser != null) {
      return HomePage();
    }
    return SignInPage();
  }
}

它正在工作,但是我现在使用 AsyncValue 做错了什么

【问题讨论】:

    标签: flutter riverpod


    【解决方案1】:

    扩展上一个答案AsyncValue&lt;T&gt; 是一个密封类,将其视为 Flutter 中的 StreamBuilder,具有 AsyncSnapshot&lt;T&gt;,它包装了从流返回的值,并为您提供检查其是否为 connectingwaitingwithErrorwithData。你的情况

    class AuthenticationWrapper extends ConsumerWidget {
      @override
      Widget build(BuildContext context, ScopedReader watch) {
        return watch(authStateChangeProvider).when(
          data: (user) => user == null ? SignInPage() : HomePage(),
          loading: () => CircularProgressIndicator(),
          error: (err, stack) => SignInPage(),
        );
      }
    }
    

    应该处理所有选项,现在加载时会显示进度指示器,如果出现错误(连接、错误结果等),它将显示 SignInPage,最后当有值时你仍然需要检查从流返回的值是否为空(据我了解,当没有用户登录时,Firebase 返回空,这并不意味着流为空)并显示正确的小部件是否为空。

    就像 Provider 一样,在检索到用户之后,你仍然需要用它来做逻辑

    【讨论】:

      【解决方案2】:

      the documentation

      您应该使用 AsyncValue 的公开状态来决定渲染什么。您的代码可能如下所示:

      class AuthenticationWrapper extends ConsumerWidget {
        @override
        Widget build(BuildContext context, ScopedReader watch) {
          return watch(authStateChangeProvider).when(
            data: (user) => user == null ? SignInPage() : HomePage(),
            loading: () => CircularProgressIndicator(),
            error: (err, stack) => SignInPage(),
          );
        }
      }
      

      因此,将您的返回逻辑调整为您想要的数据、加载和错误状态,但这应该让您大致了解如何使用 AsyncValue。

      【讨论】:

      • 谢谢亚历克斯,您的示例不适用于我的情况,因为数据值为 null 并且当用户不再登录时不会出现错误。当我这样做时:最终用户 firebaseUser = watch (authStateChangeProvider).data?.value;然后我可以检查数据是否为空,FirebaseAuth 包没有在 Stream 上放置错误
      • 更新了我的答案以处理用户为空的情况。 @EdwynZN 的回答还提供了更多关于 AsyncValue 实际发生的情况。
      【解决方案3】:

      我发现的另一种方法是像 tutorial 那样使用它,但新的 riverpod 发生了变化:

      import 'dart:async';
      
      import 'package:firebase_auth/firebase_auth.dart';
      import 'package:flutter_shopping_list/repositories/auth_repository.dart';
      import 'package:hooks_riverpod/hooks_riverpod.dart';
      
      final authControllerProvider = StateNotifierProvider<AuthController, User?>(
        (ref) => AuthController(ref.read)..appStarted(),
      );
      
      class AuthController extends StateNotifier<User?> {
        final Reader _read;
      
        StreamSubscription<User?>? _authStateChangesSubscription;
      
        AuthController(this._read) : super(null) {
          _authStateChangesSubscription?.cancel();
          _authStateChangesSubscription = _read(authRepositoryProvider)
              .authStateChanges
              .listen((user) => state = user);
        }
      
        @override
        void dispose() {
          _authStateChangesSubscription?.cancel();
          super.dispose();
        }
      
        void appStarted() async {
          final user = _read(authRepositoryProvider).getCurrentUser();
          if (user == null) {
            await _read(authRepositoryProvider).signInAnonymously();
          }
        }
      }
      

      然后我像这样使用它:

      @override
        Widget build(BuildContext context, WidgetRef ref) {
          User? user = ref.watch<User?>(authControllerProvider);
          return user != null
              ? MaterialApp(
                  title: 'My App',
                  builder: (context, child) => _Unfocus(child: child!),
                  home: MainNavigation(),
                  debugShowCheckedModeBanner: false,
                )
              : const MaterialApp(
                  title: 'My App,
                  home: LoginPage(),
                  debugShowCheckedModeBanner: false,
                );
        }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-11-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-23
        • 2021-10-20
        • 1970-01-01
        相关资源
        最近更新 更多