Flutter : How to make an http stream for StreamBuilder
Solution 1
I'm not able to replicate your sample code, but here how I understood your question.
Let's first define the difference about Future
and Streams
:
From this SO post
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)
Now here is an example of how you can 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,);
},
)
),
);
}
}
You can also check this SO post for some references.
Here, I tweaked the sample in the SO post above to create a mini simple chat server to show how the messages updates.
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());
}
And here are some tutorials for better references:
Solution 2
this works for me
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(),
);
}
}
Unknow
I'm a French,student in IT Engineering at EPSI. I like the possibility to create anything from dust with just a computer for only expense !
Updated on December 21, 2022Comments
-
Unknow over 1 year
Hello
I'm trying to make my first social app with Flutter and I'm stuck. I would like to get my messages (in a conversasion between tow users) from my api. Not a probleme when I use Future and Future Builder, but I would like the message list to update when a new message is send !
I found we can achieve it with stream, but every time I try to convert my Future In Stream, it still work, but just as if it was a Future (it never upadate on new message).
here I a simplified part of my code :
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); }); }); } }
it would be so nice if you could help me !