Event added to Bloc and yield state but it not recall bloc builder - flutter

when I add an event from a stateless widget by using BlocProvider.of<>, it really adds event and yield state, and BlocBuilder work and change UI,
But, when adding an event from a separate class, it really adds an event to the bloc and onTransition work, but not yield a new state, and BlocBuilder not work to change UI.
the main :
main(){
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
MQTTManager x = MQTTManager();
return MaterialApp(
home: BlocProvider(
lazy: false,
create:(context)=>MqttblocBloc(MQTTManager())..add(StartConnect()) ,
child:Home() ,
),
);
}
}
the bloc :
class MqttblocBloc extends Bloc<MqttblocEvent, MqttblocState> {
MQTTManager manager = MQTTManager() ;
MqttblocBloc(this.manager) : super(MqttblocInitial());
#override
Stream<MqttblocState> mapEventToState(
MqttblocEvent event,
) async* {
if(event is StartConnect){
try{
manager.initializeMQTTClient();
manager.connect();
yield MqttblocInitial();
}catch(e){
print(e);
}
}
else if(event is ConnectedEvent) {
try{
print('inBloc connect....');
yield ConnectedState(MQTTManager.s);
}catch(e){
print(e);
}
}
else if(event is PublishedEvent){
try{
manager.publishsw1('on');
print('inBloc publish........');
yield PublishState(manager.getText1());
}catch(e){
print(e);
}
}
else if(event is DisconnectedEvent) {
try{
print('inBloc And Disconnnnn....');
yield DisconnectState(MQTTManager.u);
}catch(e){
print(e);
}
}
}
#override
void onTransition(Transition<MqttblocEvent, MqttblocState> transition) {
super.onTransition(transition);
print(transition);
}
}
and here separate class where I listen to server and add events to bloc :
class MQTTManager {
MqttblocBloc bloc ;
static var s ;
static var u ;
MqttServerClient client;
String text ;
String text1 ;
String text2 ;
static List<String> conn = [] ;
void initializeMQTTClient(){
client = MqttServerClient("broker.shiftr.io","User");
client.port = 1883;
client.secure = false;
client.logging(on: true);
client.onConnected = onConnected;
final MqttConnectMessage connMess = MqttConnectMessage()
.authenticateAs('889514b9', 'd5459e3f6b0422cb')
.withClientIdentifier("User")
.withWillTopic('willtopic')
.withWillMessage('My Will message')
.startClean() // Non persistent session for testing
.withWillQos(MqttQos.atLeastOnce);
print('EXAMPLE::Mosquitto client connecting....');
client.connectionMessage = connMess;
}
// Connect to the host
void connect() async{
assert(client != null);
try {
print('EXAMPLE::Mosquitto start client connecting....');
await client.connect();
Amar(); // <...... here calling this fun to start listen to Server
} on Exception catch (e) {
print('EXAMPLE::client exception - $e');
disconnect();
}
}
void disconnect() {
print('Disconnected');
client.disconnect();
}
void publishsw1(String message){
final MqttClientPayloadBuilder builder = MqttClientPayloadBuilder();
builder.addString(message);
client.publishMessage('hello/sw1', MqttQos.exactlyOnce, builder.payload);
}
void onConnected() {
print('EXAMPLE::shiftr client connected....');
client.subscribe("hello/sw1", MqttQos.atLeastOnce);
client.updates.listen((List<MqttReceivedMessage<MqttMessage>> c) {
final MqttPublishMessage recMess = c[0].payload;
final String pt =
MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
setText(pt);
});
}
Amar() { //<....... here h listen to server
bloc = MqttblocBloc(this);
client.subscribe("\$events", MqttQos.atLeastOnce);
client.updates.listen((List<MqttReceivedMessage<MqttMessage>> c) {
final MqttPublishMessage recMess = c[0].payload;
final String pt =
MqttPublishPayload.bytesToStringAsString(recMess.payload.message);
var z = DetectEvent.fromJson(json.decode(pt));
if(z.type == 'connected'){
var connected = Connected.fromJson(json.decode(pt));
print (connected.type);
bloc.add(ConnectedEvent()); // <... here I add event to Bloc , but it not changeing UI
}
else if(z.type == 'disconnected'){
var disconnected = Disconnected.fromJson(json.decode(pt));
print (disconnected.type) ;
bloc.add(DisconnectedEvent()); // <... here I add event to Bloc , but it not changeing UI
}
else if(z.type == 'published'){
var published = Published.fromJson(json.decode(pt));
print(published.type) ;
}
}
}
and that is a stateless widget and use blocbuider :
class Home extends StatelessWidget {
MQTTManager v = MQTTManager();
#override
Widget build(BuildContext context) {
MqttblocBloc p = BlocProvider.of<MqttblocBloc>(context);
return Scaffold(
appBar: AppBar(
title: Text('Bloc MQTT'),
actions: [
Row(
children: [
IconButton(
icon: Icon(Icons.wb_incandescent,
),
onPressed: (){
p.add(PublishedEvent());//<.... here it change UI ,
},
),
],
)
],
),
body: Center(
child: BlocBuilder<MqttblocBloc,MqttblocState>(
// buildWhen: (previuosState , currentState)=>currentState.runtimeType !=previuosState.runtimeType,
builder:(context , state){
if(state is MqttblocInitial){
return CircularProgressIndicator();
}
else if(state is ConnectedState){
return IconButton(
icon: Icon(Icons.wifi),
onPressed: (){},
);
}
else if(state is PublishState){
return RaisedButton(
child: Text('${state.x}'),
onPressed: (){},
);
}
else if(state is DisconnectState){
return IconButton(
icon: Icon(Icons.wb_incandescent),
onPressed: (){
},
);
}
return CircularProgressIndicator();
} ,
),
)
);
}
}
bloc State :
#immutable
abstract class MqttblocState extends Equatable {
const MqttblocState();
#override
List<Object> get props => [];
}
class MqttblocInitial extends MqttblocState {}
class ConnectedState extends MqttblocState{
final String x ;
ConnectedState(this.x);
#override
List<Object> get props => [x];
}
class PublishState extends MqttblocState{
final String x ;
PublishState(this.x);
#override
List<Object> get props => [x];
}
class DisconnectState extends MqttblocState{
final String x ;
DisconnectState(this.x);
#override
List<Object> get props => [x];
}
and bloc events
#immutable
abstract class MqttblocEvent extends Equatable {
MqttblocEvent();
#override
List<Object> get props => [];
}
class StartConnect extends MqttblocEvent{}
class ConnectedEvent extends MqttblocEvent{}
class PublishedEvent extends MqttblocEvent{}
class DisconnectedEvent extends MqttblocEvent{}

The UI won't rebuild upon sending the same state again. You need to send some other state first. So for any event, your mapping should look like this:
if(event is StartConnect){
yield MqrrblocInProgress(); // <============== ADDED
try{
manager.initializeMQTTClient();
manager.connect();
yield MqttblocInitial();
}catch(e){
print(e);
}
Of course, you need to define this state (InProgress), too, as well as define some widget in the UI for this state (eg spinning wheel)

here is why, You're yielding the same state and using Equatable without being any different props to compare to so the BlocBuilder is not seeing any change.
You have two solutions ether un-inherit Equatable from class MqttblocEvent:
abstract class MqttblocEvent {
MqttblocEvent();
}
Or yield different state in-between like MqrrblocInProgress (Recommended) :
Stream<MqttblocState> mapEventToState(
MqttblocEvent event,
) async* {
yield MqrrblocInProgress();
.....

Related

Flutter BlocConsumer doesn't listen to state change when searching

I have been battling with this flutter bloc problem. I am currently using flutter Bloc 7.0.1. The BlocConsumer doesn't listen to the state changes at all. Anytime I enter values inside the search field, event is been called and state is yielded but the listener fail to listen to state changes.
This issue is really driving me mad.
STATE
part of 'people_bloc.dart';
#immutable
abstract class PeopleState {}
class PeopleInitial extends PeopleState {}
class PeopleLoadingState extends PeopleState {
#override
List<Object?> get props => [];
}
class SearchLoadingState extends PeopleState {
#override
List<Object?> get props => [];
}
BLOC
List<SearchPeopleResponseData> people = [];
#override
Stream<PeopleState> mapEventToState(
PeopleEvent event,
) async* {
if (event is SearchPeopleEvent) {
yield SearchLoadingState();
try {
var token = await getToken();
//print(token);
SearchPeopleResponse responseData =
await client.getPeople(token!, event.term);
if (responseData.status == 200) {
yield GetSearchResultState(getPeopleResponse: responseData);
} else {
yield PeopleErrorState(message: responseData.msg);
print("loadingE");
}
} catch (e) {
//print("error msg here ${e.toString()}");
PeopleErrorState(message: e.toString());
}
}
EVENT
part of 'people_bloc.dart';
#immutable
abstract class PeopleEvent {
const PeopleEvent();
}
class GetPeopleEvent extends PeopleEvent {
final String term;
GetPeopleEvent({required this.term});
#override
List<Object> get props => [term];
}
class SearchPeopleEvent extends PeopleEvent {
final String term;
SearchPeopleEvent({required this.term});
#override
List<Object> get props => [term];
}
VIEW
Widget build(BuildContext context) {
return BlocConsumer<PeopleBloc, PeopleState>(
listener: (context, state) {
print("Listener has been called");
if (state is GetSearchResultState) {
loading = false;
print("Result Found in view");
} else if (state is SearchLoadingState) {
loading = true;
print("Search loading");
} else if (state is PeopleLoadingState) {
loading = true;
}
See screenshot

Using a bloc with Navigator 2.0

Hi I am trying to use a bloc instead of ChangeNotifierDelegate in my RouterDelegate class. Unfortunately the bloc is not being called when a route is changed through my routebloc, not sure why. I have tried wrapping the delegate in a BlocProvider, but it made no difference (I currently have it injected above in the main file.)
runApp(MyApp());
class _MyApp AppState extends State<MyApp> {
MyAppRouterDelegate _routerDelegate = MyAppRouterDelegate();
MyAppRouteInformationParser _routeInformationParser = MyAppRouteInformationParser();
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
lazy: false,
create: (context) => getIt<AuthBloc>()//..add(AppStarted()),
),
BlocProvider(
lazy: false,
create: (context) => getIt<RouterBloc>(),
),
],
child: MaterialApp.router(
title: 'MyApp',
theme: globalAppThemeData,
routerDelegate: _routerDelegate,
routeInformationParser: _routeInformationParser,
),
);
}
}
In my RouterDelegate I have .....
lass MyAppRouterDelegate extends RouterDelegate<MyAppConfiguration>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<MyAppConfiguration> {
final GlobalKey<NavigatorState> _navigatorKey;
String currentPage = '';
String selectedItem = '';
#override
GlobalKey<NavigatorState> get navigatorKey => _navigatorKey;
MyAppRouterDelegate() : _navigatorKey = GlobalKey<NavigatorState>();
#override
MyAppConfiguration get currentConfiguration {
currentPage = currentConfiguration.screen;
selectedItem = currentConfiguration.selectedItemId;
if (currentPage == UNKNOWN) {
return MyAppConfiguration.unknown();
} else if (currentPage == SPLASH) {
return MyAppConfiguration.splash();
} else if (currentPage == LOGIN) {
return MyAppConfiguration.login();
} else {
return MyAppConfiguration.unknown();
}
}
#override
Widget build(BuildContext context) {
List<Page> pages = [SplashPage(SPLASH)];
return BlocBuilder<RouterBloc, RouterState>(
builder: (context, state) {
if (state is ChangedRoute) {
pages.clear();
pages = state.pages;
}
return Navigator(
key: navigatorKey,
pages: pages,
onPopPage: (route, result) {
if (!route.didPop(result)) return false;
context.read<AuthBloc>().add(AuthEventLoggedOut());
return true;
},
);
},
);
}
#override
Future<void> setNewRoutePath(MyAppConfiguration configuration) async {
if (configuration.unknown) {
currentPage = UNKNOWN;
selectedItem = configuration.selectedItemId;
} else if (configuration.isSplashPage) {
currentPage = SPLASH;
selectedItem = configuration.selectedItemId;
} else if (configuration.isLoginPage) {
currentPage = LOGIN;
selectedItem = configuration.selectedItemId;
} else if (configuration.isSignUpPage)
currentPage = SIGNUP;
selectedItem = configuration.selectedItemId;
} else {
print(Constants.failureCouldNotSetRoute);
}
}
_clear() {
currentPage = UNKNOWN;
selectedItem = '';
}
}
In my app configuration...
class MyAppInformationParser
extends RouteInformationParser<MyAppConfiguration> {
#override
Future<MyAppConfiguration> parseRouteInformation(RouteInformation? routeInformation) async {
final uri = Uri.parse(routeInformation!.location!);
if (uri.pathSegments.length == 0) {
return MyAppConfiguration.splash();
} else if (uri.pathSegments.length == 1) {
final first = uri.pathSegments[1].toLowerCase();
if (first == LOGIN) {
return MyAppConfiguration.login();
} else {
return MyAppConfiguration.unknown();
}
} else {
return MyAppConfiguration.unknown();
}
}
#override
RouteInformation restoreRouteInformation(MyAppConfiguration configuration) {
if (configuration.isUnknownPage) {
return RouteInformation(location: '/unknown');
} else if (configuration.isSplashPage) {
return RouteInformation(location: '/splash');
} else if (configuration.isLoginPage) {
return RouteInformation(location: '/login');
} else {
return RouteInformation(location: '/unknown');
}
}
}
My auth bloc ...
#injectable
class AuthBloc extends Bloc<AuthEvent, AuthState> {
IAuthFacade authRepo;
RouterBloc routerBloc;
AuthBloc(this.authRepo, this.routerBloc) : super(Uninitialized());
#override
Stream<AuthState> mapEventToState(
AuthEvent event,
) async* {
if (event is AppStarted) {
yield AuthenticationLoading();
Option<CurrentUser> user = await authRepo.getSignedInUser();
yield user.fold(() {
routerBloc.add(RouterEventNewPage(pages: [LoginPage(LOGIN)]));
return Unauthenticated();
}, (user) {
routerBloc.add(RouterEventNewPage(pages: [HomePage(HOME)]));
return Authenticated(user);
});
}
if (event is AuthEventLoggedOut) {
authRepo.signOut();
///TODO: clear hive here??
}
}
}
abstract class AuthEvent extends Equatable {
#override
List<Object> get props => [];
}
//
class AppStarted extends AuthEvent {}
//
class AuthEventLoggedOut extends AuthEvent {}
abstract class AuthState extends Equatable {
#override
List<Object> get props => [];
}
//
class Uninitialized extends AuthState {}
//
class Authenticated extends AuthState {
final CurrentUser user;
Authenticated(this.user);
}
//
class Unauthenticated extends AuthState {}
//
class AuthenticationLoading extends AuthState {}
My Router Bloc...
#injectable
class RouterBloc extends Bloc<RouterEvent, RouterState> {
RouterBloc() : super(RouterInitial());
#override
Stream<RouterState> mapEventToState(
RouterEvent event,
) async* {
if (event is RouterEventNewPage) {
yield ChangingRoute();
yield ChangedRoute(pages: event.pages);
}
}
}
abstract class RouterEvent extends Equatable {
const RouterEvent();
#override
List<Object> get props => [];
}
class RouterEventNewPage extends RouterEvent {
final List<Page> pages;
RouterEventNewPage({required this.pages});
#override
List<Object> get props => [pages];
}
abstract class RouterState extends Equatable {
const RouterState();
#override
List<Object> get props => [];
}
class RouterInitial extends RouterState {}
class ChangingRoute extends RouterState {}
class ChangedRoute extends RouterState {
final List<Page> pages;
ChangedRoute({required this.pages});
#override
List<Object> get props => [pages];
}
The app runs through the Navigator in the build function of the delegate first, it navigates to the splash screen perfectly, then after my animation finishes in the splash screen it calls the auth bloc to check if user is authorised, this works perfectly which then calls the routerbloc. The router bloc adds the new login screen (as the user is logged out). However, the bloc inside the build function of the MyAppRouterDelegate is not firing again.
Any help provided would be very much appreciated.
When it runs through the MyAppRouterDelegates build function the first time I do receive the error
"
════════ Exception caught by scheduler library ═════════════════════════════════
The following StackOverflowError was thrown during a scheduler callback:
Stack Overflow
When the exception was thrown, this was the stack
#0 CrokettRouterDelegate.currentConfiguration
package:crokett/routes/crokett_router_delegate.dart:20
"
But I don't receive any more information on the error.
Don't you need a notifyListeners() somewhere in your blocBuilder after you update the page stack?
I am interested to know if you got it working.

Flutter Bloc is not updating the state/ not working

I am developing a mobile application using Flutter. I am using the flutter bloc package, https://pub.dev/packages/flutter_bloc for managing and setting up the bloc. But when the state change it is not updating the widgets or views.
I have a bloc class file called home_bloc.dart with the following implementation.
class HomeEvent {
static const int FETCH_ARTICLES = 1;
static const int TOGGLE_IS_FILTERING = 2;
int _event = 0;
String _filterKeyword = "";
int get event => _event;
void set event(int event) {
this._event = event;
}
String get filterKeyword => _filterKeyword;
void set filterKeyword(String filterKeyword) {
this._filterKeyword = filterKeyword;
}
}
class HomeBloc extends Bloc<HomeEvent, HomeState> {
Repository _repository = Repository();
HomeState state = HomeState();
#override
HomeState get initialState => state;
#override
Stream<HomeState> mapEventToState(HomeEvent event) async* {
switch (event.event) {
case HomeEvent.FETCH_ARTICLES:
{
List<dynamic> articles = List<dynamic>();
fetchArticles(filter: event.filterKeyword).listen((dynamic article) {
articles.add(article);
});
state = state.copyWith(articles: articles);
break;
}
case HomeEvent.TOGGLE_IS_FILTERING:
{
state.isFiltering = ! state.isFiltering;
state = state.copyWith();
break;
}
default:
{
state = state.initial();
break;
}
}
yield state;
}
Stream<dynamic> fetchArticles({String filter = ""}) async* {
List<dynamic> list = (this.state.articles.length > 0)
? this.state.articles
: await _repository.getArticles();
if (filter.isNotEmpty) {
for (var article in list) {
if (article is String) {
yield article;
} else if (article.title.contains(filter)) {
yield article;
}
}
} else {
for (var article in list) {
yield article;
}
}
}
}
class HomeState {
bool _isFiltering = false;
List<dynamic> _articles = List<dynamic>();
bool get isFiltering => _isFiltering;
void set isFiltering(bool isFiltering) {
this._isFiltering = isFiltering;
}
List<dynamic> get articles => _articles;
void set articles(List<dynamic> list) {
this._articles = list;
}
HomeState initial() {
HomeState state = HomeState();
state.isFiltering = false;
state.articles = List<dynamic>();
return state;
}
HomeState copyWith({ bool isFiltering, List<dynamic> articles }) {
HomeState state = HomeState();
state.isFiltering = isFiltering != null? isFiltering: this._isFiltering;
state.articles = articles!=null && articles.length > 0? articles: this._articles;
return state;
}
}
This is my repository class returning dummy data.
class Repository {
Future<List<dynamic>> getArticles() async {
List<dynamic> list = List<dynamic>();
list.add("A");
Article article1 = Article();
article1.id = 1;
article1.title = "A start is born";
list.add(article1);
Article article2 = Article();
article2.id = 2;
article2.title = "Asking for help";
list.add(article2);
Article article3 = Article();
article3.id = 3;
article3.title = "Angel is comming";
list.add(article3);
list.add("B");
Article article4 = Article();
article4.id = 4;
article4.title = "Baby Boss";
list.add(article4);
Article article5 = Article();
article5.id = 5;
article5.title = "Beginner guide to Staying at Home";
list.add(article5);
list.add("C");
Article article6 = Article();
article6.id = 6;
article6.title = "Care each other";
list.add(article6);
Article article7 = Article();
article7.id = 7;
article7.title = "Controlling the world";
list.add(article7);
Article article8 = Article();
article8.id = 8;
article8.title = "Chasing the dream";
list.add(article8);
return list;
}
}
This is my HomePage widget
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomePageState();
}
}
class _HomePageState extends State<HomePage> {
IconData _searchIcon = Icons.search;
Widget _appBarTitle;
HomeBloc _homeBloc;
#override
void initState() {
super.initState();
this._homeBloc = BlocProvider.of(context);
WidgetsBinding.instance.addPostFrameCallback((_) => this.fetchArticles());
WidgetsBinding.instance
.addPostFrameCallback((_) => this.buildAppBarTitle());
}
#override
Widget build(BuildContext context) {
return BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(title: Text("Home"),),
body: Container(
child: buildListView(context, state),
),
);
},
);
}
#override
void dispose() {
super.dispose();
}
void buildAppBarTitle() {
this.setState(() {
if (_searchIcon == Icons.search) {
this._appBarTitle = Text("Home");
} else {
this._appBarTitle = TextField(
onChanged: (String inputValue) {
debugPrint("Search term has changed $inputValue");
//homeBloc.fetchArticles(filter: inputValue);
},
style: TextStyle(
color: Colors.white,
),
decoration: InputDecoration(
hintText: "Search",
),
);
}
});
}
Widget buildAppBarSearchIcon() {
return IconButton(
icon: Icon(
_searchIcon,
color: Colors.white,
),
onPressed: () {
if (this._searchIcon == Icons.search) {
//display the search text field and close icons
this.setState(() {
this._searchIcon = Icons.close;
this.buildAppBarTitle();
//homeBloc.toggleFiltering();
});
} else {
this.fetchArticles();
this.setState(() {
this._searchIcon = Icons.search;
this.buildAppBarTitle();
//homeBloc.toggleFiltering();
});
}
});
}
Widget buildListView(
BuildContext context, HomeState state) {
if (state.articles.length > 0) {
var listView = ListView.builder(
itemCount: state.articles.length,
itemBuilder: (context, index) {
var item = state.articles[index];
if (item is String) {
return buildListFirstInitialView(item);
}
Article article = item as Article;
return buildListArticleView(article);
});
return listView;
} else {
return Center(
child: Text("No resources found."),
);
}
}
Widget buildListFirstInitialView(String initial) {
return ListTile(
title: Text(initial),
);
}
Widget buildListArticleView(Article article) {
return ListTile(
title: Text(article.title),
);
}
Widget buildBottomNavigationBar() {
return BottomNavigationBar(
currentIndex: 0,
onTap: (int position) {},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text('Settings'),
),
]);
}
void fetchArticles({String filter = ""}) {
HomeEvent event = HomeEvent();
event.event = HomeEvent.FETCH_ARTICLES;
_homeBloc.add(event);
}
}
As you can see this is my HomePage widget is doing. It will fetch the articles after the widget is built. Then the list view will be updated with the dummy data.
My main.dart file.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: BlocProvider(
create: (context) => HomeBloc(),
child: HomePage(),
),
);
}
}
When I run my app, it is not updating the list view with the dummy data. Instead, it is always showing the message for no records found.
Why is it not working?
Bloc will never change If the state didn't change, you might be confused that you assigned a state, but the fact is that Bloc is a Stream, you'll need to yield a state instead of assigning It directly.
Hopefully, you had implemented a copyWith, so you could do It as below:
yield state.copyWith(articles: articles)
For keeping your structure, you could still use:
// state = state.copyWith(articles: articles);
newState = state.copyWith(articles: articles);
...
yield newState;
Because state variable is used by Bloc, you must use another variable to prevent Bloc from comparing the same variable (state == state will always true, so the state never changed.)
your state class is not equatable ... Dart can't tell when the state has changed
You must use:
import 'package:equatable/equatable.dart';
I also don't think you should SET the state property on your bloc class. You should only yield it and let the bloc update it...
please check the docs as I could be wrong
I had the same issue and solved this problem changing:
yield status;
to
yield status.toList();
At the end of my mapEventToState() method.
And probably you had to make for all yield that is passing a List.
If it worked for you let me know

State not changing (stuck on initial)

Recently i'm learning about bloc_pattern , so i follow this https://medium.com/flutter-community/implementing-bloc-pattern-for-parsing-json-from-api-5ac538d5179f .
Here is my script
My Bloc File
class HotProductBloc extends Bloc<HotProductEvent, HotProductState> {
HotProductRepository repository;
HotProductBloc({#required this.repository});
#override
HotProductState get initialState => InitialHotProductsState();
#override
Stream<HotProductState> mapEventToState(HotProductEvent event) async* {
print(event);
if (event is FetchHotProductEvent) {
yield HotProductsLoading();
try {
List<HotProducts> dataHotProduct = await repository.getHotProduct();
yield HotProductsLoaded(hotproduct: dataHotProduct);
} catch (e) {
yield HotProductsError(message: e.toString());
}
}
}
}
Repository file
abstract class HotProductRepository {
Future<List<HotProducts>> getHotProduct();
}
class HotProductImplement implements HotProductRepository {
#override
Future<List<HotProducts>> getHotProduct() async {
print("running");
final response = await http.post(Configuration.url + "api/getHotProducts",
body: {"userId": "abcde"});
if (response.statusCode == 200) {
var data = json.decode(response.body);
List<dynamic> responseData = jsonDecode(response.body);
final List<HotProducts> hotProducts = [];
responseData.forEach((singleUser) {
hotProducts.add(HotProducts(
productId: singleUser['productId'],
isNew: singleUser['isNew'],
productName: singleUser['productName'],
isHot: singleUser['isHot'],
productImage: singleUser['productImage'],
categoryId: singleUser['categoryId'],
productPrice: singleUser['productPrice'],
productDescription: singleUser['productDescription'],
isLiked: singleUser['isLiked'],
image1: singleUser['image1'],
image2: singleUser['image2'],
image3: singleUser['image3'],
productColorId: singleUser['productColorId'],
));
});
return hotProducts;
} else {
throw Exception();
}
}
Event file
abstract class HotProductEvent extends Equatable {
HotProductEvent([List props = const []]) : super(props);
}
class FetchHotProductEvent extends HotProductEvent {
#override
List<Object> get props => null;
}
State file
abstract class HotProductState extends Equatable {
HotProductState([List props = const []]) : super(props);
}
class InitialHotProductsState extends HotProductState {}
class HotProductsLoading extends HotProductState {}
class HotProductsLoaded extends HotProductState {
final List<HotProducts> hotproduct;
HotProductsLoaded({#required this.hotproduct})
: assert(hotproduct != null),
super([hotproduct]);
}
class HotProductsError extends HotProductState {
String message;
HotProductsError({#required this.message});
#override
// TODO: implement props
List<Object> get props => [message];
}
and here is how i implement the bloc
BlocListener <HotProductBloc, HotProductState>(
listener: (context, state) {
if (state is HotProductsError) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
),
);
}
},
child: Container(
child: BlocBuilder<HotProductBloc, HotProductState>(
builder: (context, state) {
print("BLoc State "+ state.toString());
if (state is InitialHotProductsState) {
return Text("Siaaap");
} else if (state is HotProductsLoading) {
return Text("Loading");
} else if (state is HotProductsLoaded) {
return Text("DONE");
} else if (state is HotProductsError) {
return Text("ERROR");
}else{
return Text("Unknown error");
}
},
),
),
),
when i run my script above, i get this on my Log
I/flutter ( 390): BLoc State InitialHotProductsState I/flutter (
390): BLoc State InitialHotProductsState
You need to fire an event to start any change of state. Try adding this to somewhere in your build() method:
BlocProvider.of<HotProductBloc>(context).add(FetchHotProductEvent());

BlocProvider.of() called with a context that does not contain a Bloc of type CLASS

in flutter i just learn how can i use Bloc on applications and i want to try to implementing simple login with this feature. after implementing some class of bloc to using that on view
i get error when i try to use this code as
BlocProvider.of<LoginListingBloc>(context).dispatch(LoginEvent(loginInfoModel: testLogin));
inside RaisedButton
Error:
BlocProvider.of() called with a context that does not contain a Bloc
of type LoginListingBloc.
My view :
class _HomePageState extends State<HomePage> {
LoginListingBloc _loginListingBloc;
#override
void initState() {
super.initState();
_loginListingBloc =
LoginListingBloc(loginRepository: widget.loginRepository);
}
...
#override
Widget build(BuildContext context) {
return BlocProvider(
bloc: _loginListingBloc,
child: Scaffold(
appBar: AppBar(
elevation: 5.0, title: Text('Sample Code', style: appBarTextStyle)),
body: Center(
child: RaisedButton(
child: Text(
'click here',
style: defaultButtonStyle,
),
onPressed: () {
BlocProvider.of<LoginListingBloc>(context).dispatch(LoginEvent(loginInfoModel: testLogin));
}),
),
),
);
}
}
LoginListingBloc class:
class LoginListingBloc extends Bloc<LoginListingEvent, LoginListingStates> {
final LoginRepository loginRepository;
LoginListingBloc({this.loginRepository});
#override
LoginListingStates get initialState => LoginUninitializedState();
#override
Stream<LoginListingStates> mapEventToState(
LoginListingStates currentState, LoginListingEvent event) async* {
if (event is LoginEvent) {
yield LoginFetchingState();
try {
final loginInfo = await loginRepository.fetchLoginToPage(
event.loginInfoModel.username, event.loginInfoModel.password);
yield LoginFetchedState(userInfo: loginInfo);
} catch (_) {
yield LoginErrorState();
}
}
}
}
and other classes if you want to see theme
AppApiProvider class:
class AppApiProvider {
final successCode = 200;
Future<UserInfo> fetchLoginToPage(String username, String password) async {
final response = await http.get(Constants.url + "/api/v1/getPersons");
final responseString = jsonDecode(response.body);
if (response.statusCode == successCode) {
print(responseString);
return UserInfo.fromJson(responseString);
} else {
throw Exception('failed to get information');
}
}
}
LoginEvent:
class LoginEvent extends LoginListingEvent {
final LoginInfoModel loginInfoModel;
LoginEvent({#required this.loginInfoModel}) : assert(loginInfoModel != null);
}
LoginInfoModel:
class LoginInfoModel {
String username;
String password;
LoginInfoModel({this.username, this.password});
}
final testLogin = LoginInfoModel(username:'exmaple',password:'text');
No need to access loginListingBloc from context since it exists in the current class and not up the widget tree.
change:
BlocProvider.of<LoginListingBloc>(context).dispatch(LoginEvent(loginInfoModel: testLogin));
to:
_loginListingBloc.dispatch(LoginEvent(loginInfoModel: testLogin));
For all others who come here for the error message:
Make sure you always specify the types and don't omit them:
BlocProvider<YourBloc>(
create: (context) => YourBloc()
child: YourWidget()
);
and also for
BlocProvider.of<YourBloc>(context);