user987654
user987654

Reputation: 6031

Using multiple BlocConsumer for the same bloc on the same page

I'm using BlocConsumer in 'MyButton' widget which is used in all items in a list. When a user press a button, I want to redirect the user to another page based on which button the user clicked (in the below code, a button text is printed instead).

If I run this code, one click on a button prints all texts ("Press 1", "Press 2", "Press 3"). Is it because the exact same listener attached to MyButton widget 3 times and all the listeners are called whenever any of the buttons is clicked? Does it mean that there should not be more than one widget for a bloc on the same page?

I can fix the code by moving BlocConsumer to wrap the Column instead of MyButton. Is this the right way to handle this issue? If that's the case, I have to wrap a larger widget in more complex situation. For example, if the same bloc is used for widgets in appbar and body, then I'll have to wrap the entire page with BlocConsumer.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() async {
  runApp(BlocProvider<MyBloc>(
    create: (BuildContext context) => MyBloc(),
    child: MaterialApp(
      home: Column(
        children: [
          MyButton(text: "Press 1"),
          MyButton(text: "Press 2"),
          MyButton(text: "Press 3"),
        ],
      ),
    ),
  ));
}

class MyButton extends StatelessWidget {
  const MyButton({Key? key, required this.text}) : super(key: key);

  final String text;

  @override
  Widget build(BuildContext context) {
    return BlocConsumer<MyBloc, MyState>(
      listenWhen: (previous, current) {
        return true;
      },
      listener: (context, state) {
        print(text);
      },
      builder: (context, state) {
        return TextButton(
          key: UniqueKey(),
          child: Text(text),
          onPressed: () {
            context.read<MyBloc>().add(MyEvent());
          },
        );
      },
    );
  }
}

class MyEvent {}

class MyState {}

class MyBloc extends Bloc<MyEvent, MyState> {
  MyBloc() : super(MyState());

  @override
  Stream<MyState> mapEventToState(MyEvent event) async* {
    yield MyState();
  }
}

Upvotes: 3

Views: 11343

Answers (2)

mrgnhnt96
mrgnhnt96

Reputation: 3945

What I do in the case where you have an undefined count of pages, I use a Cubit with a state type of int. This allows you to have as many pages as you want, and the index of the page can be used as the cubit's state.

class PagesCubit extends Cubit<int> {
  PagesCubit() : super(0);

  void selectPage(int index) => emit(index);

}

in your code, you can use it this way.

BlocProvider<PagesCubit>(
  lazy: false,
  create: (BuildContext context) => PagesCubit(),
  child: BlocListener<PagesCubit, int>(
    listener: (context, state) {
      print(state); // expects int value
      //TODO: navigate to page
    },
    child: MaterialApp(
    home: Column(
        children: [
          MyButton(text: "Press 1"),
          MyButton(text: "Press 2"),
          MyButton(text: "Press 3"),
        ],
      ),
    ),
  ),
)

//somewhere anywhere else in your code
context.read<PagesCubit>().selectPage(index);

Note: Don't handle navigation inside of your button, unless you know the page you want to navigate to. You'll end up copying and pasting a lot of code. Instead, pull the navigation up a level (wrapped around your Column for example) and manage the navigation there. You can do this because BLoC allows you to move the code anywhere within the tree that it is providing your bloc.

Upvotes: 0

F.  Malaza
F. Malaza

Reputation: 59

To handle different clicks in bloc, you have to define events and states.

// events
class FirstClick extends Event{}

class SecondClick extends Event{}

class ThirdClick extends Event{}

// states
class FirstClickState extends State{}

class SecondClickState extends State{}

class ThirdClickState extends State{}

// bloc function 
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc() : super(MyState());

  @override
  Stream<MyState> mapEventToState(MyEvent event) async* {
    if(event is FirstClick){
      yield FirstClickState();
    }

    if(event is SecondClick){
      yield SecondClickState();
    }

    if(event is ThirdClick){
      yield ThirdClickState();
    }
  }  
}

// view definition
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() async {
  runApp(BlocProvider<MyBloc>(
    create: (BuildContext context) => MyBloc(),
    child: MaterialApp(
    home: Column(
      children: [
        MyButton(text: "Press 1", onPressed:() => BlocProvider.of<MyBloc>(context).add(FirstClick())),
        MyButton(text: "Press 2", onPressed:() => BlocProvider.of<MyBloc>(context).add(SecondClick())),
        MyButton(text: "Press 3", onPressed:() => BlocProvider.of<MyBloc>(context).add(ThirdClick())),
        ],
      ),
    ),
  ));
}

class MyButton extends StatelessWidget {
const MyButton({Key? key, required this.text}) : super(key: key);

final String text;

@override
Widget build(BuildContext context) {
  return BlocConsumer<MyBloc, MyState>(
    listenWhen: (previous, current) {
      return true;
    },
    listener: (context, state) {
      if(state is FirstClickState){
        // go to where ever 
        Navigator.push(context, Somewhere());
      }
      
      if(state is SecondClickState){
        // go to where ever 
        Navigator.push(context, Somewhere());
      }

      if(state is ThirdClickState){
        // go to where ever 
        Navigator.push(context, Somewhere());
      }
    },
    builder: (context, state) {
      return TextButton(
        key: UniqueKey(),
        child: Text(text),
        onPressed: () {
          context.read<MyBloc>().add(MyEvent());
        },
      );
    },
  );
 }
}

Upvotes: 0

Related Questions