I'm facing a problem in BlocLibrary(https://bloclibrary.dev/)
I need to receive same state in BlocListner/BlocBuilder, let me explain in code:
This is Bloc(for little explain):
class CounterBloc extends Bloc<CounterEvent, AppState> {
#override
AppState get initialState => InititalState();
#override
Stream<AppState> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.getState:
yield Counter(value: 0);
break;
}
HERE IS STATE CLASS:
import 'package:flutter_bloc/flutter_bloc.dart';
enum StateEvent { getState }
abstract class AppState extends Equatable {
const AppState();
#override
List<Object> get props => [];
}
class Counter extends AppState {
final int count;
const Counter({#required this.count});
#override
List<Object> get props => [count];
#override
String toString() => 'Counter { count: $count }';
}
Here you go for my bloc listener/builder:
BlocListener<CounterBloc, AppState>(
listener: (context, state) {
if (state is Counter) {
**Here I needed same state, means if I press getState, it should print 0 everytime**
print(state. value);
}
},
child: BlocBuilder<CounterBloc, AppState>(
builder: (context, state) {
return Center(
child: Text(
'${value}',
style: TextStyle(fontSize: 24.0),
),
);
},
),
)
Change your AppState, you can read more about Equatable usage here. This post is mostly a duplication of this.
You should not use Equatable if you want the same state back-to-back to trigger multiple transitions.
abstract class AppState {
const AppState();
}
class Counter extends AppState {
final int count;
const Counter({#required this.count});
#override
String toString() => 'Increment { count: $count }';
}
Related
I am using flutter bloc for the for hover a text.But the problem is when i am hovering a text the full text in list is hovering. Now i want to just hover a single text in list like if i want to move my mouse courser on Home it will just change the color of Home. How can i do that? any help will be good for me.
Bloc
class HomeBloc extends Bloc<HomeEvent, HomeState> {
HomeBloc() : super(HomeInitial()) {
on<HoverEvent>((event, emit) {
emit(HoverState(color: neonColor));
});
on<HoverOutEvent>((event, emit) {
emit(HoverOutState());
});
}
}
Event
abstract class HomeEvent extends Equatable {
const HomeEvent();
}
// ignore: must_be_immutable
class HoverEvent extends HomeEvent {
#override
List<Object?> get props => [];
}
class HoverOutEvent extends HomeEvent {
#override
List<Object?> get props => [];
}
State
abstract class HomeState extends Equatable {
const HomeState();
}
class HomeInitial extends HomeState {
#override
List<Object> get props => [];
}
// ignore: must_be_immutable
class HoverState extends HomeState {
Color color;
HoverState({
required this.color,
});
#override
List<Object?> get props => [color];
}
class HoverOutState extends HomeState {
#override
List<Object?> get props => [];
}
Implement Code
Widget build(BuildContext context) {
return BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) {
return Row(
children: [
TextButton(
onPressed: () {},
onHover: (value) {
if (value) {
BlocProvider.of<HomeBloc>(context).add(HoverEvent());
} else {
BlocProvider.of<HomeBloc>(context).add(HoverOutEvent());
}
},
child: Text(
" $appbarText ",
style: listPageTitleStyle(
color: state is HoverState ? state.color : appBarColor),
),
)
],
);
},
);
}
}
List
class NavBarName {
static const List<String> names = [
'HOME',
'ABOUT',
'SERVICES',
'PROJECTS',
'CONTACT',
];
}
I just want to change the text color of a single text which i will hover.
Thanks
In the application, when you click on the "Add Group" button, a page with a text field opens, the user enters the name of the group and it is added to the database and drawn on the screen.
The problem is that only the name of the first group is displayed on the screen, and when you enter the name of the group a second time, it does not appear on the screen. But when you click on Hot Reload, all added groups are displayed. How to make the screen refresh every time a group is added and they are displayed as a list?
In my code, I access the bloc and pass the entered group name to the event:
void _saveGroup() {
final isValid = _formKey.currentState!.validate();
if (isValid) {
BlocProvider.of<NewGroupBloc>(context)
.add(AddGroupEvent(_groupController.text.trim()));
Navigator.pop(context);
}
}
Further, this name is converted into a database module and saved. I then add the model from the database to the list and output it:
lass GroupRepository extends BaseRepository<GroupDao, GroupDBData, Group> {
#override
GroupDao dao = injector<AppDb>().groupDao;
#override
BaseConverter<GroupDBData, Group> converter = GroupDbConverter();
final List<Group> groups = [];
Future<Group> convertGroup(Group group) async {
final convert = converter.outToIn(group);
final groupId = await dao.insert(convert);
Group groupWithID = Group(
id: groupId,
groupName: group.groupName,
);
return groupWithID;
}
Future<List<Group>> getList(Group group) async {
final groupWithID = await convertGroup(group);
groups.add(groupWithID);
return groups;
}
}
class GroupsPage extends StatefulWidget {
const GroupsPage({Key? key}) : super(key: key);
#override
State<GroupsPage> createState() => _GroupsPageState();
}
class _GroupsPageState extends State<GroupsPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Groups'),
),
body: BlocBuilder<NewGroupBloc, NewGroupState>(
builder: (context, state) {
if (state is AddGroupState) {
return ListView.builder(
itemCount: state.groups.length,
itemBuilder: (context, index) {
final group = state.groups[index];
return ListTile(
title: Text(group.groupName),
);
},
);
}
return Container();
},
),
floatingActionButton: Padding(
child: FloatingActionButton.extended(
onPressed: () => _onAddGroup(context),
icon: const Icon(Icons.add),
label: const Text('Add Group'),
),
),
);
}
My bloc:
//bloc
class NewGroupBloc extends Bloc<NewGroupEvent, NewGroupState> {
NewGroupBloc() : super(const NewGroupInitial()) {
on<AddGroupEvent>(
(event, emit) async => emit(
await _addGroup(event),
),
);
}
final _provider = ProviderInjector.instance.addGroup;
Future<NewGroupState> _addGroup(AddGroupEvent event) async => _provider
.addGroup(
Group(groupName: event.groupName),
)
.then(AddGroupState.new);
}
//state
#immutable
abstract class NewGroupState extends Equatable {
const NewGroupState();
#override
List<Object?> get props => [];
}
class NewGroupInitial extends NewGroupState {
const NewGroupInitial();
}
class AddGroupState extends NewGroupState {
const AddGroupState(this.groups);
final List<Group> groups;
#override
List<Object?> get props => [groups];
}
//event
#immutable
abstract class NewGroupEvent extends Equatable {
#override
List<Object?> get props => [];
}
class AddGroupEvent extends NewGroupEvent {
AddGroupEvent(this.groupName);
final String groupName;
#override
List<Object?> get props => [groupName];
}
_onAddGroup:
void _onAddGroup(BuildContext context) {
Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute(
builder: (context) => const AddGroupPage(),
),
);
}
class AddGroupState extends NewGroupState {
const AddGroupState(this.groups);
final List<Group> groups;
#override
List<Object?> get props => [this.groups];
}
Try this
I'm new to Bloc ( coming from provider ), everything works fine except whenever i try to add a second event to the same bloc on the same widget it never gets triggered, even the mapEventToState doesn't trigger,
i first add an event to get posts in the initState, this works fine, later on i add event on the refresh indicator onRefresh function, everything inside the function triggers Except the bloc add event.
I'm using Equatable, so i tried without it and no difference .
i tried to make separate class for each state, still Not working as well .
i tried Not to close the bloc on dispose, also not working.
ForumMain widget is a child of a BottomNavigationTabBar
here is my code :
main.dart
List<BlocProvider<Bloc>> _blocProviders() => [
BlocProvider<UserBloc>(
create: (context) => UserBloc(userRepository: RepositoryProvider.of(context),navigator: RepositoryProvider.of(context),prefs: RepositoryProvider.of(context),
),
),
BlocProvider<ForumBloc>(
create: (context) => ForumBloc( RepositoryProvider.of(context),_userBloc, RepositoryProvider.of(context),),
),
BlocProvider<WUpdateBloc>(
create: (context) => WUpdateBloc(
RepositoryProvider.of(context),
RepositoryProvider.of(context)),
),
BlocProvider<PlanBloc>(
create: (context) => PlanBloc(RepositoryProvider.of(context),RepositoryProvider.of(context),
)),
];
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return OrientationBuilder(builder: (context, orientation) {
return RepositoryProvider(
create: (context) => _dioInstance(),
child: MultiRepositoryProvider(
providers: _repositoryProviders(),
child: MultiBlocProvider(
providers: _blocProviders(),
child: Builder(
builder: (context) => MaterialApp(
// theme: AppTheme.theme,
navigatorKey: navigatorKey,
navigatorObservers: [appNavigatorObserver],
localizationsDelegates: _getLocalizationsDelegates(),
supportedLocales: S.delegate.supportedLocales,
home: LoadingPage(),
debugShowCheckedModeBanner: false,
),
)),
),
);
});
});
}
forum_bloc.dart
class ForumBloc extends Bloc<ForumEvent, ForumState> {
ForumBloc(this._forumRepository, this._userBloc, this.navigator) : super(ForumState.defaultState());
final ForumRepository _forumRepository;
final UserBloc _userBloc;
final AppNavigator navigator;
#override
Stream<ForumState> mapEventToState(
ForumEvent event,
) async* {
if (event is GetPostsEvent) yield* _getPosts(event);
if (event is GetCommentsEvent) yield* _getComments(event);
if (event is RefreshPostsEvent) yield* _refreshPosts(event);
if (event is RefreshCommentsEvent) yield* _refreshComments(event);
if (event is NewPostRequest) yield* _newPost(event);
if (event is NewCommentRequest) yield* _newComment(event);
}
Stream<ForumState> _getPosts(GetPostsEvent event) async* {
print("get posts event called");
yield state.copyWith(status: BlocStatus.pending);
try {
// show progress hud
final postsResponse = await _forumRepository.getPosts(event.trainerID);
postsResponse.posts.sort((a, b) => b.datetimeCreated.compareTo(a.datetimeCreated));
print(postsResponse.posts[0].question);
yield state.copyWith(posts: postsResponse.posts, status: BlocStatus.success, total: postsResponse.posts.length);
} on DioError catch (error) {
// report error
yield state.copyWith(status: BlocStatus.error, appError: error.toAppError());
}
}
Stream<ForumState> _refreshPosts(RefreshPostsEvent event) async* {
print("refresh posts event called");
try {
// show progress hud
final postsResponse = await _forumRepository.getPosts(event.trainerID);
print(postsResponse.posts.length);
postsResponse.posts.sort((a, b) => b.datetimeCreated.compareTo(a.datetimeCreated));
yield state.copyWith(posts: postsResponse.posts, status: BlocStatus.success, total: postsResponse.posts.length);
} on DioError catch (error) {
// report error
// yield state.copyWith(status: BlocStatus.error, appError: error.toAppError());
}
}
forum_state.dart
class ForumState extends Equatable {
ForumState({
this.total,
this.posts,
this.status,
this.appError
});
final int total;
final List<BlogPost> posts;
final BlocStatus status;
final AppError appError;
factory ForumState.defaultState() => ForumState(
total: 0,
posts: [],
status: BlocStatus.idle,
appError: null
);
ForumState copyWith({
int total,
List<BlogPost> posts,
AppError appError,
BlocStatus status, }) { return ForumState(
total: total ?? this.total,
posts: posts ?? this.posts,
status: status,
);}
#override
List<Object> get props => [posts,total,status,appError];
#override
bool get stringify => true;
}
forum_state.dart failed trial to make separate class for each state
class ForumState extends Equatable {
#override
// TODO: implement props
List<Object> get props => [];
}
class ForumStateLoading extends ForumState {
}
class ForumStateSuccess extends ForumState {
ForumStateSuccess({
this.total,
this.posts,
});
final int total;
final List<BlogPost> posts;
#override
List<Object> get props => [posts,total];
}
class ForumStateRefresh extends ForumState {
ForumStateRefresh({
this.total,
this.posts,
});
final int total;
final List<BlogPost> posts;
#override
List<Object> get props => [posts,total];
}
class ForumStateError extends ForumState {
ForumStateError({
this.error,
});
final AppError error;
#override
List<Object> get props => [error];
}
forum_event.dart
abstract class ForumEvent extends Equatable {
const ForumEvent();
#override
List<Object> get props => [];
}
class GetPostsEvent extends ForumEvent {
final String trainerID;
GetPostsEvent(this.trainerID);
}
class RefreshPostsEvent extends ForumEvent {
final String trainerID;
RefreshPostsEvent(this.trainerID);
}
class GetCommentsEvent extends ForumEvent {
final String postID;
final String trainerID;
GetCommentsEvent(this.postID,this.trainerID);
}
class RefreshCommentsEvent extends ForumEvent {
final String postID;
final String trainerID;
RefreshCommentsEvent(this.postID,this.trainerID);
}
class SendPostEvent extends ForumEvent {
final NewPostRequest postRequest;
SendPostEvent(this.postRequest);
}
class SendCommentEvent extends ForumEvent {
final NewCommentRequest commentRequest;
SendCommentEvent(this.commentRequest);
}
forum_screen.dart
class ForumMain extends StatefulWidget {
#override
_ForumMainState createState() => _ForumMainState();
}
class _ForumMainState extends State<ForumMain> {
TextEditingController nameController = TextEditingController();
MyTheme myTheme = MyTheme();
ForumBloc _forumBloc;
PlanBloc _planBloc;
Completer<void> _refreshCompleter;
#override
void initState() {
// TODO: implement initState
super.initState();
myTheme.initLoadingHUD();
_forumBloc = BlocProvider.of<ForumBloc>(context);
_planBloc = BlocProvider.of<PlanBloc>(context);
_forumBloc.add(GetPostsEvent(_planBloc.state.chosenOrder.trainer.id));
_refreshCompleter = Completer<void>();
}
#override
void dispose() {
_forumBloc.close();
_planBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
ScreenUtil.init(context, width: 375, height: 812, allowFontScaling: true);
return BlocConsumer<ForumBloc,ForumState>(
listener: (context, state) {
print(state.status.toString());
setState(() {
_refreshCompleter?.complete();
_refreshCompleter = Completer();
});
myTheme.errorBlocListener(context, state.appError);
},
cubit: _forumBloc,
builder: (context, state) {
return Scaffold(
backgroundColor: Colors.white,
appBar: ForumAppBar(
height: 80.h,
forum: true,
),
body: state.status == BlocStatus.success ? Stack(
children: [
RefreshIndicator(
onRefresh: () {
_forumBloc.add(RefreshPostsEvent(
_planBloc.state.chosenOrder.trainer.id));
return _refreshCompleter.future;
},
child: ListView(
children: state.posts.map((e) {
Please change your forum_event file like this:
abstract class ForumEvent extends Equatable {
const ForumEvent([List props = const []]) : super();
}
class GetPostsEvent extends ForumEvent {
final String trainerID;
GetPostsEvent(this.trainerID);
#override
List<Object> get props => [trainerID];
}
class RefreshPostsEvent extends ForumEvent {
final String trainerID;
RefreshPostsEvent(this.trainerID);
#override
List<Object> get props => [trainerID];
}
class GetCommentsEvent extends ForumEvent {
final String postID;
final String trainerID;
GetCommentsEvent(this.postID,this.trainerID);
#override
List<Object> get props => [postID, trainerID];
}
class RefreshCommentsEvent extends ForumEvent {
final String postID;
final String trainerID;
RefreshCommentsEvent(this.postID,this.trainerID);
#override
List<Object> get props => [postID, trainerID];
}
class SendPostEvent extends ForumEvent {
final NewPostRequest postRequest;
SendPostEvent(this.postRequest);
#override
List<Object> get props => [postRequest];
}
class SendCommentEvent extends ForumEvent {
final NewCommentRequest commentRequest;
SendCommentEvent(this.commentRequest);
#override
List<Object> get props => [commentRequest];
}
You can try instead of _blocProviders() just place the List of providers inside MultiBlocProvider( providers: [ ... ],.
I see. Please try removing the following code. I don't think you should close it in this widget, since this is not where you initialize it.
_forumBloc.close();
_planBloc.close();
What am I doing wrong here that my state in a Bloc Pattern changes only one time then mapEventToState doesn't react to BlocProvider.of<CounterBloc>(context).add(ActiveEvent()); request?
I am trying to get into the way of things with the Bloc Pattern but when I switch state in the switcher on a counter page the state changes and after that, it doesn't update at all. It's like don't go any further from onChanged switch function.
I guess the issue is in my stream subscription which is implemented in the CounterBloc contractor. Or I return the state incorrectly.
I would appreciate your help and if you explain to me the mistake.
my bloc
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:practicing_bloc/blocs/counter/counterEvents.dart';
import 'package:practicing_bloc/blocs/counter/counterState.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
#override
CounterState get initialState => Active(active: true, count: 0);
CounterBloc() {
_counterStream = _counter.stream;
}
StreamController<CounterState> _counter = StreamController<CounterState>();
Stream<CounterState> _counterStream;
#override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
CounterState currentState = state;
print('currect: $currentState');
if (event is ActiveEvent) {
_counter.add(Active(active: true, count: currentState.count));
yield* _counterStream;
} else if (event is InactiveEvent) {
_counter.add(Inactive(active: false, count: currentState.count));
yield* _counterStream;
}
}
}
bloc state
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
abstract class CounterState extends Equatable {
final bool active;
final int count;
const CounterState({#required this.active, #required this.count});
#override
List<Object> get props => [active, count];
#override
String toString() => 'State { active : $active, count : $count }';
}
class Active extends CounterState {
const Active({#required bool active, #required int count})
: super(active: active, count: count);
}
class Inactive extends CounterState {
const Inactive({#required bool active, #required int count})
: super(active: active, count: count);
}
bloc Event
import 'package:equatable/equatable.dart';
abstract class CounterEvent extends Equatable {
const CounterEvent();
#override
List<Object> get props => [];
}
class Increase extends CounterEvent {}
class Decrease extends CounterEvent {}
class ActiveEvent extends CounterEvent {}
class InactiveEvent extends CounterEvent {}
counterPage
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:practicing_bloc/blocs/counter/counterBloc.dart';
class CounterPage extends StatefulWidget {
#override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
bool stateActive = false;
#override
Widget build(BuildContext context) {
//ignore: close_sinks
dynamic counterBloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Flutter Counter | Page title')),
body: SafeArea(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
String stateString = state.active ? 'Active' : 'Inactive';
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Counter is : $stateString'),
Text('Current counter is : ${state.count}'),
Switch(
value: stateActive,
onChanged: (bool value) {
print(counterBloc.state);
setState(() {
stateActive = value;
});
CounterEvent newEvent =
value ? ActiveEvent() : InactiveEvent();
counterBloc.add(newEvent);
// print('BloC state: ${counterBloc.state.active} | switch state: ${state.active}');
},
)
],
),
);
},
),
),
);
}
}
Basically instead of yielding * _counterStream you need to yield the states in this i.e. Active or Inactive
Change this
if (event is ActiveEvent) {
_counter.add(Active(active: true, count: currentState.count));
yield* _counterStream;
} else if (event is InactiveEvent) {
_counter.add(Inactive(active: false, count: currentState.count));
yield* _counterStream;
}
to this
if (event is ActiveEvent) {
yield Inactive(active: false, count: currentState.count);
} else if (event is InactiveEvent) {
yield Active(active: true, count: currentState.count);
}
I recently started to dive into Bloc Pattern in flutter, and have learned using this project.
In this project, there is an adding post feature, so I wanted to implement alike one.
Even I changed Bloc's State via Event, It doesn't add Container Widget to ListView. I tried and search this problem but I can't figure:(
Here are my code below.
Here are Bloc Parts,
#immutable
abstract class ContentsState extends Equatable {
ContentsState([List props = const []]) : super(props);
}
class ContentsLoading extends ContentsState {
#override
String toString() => 'ContentsLoading';
}
class ContentsLoaded extends ContentsState {
final List<Content> contents;
ContentsLoaded(this.contents);
#override
String toString() => 'ContentsLoaded { contents: $contents }';
}
#immutable
abstract class ContentsEvent extends Equatable {
ContentsEvent([List props = const []]) : super(props);
}
class LoadContents extends ContentsEvent {
#override
String toString() => 'LoadContents';
}
class AddContent extends ContentsEvent {
final Content content;
AddContent(this.content) : super([content]);
#override
String toString() => 'AddContent { content: $content }';
}
class ContentsBloc extends Bloc<ContentsEvent, ContentsState> {
#override
ContentsState get initialState => ContentsLoading();
#override
Stream<ContentsState> mapEventToState(ContentsEvent event) async* {
if (event is LoadContents) {
yield* _mapLoadContentsToState();
} else if (event is AddContent) {
yield* _mapAddContentToState(event);
}
}
Stream<ContentsState> _mapLoadContentsToState() async* {
final List<Content> contents = [Content('message')];
yield ContentsLoaded(contents);
}
Stream<ContentsState> _mapAddContentToState(AddContent event) async* {
if (currentState is ContentsLoaded) {
final List<Content> updateContents =
List.from((currentState as ContentsLoaded).contents)
..add(event.content);
// Output update contents
print('update contents : $updateContents');
yield ContentsLoaded(updateContents);
}
}
}
and presentation part,
class AddPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
builder: (context) => ContentsBloc()..dispatch(LoadContents()),
child: Column(
children: <Widget>[
AddBody(),
InputArea(),
],
),
);
}
}
class AddBody extends StatelessWidget {
AddBody({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final contentsBloc = BlocProvider.of<ContentsBloc>(context);
return BlocBuilder(
bloc: contentsBloc,
builder: (
BuildContext context,
ContentsState state,
) {
if (state is ContentsLoading) {
return Center(child: CircularProgressIndicator());
} else if (state is ContentsLoaded) {
final contents = state.contents;
return Expanded(
child: ListView.builder(
itemCount: contents.length,
itemBuilder: (context, index) {
final content = contents[index];
return ContentItem(content: content);
},
),
);
}
},
);
}
}
class ContentItem extends StatelessWidget {
final Content content;
ContentItem({
Key key,
this.content,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
key: Key('__Content__${content.message}__'),
child: Column(
children: <Widget>[
Text('Message Here'),
Text(content.message.toString()),
],
),
);
}
}
~~ in InputArea. There is a TextEditingController and TextField.
onTap: () => contentsBloc.dispatch(AddContent(Content(_controller.text))),
~~
Finally, this is my Content class
#immutable
class Content extends Equatable {
final String message;
Content(this.message);
#override
String toString() => 'Content { message: $message }';
}
When I run this code, I can see CircleProgressIndicator() -When State is ContentsLoading()- after that, one Container with message text shows up. So far, so good.
But After I pressed add button and fired AddContent() Event, new content doesn't show up.
Here is a console Log.
AddContent { content: Content { message: new message } }
update contents : [Content { message: message }, Content { message: new message }]
I want to add Container, but it doesn't work.
Can anyone help me? Thanks in advance:)
I resolved this problem.
I just add this one line in Bloc's ContentsLoaded class which extends ContentsState.
class ContentsLoaded extends ContentsState {
final List<Content> contents;
// just add this line below.
ContentsLoaded([this.contents = const []]) : super([contents]);
#override
String toString() => 'ContentsLoaded { contents: $contents }';
}
Thanks.