DEV Community

Ashutosh Chauhan for IOTA, IIITS

Posted on

Using Stream Builder in Flutter

Dart & Flutter was made with asynchronicity in mind. Dart provide really good support for Futures and Streams. For those who do not have a basic idea of asynchronous programming in dart can refer to this playlist.

We will create an App which will call an API after every second and update the page whenever the response is received, which would look something like below:

Screenshot
Lets start with creating a new Flutter Project.

Step 1: Create the project

You can use your favorite IDE (VS Code or any other) to start a new Flutter project. Once you have create a flutter lets edit the lib/main.dart. Keep the following code and remove everything else.

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

Step 2: Requesting data from API

We will create a page which will show us the amount of likes a certain post has recieved. We won't be making the entire application only the Likes counter which will show us the amount of people who have liked a post. For simplicity we will use a dummy API which will repond with a random value.

Create a file lib/models.dart :

class Number {
  int value;

  Number(this.value);

  factory Number.fromJSON(Map<String, dynamic> json) {
    return Number(json['value']);
  }
}

We will use the class Number which will create a class from a json object.

Next, create another file lib/requests.dart :

import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import 'models.dart';

Future<Number> getNumber({int num = 20}) async {
  http.Response res = await http.get(Uri.http("dummyresponse.pythonanywhere.com", "/api/$num"));
  return Number.fromJSON(json.decode(res.body));
}

Stream<Number> getNumbers(Duration refreshTime) async* {
  while (true) {
    await Future.delayed(refreshTime);
    yield await getNumber();
  }
}

The getNumber is an async method which waits for a network call to a dummy response server which generates a random number. (https://dummyresponse.pythonanywhere.com/api/31 will give random no. from 0 to 31)

The getNumbers method returns a stream which calls the API and retrieves the latest information, hence updating the information after every fixed duration.

Now, Since we have the stream let's get to the real stuff.

Step 3: Stream Builder

Create a new file lib/home_page.dart :

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:stream_builder_example/models.dart';
import 'package:stream_builder_example/requests.dart';

class HomePage extends StatefulWidget {
  HomePage({Key key}) : super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: StreamBuilder(
        stream: getNumbers(Duration(seconds: 1)),
        initialData: Number(0),
        builder: (context, stream) {
          if (stream.connectionState == ConnectionState.done) {
            return Icon(
              Icons.check_circle,
              color: Colors.green,
              size: 20,
            );
          }
          if (stream.hasData) {
            return LikeCounter(stream.data.value);
          } else {
            return CircularProgressIndicator();
          }
        },
      )),
    );
  }
}

class LikeCounter extends StatelessWidget {
  static List<Color> colors = [ Colors.green, Colors.purpleAccent, Colors.deepPurple,  Colors.blueAccent, Colors.deepOrangeAccent ];
  final int num;

  LikeCounter(this.num);

  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: Duration(milliseconds: 250),
      padding: EdgeInsets.all(10),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(100),
        color: colors[num % colors.length],
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Icon(Icons.favorite, color: Colors.white),
          Text(
            " $num Likes",
            style: TextStyle(color: Colors.white, fontSize: 20),
          ),
        ],
      ),
    );
  }
}

The LikeCounter is a stateless widget which shows the number of likes with different colors.

Let's see how the Stream Builder works.

StreamBuilder(
    stream: getNumbers(Duration(seconds: 1)),
    initialData: Number(0),
    builder: (context, stream) {
        if (stream.connectionState == ConnectionState.done) {
            return Icon(
                Icons.check_circle,
                color: Colors.green,
                size: 20,
            );
        }
        if (stream.hasData) {
            return LikeCounter(stream.data.value);
        } else {
            return CircularProgressIndicator();
        }
    },
)

The Stream builder needs mainly 3 parameters: stream, builder, and initialData. The initial is used as a dummy or empty element to act as the first element.

The builder method receives context and snapshot of the stream.

We can use stream.connectionState to find the status of the stream, details of state is given here.

We can use stream.hasData ad stream.data to build widget when the widget is build.

We can also use stream.hasError to build when an Error occurs.

Next, change the lib/main.dart and update HomePage() as Material App home :

import 'home_page.dart';

// ...   
return MaterialApp(
    title: 'Flutter Demo',
    theme: ThemeData(
        primarySwatch: Colors.blue,
    ),
    home: HomePage(),
);
// ...

Next Step: Use Complex Objects instead of Number

Since this is a rudimentary example we have not used a complete API. But streams provide a really good way to allow continuous chunk of data flowing through and only rebuilding the app when a value is updated. This method allows you to track values in real time without needing your user to refresh manually. This can be used in a wide variety of applications such as Social Apps, Live Sports Scores, etc.

The code for the final application can be found here:

Top comments (0)