【问题标题】:Flutter: Bad state: Stream has already been listened toFlutter:错误状态:已经收听了流
【发布时间】:2023-02-16 01:28:58
【问题描述】:

我正在构建一个桌面应用程序,我使用 Firebase 进行登录。为了实现这一点,我正在使用 firedart 包来实现它。登录系统完美运行。我想在登录页面和我根据登录状态随机命名为 FirstPage() 的主页之间切换。因此,当用户注销时,他将被带到登录页面,如果登录,他将被带到 FirstPage()。每当我重新加载 FirstPage() 时,我都会收到错误消息“错误状态:已收听流”。

我在 Stackoverflow 和 GitHub 上浏览了多种解决方案,但没有找到适合我的解决方案。也许我没有正确实施解决方案,或者我缺少某些东西。

以下是我的代码:

主.dart

import 'package:ame/screens/firstPage.dart';
import 'package:ame/screens/loginPage.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:firedart/auth/firebase_auth.dart';
import 'package:firedart/auth/token_store.dart';
import 'package:firedart/firestore/firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:google_fonts/google_fonts.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Window.initialize();
  await Window.setEffect(
    effect: WindowEffect.aero,
    color: const Color.fromARGB(50, 0, 0, 0),
  );

  FirebaseAuth.initialize(
      "AIzaSyBk76lyEHpyDgMot7csMmDiIKnPS_5QiYE", VolatileStore());

  var auth = FirebaseAuth.instance;
  // auth.signInState.listen((state) => print("Signed ${state ? "in" : "out"}"));

  // var user = await auth.getUser();
  // print(user);

  runApp(const MyApp());
  doWhenWindowReady(() {
    var initialSize = const Size(600, 450);
    // appWindow.size = initialSize;
    appWindow.minSize = initialSize;
  });
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      key: UniqueKey(),
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        fontFamily: GoogleFonts.poppins().fontFamily,
        colorScheme: ColorScheme.fromSwatch().copyWith(
          primary: const Color.fromRGBO(7, 96, 49, 1),
          secondary: Colors.white,
        ),
      ),
      routes: {
        '/firstPage': (ctx) => const FirstPage(),
        '/loginPage': (ctx) => const LoginPage(),
      },
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
        stream: FirebaseAuth.instance.signInState,
        builder: (context, snapshot) {
          if (snapshot.hasData && snapshot.data == true) {
            return const FirstPage();
          } else {
            return const LoginPage();
          }
        });
  }
}

登录页面

import 'dart:ui';

import 'package:ame/widgets/rightWindowBar.dart';
import 'package:firedart/auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  List bottomNavTitles = ["Home", "Tour", "Courses", "Articles", "Blog"];
  final emailController = TextEditingController();
  final passwordController = TextEditingController();

  final auth = FirebaseAuth.instance;

  Future<void> login() async {
    await auth.signIn(
        emailController.text.trim(), passwordController.text.trim());
  }

  @override
  Widget build(BuildContext context) {
    // double deviceHeight = MediaQuery.of(context).size.height;
    double deviceWidth = MediaQuery.of(context).size.width;
    return Scaffold(
      backgroundColor: Colors.transparent,
      body: Container(
        decoration: const BoxDecoration(
          image: DecorationImage(
            image: AssetImage('assets/images/login.jpeg'),
            fit: BoxFit.cover,
          ),
        ),
        child: Stack(
          children: [
            BackdropFilter(
              filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
              child: Container(
                decoration: BoxDecoration(color: Colors.black.withOpacity(0.5)),
              ),
            ),
            Column(
              // ignore: prefer_const_literals_to_create_immutables
              children: [
                const RightWindowBar(),
                const Spacer(),
                Container(
                  margin: EdgeInsets.symmetric(horizontal: deviceWidth * 0.35),
                  child: Column(
                    children: [
                      Image.asset('assets/images/ame.png', scale: 9),
                      TextField(
                        controller: emailController,
                        style: const TextStyle(
                          color: Colors.black,
                        ),
                        decoration: const InputDecoration(
                          prefixIcon: Icon(FontAwesomeIcons.envelopesBulk,
                              size: 15, color: Colors.black),
                          hintText: "Email",
                          hintStyle: TextStyle(
                            color: Colors.black,
                          ),
                          filled: true,
                          contentPadding: EdgeInsets.symmetric(
                              horizontal: 16.0, vertical: 10.0),
                          fillColor: Color.fromARGB(31, 255, 255, 255),
                        ),
                      ),
                      const SizedBox(height: 8.0),
                      TextField(
                        controller: passwordController,
                        obscureText: true,
                        style: const TextStyle(
                          color: Colors.black,
                        ),
                        decoration: const InputDecoration(
                          prefixIcon: Icon(FontAwesomeIcons.lock,
                              size: 15, color: Colors.black),
                          hintText: "Password",
                          hintStyle: TextStyle(
                            color: Colors.black,
                          ),
                          filled: true,
                          contentPadding: EdgeInsets.symmetric(
                              horizontal: 16.0, vertical: 10.0),
                          fillColor: Color.fromARGB(31, 255, 255, 255),
                        ),
                      ),
                      const SizedBox(height: 16.0),
                      Row(
                        children: [
                          Expanded(
                            child: ElevatedButton(
                              style: ElevatedButton.styleFrom(
                                shape: BeveledRectangleBorder(),
                                padding: const EdgeInsets.all(16.0),
                              ),
                              onPressed: login,
                              child: const Text("Login"),
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
                const Spacer(),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

第一页

import 'package:ame/widgets/leftWindowBar.dart';
import 'package:ame/widgets/menu_list.dart';
import 'package:ame/widgets/rightWindowBar.dart';
import 'package:firedart/auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class FirstPage extends StatelessWidget {
  const FirstPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          const Expanded(child: MenuLlist()),
          Expanded(
            flex: 2,
            child: Container(
              color: Theme.of(context).colorScheme.secondary,
              child: Column(
                children: const [RightWindowBar()],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

【问题讨论】:

    标签: flutter firebase dart


    【解决方案1】:

    问题是,正如错误所说,FirebaseAuth.instance.signInState 流已被收听不止一次。

    FirebaseAuth.instance.signInState,在引擎盖下,是一个简单的StreamController(包的source code,另请参阅 StreamController 的broadcast constructor,并注意区别)。而且只能听一次。不管是有意还是无意,这个包的开发者都是这样做的(所以 signInState 流只能被收听一次)。

    好的,但是你能做些什么来解决这个问题呢?

    1. 您可以那样更改您的代码,以收听一次流。比如调用main中的listen()函数:
      void main() {
        // ...
      
        final auth = FirebaseAuth.instance;
        final notifier = ValueNotifier<bool>(false);
        // Notice that usually it is a good practice to dispose 
        // subscriptions, streams, notifiers etc. when they 
        // are no longer needed, but in this case it does not play a big role
        auth.signInState.listen((state) => notifier.value = state);
        
        // And then you can pass down the tree this notifier whatever way you like
        // E.g. by using provider, or simply pass through constructor:
        runApp(const MyApp(signInNotifier: notifier));
      }
      

      然后,在 MyHomePage() 中,您可以像这样使用此通知程序:

      class MyHomePage extends StatelessWidget {
        const MyHomePage({super.key, required this.signInNotifier});
      
        @protected
        final ValueNotifier<bool> signInNotifier;
      
        @override
        Widget build(BuildContext context) {
          return ValueListenableBuilder<bool>(
            valueListenable: signInNotifier,
            builder: (context, signedIn, child) {
              if (signedIn) return const FirstPage();
      
              return const LoginPage();
            },
          );
        }
      }
      
      1. 通过某种方式使FirebaseAuth.instance.signInState 流成为广播。有很多选择。比如你可以开个issue,让作者把StreamController()constructor换成StreamController.broadcast()constructor(或者做一个选项让开发者选择是否使用默认构造函数,或者broadcast),或者你也可以PR, ETC。

    【讨论】:

      猜你喜欢
      • 2021-12-26
      • 1970-01-01
      • 2018-09-01
      • 2018-12-26
      • 1970-01-01
      • 2020-03-08
      • 2021-06-19
      • 2020-02-18
      • 2019-07-23
      相关资源
      最近更新 更多