Bloc: BlocBuilder is not rebuilding the ui

Created on 19 Aug 2020  路  15Comments  路  Source: felangel/bloc

I have scenario where I want to show the user search result page with option of filtration button when he click the button I want to show him filter page where he can choose filtrations(by size, by cloth type....) then he clicks filter button in which I want to pop that page and show the previous page(search result page) by refreshing so the new filtration take place ,the request is sent to the server and exited the filter page to search result but refreshing is not happening.

I have tried calling setState on pop but no result I think there is something I missed any idea or help ?

question

Most helpful comment

Here is the full example:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:issu/bloc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(create: (_) => SearchBloc(), child: Home()),
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('home page'),
      ),
      body: Padding(
          padding: const EdgeInsets.only(top: 58.0),
          child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                GestureDetector(
                  onTap: () {
                    Navigator.of(context).push(MaterialPageRoute(
                      builder: (_) => BlocProvider.value(
                        value: context.bloc<SearchBloc>(),
                        child: Filter(),
                      ),
                    ));
                  },
                  child: Center(child: Icon(Icons.sort)),
                ),
                BlocBuilder<SearchBloc, SearchState>(
                    builder: (BuildContext context, SearchState state) {
                  print(state.runtimeType);
                  if (state is SearchLoading) {
                    return Center(child: CircularProgressIndicator());
                  } else if (state is SearchLoaded) {
                    return Text(state.done);
                  }

                  return Text('here should change after I back');
                }),
              ])),
    );
  }
}

class Filter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('filter page'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  context.bloc<SearchBloc>().add(GetNextPage());
                  Navigator.of(context).pop();
                },
                child: Text('send request and go back to home to refresh'),
              ),
            )
          ],
        ));
  }
}

Bloc: Nothing has changed here ^^

All 15 comments

Hi @EngAddow 馃憢
Thanks for opening an issue!

Can you please provide a sample app which illustrates the issue? Also, you likely don't need to be calling setState when using bloc unless you're managing state outside of the bloc. Thanks 馃憤

Hi @EngAddow 馃憢
Thanks for opening an issue!

Can you please provide a sample app which illustrates the issue? Also, you likely don't need to be calling setState when using bloc unless you're managing state outside of the bloc. Thanks 馃憤

I will provide sample soon.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:input_bugs/bloc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(create: (_) => SearchBloc(), child: Home()),
    );
  }
}

class Home extends StatelessWidget {
  String TextToshow;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('home page'),
      ),
      body: BlocBuilder<SearchBloc, SearchState>(builder: (context, state) {
        if (state is SearchLoaded) {
          TextToshow = state.done;
        }
        return Padding(
            padding: const EdgeInsets.only(top: 58.0),
            child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: [
                  GestureDetector(
                    onTap: () {
                      Navigator.of(context).push(MaterialPageRoute(
                        builder: (_) => BlocProvider(
                          create: (_) => SearchBloc(),
                          child: Filter(),
                        ),
                      ));
                    },
                    child: Center(child: Icon(Icons.sort)),
                  ),
                  if (state is SearchLoading)
                    Center(
                      child: CircularProgressIndicator(),
                    ),
                  Text(TextToshow ?? 'here should change after I back')
                ]));
      }),
    );
  }
}

class Filter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('filter page'),
      ),
      body: BlocBuilder<SearchBloc, SearchState>(builder: (context, state) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  context.bloc<SearchBloc>().add(GetNextPage());
                  Navigator.of(context).pop();
                },
                child: Text('send request and go back to home to refresh'),
              ),
            )
          ],
        );
      }),
    );
  }
}

...bloc

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';

class SearchBloc extends Bloc<SearchEvent, SearchState> {
  SearchBloc() : super(SearchInitial());
  @override
  Stream<SearchState> mapEventToState(
    SearchEvent event,
  ) async* {
    if (event is GetNextPage) {
      yield SearchLoading();
      Future.delayed(Duration(seconds: 3));
      print('came here');
      yield SearchLoaded('search results');
    }
  }
}

abstract class SearchEvent extends Equatable {
  const SearchEvent();
}

class GetNextPage extends SearchEvent {
  @override
  List<Object> get props => [];
}

abstract class SearchState extends Equatable {
  const SearchState();
}

class SearchInitial extends SearchState {
  @override
  List<Object> get props => [];
}

class SearchLoading extends SearchState {
  @override
  List<Object> get props => [];
}

class SearchLoaded extends SearchState {
  final String done;

  SearchLoaded(this.done);
  @override
  List<Object> get props => [done];
}

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:input_bugs/bloc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(create: (_) => SearchBloc(), child: Home()),
    );
  }
}

class Home extends StatelessWidget {
  String TextToshow;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('home page'),
      ),
      body: BlocBuilder<SearchBloc, SearchState>(builder: (context, state) {
        if (state is SearchLoaded) {
          TextToshow = state.done;
        }
        return Padding(
            padding: const EdgeInsets.only(top: 58.0),
            child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: [
                  GestureDetector(
                    onTap: () {
                      Navigator.of(context).push(MaterialPageRoute(
                        builder: (_) => BlocProvider(
                          create: (_) => SearchBloc(),
                          child: Filter(),
                        ),
                      ));
                    },
                    child: Center(child: Icon(Icons.sort)),
                  ),
                  if (state is SearchLoading)
                    Center(
                      child: CircularProgressIndicator(),
                    ),
                  Text(TextToshow ?? 'here should change after I back')
                ]));
      }),
    );
  }
}

class Filter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('filter page'),
      ),
      body: BlocBuilder<SearchBloc, SearchState>(builder: (context, state) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  context.bloc<SearchBloc>().add(GetNextPage());
                  Navigator.of(context).pop();
                },
                child: Text('send request and go back to home to refresh'),
              ),
            )
          ],
        );
      }),
    );
  }
}

...bloc

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';

class SearchBloc extends Bloc<SearchEvent, SearchState> {
  SearchBloc() : super(SearchInitial());
  @override
  Stream<SearchState> mapEventToState(
    SearchEvent event,
  ) async* {
    if (event is GetNextPage) {
      yield SearchLoading();
      Future.delayed(Duration(seconds: 3));
      print('came here');
      yield SearchLoaded('search results');
    }
  }
}

abstract class SearchEvent extends Equatable {
  const SearchEvent();
}

class GetNextPage extends SearchEvent {
  @override
  List<Object> get props => [];
}

abstract class SearchState extends Equatable {
  const SearchState();
}

class SearchInitial extends SearchState {
  @override
  List<Object> get props => [];
}

class SearchLoading extends SearchState {
  @override
  List<Object> get props => [];
}

class SearchLoaded extends SearchState {
  final String done;

  SearchLoaded(this.done);
  @override
  List<Object> get props => [done];
}

I hope that what I am trying is clear @felangel

Hey there @EngAddow

First of all you don't need to wrap the RaisedButton in an BlocBuilder:

class Filter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('filter page'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  context.bloc<SearchBloc>().add(GetNextPage());
                  Navigator.of(context).pop();
                },
                child: Text('send request and go back to home to refresh'),
              ),
            )
          ],
        ));
  }
}

The BlocBuilder is used when you want to rebuild the things you'll define in the builder everytime a new state comes in.

As your RaisedButton does not depend on anything that's coming from the state you'll don't need to listen here.

via context.bloc<YourBloc>() you'll get the closest YourBloc instance that was registered in your Widget Tree via BlocProvider.
( You should look up InheritedWidget and Provider for a better explanation on how the YourBloc is accessible from other widgets)

The you'll have to know each time you're using Navigator.of(context).push (setting a new Route that is layered on top) you'll loose all previously registered Providers in your context.

You can Imagine that context holds all your Blocs for the underlying widgets. If however you poulate a new Route (via Navigator) you'll get a new context that has to be filled again with all you'r values you want to provide to underlying wdgets (here your SearchBloc).

Inside of your Navigator call to push a new Route to the Stack you've already tried to give a new Bloc but you've did exactly this. You've provided a new bloc:

class Filter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('filter page'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  context.bloc<SearchBloc>().add(GetNextPage());
                  Navigator.of(context).pop();
                },
                child: Text('send request and go back to home to refresh'),
              ),
            )
          ],
        ));
  }
}

Here you can see on the create method of BlocBuilder that you create a new SearchBloc.

You'll now have the Following behavior:

Page A has SearchBlocA in it's context. You listen to SearchBlocA's changes to rebuild your UI.
If you click on the filter button you'll Populate a new page via navigator and provide a new SearchBlocB to the FilterPage.
You now click on the button in the FilterPage and add a event to SearchBloc
B which you have newly created for this page.
Going back to Page A you notice nothing happened, because you've just manipulated SearchBlocB instead of SearchBlocA which is being listened on by Page A.

Fortunately BlocBuilder also gives the opportunity to provide a already existing value. You can do this by using the named constructor BlocBuilder.value. This constructor wants a value instead of a create parameter.

BlocProvider.value(
    value: context.bloc<SearchBloc>(),
    child: Filter(),
)

By doing this you grab the currently available SearchBloc (here SearchBlocA) and provide the same SearchBlocA to the next populated route.
If you now press the RaisedButton you'll emit a new event to the same SeachBloc*A you're listening on on Page A.

One more thing:

I've seen that you've wrapped a whole Column into your BlocBuilder's build function as a return value.
As only the Text changes you'll probably rather want just this Text wrapped with the BlocBuilder. By doing this, only the text gets rebuild which saves resources.

Also, you'd likely want to listen on the state that is coming in and depending on which state is coming in you'd want to return other widgets.

BlocBuilder<SomeBloc, SomeState>(
  builder: (_, SomeState state) {
    if (state is Loading) {
      return LoadingWidget();
    } else if (state is Error) {
      return ErrorWidget();
    } 

    return DefaultWidget();
  }
)

Here is the full example:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:issu/bloc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(create: (_) => SearchBloc(), child: Home()),
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('home page'),
      ),
      body: Padding(
          padding: const EdgeInsets.only(top: 58.0),
          child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                GestureDetector(
                  onTap: () {
                    Navigator.of(context).push(MaterialPageRoute(
                      builder: (_) => BlocProvider.value(
                        value: context.bloc<SearchBloc>(),
                        child: Filter(),
                      ),
                    ));
                  },
                  child: Center(child: Icon(Icons.sort)),
                ),
                BlocBuilder<SearchBloc, SearchState>(
                    builder: (BuildContext context, SearchState state) {
                  print(state.runtimeType);
                  if (state is SearchLoading) {
                    return Center(child: CircularProgressIndicator());
                  } else if (state is SearchLoaded) {
                    return Text(state.done);
                  }

                  return Text('here should change after I back');
                }),
              ])),
    );
  }
}

class Filter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('filter page'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  context.bloc<SearchBloc>().add(GetNextPage());
                  Navigator.of(context).pop();
                },
                child: Text('send request and go back to home to refresh'),
              ),
            )
          ],
        ));
  }
}

Bloc: Nothing has changed here ^^

Thanks so much @kiesman99 for taking the time to give such a detailed answer -- I really appreciate it! 馃檹 馃挴
@EngAddow closing this for now but feel free to comment with any additional questions 馃憤

Glad I could help @felangel !

Hi @kiesman99 thanks for this wonderful explanation I got this message:

Couldn't infer type parameter 'T'.

Tried to infer 'ProductsBloc Function(dynamic)' for 'T' which doesn't work:
Type parameter 'T' declared to extend 'Cubit'.
The type 'ProductsBloc Function(dynamic)' was inferred from:
Parameter 'value' declared as 'T'
but argument is 'ProductsBloc Function(dynamic)'.

Consider passing explicit type argument(s) to the generic.

when I tried using :

 Navigator.of(context).push(
   MaterialPageRoute(
       builder: (_) => BlocProvider.value(
             value: (_) => context.bloc<ProductsBloc>(),
                 child: Filter(),
                    ),
            ));

Hi @kiesman99 thanks for this wonderful explanation I got this message:

Couldn't infer type parameter 'T'.

Tried to infer 'ProductsBloc Function(dynamic)' for 'T' which doesn't work:
Type parameter 'T' declared to extend 'Cubit'.
The type 'ProductsBloc Function(dynamic)' was inferred from:
Parameter 'value' declared as 'T'
but argument is 'ProductsBloc Function(dynamic)'.

Consider passing explicit type argument(s) to the generic.

when I tried using :

 Navigator.of(context).push(
   MaterialPageRoute(
       builder: (_) => BlocProvider.value(
             value: (_) => context.bloc<ProductsBloc>(),
                 child: Filter(),
                    ),
            ));

Sorry bro @kiesman99 when I copied from here and changed the bloc to my real bloc it worked but this error came when I write it my self and it is refreshing my page as I wanted.

And I want to mention that I learned a lot from your above detailed answer(more than I asked)Thank you very much.

Glad i could help @EngAddow 馃槉

I didn't really understand if you're still having some issues.
Ist your code working now?馃槄

Glad i could help @EngAddow 馃槉

I didn't really understand if you're still having some issues.
Ist your code working now?馃槄

yes it is working because of you thanks

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nerder picture nerder  路  3Comments

clicksocial picture clicksocial  路  3Comments

wheel1992 picture wheel1992  路  3Comments

Reidond picture Reidond  路  3Comments

timtraversy picture timtraversy  路  3Comments