flutter_login and flutter_bloc navigation after authentication: BlocListener not listening to state change - flutter

I am trying to combine this with bloc, using this design pattern from the docs.
After the state has been instantiated, BlocListener stops listening to the authentication bloc and I am kind of forced to use the login form's onSubmitAnimationCompleted method for routing, which makes the listener useless in the first place.
MaterialApp() is identical to the example provided in the docs (I am trying to navigate from the login screen, which is the initialRoute in this case, to the home screen)
the login form looks like this:
#override
Widget build(BuildContext context) {
return BlocListener<AuthenticationBloc, AuthenticationState> (
listener: (context, state) {
// first time around state is read
if (state is AuthenticationAuthenticated) {
Navigator.of(context).pushNamed(Home.routeName);
}
},
child: BlocBuilder(
bloc: _loginBloc,
builder: (BuildContext context, state) {
return FlutterLogin(
title: 'Login',
logo: const AssetImage('lib/assets/madrid.png'),
onLogin: _authUser,
onSignup: _signupUser,
onRecoverPassword: _recoverPassword,
loginProviders: <LoginProvider>[
... Providers here...
],
// if this method is omitted, I'll get a [ERROR:flutter/lib/ui/ui_dart_state.cc(209)]
onSubmitAnimationCompleted: () {
Navigator.of(context).pushNamed(Home.routeName);
},
);
},
),
);
}
I am splitting events an state between two blocs, 'AuthenticationBloc' (wraps entire app, if a token has been stored then the state will be 'AuthenticationAuthenticated') and 'LoginBloc' (used for login/logout events)
#1 when I click on the sign up button, the associated method will call _loginBloc?.add(SignUpButtonPressed(email: email, password: password))
#2 fast forward to the bloc:
LoginBloc({required this.authenticationBloc, required this.loginRepository})
: super(const SignInInitial()) {
on<SignUpButtonPressed>(_signUp);
}
...
FutureOr<void> _signUp<LoginEvent>(SignUpButtonPressed event, Emitter<LoginState> emit) async {
emit(const SignInLoading());
try {
final credentials = User(email: event.email, password: event.password);
final success = await loginRepository.signUp(credentials);
if (success) {
final token = await loginRepository.signIn(credentials);
authenticationBloc.add(LoggedIn(email: event.email, token: token));
} else {
emit(const SignInFailure(error: 'Something went wrong'));
}
} on Exception {
emit(const SignInFailure(error: 'A network Exception was thrown'));
} catch (error) {
emit(SignInFailure(error: error.toString()));
}
}
this is successful, and it triggers the authentication bloc:
AuthenticationBloc({required this.userRepository})
: super(const AuthenticationUninitialized()) {
on<LoggedIn>(_loggedIn);
}
...
FutureOr<void> _loggedIn<AuthenticationEvent>(LoggedIn event, Emitter<AuthenticationState> emit) async {
await userRepository?.persistEmailAndToken(
event.email, event.token);
await _initStartup(emit);
}
...
Future<void> _initStartup(Emitter<AuthenticationState> emit) async {
final hasToken = await userRepository?.hasToken();
if (hasToken != null && hasToken == true) {
emit(const AuthenticationAuthenticated());
return;
} else {
emit(const AuthenticationUnauthenticated());
}
}
... and at the end of this, the state is updated to AuthenticationAuthenticated, which is the expected behaviour, and the observer logs the transition as expected.
Now, this state change should trigger the navigation from within the BlocListener, but nope.
I would like to get rid of the Navigator inside the onSubmitAnimationCompleted, and rely on the state change.
I reckon this might be caused by Equatable, as my state extends that:
abstract class AuthenticationState extends Equatable {
const AuthenticationState();
#override
List<Object> get props => [];
}
class AuthenticationAuthenticated extends AuthenticationState {
const AuthenticationAuthenticated();
}
However, I've tried for hours, but I can't find anything in the docs, github, or SO that works.

So, I have not been able to get rid of the Navigator inside of onSubmitAnimationCompleted (I guess the BlocListener is disposed when the form is submitted, and before the animation is completed), but in the process I've managed to make my state management clean and robust, so I'll leave a little cheatsheet below, feel free to comment or give your opinion:
Assuming your widget's build method looks something like this:
#override
Widget build(BuildContext context) {
return BlocListener<AuthenticationBloc, AuthenticationState> (
bloc: _authenticationBloc,
listener: (context, state) {
if (state.status == AuthenticationAppState.authenticated) {
Navigator.of(context).pushNamed(Home.routeName);
}
},
child: BlocBuilder(
bloc: _loginBloc,
builder: (BuildContext context, state) {
return FlutterLogin(
...
and that your events extend Equatable
import 'package:equatable/equatable.dart';
abstract class AuthenticationEvent extends Equatable {
const AuthenticationEvent();
#override
List<Object> get props => [];
}
class LoggedIn extends AuthenticationEvent {
final String email;
final dynamic token;
const LoggedIn({ required this.email, this.token });
#override
List<Object> get props => [email, token];
}
your Bloc will look like:
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
final SecureStorage? userRepository;
AuthenticationBloc({required this.userRepository})
: super(const AuthenticationState.uninitialized()) {
on<LoggedIn>(_loggedIn);
on<LoggedOut>(_loggedOut);
on<UserDeleted>(_userDeleted);
}
...
FutureOr<void> _loggedOut<AuthenticationEvent>(LoggedOut event, Emitter<AuthenticationState> emit) async {
emit(const AuthenticationState.loggingOut());
await userRepository?.deleteToken();
// API calls here
// event has access the event's properties e.g. event.email etc
}
the state has been refactored to:
import 'package:equatable/equatable.dart';
enum AuthenticationAppState {
uninitialized,
unauthenticated,
authenticated,
loggingOut,
loading,
}
class AuthenticationState extends Equatable {
const AuthenticationState._({
required this.status,
});
const AuthenticationState.uninitialized() : this._(status: AuthenticationAppState.uninitialized);
const AuthenticationState.unauthenticated() : this._(status: AuthenticationAppState.unauthenticated);
const AuthenticationState.authenticated() : this._(status: AuthenticationAppState.authenticated);
const AuthenticationState.loggingOut() : this._(status: AuthenticationAppState.loggingOut);
const AuthenticationState.loading() : this._(status: AuthenticationAppState.loading);
final AuthenticationAppState status;
#override
List<Object> get props => [status];
}

Related

Why emit.forEach(..) doesn't persist the state of the bloc in this case?

Simplified example using a Todo App approach where data is submitted from different pages and bloc reacts to it subscribing to a stream.
View, I have four pages.
Page A: Shows a ListView of Todos.
Page B-1: Shows a Form to update the overview data related to a Todo.
Page B-2: Shows a ListView of Actions that has a Todo.
Page C: Shows a Form to update the data related to an Action.
Logic, I have four blocs. CollectionBloc which subscribes to a stream of data using Hive and it is supposed to emit states every time there is an update in the repository. Also, EditTodoBloc and ActionBloc which submit data to the same repository. TodoBloc is for managing a Todo in general.
EditTodoBloc
----------> Page B-1
Page A ----------> | ActionBloc
CollectionBloc ----------> Page B-2 ----------> Page C
TodoBloc
Models
#HiveType(typeId: 0)
class Action extends Equatable {
Action({this.id, this.name});
#HiveField(0)
String id;
#HiveField(1)
String name;
#override
List<Object?> get props => [id, name];
}
#HiveType(typeId: 1)
class Todo extends Equatable {
Todo({this.id, this.actions});
#HiveField(0)
String id;
#HiveField(1)
String name;
#HiveField(2)
List<Action> actions;
...
#override
List<Object?> get props => [id, name, actions];
}
Database / Repository
class HiveDatabase {
late Box<List<Todo>> todos;
...
Stream<List<Todo>> watchTodos() {
return todos
.watch()
.map((event) => todos.values.toList())
.startWith(todos.values.toList());
}
Future<void> saveTodo(Todo todo) async {
await todos.put(todo.id, todo);
}
Future<void> saveAction(Todo todo, Action action) async {
todo.actions.add(action);
await todos.put(todo.id, todo);
}
}
Blocs
EditTodoBloc:
class EditTodoBloc extends Bloc<EditTodoEvent, EditTodoState> {
EditTodoBloc({
required TodosRepository todosRepository,
required Todo? todo,
}) : _todosRepository = todosRepository, super(EditTodoState(todo)) {
on<TodoSubmitted>(_onTodoSubmitted);
}
...
Future<void> _onTodoSubmitted(
TodoSubmitted event,
Emitter<EditTodoState> emit,
) async {
emit(state.copyWith(status: EditTodoStaus.loading));
try {
await _todosRepository.saveTodo(state.todo!);
emit(state.copyWith(status: EditTodoStaus.success));
} catch (e) {}
}
}
class EditTodoState extends Equatable {
final EditTodoStatus status;
final Todo? todo;
...
}
TodoBloc:
class TodoBloc extends Bloc<TodoEvent, TodoState> {
TodoBloc({
required TodosRepository todosRepository,
required Todo todo,
}) : _todosRepository = todosRepository, super(TodoState(todo)) {
...
}
}
class TodoState extends Equatable {
final TodoStatus status;
final Todo todo,
final List<Action> actions;
...
}
Action Bloc:
class ActionBloc extends Bloc<ActionEvent, ActionState> {
ActionBloc({
required TodosRepository todosRepository,
required Todo todo,
required Action? action,
}) : _todosRepository = todosRepository, super(ActionState(todo, action)) {
on<ActionSubmitted>(_onActionSubmitted);
}
...
Future<void> _onActionSubmitted(
ActionSubmitted event,
Emitter<ActionState> emit,
) async {
emit(state.copyWith(status: ActionStatus.loading));
try {
await _todosRepository.saveAction(todo, state.action!);
emit(state.copyWith(status: ActionStatus.success));
} catch(e) {}
}
}
class ActionState extends Equatable {
final ActionStatus status;
final Todo todo,
final Action? action;
...
}
And the problem is here.
CollectionBloc can't persist the state of the bloc when I submit data by adding an event from Page C (ActionBloc). Unlike when sending data from Page B-1 (EditTodoBloc) which works successfully.
CollectionBloc:
class CollectionBloc extends Bloc<CollectionEvent, CollectionState> {
CollectionBloc({
required TodosRepository todosRepository,
}) : super(CollectionState()) {
on<CollectionRequested>(_onCollectionRequested);
}
...
Future<void> _onCollectionRequested(
CollectionRequested event,
Emitter<CollectionState> emit,
) async {
emit(state.copyWith(status: TodoStatus.loading));
await emit.forEach<List<Todo>>(
_todosRepository.watchTodos(),
onData: (todos) {
print('newTodos: ${todos}');
print('oldTodos: ${state.todos}');
// Why oldTodos shows the same modified todo list (with its actions)
// as the one returned from onData
// Page-B2 does not update coming back from Page-C unless I pop up to
// Page-A and then push to Page-B2.
return state.copyWith(status: CollectionStatus.success, todos: todos);
},
onError: (_, __) => state.copyWith(status: CollectionStatus.failure),
);
}
}
class CollectionState extends Equatable {
final CollectionStatus status;
final List<Todo> todos;
...
}
Page A:
class PageA extends StatelessWidget {
const PageA({Key? key}) : super(key: key);
...
#override
Widget build(BuildContext context) {
final todos = context.watch<CollectionBloc>().state.todos;
return ListView(
children: [
for (final todo in todos) ...[
ListTile(
title: Text(todo.name),
onTap: () {
Navigator.of(context).push(
PageB2.route(todo),
);
},
),
],
],
);
}
}
All in all, I would like to be able to show the list of actions updated when popping back from submitting the form in Page C to Page B-2.
class PageB2 extends StatelessWidget {
const PageB2({Key? key}) : super(key: key);
static Route<void> route(Todo todo) {
return MaterialPageRoute(
builder: (context) => BlocProvider(
create: (context) => TodoBloc(
todoRepository: context.read<TodosRepository>(),
todo: todo,
),
child: const PageB2(),
),
);
}
#override
Widget build(BuildContext context) {
final todos = context.watch<CollectionBloc>().state.todos;
final todo = todos.firstWhere((element) => element.id == state.todo.id);
return BlocBuilder<TodoBloc, TodoState>(
builder: (context, state) {
return ListView.separated(
itemCount: todo.actions.length,
separatorBuilder: (context, index) => const Divider(height: 8),
itemBuilder: (context, index) {
return ListTile(
title: Text(todo.actions[index].name),
onTap: () {
Navigator.of(context).push(
PageC.route(todo, todo.actions[index]),
);
},
);
},
);
},
);
}
}
I think I don't know how to apply emit.forEach() in this case. XD

Why can't I access the User's uid from the bloc's state in this example?

After successfully signing in to Firestore using the flutterfire_ui pacakge, the user is taken to HomeScreen where initState adds a GetUser event, which eventually causes the UserBloc to yield a state object called UserLoaded with a property called activeUser, which should contain a User object with a uid property. However, when I try to access state.activeUser.uid from inside the Blockbuilder, it throws the following error:
The getter 'uid' isn't defined for the class 'Stream<User?>'.
lib/screens/home_page.dart:38
'Stream' is from 'dart:async'.
'User' is from 'package:firebase_practice/models/user.dart' ('lib/models/user.dart').
Try correcting the name to the name of an existing getter, or defining a getter or field named 'uid'.
'HomeScreen state is: ${state.activeUser?.uid}',
Is this because I'm using both flutterfire_ui and FirebaseAuth? Any help would be greatly appreciated.
User Model
class User {
final uid;
final userName;
final email;
User({required this.uid, this.userName, this.email});
}
AuthService:
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:firebase_practice/models/user.dart';
class AuthService {
final auth.FirebaseAuth _firebaseAuth;
AuthService({auth.FirebaseAuth? firebaseAuth})
: _firebaseAuth = firebaseAuth ?? auth.FirebaseAuth.instance;
//create a dart User from Firebase user
User? _userFromFirebaseAuth(auth.User? user) {
return User(uid: user!.uid, email: user!.email);
}
Stream<User?>? get user {
return _firebaseAuth.authStateChanges().map(_userFromFirebaseAuth);
}
UserBloc:
class UserBloc extends Bloc<UserEvent, UserState> {
final AuthService _authService;
UserBloc( this._authService) : super(UserInitial()) {
on<GetUser>(_getUser);
}
FutureOr<void> _getUser(GetUser event, Emitter<UserState> emit) async {
Stream<User?>? user = await _authService.user;
if(user != null){
emit(UserLoaded(activeUser: user));
}
}
}
UserState:
class UserLoaded extends UserState {
Stream<User?> activeUser;
UserLoaded({required this.activeUser});
#override
List<Object> get props => [activeUser];
}
HomeScreen:
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if (state is UserLoaded) {
return Scaffold(
body: Center(
child: Text(
'HomeScreen with state is: ${state.activeUser.uid}',
style: TextStyle(fontSize: 40),
),
),
);
}
return CircularProgressIndicator();
},
);
}
}
You can't access the 'uid' directly because 'activeUser' is a stream of 'user'. So you could wrap your Text-widget with a StreamBuilder and provide 'state.activeUser' as the stream:
StreamBuilder(
stream: state.activeUser,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data?.uid : "");
}
return Text("");
},
);
But I have a question there, why do you save the stream itself inside your UserState? Why not save only the User and emit a new state whenever authStateChanges fires? You could do something like this:
class UserBloc extends Bloc<UserEvent, UserState> {
final AuthService _authService;
StreamSubscription<User?> _userSubscription;
UserBloc(this._authService) : super(UserInitial()) {
on<GetUser>(_getUser);
}
void _getUser(GetUser event, Emitter<UserState> emit) {
_userSubscription ??= _authService.user.listen((user) {
emit(UserLoaded(activeUser: user));
});
}
}
So you can change the UserState to hold a User? instead of a stream and you can access it directly inside you widget how you did it in your sample.
Attention: The code samples are only from my memory and probably wont work out of the box.

BlocListener Only returning intial loading state

I am building an app with flutter bloc. The issue i have is my bloc listener is only firing the initial state and not subsequent state change. All other questions have not been helpful as my state extends equatable to compare state. Here is my code below;
my login bloc
import 'package:bloc/bloc.dart';
import 'package:mobile_app/classes/custom_exception.dart';
import 'package:mobile_app/repositories/auth_repository.dart';
import 'package:mobile_app/states/login_status.dart';
class LoginBloc extends Cubit<LoginState> {
LoginBloc(this.auth) : super(LoginState.initial());
final AuthRepository auth;
void login(String email, String password) async {
emit(state.copyWith(loginStatus: Status.LOADING, isAuthenticated: false));
final response = await auth.doLogin(email, password);
if (response is AppException) {
emit(state.copyWith(
loginStatus: Status.ERROR,
error: response.toString(),
isAuthenticated: false));
} else {
emit(
state.copyWith(loginStatus: Status.COMPLETED, isAuthenticated: true));
}
}
}
My state file;
enum Status { INITIAL, LOADING, COMPLETED, ERROR }
class LoginState extends Equatable {
final Status loginStatus;
final String? error;
final bool isAuthenticated;
LoginState(
{required this.loginStatus, this.error, required this.isAuthenticated});
factory LoginState.initial() {
return LoginState(loginStatus: Status.INITIAL, isAuthenticated: false);
}
LoginState copyWith(
{required Status loginStatus,
String? error,
required bool isAuthenticated}) {
return LoginState(
loginStatus: loginStatus,
error: error,
isAuthenticated: isAuthenticated);
}
#override
List<Object?> get props => [loginStatus, error, isAuthenticated];
}
Then my listener
return BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
if (state.loginStatus == Status.COMPLETED) {
Navigator.of(context).pushReplacementNamed('/dashboard');
}
if (state.loginStatus == Status.ERROR) {
final snackBar = SnackBar(
backgroundColor: Colors.black,
content: Text(state.error!),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
print(state);
},
I understand the listener is only called once for every state change but it's as if the listener is not registering any state change. Help will be appreciated!
Okay so i think i know where the error is coming from. i have a blocbuilder that is showing different pages based on the current state and on of those pages include the login page that has the bloc listener. So i removed the bloc builder and just returned the login page with the bloc listener the snackbar is called as it should be. I used a blocconsumer to achieve what i want to achieve.
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocConsumer<LoginBloc, LoginState>(
listener: (context, state) {
if (state is LoginError) {
final snackBar = SnackBar(
backgroundColor: Colors.black,
content: Text(state.error),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
},
builder: (context, state) {
if (state is LoginLoading) {
return ProgressIndication();
} else if (state is LoginSuccess) {
return DashboardScreen();
}
return Login();
},
);
}
}

Cubit not rebuilding when emiting a new state

Whenever I call the toggleLocked event, the BlocBuilder does not rebuild the widget.
I have looked around a lot on the internet and found this explanation: https://stackoverflow.com/a/60869187/3290471
I think that somewhere I incorrectly use the equatable package which results in the fact that the BlocBuilder thinks nothing has changed (while is has).
I have read the FAQ from the Bloc libray and the three provided solution (props for equatable / not reusing the same state / using fromList) seem to not fix the problem.
My Cubit:
class LockCubit extends Cubit<LockState> {
LockCubit({#required this.repository})
: assert(repository != null),
super(LockInitial());
final LocksRepository repository;
Future<void> fetch() async {
try {
final locks = await repository.fetchLocks();
emit(LocksDisplayed().copyWith(locks));
} on Exception {
emit(LockError());
}
}
Future<void> toggleLocked(int id) async {
try {
final locks = await repository.toggleLocked(id);
emit(LocksDisplayed().copyWith(List.from(locks)));
} on Exception {
emit(LockError());
}
}
}
My states:
abstract class LockState extends Equatable {
const LockState();
#override
List<Object> get props => [];
}
class LockInitial extends LockState {
#override
String toString() => 'LocksUninitialized';
}
class LockError extends LockState {
#override
String toString() => 'LockError';
}
class LocksDisplayed extends LockState {
final List<Lock> locks;
const LocksDisplayed([this.locks = const []]);
LocksDisplayed copyWith(locks) => LocksDisplayed(locks ?? this.locks);
#override
List<Object> get props => [locks];
#override
String toString() => 'LocksDisplayed { locks: $locks }';
}
My model:
class Lock extends Equatable {
Lock({this.id, this.name, this.locked, this.displayed});
final int id;
final String name;
final bool locked;
final bool displayed;
#override
String toString() =>
'Lock { id: $id name: $name locked: $locked displayed: $displayed }';
Lock copyWith({id, name, locked, displayed}) => Lock(
id: id ?? this.id,
name: name ?? this.name,
locked: locked ?? this.locked,
displayed: displayed ?? this.displayed);
#override
List<Object> get props => [id, name, locked, displayed];
}
My repositotory:
class LocksRepository {
List<Lock> locks = [];
Future<List<Lock>> fetchLocks() async {
// This is a temporary implementation
// In the future the data should be fetched from a provider
locks = [
new Lock(
id: 0,
name: 'Voordeur',
locked: false,
),
new Lock(
id: 1,
name: 'Achterdeur',
locked: false,
)
];
return locks;
}
Future<List<Lock>> toggleLocked(int id) async {
// This is a temporary implementation
// In the future a request to change a lock should be made and then the specific lock should be retrieved back and edited.
locks[id] = locks[id].copyWith(locked: !locks[id].locked);
return locks;
}
}
I am changing a state with the following trigger:
context.read<LockCubit>().toggleLocked(focusedIndex);
I am using BlocBuilder like this to build the state:
BlocBuilder<LockCubit, LockState>(builder: (context, state) {
print('State Changed');
if (state is LockInitial) {
return Text('lockInitial');
}
if (state is LocksDisplayed) {
return Swiper(
itemBuilder: (BuildContext context, int index) {
return Column(
children: [
Text(state.locks[index].name),
Text(state.locks[index].locked.toString())
],
);
},
onIndexChanged: onIndexChanged,
loop: true,
itemCount: state.locks.length);
}
if (state is LockError) {
return Text('lockError');
}
return Container();
});
All help would be very appreciated.
Can you check BlocProvider ? I got the same problem. If this bloc inside materialApp, you must pass BlocProvider.value not create in widget.
I am a bit confused, if this could work. But with a bloc you would use an event not a cubit (even though events are based on cubits).
So first of all I would use the standard pattern:
state
event
bloc with mapEventToState
Then, what I also do not see in your code, if you toggle your lock it would look like this in pseudo code
if (event is toggleLock) {
yield lockInProgress();
toggleLock();
yield locksDisplayed;
}
This way your state always changes from locksDisplayed to lockInProgress to locksDisplayed - just as you read in your link above

How to access data in Bloc's state from another bloc

I am developing a Flutter application using Bloc pattern. After success authentication, UserSate has User object. In all other Blocs, I need to access User object in UserState. I tried with getting UserBloc on other Bloc's constructor parameters and accessing User object. But it shows that User object is null. Anyone have a better solution?
class SectorHomeBloc extends Bloc<SectorHomeEvent, SectorHomeState> {
final OutletRepository outletRepository;
UserBloc userBloc;
final ProductRepository productRepository;
final ProductSubCategoryRepository productSubCategoryRepository;
final PromotionRepository promotionRepository;
final ProductMainCategoryRepository mainCategoryRepository;
SectorHomeBloc({
#required this.outletRepository,
#required this.userBloc,
#required this.productSubCategoryRepository,
#required this.productRepository,
#required this.promotionRepository,
#required this.mainCategoryRepository,
});
#override
SectorHomeState get initialState => SectorHomeLoadingState();
#override
Stream<SectorHomeState> mapEventToState(SectorHomeEvent event) async* {
try {
print(userBloc.state.toString());
LatLng _location = LatLng(
userBloc.state.user.defaultLocation.coordinate.latitude,
userBloc.state.user.defaultLocation.coordinate.longitude);
String _token = userBloc.state.user.token;
if (event is GetAllDataEvent) {
yield SectorHomeLoadingState();
List<Outlet> _previousOrderedOutlets =
await outletRepository.getPreviousOrderedOutlets(
_token, _location, event.orderType, event.sectorId);
List<Outlet> _featuredOutlets =
await outletRepository.getFeaturedOutlets(
_token, _location, event.orderType, event.sectorId);
List<Outlet> _nearestOutlets = await outletRepository.getOutletsNearYou(
_token, _location, event.orderType, event.sectorId);
List<Product> _newProducts = await productRepository.getNewItems(
_token, _location, event.orderType, event.sectorId);
List<Product> _trendingProducts =
await productRepository.getTrendingItems(
_token, _location, event.orderType, event.sectorId);
List<Promotion> _promotions = await promotionRepository
.getVendorPromotions(_token, event.sectorId);
yield SectorHomeState(
previousOrderedOutlets: _previousOrderedOutlets,
featuredOutlets: _featuredOutlets,
nearByOutlets: _nearestOutlets,
newItems: _newProducts,
trendingItems: _trendingProducts,
promotions: _promotions,
);
}
} on SocketException {
yield SectorHomeLoadingErrorState('could not connect to server');
} catch (e) {
print(e);
yield SectorHomeLoadingErrorState('Error');
}
}
}
The print statement [print(userBloc.state.toString());] in mapEventToState method shows the initial state of UserSate.
But, at the time of this code executing UserState is in UserLoggedInState.
UPDATE (Best Practice):
please refer to the answer here enter link description here
so the best way for that is to hear the changes of another bloc inside the widget you are in, and fire the event based on that.
so what you will do is wrap your widget in a bloc listener and listen to the bloc you want.
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<FirstBloc, FirstBlocState>(
listener: (context, state) {
if(state is StateFromFirstBloc){
BlocProvider.of<SecondBloc>(context).add(SecondBlocEvent());}//or whatever you want
},
child: ElevatedButton(
child: Text('THIS IS NEW SCREEN'),
onPressed: () {
BlocProvider.of<SecondBloC>(context).add(SecondBloCEvent());
},
),
);
}
}
the lovely thing about listener is that you can listen anywhere to any bloc and do whatever you want
here is the official documentation for it
OLD WAY (NOT Recommended)
there is an official way to do this as in the documentation, called Bloc-to-Bloc Communication
and here is the example for this as in the documentation
class MyBloc extends Bloc {
final OtherBloc otherBloc;
StreamSubscription otherBlocSubscription;
MyBloc(this.otherBloc) {
otherBlocSubscription = otherBloc.listen((state) {
// React to state changes here.
// Add events here to trigger changes in MyBloc.
});
}
#override
Future<void> close() {
otherBlocSubscription.cancel();
return super.close();
}
}
sorry for the late update for this answer and thanks to #MJ studio
The accepted answer actually has a comment in the above example in the official docs saying "No matter how much you are tempted to do this, you should not do this! Keep reading for better alternatives!"!!!
Here's the official doc link, ultimately one bloc should not know about any other blocs, add methods to update your bloc and these can be triggered from blocListeners which listen to changes in your other blocs: https://bloclibrary.dev/#/architecture?id=connecting-blocs-through-domain
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<WeatherCubit, WeatherState>(
listener: (context, state) {
// When the first bloc's state changes, this will be called.
//
// Now we can add an event to the second bloc without it having
// to know about the first bloc.
BlocProvider.of<SecondBloc>(context).add(SecondBlocEvent());
},
child: TextButton(
child: const Text('Hello'),
onPressed: () {
BlocProvider.of<FirstBloc>(context).add(FirstBlocEvent());
},
),
);
}
}