Flutter Bloc: state not rebuilding - flutter

I have a bloc listening to chats. Through the debugger, I can see that the bloc is catching live updates. However, the ui is not rebuilding when these changes occur. To see a change, I have to leave and reload the screen.
My chat state:
part of 'chat_bloc.dart';
abstract class ChatState extends Equatable {
const ChatState();
#override
List<Object?> get props => [];
}
class ChatLoading extends ChatState {}
class ChatLoaded extends ChatState {
final List<Chat?>? compiledChats;
const ChatLoaded({required this.compiledChats});
#override
List<Object?> get props => [compiledChats];
}
My chat events:
part of 'chat_bloc.dart';
abstract class ChatEvent extends Equatable {
const ChatEvent();
#override
List<Object?> get props => [];
}
class LoadChat extends ChatEvent {
const LoadChat();
#override
List<Object> get props => [];
}
class CloseChat extends ChatEvent {
const CloseChat();
#override
List<Object?> get props => [];
}
class UpdateChat extends ChatEvent {
final List<List<Chat?>> chats;
const UpdateChat({required this.chats});
#override
List<Object> get props => [chats];
}
My actual bloc:
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:hero/blocs/auth/auth_bloc.dart';
import 'package:hero/models/chat_model.dart';
import 'package:hero/models/user_model.dart';
import 'package:hero/repository/firestore_repository.dart';
part 'chat_event.dart';
part 'chat_state.dart';
class ChatBloc extends Bloc<ChatEvent, ChatState> {
final FirestoreRepository _firestoreRepository;
late StreamSubscription _chatListener;
ChatBloc({
required FirestoreRepository firestoreRepository,
}) : _firestoreRepository = firestoreRepository,
super(ChatLoading()) {
on<LoadChat>(_onLoadChat);
on<UpdateChat>(_onUpdateChat);
on<CloseChat>(_onCloseChat);
}
void _onLoadChat(
LoadChat event,
Emitter<ChatState> emit,
) {
_chatListener = _firestoreRepository.chats.listen((chats) {
add(
UpdateChat(
chats: chats,
),
);
});
}
void _onUpdateChat(
UpdateChat event,
Emitter<ChatState> emit,
) {
//generate compiledChats from event.chats
List<Chat?>? compiledChats = [];
for (List<Chat?> chatList in event.chats) {
for (Chat? chat in chatList) {
if (chat != null) {
compiledChats.add(chat);
}
}
}
emit(ChatLoaded(compiledChats: compiledChats));
}
void _onCloseChat(
CloseChat event,
Emitter<ChatState> emit,
) {
_chatListener.cancel();
print('ChatBloc disposed');
emit(ChatLoading());
}
#override
Future<void> close() async {
super.close();
}
}
For the scope of this problem, all that really matters is just looking at the updateChat and LoadChat methods. Any ideas? Thanks!

Related

mapEventToState is deprecated and not working anymore

i am Working on Flutter Chat Application and when i tried to implement the MessageBloc i found that mapEventToState is not Working anymore in Version 8.0 of Bloc
This is MessageEvent
import 'package:chat/chat.dart';
import 'package:equatable/equatable.dart';
abstract class MessageEvent extends Equatable {
const MessageEvent();
factory MessageEvent.onSubscribed(User user) => Subscribed(user);
factory MessageEvent.onMessageSent(List<Message> messages) =>
MessageSent(messages);
#override
List<Object?> get props => [];
}
class Subscribed extends MessageEvent {
final User user;
Subscribed(this.user);
#override
List<Object?> get props => [user];
}
class MessageSent extends MessageEvent {
final List<Message> messages;
MessageSent(this.messages);
#override
List<Object> get props => [messages];
}
class MessageReceived extends MessageEvent {
MessageReceived(this.message);
final Message message;
#override
List<Object> get props => [message];
}
And this is MessageState
import 'package:chat/chat.dart';
import 'package:equatable/equatable.dart';
abstract class MessageState extends Equatable {
const MessageState();
factory MessageState.initial() => MessageInitial();
factory MessageState.sent(Message message) => MessageSentSuccesfully(message);
factory MessageState.received(Message message) =>
MessageReceivedSuccesfully(message);
#override
List<Object> get props => [];
}
class MessageInitial extends MessageState {}
class MessageSentSuccesfully extends MessageState {
final Message message;
const MessageSentSuccesfully(this.message);
#override
List<Object> get props => [message];
}
class MessageReceivedSuccesfully extends MessageState {
final Message message;
const MessageReceivedSuccesfully(this.message);
#override
List<Object> get props => [message];
}
and finally this is MessageBloc
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:chat/chat.dart';
import 'package:flutter_newapp/src/blocs/message/message_event.dart';
import 'package:flutter_newapp/src/blocs/message/message_state.dart';
class MessageBloc extends Bloc<MessageEvent, MessageState> {
final IMessageService _messageService;
StreamSubscription? _subscription;
MessageBloc(this._messageService) : super(MessageState.initial());
#override
Stream<MessageState> mapEventToState(MessageEvent event) async* {
if (event is Subscribed) {
await _subscription?.cancel();
_subscription = _messageService
.messages(activeUser: event.user)
.listen((message) => add(MessageReceived(message)));
}
if (event is MessageReceived) {
yield MessageState.received(event.message);
}
if (event is MessageSent) {
final message = await _messageService.send(event.messages);
yield MessageState.sent(message);
}
}
#override
Future<void> close() {
_subscription?.cancel();
_messageService.dispose();
return super.close();
}
}
Can anyone propose me a Solution to Rewrite the MessageBloc in the new Version of Bloc ?
i am trying to move from version 7.1 of Bloc to 8.1 or the Latest version of Bloc and try to learn the new implementation of Blocs in Flutter

State is not yielded thus unable to change icon color

I'm working on a metronome app and having problem with the bloc states. I'm toggling the metronome on and off with a FAB button and expecting the FAB icon color to change upon toggling. Could you have a look at the code and direct me towards what I am doing wrong here?
/// EVENTS
abstract class MetronomeEvent extends Equatable {
const MetronomeEvent();
#override
List<Object> get props => [];
}
class InitMetronomeEvent extends MetronomeEvent {}
class DisposeMetronomeEvent extends MetronomeEvent {}
class ToggleOnOffMetronomeEvent extends MetronomeEvent {}
class IncreaseTempoMetronomeEvent extends MetronomeEvent {}
class DecreaseTempoMetronomeEvent extends MetronomeEvent {}
class ChangeTempoMetronomeEvent extends MetronomeEvent {
final int givenTempo;
const ChangeTempoMetronomeEvent({required this.givenTempo});
#override
List<Object> get props => [givenTempo];
#override
String toString() {
return 'ChangeTempoMetronomeEvent{givenTempo: $givenTempo}';
}
}
Here is the states:
/// STATES
abstract class MetronomeState extends Equatable {
const MetronomeState();
}
class MetronomeInitial extends MetronomeState {
#override
List<Object> get props => [];
}
class ToggleOnOffMetronomeState extends MetronomeState {
final bool isMetronomeOn;
const ToggleOnOffMetronomeState({required this.isMetronomeOn});
#override
List<Object?> get props => [isMetronomeOn];
}
And my bloc:
/// BLOC
class MetronomeBloc extends Bloc<MetronomeEvent, MetronomeState> {
final _metronome = getIt<MetronomeService>();
MetronomeBloc() : super(MetronomeInitial()) {
on<InitMetronomeEvent>((event, emit) {
_metronome.init();
});
on<DisposeMetronomeEvent>((event, emit) {
_metronome.dispose();
});
on<ToggleOnOffMetronomeEvent>((event, emit) {
_metronome.toggleOnOff();
emit(ToggleOnOffMetronomeState(isMetronomeOn: _metronome.isMetronomeOn));
debugPrint("TOGGLED METRONOME ${_metronome.isMetronomeOn ? "ON" : "OFF"}");
});
on<IncreaseTempoMetronomeEvent>((event, emit) {
_metronome.increaseTempo();
});
on<DecreaseTempoMetronomeEvent>((event, emit) {
_metronome.decreaseTempo();
});
on<ChangeTempoMetronomeEvent>((event, emit) {
_metronome.changeTempo(event.givenTempo);
});
}
}
And I'm using BlocBuilder to listen and rebuild the widget below:
FloatingActionButton(
onPressed: () {
_bloc.add(ToggleOnOffMetronomeEvent());
},
child: BlocBuilder<MetronomeBloc, MetronomeState>(
builder: (context, state) {
if (state is ToggleOnOffMetronomeState) {
_isMetronomeOn = state.isMetronomeOn;
}
return Icon(
Icons.power_settings_new,
color: _isMetronomeOn
? Colors.red
: Theme
.of(context)
.floatingActionButtonTheme
.backgroundColor,
);
},
),
)

State doesn't change in Flutter app using BLoC

I'm building an app which fetches hotel names from an API. I'm using the BLoC library. I managed to create whole service which downloads the data, but the result doesn't show in my terminal.
My BLoC works, it downloads the data. I saw it in Dart DevTools, but the state doesn't change and it does not show up.
Here's my code:
hotel_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hotels/controllers/hotel/hotel_controller.dart';
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:meta/meta.dart';
part 'hotel_event.dart';
part 'hotel_state.dart';
class HotelBloc extends Bloc<HotelEvent, HotelState> {
HotelBloc() : super(HotelLoading());
final HotelController hotelController = HotelController();
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
yield HotelLoading();
try {
final Hotels hotels = await hotelController.parseHotels();
yield HotelFinal(hotels);
} catch (error) {
HotelError(error);
}
}
}
}
hotel_state.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotels hotels;
HotelFinal(this.hotels);
Hotels getHotel() {
return hotels;
}
}
class HotelLoading extends HotelState {
HotelLoading();
}
class HotelError extends HotelState {
final String error;
HotelError(this.error);
}
hotel_event.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelEvent {
HotelEvent();
}
class FetchEvent extends HotelEvent {
FetchEvent();
}
hotel_service.dart
import 'package:http/http.dart' as http;
abstract class DownloadService {
Future<http.Response> fetchHotels();
}
class HotelService extends DownloadService {
#override
Future<http.Response> fetchHotels() {
final Uri uri = Uri.https('services.lastminute.com', 'mobile/stubs/hotels');
return http.get(uri);
}
}
hotel_controller.dart
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:hotels/services/hotel/hotel_service.dart';
class HotelController {
final HotelService hotelService = HotelService();
Future<Hotels> parseHotels() async {
final response = await hotelService.fetchHotels();
final hotels = hotelsFromJson(response.body);
return hotels;
}
}
And finally the HomeScreen
home_screen.dart
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hotels/blocs/hotel/hotel_bloc.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
HotelBloc hotelBloc;
#override
void initState() {
hotelBloc = HotelBloc()..add(FetchEvent());
super.initState();
}
#override
void dispose() {
hotelBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('title').tr(),
),
body: BlocConsumer<HotelBloc, HotelState>(
listener: (context, state) {
if (state is HotelError) {
print(state.error);
}
},
builder: (context, state) {
if (state is HotelLoading) {
print('It\'s loading!');
}
if (state is HotelFinal) {
print(state.hotels.toString());
}
return Text('Default text');
},
),
);
}
}
The result is this:
The problem is the you haven't provided the BlocConsumer with your hotelBloc. You want to either have BlocProvider as a parent or use the cubit parameter on BlocConsumer.
BlocConsumer<HotelBloc, HotelState>(
cubit: hotelBloc,
listener:...
builder:...
)

How to use the BLoC library?

I'm trying to figure out the BLoC library, but it gives me headaches.
I'm trying to fetch hotel names from an API. I have a model and a service responsible for contacting the API and fetching the data. However, I don't know how to connect it to the BLoC library.
Once my app starts, I want BLoC to fetch the data from the API and then show it in the app.
Here's my code:
hotel_model.dart
class Hotels {
final List<Hotel> hotels;
Hotels({this.hotels});
factory Hotels.fromJson(Map<String, dynamic> json) {
return Hotels(
hotels: List<Hotel>.from(
json['hotels'].map(
(x) => Hotel.fromJson(x),
),
),
);
}
}
class Hotel {
final String hotelName;
Hotel({this.hotelName});
factory Hotel.fromJson(Map<String, dynamic> json) {
return Hotel(
hotelName: json['name'],
);
}
}
hotel_service.dart
import 'package:http/http.dart' as http;
abstract class DownloadService {
Future<http.Response> fetchHotels();
}
class HotelService extends DownloadService {
#override
Future<http.Response> fetchHotels() {
final Uri uri = Uri.https('services.lastminute.com', 'mobile/stubs/hotels');
return http.get(uri);
}
}
And here's what I did wit the BLoC lib.
hotel_event.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelEvent {}
class OnAppStartEvent extends HotelEvent {}
hotel_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:hotels/services/hotel/hotel_service.dart';
import 'package:meta/meta.dart';
part 'hotel_event.dart';
part 'hotel_state.dart';
class HotelBloc extends Bloc<HotelEvent, HotelState> {
HotelBloc() : super(HotelFinal());
final HotelService hotelService = HotelService();
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
final response = hotelService.fetchHotels();
yield
}
}
}
hotel_state.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
First of all add await to this line in your bloc
final response = await hotelService.fetchHotels();
return List<Hotel> from your fetchHotels function
you must have stateful class for your screen and in the initState
you can create your bloc object and call .add method on it
in your build method wrap your widget with BlocBuilder and on builder callback check your bloc state, if the state is HotelFinal return your ui with list of hotels in your state object.
It'll be useful to add another state for your HotelState for when your bloc is fetching the data, and even for when there's an error. e.g;
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
class HotelLoading extends HotelState {
HotelLoading();
}
class HotelError extends HotelState {
final String error;
HotelError(this.error);
}
You would want to change your mapEventToState to something like this:
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
yield HotelLoading();
try {
final response = await hotelService.fetchHotels();
// It seems like your service doesn't return an hotel directly, so you'll have to deal with this as it is not part of the question.
final hotel = getYourHotelHereWithTheResponse;
yield HotelFinal(hotel);
} catch (e) {
yield HotelError('something went wrong getting the hotel info');
}
}
}
Lastly, add a widget to your widget tree that adds FetchEvent to your bloc and add a BlocBuilder to react to the change of states. Note that this is very flexible and can be done in many ways, but it is out of the scope of your very broad question, I'm just showing you how to use the library at a minimal:
class MyStatefulWidget extends StatefulWidget {
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
HotelBloc hotelBloc;
#override
void initState() {
hotelBloc = HotelBloc..add(FetchEvent());
super.initState();
}
#override
void dispose() {
hotelBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder(builder: (context, state) {
if(state is HotelLoading) {
// return a widget to deal with loading
}
if(state is HotelFinal) {
// return a widget to deal with success
}
if(state is HotelError) {
// return a widget to deal with error
}
});
}
}

flutter_bloc - how can i get value without BlocBuilder?

I'm still a beginner with streams and bloc pattern.
I would like to do following:
Trigger an event.
Based on the event get back a state with an object
Store this object as JSON in a database.
All examples are showing, how an object can be displayed in a widget with BlocBuilder. But I don't need to display the value, only get it and store it. I can't figure out how to get the value into a variable.
How can I do that? In the View class I'm dispatching the event, but now I need to know how to get the object in the state back without using BlocBuilder.
Here are the details:
Bloc
class SchoolBloc extends Bloc<SchoolEvent, SchoolState> {
final SchoolRepository _schoolRepository;
StreamSubscription _schoolSubscription;
SchoolBloc({#required SchoolRepository schoolRepository})
: assert(schoolRepository != null),
_schoolRepository = schoolRepository;
#override
SchoolState get initialState => SchoolsLoading();
#override
Stream<SchoolState> mapEventToState(SchoolEvent event) async* {
if (event is LoadSchool) {
yield* _mapLoadSchoolToState();
Stream<SchoolState> _mapLoadSchoolToState(LoadSchool event) async* {
_schoolSubscription?.cancel();
_schoolSubscription = _schoolRepository.school(event.id).listen(
(school) {
SchoolLoaded(school);
}
);
}
Event
#immutable
abstract class SchoolEvent extends Equatable {
SchoolEvent([List props = const []]) : super(props);
}
class LoadSchool extends SchoolEvent {
final String id;
LoadSchool(this.id) : super([id]);
#override
String toString() => 'LoadSchool';
}
State
#immutable
abstract class SchoolState extends Equatable {
SchoolState([List props = const []]) : super(props);
}
class SchoolLoaded extends SchoolState {
final School school;
SchoolLoaded([this.school]) : super([school]);
#override
String toString() => 'SchoolLoaded { school: $school}';
}
View
class CourseView extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CourseViewState();
}
class _CourseViewState extends State<CourseView> {
#override
initState() {
super.initState();
print("this is my init text");
final _schoolBloc = BlocProvider.of<SchoolBloc>(context);
_schoolBloc.dispatch(LoadSchool("3kRHuyk20UggHwm4wrUI"));
// Here I want to get back the school object and save it to a db
}
Test that fails
For testing purposes I have done following:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:teach_mob/core/blocs/school/school.dart';
class CourseView extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CourseViewState();
}
class _CourseViewState extends State<CourseView> {
#override
void initState() {
super.initState();
BlocProvider.of<SchoolBloc>(context)
.dispatch(LoadSchool("3kRHuyk20UggHwm4wrUI"));
}
#override
Widget build(BuildContext context) {
return BlocListener<SchoolBloc, SchoolState>(
listener: (context, state) {
print("BlocListener is triggered");
},
child: Text("This is a test")
);
}
}
The LoadSchool event is triggered. The text in the child attribute of BlocListener is displayed, but the listener function that should print "BlocListener is triggered" is not executed.
Use BlocListener. It is meant to be used for those cases you mention.