Search code examples
flutterbloc

How to emit Object using Bloc


I copy official sample and it works.

enter image description here

but when I change int to viewmodel it won't change value when I click.

How to solve it, thanks.

class MainViewModel {
  int selectedColorIndex = -1;
}

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

import 'main_view_model.dart';

void main() {
  Bloc.observer = const AppBlocObserver();
  runApp(const App());
}

class AppBlocObserver extends BlocObserver {
  const AppBlocObserver();

  @override
  void onChange(BlocBase bloc, Change change) {
    super.onChange(bloc, change);
    if (bloc is Cubit) print(change);
  }

  @override
  void onTransition(Bloc bloc, Transition transition) {
    super.onTransition(bloc, transition);
    print(transition);
  }
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return AppView();
  }
}

class AppView extends StatelessWidget {
  const AppView({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const CounterPage(),
    );
  }
}

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

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

class CounterView extends StatelessWidget {
  const CounterView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: BlocBuilder<CounterBloc, MainViewModel>(
          builder: (context, viewModel) {
            return Text(
              '${viewModel.selectedColorIndex}',
              style: Theme.of(context).textTheme.displayLarge,
            );
          },
        ),
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            child: const Icon(Icons.add),
            onPressed: () {
              context.read<CounterBloc>().add(CounterIncrementPressed());
            },
          ),
          const SizedBox(height: 4),
          FloatingActionButton(
            child: const Icon(Icons.remove),
            onPressed: () {
              context.read<CounterBloc>().add(CounterDecrementPressed());
            },
          ),
        ],
      ),
    );
  }
}

abstract class CounterEvent {}

class CounterIncrementPressed extends CounterEvent {}

class CounterDecrementPressed extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, MainViewModel> {
  CounterBloc() : super(MainViewModel()) {
    on<CounterIncrementPressed>((event, emit) {
      state.selectedColorIndex + 1;
      emit(state);
    });
    on<CounterDecrementPressed>((event, emit) {
      state.selectedColorIndex - 1;
      emit(state);
    });
  }
}

Solution

  • The issue is that you're not updating the state correctly inside your event handlers. You need to create a new instance of MainViewModel with the updated value and emit it. Emitting the same object will not trigger a state update in the Bloc

    class MainViewModel {
      final int selectedColorIndex;
    
      MainViewModel({this.selectedColorIndex = 0});
    
      MainViewModel copyWith({int? selectedColorIndex}) {
        return MainViewModel(
          selectedColorIndex: selectedColorIndex ?? this.selectedColorIndex,
        );
      }
    }
    
    class CounterBloc extends Bloc<CounterEvent, MainViewModel> {
      CounterBloc() : super(MainViewModel()) {
        on<CounterIncrementPressed>((event, emit) {
          emit(state.copyWith(selectedColorIndex: state.selectedColorIndex + 1));
        });
        on<CounterDecrementPressed>((event, emit) {
          emit(state.copyWith(selectedColorIndex: state.selectedColorIndex - 1));
        });
      }
    }