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());
Related
For filter my list I use a state FilterState.
I have in this state my list filter but my widget for build list is not rebuild.
My print shows that the list is correct according to the state.
But GameList keeps its initial state which is not filtered : it's not rebuild.
Thanks,
my page for state :
BlocBuilder<GameBloc, GameState>(
builder: (context, state) {
if (state is LoadingState) {
return buildLoading();
} else if (state is FailState) {
return ErrorUI(message: state.message);
} else if (state is ListLoadedState) {
_list = state.list;
return GameList(list: state.list!);
} else if (state is FilterGamesState) {
print(state.list);
return GameList(list: state.list!);
}
return Container();
},
),
Bloc Page :
class GameBloc extends Bloc<GameEvent, GameState> {
GameBloc({required this.repository}) : super(LoadingState()) {
on<BackEvent>(_onBackEvent);
on<FetchGamesEvent>(_onFetchList);
on<FetchGameEvent>(_onFetchItem);
on<SavingEvent>(_onSavingEvent);
on<UpdateGameEvent>(_onUpdate);
on<CreateGameEvent>(_onCreate);
on<FilterGamesEvent>(_onFilter);
}
GameRepository repository;
final currentFilter = BehaviorSubject<Map<String, dynamic>>();
Future<void> _onFilter(
FilterGamesEvent event,
Emitter<GameState> emit,
) async {
try {
emit(LoadingState());
final list = event.list?.where((Game item) {
if (currentFilter.value.containsKey('player')) {
int players = currentFilter.value['player'].nb;
return players.isBetween(from: item.nopMin, to: item.nopMax);
}
return true;
}).where((Game item) {
if (currentFilter.value.containsKey('age')) {
return item.age!.isBetween(
from: currentFilter.value['age'].min,
to: currentFilter.value['age'].max);
}
return true;
}).where((Game item) {
if (currentFilter.value.containsKey('duration')) {
return compareToDuration(
item.durMin!,
item.durMax!,
currentFilter.value['duration'].min,
currentFilter.value['duration'].max);
}
return true;
}).where((Game item) {
if (currentFilter.value.containsKey('tags')) {
return item.tags!
.compareToList(currentFilter.value['tags'] as List<String>);
}
return true;
}).where((Game item) {
if (currentFilter.value.containsKey('collection')) {
return item.collection!
.compareToList(currentFilter.value['collection'] as List<String>);
}
return true;
}).toList();
emit(FilterGamesState(listGame: list!));
} catch (e) {
emit(const FailState(message: 'Failed to fetch all games data.'));
}
}
Event Page :
abstract class GameEvent extends Equatable {
final Game? item;
const GameEvent({this.item});
#override
List<Object> get props => [];
}
class InitialEvent extends GameEvent {
const InitialEvent({required Game item}) : super(item: item);
}
class BackEvent extends GameEvent {}
class SavingEvent extends GameEvent {}
class FetchGameEvent extends GameEvent {
const FetchGameEvent({required Game item}) : super(item: item);
}
class FetchGamesEvent extends GameEvent {}
class FilterGamesEvent extends GameEvent {
const FilterGamesEvent({required this.list});
final List<Game>? list;
}
State Page :
abstract class GameState extends Equatable {
final Game? item;
final List<Game>? list;
const GameState({this.item, this.list});
#override
List<Object> get props => [];
}
class GameInitial extends GameState {}
class FailState extends GameState {
const FailState({required this.message});
final String message;
}
class LoadingState extends GameState {}
class ListLoadedState extends GameState {
const ListLoadedState({required this.listGame}) : super(list: listGame);
final List<Game> listGame;
}
class ItemLoadedState extends GameState {
const ItemLoadedState({required this.game}) : super(item: game);
final Game game;
}
class FilterGamesState extends GameState {
const FilterGamesState({required this.listGame}) : super(list: listGame);
final List<Game> listGame;
}
I resolved this,
I send the key to GameList in FilterGameState.
else if (state is FilterGamesState) {
print(state.list);
return GameList(key: GlobalKey<GameFilterState>(), list: state.list!);
}
You are using Equatable but your props are empty. If you're using Equatable make sure to pass all properties to the props getter. (In both your state and event!)
Source: https://bloclibrary.dev/#/faqs?id=state-not-updating
The Flutter Todos Tutorial might also be helpful because it uses a filter too.
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.
Recently I am learning flutter_bloc, and I refer to the project flutter_weather.
What I am puzzled is that if a Bloc class has many Events, and most of the Events will have values returned by State, and there are many BlocBuilders in the project, what should I do if I want a BlocBuilder to only respond to a certain Event?
The method I can think of is to divide this Bloc into multiple Blocs, or treat each value to be returned as an attribute of Bloc, BlocBuilder uses the buildwhen method to determine whether to rebuild.
But both of these methods are not good for me. Is there any good method? It is best to have projects on github for reference.
For example:
This is Event:
abstract class WeatherEvent extends Equatable {
const WeatherEvent();
}
class WeatherRequested extends WeatherEvent {
final String city;
const WeatherRequested({#required this.city}) : assert(city != null);
#override
List<Object> get props => [city];
}
class WeatherRefreshRequested extends WeatherEvent {
final String city;
const WeatherRefreshRequested({#required this.city}) : assert(city != null);
#override
List<Object> get props => [city];
}
This is State:
abstract class WeatherState extends Equatable {
const WeatherState();
#override
List<Object> get props => [];
}
class WeatherInitial extends WeatherState {}
class WeatherLoadInProgress extends WeatherState {}
class WeatherLoadSuccess extends WeatherState {
final Weather weather;
const WeatherLoadSuccess({#required this.weather}) : assert(weather != null);
#override
List<Object> get props => [weather];
}
class WeatherLoadFailure extends WeatherState {}
This is Bloc:
class WeatherBloc extends Bloc<WeatherEvent, WeatherState> {
final WeatherRepository weatherRepository;
WeatherBloc({#required this.weatherRepository})
: assert(weatherRepository != null),
super(WeatherInitial());
#override
Stream<WeatherState> mapEventToState(WeatherEvent event) async* {
if (event is WeatherRequested) {
yield* _mapWeatherRequestedToState(event);
} else if (event is WeatherRefreshRequested) {
yield* _mapWeatherRefreshRequestedToState(event);
}
}
Stream<WeatherState> _mapWeatherRequestedToState(
WeatherRequested event,
) async* {
yield WeatherLoadInProgress();
try {
final Weather weather = await weatherRepository.getWeather(event.city);
yield WeatherLoadSuccess(weather: weather);
} catch (_) {
yield WeatherLoadFailure();
}
}
Stream<WeatherState> _mapWeatherRefreshRequestedToState(
WeatherRefreshRequested event,
) async* {
try {
final Weather weather = await weatherRepository.getWeather(event.city);
yield WeatherLoadSuccess(weather: weather);
} catch (_) {}
}
}
This is BlocConsumer:
// BlocBuilder1
BlocBuilder<WeatherBloc, WeatherState>(
builder: (context, state) {
if (state is WeatherLoadInProgress) {
return Center(child: CircularProgressIndicator());
}
if (state is WeatherLoadSuccess) {
final weather = state.weather;
return Center(child: Text("WeatherRequested "))
}
)
// BlocBuilder2
BlocBuilder<WeatherBloc, WeatherState>(
builder: (context, state) {
if (state is WeatherLoadInProgress) {
return Center(child: CircularProgressIndicator());
}
if (state is WeatherLoadSuccess) {
final weather = state.weather;
return Center(child: Text("WeatherRefreshRequested"))
}
)
The problem is that I want BlocBuilder1 only to work when the type of Event is WeatherRequested and BlocBuilder2 only works when the type of Event is WeatherRefreshRequested. One of my ideas is that each Event has its own State, and then judge the type of State in buildwhen.
Is there any good method?
if you want to build you widget to respond for certain states you should use
BlocConsumer and tell that bloc in buildWhen to tell it what state it should build/rebuild you widget on.
BlocConsumer<QuizBloc, QuizState>(
buildWhen: (previous, current) {
if (current is QuizPoints)
return true;
else
return false;
},
listener: (context, state) {},
builder: (context, state) {
if (state is QuizPoints)
return Container(
child: Center(
child: Countup(
begin: 0,
end: state.points,
duration: Duration(seconds: 2),
separator: ',',
),
),
);
else
return Container();
},
);
I have a widget that utilizes CourseBloc
class CourseBloc extends Bloc<CourseEvent, CourseState> {
final GetCoursesQuery getCoursesQuery;
final GetSettingsQuery getSettingsQuery;
final PullCoursesFromServerCommand pullCoursesFromServerCommand;
final UpdateTasksToServerCommand updateTasksToServerCommand;
final GetNetworkInfoQuery getNetworkInfoQuery;
CourseBloc({
#required GetCoursesQuery getCoursesQuery,
#required GetSettingsQuery getSettingsQuery,
#required PullCoursesFromServerCommand pullCoursesFromServerCommand,
#required UpdateTasksToServerCommand updateTasksToServerCommand,
#required GetNetworkInfoQuery getNetworkInfoQuery,
}) : assert(getCoursesQuery != null),
assert(getSettingsQuery != null),
assert(pullCoursesFromServerCommand != null),
assert(updateTasksToServerCommand != null),
assert(getNetworkInfoQuery != null),
this.getCoursesQuery = getCoursesQuery,
this.getSettingsQuery = getSettingsQuery,
this.pullCoursesFromServerCommand = pullCoursesFromServerCommand,
this.updateTasksToServerCommand = updateTasksToServerCommand,
this.getNetworkInfoQuery = getNetworkInfoQuery;
#override
CourseState get initialState => CourseInitialState();
#override
Stream<CourseState> mapEventToState(
CourseEvent event,
) async* {
if (event is CoursesReturnedFromTasksEvent) {
yield CourseInitialState();
} else if (event is CoursesPageLoadedEvent) {
yield CourseLoadingState();
final getCoursesEither = await getCoursesQuery(
GetCoursesParams(
truckNumber: event.truckNumber,
),
);
yield* getCoursesEither.fold((failure) async* {
yield CourseFetchedStateFailureState(error: "coursesDatabaseError");
}, (result) async* {
if (result != null) {
final getSettingsEither = await getSettingsQuery(NoQueryParams());
yield* getSettingsEither.fold((failure) async* {
yield CourseFetchedStateFailureState(error: "coursesDatabaseError");
}, (settingsResult) async* {
if (result != null) {
final networkInfoEither =
await this.getNetworkInfoQuery(NoQueryParams());
yield* networkInfoEither.fold((failure) async* {
yield CourseErrorState();
}, (success) async* {
yield CourseFetchedState(
settings: settingsResult,
courses: result,
isThereInternet: success,
);
});
} else {
yield CourseFetchedStateFailureState(
error: "coursesFetchFromDatabaseError");
}
});
} else {
yield CourseFetchedStateFailureState(
error: "coursesFetchFromDatabaseError");
}
});
} else if (event is CoursesRefreshButtonPressedEvent) {
yield CourseLoadingState();
final networkInfoEither = await this.getNetworkInfoQuery(NoQueryParams());
yield* networkInfoEither.fold((failure) async* {
yield CourseErrorState();
}, (success) async* {
if (success) {
final updateTasksToServerEither = await updateTasksToServerCommand(
UpdateTasksParams(truckNumber: event.truckNumber));
yield* updateTasksToServerEither.fold((failure) async* {
yield CourseFetchedStateFailureState(error: "coursesDatabaseError");
}, (result) async* {
final pullCoursesFromServerEither =
await pullCoursesFromServerCommand(
PullCoursesParams(truckNumber: event.truckNumber));
yield* pullCoursesFromServerEither.fold((failure) async* {
yield CourseFetchedStateFailureState(
error: "coursesDatabaseError");
}, (result) async* {
if (result != null) {
yield CoursePulledFromServerState();
} else {
yield CourseFetchedStateFailureState(
error: "coursesFetchFromDatabaseError");
}
});
});
} else {
yield CourseNoInternetState();
}
});
} else if (event is CoursesRefreshFromTasksButtonPressedEvent) {
serviceLocator<TaskBloc>().add(
TasksLoadingEvent(),
);
final networkInfoEither = await this.getNetworkInfoQuery(NoQueryParams());
yield* networkInfoEither.fold((failure) async* {
serviceLocator<TaskBloc>().add(
TasksReloadingErrorEvent(),
);
}, (success) async* {
if (success) {
final updateTasksToServerEither = await updateTasksToServerCommand(
UpdateTasksParams(truckNumber: event.truckNumber));
yield* updateTasksToServerEither.fold((failure) async* {
yield CourseFetchedStateFailureState(error: "coursesDatabaseError");
}, (result) async* {
final pullCoursesFromServerEither =
await pullCoursesFromServerCommand(
PullCoursesParams(truckNumber: event.truckNumber));
yield* pullCoursesFromServerEither.fold((failure) async* {
serviceLocator<TaskBloc>().add(
TasksFetchedFailureEvent(failure: "coursesDatabaseError"),
);
}, (result) async* {
if (result != null) {
serviceLocator<TaskBloc>().add(
TasksPulledFromServerEvent(
truckNumber: event.truckNumber,
courseNumber: event.courseNumber,
courseId: event.courseId,
),
);
} else {
serviceLocator<TaskBloc>().add(
TasksFetchedFailureEvent(
failure: "coursesFetchFromDatabaseError"),
);
}
});
});
} else {
yield CourseNoInternetState();
}
});
}
}
}
I use BlocBuilder and BlocListener in the widget page as follows:
BlocBuilder<CourseBloc, CourseState>(
builder: (context, state) {
if (state is CourseFetchedState) {
// here I have logic if the course is fetched
}
...
),
BlocListener<CourseBloc, CourseState>(
listener: (context, state) {
if (state is CourseNoInternetState) {
...
}
...
),
At some point I am navigating away of this widget. Then I want to come back to this first (Course) widget. I do:
serviceLocator<CourseBloc>().add(
CoursesReturnedFromTasksEvent(),
);
serviceLocator.resetLazySingleton<TaskBloc>(
instance: serviceLocator<TaskBloc>(),
);
Navigator.of(context).pop(true);
This tells the CourseBloc to expect a CoursesReturnedFromTasksEvent() , resets the new TaskBloc (because I am not on this page anymore and I don't need to know what state is it at) and pops the current context.
Then I am navigated back. The CourseBloc's mapping method works with the new state and according to the 'if' it yields:
if (event is CoursesReturnedFromTasksEvent) {
yield CourseInitialState();
}
But the Builder in the Course page is at the previous state. CourseFetchedState. And it has not 'taken' the new state (Initial).
Any ideas why that might happen?
Here are the States:
abstract class CourseState {
CourseState();
}
class CourseInitialState extends CourseState {}
class CourseReturnedFromTasksState extends CourseState {}
class CourseLoadingState extends CourseState {}
class CourseErrorState extends CourseState {}
class CourseNoInternetState extends CourseState {}
class CoursePulledFromServerState extends CourseState {}
class CourseFetchedState extends CourseState {
final SettingsAggregate settings;
final List<CourseAggregate> courses;
final bool isThereInternet;
CourseFetchedState({
#required this.settings,
#required this.courses,
#required this.isThereInternet,
});
}
class CourseFetchedStateFailureState extends CourseState {
final String error;
CourseFetchedStateFailureState({#required this.error});
}
And Events:
abstract class CourseEvent {
CourseEvent();
}
class CoursesRefreshButtonPressedEvent extends CourseEvent {
final String truckNumber;
CoursesRefreshButtonPressedEvent({#required this.truckNumber});
}
class CoursesRefreshFromTasksButtonPressedEvent extends CourseEvent {
final String courseNumber;
final String truckNumber;
final int courseId;
CoursesRefreshFromTasksButtonPressedEvent({
#required this.courseNumber,
#required this.truckNumber,
#required this.courseId,
});
}
class CoursesReturnedFromTasksEvent extends CourseEvent {}
class CoursesPageLoadedEvent extends CourseEvent {
final String truckNumber;
CoursesPageLoadedEvent({
this.truckNumber,
});
}
EDIT
Here is how the Bloc is provided:
Column buildBody(BuildContext context) {
return Column(
children: <Widget>[
BlocProvider(
create: (_) => serviceLocator<CourseBloc>(),
child: BlocBuilder<CourseBloc, CourseState>(
builder: (context, state) {
if (state is CourseFetchedState) {
...
}
...
}
),
),
],
);
}
The serviceLocator() is instantiated only once, at app startup. It registers all instances needed throughout the app life in order to achieve dependency injection. Here is the CourseBloc registration:
import 'package:get_it/get_it.dart';
final serviceLocator = GetIt.instance;
...
serviceLocator.registerLazySingleton(
() => CourseBloc(
getCoursesQuery: serviceLocator(),
getSettingsQuery: serviceLocator(),
pullCoursesFromServerCommand: serviceLocator(),
updateTasksToServerCommand: serviceLocator(),
getNetworkInfoQuery: serviceLocator(),
),
);
...
I am not really familiar with GetIt, but from what I can tell:
You make a singleton of CourseBloc with serviceLocator.
You use BlocProvider to provide that singleton to the relevant context (Resulting in 2 instances of CourseBloc).
Your BlocBuilder uses the instance provided via BlocProvider because you don't explicitly tell via bloc parameter (It will look up the widget tree to find CourseBloc within the same context).
You use the instance of CourseBloc made by serviceLocator to add CoursesReturnedFromTasksEvent().
Try adding event the usual way via BlocProvider.of, but you still need manually dispose the CourseBloc singleton made by serviceLocator
BlocProvider.of<CourseBloc>(context).add(
CoursesReturnedFromTasksEvent(),
);
I am using flutter_bloc in my application and referring to this sample. Now when I am running my application it gives me an error saying:
flutter: type 'MovieUninitialized' is not a subtype of type
'MovieLoaded' in type cast
My bloc contains three files: movie_state, movie_event, and movie_bloc
movie_state.dart
import 'package:equatable/equatable.dart';
import 'package:movie_project/models/movie.dart';
abstract class MovieState extends Equatable {
MovieState([List props = const []]) : super(props);
}
class MovieUninitialized extends MovieState {
#override
String toString() => 'MovieUninitialized';
}
class MovieError extends MovieState {
#override
String toString() => 'MovieError';
}
class MovieLoaded extends MovieState {
final List<Movie> movies;
final bool hasReachedMax;
// Keeps track of the page to fetch the latest movies from the api
final latestMoviesPage;
MovieLoaded({
this.movies,
this.hasReachedMax,
this.latestMoviesPage,
}) : super([movies, hasReachedMax, latestMoviesPage]);
MovieLoaded copyWith({
List<Movie> movies,
bool hasReachedMax,
int latestMoviesPage,
}) {
return MovieLoaded(
movies: movies ?? this.movies,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
latestMoviesPage: this.latestMoviesPage,
);
}
#override
String toString() =>
'PostLoaded { posts: ${movies.length}, hasReachedMax: $hasReachedMax }';
}
movie_event.dart
import 'package:equatable/equatable.dart';
abstract class MovieEvent extends Equatable {}
class Fetch extends MovieEvent {
#override
String toString() => 'Fetch';
}
movie_bloc.dart
class MovieBloc extends Bloc<MovieEvent, MovieState> {
final MovieRepository movieRepository;
MovieBloc({#required this.movieRepository});
#override
Stream<MovieState> transform(
Stream<MovieEvent> events,
Stream<MovieState> Function(MovieEvent event) next,
) {
return super.transform(
(events as Observable<MovieEvent>).debounceTime(
Duration(milliseconds: 500),
),
next,
);
}
#override
MovieState get initialState => MovieUninitialized();
#override
Stream<MovieState> mapEventToState(MovieEvent event) async* {
if (event is Fetch && !_hasReachedMax(currentState)) {
try {
if (currentState is MovieUninitialized) {
final movies = await movieRepository.fetchMovies(1);
yield MovieLoaded(
movies: movies,
hasReachedMax: false,
latestMoviesPage:
(currentState as MovieLoaded).latestMoviesPage + 1,
);
return;
}
if (currentState is MovieLoaded) {
final movies = await movieRepository
.fetchMovies((currentState as MovieLoaded).latestMoviesPage);
yield movies.isEmpty
? (currentState as MovieLoaded).copyWith(hasReachedMax: true)
: MovieLoaded(
movies: (currentState as MovieLoaded).movies + movies,
hasReachedMax: false,
latestMoviesPage:
(currentState as MovieLoaded).latestMoviesPage + 1,
);
}
} catch (_) {
print(_);
yield MovieError();
}
}
}
bool _hasReachedMax(MovieState state) =>
state is MovieLoaded && state.hasReachedMax;
}
I need to increment the latestMoviesPage until it reaches the max limit. If I remove latestMoviesPage from my bloc code issue gets resolves but I really need it to load more pages.
Instead of
latestMoviesPage: (currentState as MovieLoaded).latestMoviesPage + 1,
I need to write:
latestMoviesPage: 2,