Prevent flutter bloc state changes when navigate to new page

Viewed 1458

I'm new in using flutter_bloc and currently facing an issue in my project.

So, i have bloc class like this:

class TestBloc extends Bloc<TestEvent, TestState> {
  @override
  TestState get initialState => InitialTestState();

  @override
  Stream<TestState> mapEventToState(
    TestEvent event,
  ) async* {
    if (event is StateOneEvent) {
      yield StateOne("one");
    } else if (event is StateTwoEvent) {
      yield StateTwo("two");
    }
  }
}

/// === Event class
abstract class TestEvent extends Equatable {
  const TestEvent();
}

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

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


// == State class
abstract class TestState extends Equatable {
  const TestState();
}

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

class StateOne extends TestState {
  final String value;

  StateOne(this.value);

  @override
  List<Object> get props => [value];
}

class StateTwo extends TestState {
  final String value;

  StateTwo(this.value);

  @override
  List<Object> get props => [value];
}

And here is my main function:

void main() {
  runApp(
    MultiBlocProvider(
      providers: [
        BlocProvider(create: (context) {
          return TestBloc();
        }),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => FirstPage(),
        '/second_page': (context) => SecondPage(),
      },
    );
  }
}

So here i have two page, FirstPage and SecondPage like here. Each of this class will show current state of the bloc.

class FirstPage extends StatefulWidget {
  @override
  _FirstPageState createState() => _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {
  @override
  void initState() {
    super.initState();
    BlocProvider.of<TestBloc>(context).add(StateOneEvent());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: BlocBuilder<TestBloc, TestState>(
      builder: (_, state) {
        String value = '-';
        if (state is StateOne) {
          value = state.value;
        } else if (state is StateTwo) {
          value = state.value;
        }
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(value),
              RaisedButton(
                onPressed: () {
                  Navigator.pushNamed(context, '/second_page');
                },
                child: Text('Goto Second Page'),
              )
            ],
          ),
        );
      },
    ));
  }
}

class SecondPage extends StatefulWidget {
  @override
  _SecondPageState createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
  @override
  void initState() {
    super.initState();
    BlocProvider.of<TestBloc>(context).add(StateTwoEvent());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: BlocBuilder<TestBloc, TestState>(
      builder: (_, state) {
        String value = '-';
        if (state is StateOne) {
          value = state.value;
        } else if (state is StateTwo) {
          value = state.value;
        }
        return Center(
          child: Text(value),
        );
      },
    ));
  }
}

Both of them use same bloc (TestBloc). In the initial state of FirstPage i tried to dispatch event to TestBloc to change current state to StateOne and display the current state value in a Text. So for FirstPage it will show one.

After that, i tried to navigate to SecondPage which have almost the same as FirstPage. In the inital state of SecondPage i tried to dispatch event to TestBloc to change current state to StateTwo so it will then show two in the Text.

But the problem is when i navigate.pop from SecondPage or pressing back button, the text inside FirstPage become two. Seems that TestBloc streams is not closed when i navigate to new page.

My expectation is FirstPage is supposed to still with its last state which is displaying one and not affected by what i do in SecondPage.

How to address this issue?

Thanks


[UPDATE]

I tried to changed SecondPage like here so it will have another bloc scope. This solves the problem actually, but does this seems to be the right and good solution?

class SecondPage extends StatefulWidget {
  @override
  _SecondPageState createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
  TestBloc testBloc = TestBloc();
  @override
  void initState() {
    super.initState();
    testBloc.add(StateTwoEvent());
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => testBloc,
      child: Scaffold(body: BlocBuilder<TestBloc, TestState>(
        builder: (_, state) {
          String value = '-';
          if (state is StateOne) {
            value = state.value;
          } else if (state is StateTwo) {
            value = state.value;
          }
          return Center(
            child: Text(value),
          );
        },
      )),
    );
  }
}
2 Answers

It is hard to tell what exactly you are trying to achieve.

If you want both pages to be able to have separate states on the same data, using a distinct Bloc instance for each page is the way to go.

If you want both pages to have the same Bloc but react to states differently you can use the buildWhen method of the BlocBuilder to control when the builder actually has to rebuild the UI. Example for the FirstPage:

BlocBuilder<TestBloc, TestState>(
    buildWhen: (prevState, curState) {
        return curState is StateOne
    }
    builder: (_, state) {
        String value = state.value;
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(value),
              RaisedButton(
                onPressed: () {
                  Navigator.pushNamed(context, '/second_page');
                },
                child: Text('Goto Second Page'),
             )
           ],
        ),
     );
},

But i think in your case you want to have separate instances of the same Bloc. The buildWhen is mostly used when you only want to rebuild if part of the state changes which needs to be reflected in the UI.

You should create new StateInProgress then your bloc should like

if (event is StateOneEvent) {
  yield StateInProgress();
  //action call
  yield StateOne("one");
} else if (event is StateTwoEvent) {
  yield StateInProgress();
  //action call
  yield StateTwo("two");
}
Related