Search code examples
flutterstreambuilder

Flutter streambuilder between screens


I'm new in Flutter and I implemented the bloc architecture with streambuilder. I created 2 pages with just a button which change my background color. All of theses pages are listening a stream to change the background color but when I change on the first page, it doesn't on the second. But I want all my application change if 1 page decide to change it

Do I need to initialize a singleton bloc that my 2 screens used it ? Because for the moment each screen initializes its own bloc Here is an example of 1 page (the second one is the same)

class Test extends StatelessWidget {
  final ColorBloc _bloc = ColorBloc();

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<Response<ColorResponse>>(
      stream: _bloc.stream,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Scaffold(
              appBar: AppBar(
                title: Text('First Route clicked'),
              ),
              backgroundColor: snapshot.data.data.color,
              body: new Center(
                  child: new InkWell(
                onTap: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(builder: (context) => Act2()),
                  );
                }, // Handle your callback
                child: Ink(height: 100, width: 100, color: Colors.blue),
              )),
              floatingActionButton: FloatingActionButton(
                onPressed: () {
                  _bloc.changeColor(Colors.yellow);
                },
                child: Icon(Icons.navigation),
                backgroundColor: Colors.green,
              ));
        }
        return Scaffold(
            appBar: AppBar(
              title: Text('First Route'),
            ),
            body: Center(
                child: new InkWell(
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(builder: (context) => Act2()),
                      );
                    }, // Handle your callback
                    child: Ink(height: 200, width: 200, color: Colors.red))),
            floatingActionButton: FloatingActionButton(
              onPressed: () {
                _bloc.changeColor(Colors.yellow);
              },
              child: Icon(Icons.navigation),
              backgroundColor: Colors.green,
            ));
      },
    );
  }
}

Solution

  • To change the state of all screen when a bloc fires an event, you can use multiple StreamBuilder, but all of them need to listen to the bloc that fire the event. You can try these 2 ways:

    • Passing the bloc as parameter into the 2nd screen
    class Test extends StatelessWidget {
      final ColorBloc _bloc = ColorBloc();
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder<Response<ColorResponse>>(
    
               // ... other lines
    
                  body: new Center(
                      child: new InkWell(
                    onTap: () {
    
                      // Pass your bloc to the 2nd screen
                      Navigator.push(
                        context,
                        MaterialPageRoute(builder: (context) => Act2(bloc: _bloc)),
                      );
                    }, 
    
                    // ... other lines
    
    • Use package such as provider package to pass the bloc down the tree. In your first screen, you can do this:
    class Test extends StatelessWidget {
      final ColorBloc _bloc = ColorBloc();
    
      @override
      Widget build(BuildContext context) {
    
        // Use Provider to provide the bloc down the widget tree
        return Provider(
          create: (_) => _bloc,
          child: StreamBuilder<Response<ColorResponse>>(
            
            // ... other lines
    

    Then in the 2nd screen (which I assume is Act2()), you get the ColorBloc from the Provider:

    
    class Act2 extends StatefulWidget {
      @override
      _Act2State createState() => _Act2State();
    }
    
    class _Act2State extends State<Act2> {
      ColorBloc _colorBloc;
    
      @override
      void didChangeDependencies() {
        // Get the bloc in the 1st page
        _colorBloc = Provider.of<ColorBloc>(context);
        super.didChangeDependencies();
      }
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder<Response<ColorResponse>>(
    
          // Use the bloc like in the 1st page
          stream: _colorBloc.stream, 
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              // ... other lines
    

    Small note: When using StreamBuilder you could initiate the value without the need to duplicate codes. Since I don't know the structure of your Response object, I'm taking Response(ColorResponse(color: Colors.green)) as the example:

    // ... other lines
    
    @override
      Widget build(BuildContext context) {
        return Provider(
          create: (_) => _bloc,
          child: StreamBuilder<Response<ColorResponse>>(
    
            // Initiate your data here
            initialData: Response(ColorResponse(color: Colors.green)), 
    
            stream: _bloc.stream,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Scaffold(
                    appBar: AppBar(
                      title: Text('First Route clicked'),
                    ),
                    backgroundColor: snapshot.data.data.color,
    
                    // ... other lines
    
              }
    
              // Don't need to copy the above code block for the case when the data is not streamed yet
              return Container(child: Center(child: CircularProgressIndicator()));
            },
          ),
        );
      }