My goal is to provide all the data of the logged in user throughout the app using provider and use it to customize the UI based on the user.
The problem is that the data takes time to arrive from Firestore and at the start of the application the provider is not able to provide the widgets of the home screen, so I get the next error:
Exception has occurred.
_CastError (Null check operator used on a null value)
When I click "continue" in debug options then the interface get the data from the user correctly and all works fine, so I understand that I need a way to wait the data to be available.
I know that the returns of type Future have methods to deal with the asynchronous response from Firestore, but in order to use provider I have "synchronized" that data using try{}catch(e){} as shown in the code.
class CurrentUserProvider extends ChangeNotifier {
UserModel _currentUser = UserModel();
UserModel get getCurrentUser => _currentUser;
void updateStateFromFirebase(String uid) async {
try {
_currentUser = await OurDatabase().getUserInfo(uid);
notifyListeners();
} catch (e) {
print(e);
}
}
}
getUserInfo(uid){} is the async function that download the current user data from firestore using the uid provided by Authentification Firebase after the user logged in.
To my knowledge this implies that _currentUser is not going to be async anymore, so I cannot create an alternative to represent something on the screen while the data is arriving.
This is the code where I receive the data from the provider and try to render _currentUser.uid as text on the screen.
import 'package:beeteam/providers/currentUserProvider.dart';
import 'package:flutter/material.dart';
import 'package:beeteam/models/user_model.dart';
import 'package:provider/provider.dart';
class MyTeams extends StatefulWidget {
#override
State<MyTeams> createState() => _MyTeamsState();
}
class _MyTeamsState extends State<MyTeams> {
#override
Widget build(BuildContext context) {
UserModel? _currentUser =
Provider.of<CurrentUserProvider>(context, listen: true).getCurrentUser;
return Scaffold(
body: Container(
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20),
child: Text(_currentUser.uid!),
//Text(_currentUser.uid!),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
backgroundColor: Color.fromARGB(255, 190, 158, 62),
onPressed: () {
Navigator.of(context).pushNamed('/join_team_screen');
}));
}
I got an error if I dont code ! at the end of _currentUser.uid.
This is the UserModel code.
class UserModel {
String? uid;
String? email;
String? firstName;
String? secondName;
String? userName;
String? teamSelected;
List<String>? teamsMemberUid;
List<String>? notifications;
UserModel(
{this.uid,
this.email,
this.firstName,
this.secondName,
this.userName,
this.teamSelected,
this.teamsMemberUid,
this.notifications});
Do you have any idea how to solve this problem?
Make currentUser nullable allowing null value before loading is finished.
class CurrentUserProvider extends ChangeNotifier {
UserModel? _currentUser;
UserModel? get currentUser => _currentUser;
void updateStateFromFirebase(String uid) async {
try {
_currentUser = await OurDatabase().getUserInfo(uid);
notifyListeners();
} catch (e) {
print(e);
}
}
}
Show loading state while currentUser is null.
class MyTeams extends StatelessWidget {
#override
Widget build(BuildContext context) {
UserModel? currentUser =
Provider.of<CurrentUserProvider>(context, listen: true).currentUser;
return Scaffold(
body: currentUser == null
? const Center(child: CircularProgressIndicator())
: Container(
margin:
const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20),
child: Text(currentUser.uid!),
),
...
}
}
Related
I want to know how can I refresh a table data (which is fetched from an API using a future provider) and re-render the table widget based on dropdown value change.
Following is the Repo file with providers:
import 'package:ct_analyst_app/src/features/dashboard/domain/dashboard_data.dart';
import 'package:dio/dio.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../authentication/application/auth_local_service.dart';
abstract class IDashboardRepository {
Future<void> fetchDashboard(String name);
Future<void> fetchNames();
}
final clientProvider = Provider.family((ref, token) => Dio(BaseOptions(
baseUrl: "http://${dotenv.env['IP']}/excel/",
headers: {"authorization": token})));
class DashboardRepository implements IDashboardRepository {
DashboardRepository(this.read);
final Reader read;
DashboardData? _data;
DashboardData? get dashboardData => _data;
List<dynamic>? _names;
List<dynamic>? get names => _names;
#override
Future<DashboardData?> fetchDashboard(String name) async {
final token = await read(authServiceProvider).getToken();
final response = await read(clientProvider(token))
.get('/getData', queryParameters: {"name": name});
_data = DashboardData.fromJson(response.data);
print(name);
return _data;
}
#override
Future<void> fetchNames() async {
final token = await read(authServiceProvider).getToken();
final response = await read(clientProvider(token)).get('/analystNames');
_names = response.data["names"];
}
}
final dashboardRepositoryProvider =
Provider((ref) => DashboardRepository(ref.read));
final fetchDashboardData = FutureProvider.family<void, String>((ref, name) {
final repoProvider = ref.watch(dashboardRepositoryProvider);
return repoProvider.fetchDashboard(name);
});
final fetchAnalystNames = FutureProvider((ref) {
final repoProvider = ref.watch(dashboardRepositoryProvider);
return repoProvider.fetchNames();
});
I have tried to refresh the future provider in the dropdown onChange and it does fetch the new table data from the API. However, the widget which renders the data in the table is not getting re-rendered when the refresh is called.
Done as following:
onChanged: (String? newValue) {
ref.read(dropItemProvider.notifier).state = newValue as String;
ref.refresh(fetchDashboardData(newValue));
setState(() {
widget.value = newValue;
});
},
I am using ref.watch on the data, still it does not re-render the widget if the data is changed.
class TableGenerator extends ConsumerWidget {
const TableGenerator({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(dashboardRepositoryProvider);
return data.dashboardData != null
? SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Row(
children: [
const FixedColumnWidget(data: [
"one",
"two",
"three",
"four",
"fifth",
]),
ScrollableColumnWidget(
data: data.dashboardData as DashboardData)
],
))
: const CircularProgressIndicator();
}
}
Am I missing something or how should I approach this problem? like different providers or something else?
Thanks!
Your Widget is watching dashboardRepositoryProvider, which doesn't update after the ref.refresh call.
There's two things to consider:
dashboardRepository should just expose your repo / services, and instead it is used to observe actual data. This is not affecting your app directly, but it is part of the problem imho. I'd expect your Widget to observe a FutureProvider that exposes (and caches, etc.) the data by calling the methods inside your repository;
Then, let's analyze why your Widget isn't updating: dashboardRepository isn't depending, i.e. performing a watch, on the Provider you're refreshing, which is fetchDashboardData, nor it is depending on dropItemProvider (I am specifying this since your onChanged callback updates / refreshes two different Providers).
I think your should refactor your code so that it will expose actual data from a FutureProvider which exploits your repositories and can be simply refreshed similarly as what you already are doing.
Quick FutureProvider example:
// WARNING: PSEUDOCODE
final myDataProvider = FutureProvider<MyClass>((ref) {
final repo = ref.watch(myRepo);
final response = repo.getSomeData(...);
// TODO: add error handling, debouncing, cancel tokens, etc.
return MyClass.fromJson(response.data); // e.g.
});
Quick usage:
// WARNING: PSEUDOCODE
#override
Widget build(BuildContext context, WidgetRef ref) {
final myData = ref.watch(myDataProvider);
return ElevatedButton(
onTap: () {
ref.refresh(myDataProvider);
},
child: Text("Click me to refresh me (data: $myData)"),
);
}
Im experimenting and learning on a small project and im trying to implement an authentication workflow. Ive implemented everything and by printing my steps everything should just work fine. Im wondering why my BlocBuilder isn't going to update. The project is really small so I can provide you everything easy. Since im learning bloc, i appreciate every hint, approach and I want to thank you in advance.
terminal output when the app is starting:
flutter: building main.dart
flutter: AppLoaded()
flutter: user is NOT signed in
flutter: false
flutter: Transition { currentState: AuthInitial(), event: AppLoaded(), nextState: UnauthenticatedState() }
flutter: block says user is NOT authenticated
This is completely fine since im checking at the beginning if there is any user data valid. Now when I press on the Login Textbutton in my home.dart my Blocbuilder should show that im logged in, but it doesnt. This is the terminal output:
flutter: AppLoaded()
flutter: signed id with credentials: User{id: 1, socketId: 123, userName: Logged in User}
flutter: user is signed in
flutter: true
flutter: currentuser is not empty: User{id: 1, socketId: 123, userName: Logged in User}
flutter: Transition { currentState: AuthInitial(), event: AppLoaded(), nextState: AuthenticatedState() }
flutter: block says user is authenticated
main.dart
import 'package:fl_auth/bloc/auth/auth_bloc.dart';
import 'package:fl_auth/repositories/user_repository.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/bloc_observer.dart';
import 'home.dart';
import 'models/auth.dart';
void main() {
BlocOverrides.runZoned(
() {
runApp(const MyApp());
},
blocObserver: SimpleBlocObserver(),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Auth _auth = Auth.instance;
UserRepository _userRepository = UserRepository(auth: _auth);
print('building main.dart');
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider(
create: (context) =>
AuthBloc(userRepository: _userRepository)..add(AppLoaded()),
child: Home(),
),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/auth/auth_bloc.dart';
import 'models/auth.dart';
import 'repositories/user_repository.dart';
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Auth _auth = Auth.instance;
UserRepository _userRepository = UserRepository(auth: _auth);
AuthBloc authBloc = AuthBloc(userRepository: _userRepository);
return Scaffold(
body: SizedBox(
height: 500,
child: Column(
children: [
Container(
height: 200,
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is UnauthenticatedState) {
return Center(child: Text('User is unauthenticated'));
} else if (state is AuthenticatedState) {
return Center(child: Text('YEAH logged in!'));
} else {
return Center(child: Text('something went wrong'));
}
}),
),
TextButton(
onPressed: () => {
authBloc.userRepository.signIn(),
authBloc.add(AppLoaded())
},
child: Text('Login')),
],
)));
}
}
auth_event.dart
part of 'auth_bloc.dart';
abstract class AuthEvent extends Equatable {
const AuthEvent();
#override
List<Object> get props => [];
}
class AppLoaded extends AuthEvent {}
auth_state.dart
// ignore_for_file: public_member_api_docs, sort_constructors_first
part of 'auth_bloc.dart';
abstract class AuthState extends Equatable {
const AuthState();
#override
List<Object> get props => [];
}
class AuthInitial extends AuthState {}
class AuthenticatedState extends AuthState {
User user;
AuthenticatedState({
required this.user,
});
}
class UnauthenticatedState extends AuthState {}
auth.bloc
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:fl_auth/repositories/user_repository.dart';
import '../../models/user.dart';
part 'auth_event.dart';
part 'auth_state.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
UserRepository userRepository;
AuthBloc({required this.userRepository}) : super(AuthInitial()) {
on<AppLoaded>((event, emit) async {
try {
var isSignedIn = await userRepository.isSignedIn();
print(isSignedIn);
if (isSignedIn) {
var user = await userRepository.getCurrentUser();
emit(AuthenticatedState(user: user));
print('block says user is authenticated');
} else {
emit(UnauthenticatedState());
print('block says user is NOT authenticated');
}
} catch (e) {
emit(UnauthenticatedState());
}
});
}
}
user_repository.dart
import 'package:fl_auth/models/user.dart';
import '../models/auth.dart';
class UserRepository {
Auth auth = Auth.instance;
UserRepository({required this.auth});
// sign in with username
//TODO: change hardcoded username to email and passwort later on
Future<User> signIn() async {
try{
await Future.delayed(Duration(seconds: 1));
var credentials = User(id: 1, socketId: '123', userName: 'Logged in User');
print('signed id with credentials: ${auth.currentUser}');
auth.currentUser = credentials;
return auth.currentUser;
}catch(e){
print(e.toString());
throw e;
}
}
// check signed in status
Future<bool> isSignedIn() async {
try{
await Future.delayed(const Duration(seconds: 1));
var currentUser = auth.currentUser;
if(currentUser.isNotEmpty){
print('user is signed in');
return true;
} else {
print('user is NOT signed in');
return false;
}
}catch(e){
print(e.toString());
throw e;
}
}
// get user
Future<User> getCurrentUser() async {
try{
await Future.delayed(const Duration(seconds: 1));
var currentUser = auth.currentUser;
if(currentUser.isNotEmpty){
print('currentuser is not empty: $currentUser');
return currentUser;
} else {
var message = 'User is empty';
print('currentuser IS empty: $currentUser');
throw message;
}
}catch(e){
print(e.toString());
throw e;
}
}
}
auth.dart
import 'user.dart';
class Auth {
/// private constructor
Auth._();
/// the one and only instance of this singleton
static final instance = Auth._();
//ChatBloc chatBloc = ChatBloc(DatabaseApi.db);
// Create a User instance. Actually it would be better if this is empty so I can notice if a user is valid or not and can react by checking if the user has values and
// if not log the user out later on
User currentUser = User.empty;
}
user.dart
import 'package:equatable/equatable.dart';
/// {#template user}
/// User model
///
/// [User.empty] represents an unauthenticated user.
/// {#endtemplate}
class User extends Equatable {
/// {#macro user}
const User({
required this.id,
this.socketId,
this.userName,
});
/// The current user's scoket id.
final String? socketId;
/// The current user's id.
final int id;
/// The current user's name (display name).
final String? userName;
/// Empty user which represents an unauthenticated user.
static const empty = User(id: 0);
/// Convenience getter to determine whether the current user is empty.
bool get isEmpty => this == User.empty;
/// Convenience getter to determine whether the current user is not empty.
bool get isNotEmpty => this != User.empty;
#override
List<Object?> get props => [id, socketId, userName];
// Convert a user into a Map. The keys must correspond to the names of the
// columns in the database.
Map<String, dynamic> toMap() {
return {
'id': id,
'socketId': socketId,
'userName': userName,
};
}
factory User.fromMap(Map<String, dynamic> map) {
return User(
id: map['id'] as int,
socketId: map['socketId'] as String,
userName: map['userName'] as String,
);
}
// Implement toString to make it easier to see information about
// each user when using the print statement.
#override
String toString() {
return 'User{id: $id, socketId: $socketId, userName: $userName}';
}
}
The issue is In your OnTap Function you can call Event like this. I have checked and It's working as expected.
TextButton(
onPressed: () => {
authBloc.userRepository.signIn(),
context.read<AuthBloc>().add(AppLoaded())
},
child: const Text('Login')),
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.
I am using a future provider to display a login page on load and then a loading indicator on loading. Here is my future provider
final loginProvider = FutureProvider.family((ref, UserInput input) =>
ref.read(authRepositoryProvider).doLogin(input.email, input.password));
In my UI I have this....
class LoginScreen extends HookWidget {
final TextEditingController emailEditingController = TextEditingController();
final TextEditingController passwordEditingController =
TextEditingController();
#override
Widget build(BuildContext context) {
var userInput =
UserInput(emailEditingController.text, passwordEditingController.text);
final login = useProvider(loginProvider(userInput));
return login.when(
data: (user) => Login(emailEditingController, passwordEditingController),
loading: () => const ProgressIndication(),
error: (error, stack) {
if (error is DioError) {
return Login(emailEditingController, passwordEditingController);
} else {
return Login(emailEditingController, passwordEditingController);
}
},
);
}
}
here is my doLogin function.
#override
Future<dynamic> doLogin(String email, String password) async {
try {
final response = await _read(dioProvider)
.post('$baseUrl/login', data: {'email': email, 'password': password});
final data = Map<String, dynamic>.from(response.data);
return data;
} on DioError catch (e) {
return BadRequestException(e.error);
} on SocketException {
return 'No Internet Connection';
}
}
I would like to know why it's stuck in the loading state. Any help will be appreciated.
First off, family creates a new instance of the provider when given input. So in your implementation, any time your text fields change, you're generating a new provider and watching that new provider. This is bad.
In your case, keeping the UserInput around for the sake of accessing the login state doesn't make a lot of sense. That is to say, in this instance, a FamilyProvider isn't ideal.
The following is an example of how you could choose to write it. This is not the only way you could write it. It is probably easier to grasp than streaming without an API like Firebase that handles most of that for you.
First, a StateNotifierProvider:
enum LoginState { loggedOut, loading, loggedIn, error }
class LoginStateNotifier extends StateNotifier<LoginState> {
LoginStateNotifier(this._read) : super(LoginState.loggedOut);
final Reader _read;
late final Map<String, dynamic> _user;
static final provider =
StateNotifierProvider<LoginStateNotifier, LoginState>((ref) => LoginStateNotifier(ref.read));
Future<void> login(String email, String password) async {
state = LoginState.loading;
try {
_user = await _read(authRepositoryProvider).doLogin(email, password);
state = LoginState.loggedIn;
} catch (e) {
state = LoginState.error;
}
}
Map<String, dynamic> get user => _user;
}
This allows us to have manual control over the state of the login process. It's not the most elegant, but practically, it works.
Next, a login screen. This is as barebones as they get. Ignore the error parameter for now - it will be cleared up in a moment.
class LoginScreen extends HookWidget {
const LoginScreen({Key? key, this.error = false}) : super(key: key);
final bool error;
#override
Widget build(BuildContext context) {
final emailController = useTextEditingController();
final passwordController = useTextEditingController();
return Column(
children: [
TextField(
controller: emailController,
),
TextField(
controller: passwordController,
),
ElevatedButton(
onPressed: () async {
await context.read(LoginStateNotifier.provider.notifier).login(
emailController.text,
passwordController.text,
);
},
child: Text('Login'),
),
if (error) Text('Error signing in'),
],
);
}
}
You'll notice we can use the useTextEditingController hook which will handle disposing of those, as well. You can also see the call to login through the StateNotifier.
Last but not least, we need to do something with our fancy new state.
class AuthPage extends HookWidget {
const AuthPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final loginState = useProvider(LoginStateNotifier.provider);
switch (loginState) {
case LoginState.loggedOut:
return LoginScreen();
case LoginState.loading:
return LoadingPage();
case LoginState.loggedIn:
return HomePage();
case LoginState.error:
return LoginScreen(error: true);
}
}
}
In practice, you're going to want to wrap this in another widget with a Scaffold.
I know this isn't exactly what you asked, but thought it might be helpful to see another approach to the problem.
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());
},
),
);
}
}