Search code examples
flutterstateblocnavigator

How to change state to next screen correctly with bloc in flutter


I am learning flutter + bloc and start with a demo. Start of project I create starting app by delay 3 second and next to home page like this:

StartCubit

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

part 'start_state.dart';

class StartCubit extends Cubit<StartState> {
  StartCubit() : super(StartInitial());

  void startRunning() {
    loadData();
  }
  
  void loadData() async {
    emit(StartDoing(0));
    await Future.delayed(Duration(seconds: 1));
    emit(StartDoing(1));
    await Future.delayed(Duration(seconds: 1));
    emit(StartDoing(2));
    await Future.delayed(Duration(seconds: 1));
    emit(StartDoing(3));
    await Future.delayed(Duration(seconds: 1));
    emit(StartDone());
  }
}

And this is code in start page:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:money_lover/home_page/home_page.dart';
import 'package:money_lover/start/bloc/start_cubit.dart';

class StartPage extends StatelessWidget {
  const StartPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => StartCubit(),
      child: StartView(),
    );
  }
}

class StartView extends StatefulWidget {
  const StartView({Key? key}) : super(key: key);

  @override
  State<StartView> createState() => _StartViewState();
}

class _StartViewState extends State<StartView> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(body: Center(child: BlocBuilder<StartCubit, StartState>(
      builder: (context, state) {
        if (state is StartInitial) {
          context.read<StartCubit>().startRunning();
        } else if (state is StartDone) {
          Future.delayed(Duration.zero, () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => const HomePage()),
            );
          });
        }
        return Text('Starting $state');
      },
    )));
  }
}

If I don't call future delay with zero time It will show error before next screen.

And I don't need print stateDone when go to next screen, have any way to code more correctly ?

I tried add delay addPostFrameCallback in this link Error: Unhandled Exception: 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 2845 pos 18: '!navigator._debugLocked': is not true it is ok but I think maybe I code not correctly way.


Solution

  • You should be using a listener to handle the navigation. In your case a blocconsumer will do

    BlocConsumer exposes a builder and listener in order react to new states.

     BlocConsumer<StartCubit,StartState>(
          listener: (context, state) {
             if (state is StartDone) {
                 
                    Navigator.push(
                      context,
                      MaterialPageRoute(builder: (context) => const HomePage()),
                    );
               
                }
          },
          builder: (context, state) {
         if (state is StartInitial) {
    // Instead of running this here, you can run it in your build if you want init state like behavior.
                  context.read<StartCubit>().startRunning();
                }
             return Text('Starting $state');
          }
        )
    

    Running in build method

    class _StartViewState extends State<StartView> {
      @override
      Widget build(BuildContext context) {
     context.read<StartCubit>().startRunning();
        return Scaffold(body: Center(child: BlocBuilder<StartCubit, StartState>(