Flutter:如何为 StreamBuilder 创建一个 http 流

Flutter : How to make an http stream for StreamBuilder

你好

我正在尝试使用 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);
              });
        });
  }
}




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

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

先定义一下FutureStreams的区别:

来自

A Future is like the token with a number on it that they give you when you order takeout; you made the request, but the result is not yet ready but you have a placeholder. And when the result is ready, you get a callback (the digital board above the takeout counter shows your number or they shout it out) - you can now go in and grab your food (the result) to take out.

A Stream is like that belt carrying little sushi bowls. By sitting down at that table, you've "subscribed" to the stream. You don't know when the next sushi boat will arrive - but when the chef (message source) places it in the stream (belt), then the subscribers will receive it. The important thing to note is that they arrive asynchronously (you have no idea when the next boat/message will come) but they will arrive in sequence (i.e., if the chef puts three types of sushi on the belt, in some order -- you will see them come by you in that same order)

下面是一个示例,说明如何 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,);
            },
          )
      ),

    );
  }
}

您还可以查看 以获取一些参考。

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

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());
}

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

这对我有用

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(),
);
}
}