Flutter:如何为 StreamBuilder 制作 http 流

Posted

技术标签:

【中文标题】Flutter:如何为 StreamBuilder 制作 http 流【英文标题】:Flutter : How to make an http stream for StreamBuilder 【发布时间】:2020-10-07 13:28:33 【问题描述】:

你好

我正在尝试使用 Flutter 制作我的第一个社交应用,但遇到了困难。 我想从我的 api 获取我的消息(在两个用户之间的对话中)。 当我使用 Future 和 Future Builder 时没有问题,但我希望在发送新消息时更新消息列表!

我发现我们可以使用流实现它,但是每次我尝试在流中转换我的 Future 时,它​​仍然有效,但就像它是一个 Future(它永远不会更新新消息)。

这里是我的代码的简化部分:


class Test extends StatelessWidget 
  
  final Conv conv;
  final User otherUser;

  const Test(Key key, this.conv, this.otherUser) : super(key: key);
  
  

  Stream<List<Message>> messageFlow(String convId) 
    return Stream.fromFuture(getMessages(convId));
  

  Future<List<Message>> getMessages(String convId) async 
    var data = await http
        .post(MyApiUrl, headers: <String, String>, body: <String, String>
      "someParam": "param",
      "id": convId,
    );
    var jsonData = json.decode(data.body);

    List<Message> messages = [];
    for (var m in jsonData) 
      Message message = Message.fromJson(m);
      messages.add(message);
    
    return messages;
  


  
  @override
  Widget build(BuildContext context) 
    return StreamBuilder(
        stream: messageFlow(conv.id),
        builder: (BuildContext context, AsyncSnapshot snapshot) 
          if (snapshot.data == null) 
            return Container(
              child: Center(
                child: Text('Loading'),
              ),
            );
          
          return ListView.builder(
              reverse: true,
              controller: _messagesListController,
              itemCount: snapshot.data.length,
              itemBuilder: (BuildContext context, int index) 
                Message message = snapshot.data[index];
                var isMe = message.owner == otherUser.id ? false : true;
                return _buildMessage(message, isMe);
              );
        );
  





如果你能帮助我,那就太好了!

【问题讨论】:

Future 转换为Stream 与您发现的仅使用Future 基本相同。您需要创建自己的Stream,它会定期执行您现有的Future。查看this 的“从头开始创建流”部分。 【参考方案1】:

这对我有用

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as HTTP;

class PeriodicRequester extends StatelessWidget 
Stream<http.Response> getRandomNumberFact() async* 
yield* Stream.periodic(Duration(seconds: 5), (_) 
  return http.get("http://numbersapi.com/random/");
).asyncMap((event) async => await event);


@override
Widget build(BuildContext context) 
return StreamBuilder<http.Response>(
  stream: getRandomNumberFact(),
  builder: (context, snapshot) => snapshot.hasData
      ? Center(child: Text(snapshot.data.body))
      : CircularProgressIndicator(),
);


【讨论】:

【参考方案2】:

我无法复制您的示例代码,但在这里我是如何理解您的问题的。

我们先来定义一下FutureStreams的区别:

来自this SO post

Future 就像带有数字的令牌,当他们给你 你点外卖;你提出了请求,但结果还没有 准备好了,但你有一个占位符。当结果准备好时,你 获得回电(外卖柜台上方的数字板显示您的 号码或他们喊出来) - 你现在可以进去拿你的食物 (结果)取出。

溪流就像那条背着小寿司碗的腰带。通过坐 在那张桌子上,您已经“订阅”了该流。你不知道 下一艘寿司船何时到达 - 但当厨师(消息 source) 将其放在流(带)中,然后订阅者将 收到它。需要注意的重要一点是他们到达 异步(你不知道下一条船/消息什么时候来) 但它们将按顺序到达(即,如果厨师放置三种类型 腰带上的寿司,按顺序排列——你会看到它们从你身边走过 以相同的顺序)

下面是一个例子,告诉你如何create your own stream from scratch:

import 'dart:async';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(

        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  


class MyHomePage extends StatefulWidget 
  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 

  // 1st approach
  final StreamController _streamController = StreamController();
 
  addData()async
    for(int i = 1; i<= 10; i++) 
      await Future.delayed(Duration(seconds: 1));

      _streamController.sink.add(i);
    
  

  // 2nd approach
  // This approach will prevent some approach of memory leaks
  Stream<int> numberStream() async*
    for(int i = 1; i<= 10; i++) 
      await Future.delayed(Duration(seconds: 1));

      yield i;
    
  

  @override
  void dispose() 
    // TODO: implement dispose
    super.dispose();
    _streamController.close();
  

  @override
  void initState() 
    // TODO: implement initState
    super.initState();
    addData();
  

  @override
  Widget build(BuildContext context) 

    return Scaffold(
      appBar: AppBar(

        title: Text("Stream"),
      ),
      body: Center(
          child: StreamBuilder(
            stream: numberStream().map((number) => "number $number"),
            builder: (context, snapshot)
              if(snapshot.hasError)
                return Text("hey there is some error");
              else if (snapshot.connectionState == ConnectionState.waiting)
                return CircularProgressIndicator();
              return Text("$snapshot.data", style: Theme.of(context).textTheme.display1,);
            ,
          )
      ),

    );
  

您也可以查看this SO post 以获取一些参考资料。

在这里,我调整了上面 SO 帖子中的示例,以创建一个迷你简单的聊天服务器来显示消息如何更新。

import 'dart:async';
import 'package:flutter/material.dart';

class Server 
  StreamController<String> _controller = new StreamController.broadcast();
  void simulateMessage(String message) 
    _controller.add(message);
  

  Stream get messages => _controller.stream;


final server = new Server();

class HomeScreen extends StatefulWidget 
  @override
  _HomeScreenState createState() => new _HomeScreenState();


class _HomeScreenState extends State<HomeScreen> 
  List<String> _messages = <String>[];
  StreamSubscription<String> _subscription;

  @override
  void initState() 
    _subscription = server.messages.listen((message) async => setState(() 
          _messages.add(message);
        ));
    super.initState();
  

  @override
  void dispose() 
    _subscription.cancel();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 
    TextStyle textStyle = Theme.of(context).textTheme.display2;
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Sample App'),
      ),
      body: new ListView(
        children: _messages.map((String message) 
          return new Card(
            child: new Container(
              height: 100.0,
              child: new Center(
                child: new Text(message, style: textStyle),
              ),
            ),
          );
        ).toList(),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: [
          new FloatingActionButton(
            child: new Icon(Icons.account_circle_outlined),
            onPressed: () 
              // simulate a message arriving
              server.simulateMessage('Hello World');
            ,
          ),
          SizedBox(
            height: 20.0,
          ),
          new FloatingActionButton(
            child: new Icon(Icons.account_circle_rounded),
            onPressed: () 
              // simulate a message arriving
              server.simulateMessage('Hi Flutter');
            ,
          ),
        ],
      ),
    );
  


class SampleApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
      home: new HomeScreen(),
    );
  


void main() 
  runApp(new SampleApp());

这里有一些教程可以更好地参考:

https://www.youtube.com/watch?v=nQBpOIHE4eE https://www.youtube.com/watch?v=OTS-ap9_aXc

【讨论】:

以上是关于Flutter:如何为 StreamBuilder 制作 http 流的主要内容,如果未能解决你的问题,请参考以下文章

FirebaseStorage + Flutter,streamBuilder?

来自一个 StreamBuilder 的 Flutter 快照显示在另一个 StreamBuilder 中

Flutter:Streambuilder - 关闭流

Flutter:StreamBuilder 快照——无数据

Flutter Streambuilder 正在复制项目

无法从 StreamBuilder (Flutter) 查询子集合