StreamBuilder(

stream:

builder:

)

场景

在写一个登录页面,在AuthPage页面,需要判断当前登录状态而后跳转首页或登录页面。教程使用Scaffold包裹StreamBuilder。教程使用Firebase云平台,可以直接调用API为stream赋值,而我使用LeanCloud,其判断当前用户状态的API为

LCUser currentUser = await LCUser.getCurrent();

if (currentUser != null) {

// 跳到首页

} else {

// 显示注册或登录页面

}

 为Future类型,不是stream类型。无法直接填写。

尝试使用FutureBuilder,用一个异步函数包裹获取当前用户状态再返回值,可以实现登录跳转,但是由于FutureBuilder只加载一次,点击退出按钮后需要重新加载才能响应。

FutureBuilder(

future: checkCurrentUser(),

builder: (context, snapshot) {

if (snapshot.connectionState == ConnectionState.done) {

if (snapshot.data == true) {

return HomePage();

} else {

return LoginPage();

}

} else {

return Scaffold(

body: Center(

child: CircularProgressIndicator(),

),

Future checkCurrentUser() async {

LCUser currentUser = await LCUser.getCurrent();

return currentUser != null;

}

Flutter进阶-Future、FutureBuilder、StreamBuilder详解_dart streambuilder-CSDN博客

尝试过程

2023/3/1 更新

上次更新遗留一个问题,就是程序第一次启动时,没有地方调用stream,所以即使目前已经是登录状态了,也会跳到登录页面。

在AuthPage页面添加初始化函数,这里页面我使用的是StatefulWidget。由于初始化函数不能加async,另写一个异步函数,在初始化函数里面调用。

@override

void initState() {

// TODO: implement initState

super.initState();

_initializeStream();

}

_initializeStream() async {

LoginStream().addData(await LCUser.getCurrent() != null);

}

————————————————————————————————

2023/2/29  更新

继上次,其实也就是昨天,我借鉴到了一个办法也许可以解决LCUser.getCurrent()不调用的问题。

主要思路是创建一个全局controller,在登录界面登录成功后调用controller.add(0),更新stream的状态。在AuthPage界面,编写StreamBuilder,这样stream状态改变后就会触发该页面重新构建获取getCurrent的值。

具体实现我先询问了一下GPT,主要是不太确定能否创建,以及如何创建全局controller。

以下是它的回复:

在Flutter中,您可以使用`StreamController`来创建一个全局的stream controller,以便在不同界面之间共享。以下是一个简单的示例代码,演示如何创建一个全局的stream controller,并在不同界面之间进行跨界面操作同一个stream:

import 'dart:async';

class GlobalStream {

  static final GlobalStream _instance = GlobalStream._internal();

  

  factory GlobalStream() {

    return _instance;

  }

  

  GlobalStream._internal();

  

  StreamController _controller = StreamController.broadcast();

  

  Stream get stream => _controller.stream;

  

  void addData(int data) {

    _controller.add(data);

  }

}

// 在第一个界面中调用addData方法更新stream的状态

GlobalStream().addData(0);

// 在第二个界面中监听stream的更新并重新构建界面

StreamBuilder(

  stream: GlobalStream().stream,

  builder: (context, snapshot) {

    if (snapshot.hasData) {

      return Text('Data: ${snapshot.data}');

    } else {

      return Text('No data');

    }

  },

)

在上面的示例中,`GlobalStream`类是一个单例类,用于创建一个全局的stream controller。在第一个界面中调用`addData`方法更新stream的状态,然后在第二个界面中通过`StreamBuilder`监听stream的更新并重新构建界面。这样就实现了在不同界面之间跨界面操作同一个stream的功能。

又尝试了两个小时,终于成功了T^T

以下是总结:

①全局函数按照上面的方法写没有问题

②我将Stream接收类型由int改为了bool

③渲染部分,先判断有无done,再根据snapshot.data渲染不同内容

有一个奇怪的点是,初始化时判断有无done,如果返回加载画面,会一直卡在加载画面。改为未完成时跳转到login页面就正常。或者不能判断有无done,直接判断data

关键代码呈现:

全局函数:

import 'dart:async';

import 'package:leancloud_storage/leancloud.dart';

// 创建全局stream用于监听登录

class LoginStream {

static final LoginStream _instance = LoginStream._internal();

factory LoginStream() {

return _instance;

}

LoginStream._internal();

final StreamController _controller = StreamController.broadcast();

Stream get stream => _controller.stream;

void addData(bool data) {

_controller.add(data);

}

}

login登录部分

// login method

void login() async {

// show loading circle

showDialog(

context: context,

builder: (context) => const Center(

child: CircularProgressIndicator(),

));

// try sign in

try {

// 登录成功

LCUser user = await LCUser.login(

mobileController.text,

passwordController.text

);

// pop loading circle

Navigator.pop(context);

displayMessageToUser('登录成功', context);

// 每次进行登录操作就更新一下stream

// 实例类名需要通过实例引用,而不是类名

LoginStream().addData(await LCUser.getCurrent() != null);

} on LCException catch (e) {

// 登录失败(可能是密码错误)

print('${e.code} : ${e.message}');

// pop loading circle

Navigator.pop(context);

displayMessageToUser('登录失败', context);

}

}

logout登出部分

// logout user

Future logout() async {

LCUser.logout();

LoginStream().addData(await LCUser.getCurrent() != null);

}

——————————————————————————————

尝试了一个下午,估计5小时。无论怎么改写,都无法成功,主要分为两种情况:一种是页面持续处于加载中,即StreamBuilder无法变为done。一种情况是能正常显示登录页面或首页,但是无法跳转,必须重新加载应用才能切换。

使用Stream:Stream.fromFuture(LCUser.getCurrent())将Future类型直接转为Stream,发现当登录状态改变时根本无法触发StreamBuilder重新加载。

又将LCUser.getCurrent()单独由一个Future函数包裹,而后直接返回LCUser.getCurrent()的结果,将该Future函数转为Stream,状态改变时也无法触发加载。

然后我就怀疑根本无法捕捉到getCurrent()数值的变化,因为这个方法只调用了一次就结束了!

以下是我询问GPT的结果,给了我启发,我学它那样每隔一秒调用一次getCurrent(),结果就成功了,肉眼观察来看,就是登录后能跳转,登出时能跳转。 

在使用`Stream.fromFuture()`包裹一个`Future`函数时,`Stream`只能监测到`Future`的最终结果,而无法实时监测异步方法的结果变化。一旦`Future`函数执行完成并返回结果,`Stream`将发射该结果,并且之后不再监测`Future`的状态或结果变化。

如果您希望实时监测异步方法的结果变化,并在结果发生改变时通知`Stream`的监听者,您可以考虑使用`StreamController`来手动创建一个`Stream`,然后在异步方法中监听结果变化,并通过`StreamController`将结果发送到`Stream`中。

下面是一个简单的示例,演示了如何使用`StreamController`来实时监测异步方法的结果变化:

```dart import 'dart:async';

void main() {   StreamController streamController = StreamController();

  // 异步方法,模拟结果变化   void asyncMethod() async {     for (int i = 0; i < 3; i++) {       await Future.delayed(Duration(seconds: 1));       streamController.add(i);     }     streamController.close();   }

  asyncMethod();

  streamController.stream.listen((int value) {     print(value);   }); } ```

在上面的示例中,我们手动创建了一个`StreamController`对象`streamController`,并在异步方法`asyncMethod`中模拟了结果的变化。每隔1秒,我们将一个递增的整数发送到`Stream`中。通过监听`StreamController`的`stream`,我们可以实时获取到异步方法的结果变化,并在每次结果变化时打印结果。

总之,`Stream.fromFuture()`只能监测`Future`的最终结果,无法实时监测异步方法的结果变化。如果需要实时监测结果变化,可以考虑使用`StreamController`手动创建`Stream`并发送结果。如果您有任何疑问或需要进一步帮助,请随时告诉我。我很乐意继续协助您。

关键代码块,Stream: _userStreamController.Stream

void checkUser() async {

for(int i = 0;i < 50; i++) {

await Future.delayed(Duration(seconds: 1));

LCUser? user = await LCUser.getCurrent();

print(user);

_userStreamController.add(user != null);

}

}

可以实现登录登出,但是不知道为什么输入账号密码时,删除键用不了.....

但是这显然不是一个合理的方法,计数器持续占用内存?。后续寻找解决方法,替换StreamBuilder或找一个监听函数能捕捉getCurrent的变化。

精彩链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: