Flutter Bloc is not rebuilding state

Issue

I have implement a this simple bloc with the flutter_bloc package:

class MainBloc extends Bloc<MainEvent, MainState> {
  @override
  MainState get initialState => Init();

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    if (event is Event) {
      yield* _mapEventToState();
    }
  }

  Stream<MainState> _mapEventToState() async* {
    final loadState = Load();
    print("Yield state: $loadState");
    yield loadState;
    await sleep(Duration(seconds: 1));
    final initState = Init();
    print("Yield state: $initState");
    yield initState;
  }
}

with this event:

abstract class MainEvent {}

class Event extends MainEvent {}

and this state:

abstract class MainState {}

class Load extends MainState {}

class Init extends MainState {}

My UI looks like this:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material App',
      home: BlocProvider(
        create: (context) => MainBloc(),
        child: Scaffold(
          appBar: AppBar(),
          body: BlocBuilder<MainBloc, MainState>(
            builder: (context, state) {
              print("Build state: $state");
              if (state is Init) {
                return MaterialButton(
                  onPressed: () => BlocProvider.of<MainBloc>(context).add(Event()),
                  child: Text("Press"),
                );
              } else {
                return Text("Loading");
              }
            },
          ),
        ),
      ),
    );
  }
}

Unfortunately if I add my Event with the MaterialButton, the Load() state gets ignored. The UI doesn’t rebuild the Load state. The output is the following:

I/flutter ( 1955): Yield state: Instance of ‘Load’

I/flutter ( 1955): Yield state: Instance of ‘Init’

I/flutter ( 1955): Build state: Instance of ‘Init’

Solution

It’s because you’re using await sleep(Duration(seconds: 1)); (the await statement is of no use here, it is a synchronous function).

The sleep function pauses the execution of the main thread, meaning it will also block the rebuilding of the Main page of your app, and so it doesn’t get the chance to rebuild because of the new Load state. Once freezing the app is over, it immediately gets a new state, so that’s why you will never see the loading page.

Use await Future.delayed(Duration(seconds: 1)); instead, which pauses the execution of the rest of the _mapEventToState function but doesn’t block the main thread and so your MainPage gets a rebuild.

Answered By – JJuice

Answer Checked By – David Goodson (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.