I have built a Form have 2 buttons one is the login button and the other one is the registering button on the same page.
What I need to do is with the same form fields the users can register/login no need for route them to another page to do so.
So I created the login bloc/states/event and everything works great, continuing to register bloc/event/states and done (coded done and working silently, means no UI effects).
Now I don't know what next move?, how to access the register states from inside BlocListener<LoginBloc, LoginState> to show for example an indicator or a message that something is hapenning like what I have done already in LoginBloc
This the UI code:
BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
if(state is LoginFailur) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(state.errMsg))
);
}
if(state is LoginInitial) {
Navigator.of(context).pushReplacementNamed(TabsPage.routeName);
}
},
child: BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
return Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'مرحبا',
style: TextStyle(
fontSize: 45,
fontWeight: FontWeight.w900
),
),
Text(
'بك',
style: TextStyle(
fontSize: 40,
),
),
Container(
width: double.infinity,
child: RaisedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FaIcon(
FontAwesomeIcons.facebookF,
color: Colors.white,
),
SizedBox(width: 10,),
Text(
'تسحيل دخول بالفيسبوك',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: Colors.white
),
),
],
),
color: Color(0xff029487d),
onPressed: _isloading(context) ? null : () {
BlocProvider.of<LoginBloc>(context).add(
LoginByFacebook()
);
}
),
),
TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'هذا الحقل لا يمكن ان يكون خالي';
}
return null;
},
controller: _usernameController,
enabled: _isloading(context) ? false : true,
focusNode: _userFocusNode,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
labelText: 'اسم المستخدم',
labelStyle: TextStyle(
fontWeight: FontWeight.bold
),
alignLabelWithHint: true,
hintText: 'مثال: ahmed mohamed'
),
onFieldSubmitted: (String data) {
_fieldFocusChange(context, _userFocusNode, _passwordFocusNode);
},
),
TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'هذا الحقل لا يمكن ان يكون خالي';
}
return null;
},
controller: _passwordController,
enabled: _isloading(context) ? false : true,
focusNode: _passwordFocusNode,
textInputAction: TextInputAction.none,
obscureText: true,
decoration: InputDecoration(
labelText: 'الرقم السري',
labelStyle: TextStyle(
fontWeight: FontWeight.bold
),
alignLabelWithHint: true,
hintText: 'مثال: ********'
),
onFieldSubmitted: (String data) {
_passwordFocusNode.unfocus();
_loginByEmail(context);
},
),
SizedBox(height: 20,),
Container(
width: double.infinity,
child: RaisedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FaIcon(
FontAwesomeIcons.signInAlt,
color: Colors.white,
),
SizedBox(width: 10),
Text(
'تسحيل دخول',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white
),
),
],
),
onPressed: _isloading(context) ? null : () {
_loginByEmail(context);
},
color: Colors.red,
),
),
Container(
width: double.infinity,
child: RaisedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FaIcon(
FontAwesomeIcons.userPlus,
),
SizedBox(width: 10),
Text(
'تسحيل حساب جديد',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
onPressed: state is LoginLoading ? null : () {
BlocProvider.of<RegisterBloc>(context).add(
RegisterByUsername(
username: _usernameController.text,
password: _passwordController.text
)
);
}
),
),
Center(
child: Container(
child: _isloading(context)
? CircularProgressIndicator()
: null,
),
),
],
)
);
},
),
You can access any bloc from anywhere within the widget tree (assuming a BlocProvider parent exists) using BlocProvider.of<A, B>(context) or context.bloc<A, B>() using extensions. The problem with this approach is that if you access a Bloc<A, B> within a BlocListener<X, Y>, the listener will only be fired when the state of Bloc<X, Y>changes.
This means you probably want to use a separate BlocListener for your RegisterBloc. To reduce boilerplate, you can use the MultiBlocListener widget:
MultiBlocListener(
listeners: [
BlocListener<LoginBloc, LoginState>(
listener: (context, state) {...},
),
BlocListener<RegisterBloc, RegisterState(
listener: (context, state) {...},
),
],
child: BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {...}
),
)
I'd also recommend using BlocConsumer wherever you find yourself using both a BlocListener and a BlocBuilder in conjunction for the same Bloc, to minimise boilerplate:
BlocConsumer<LoginBloc, LoginState>(
listener: (context, state) {
if(state is LoginFailure) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(state.errMsg))
);
}
if(state is LoginInitial) {
Navigator.of(context).pushReplacementNamed(TabsPage.routeName);
}
},
builder: (context, state) {
...
}
)
```dart MultiBlocListener( listeners: [ BlocListener<LoginBloc, LoginState>( listener: (context, state) {...}, ), BlocListener<RegisterBloc, RegisterState( listener: (context, state) {...}, ), ], child: BlocBuilder<LoginBloc, LoginState>( builder: (context, state) {...} ), )```
Here still, I can't make an effect on the UI with for example RegisterLoading like disabling the form while it's loading or pushing an indicator down the fields!
Working just on the login actions ( login states );
My updated code:
MultiBlocListener(
listeners: <BlocListener>[
BlocListener<RegisterBloc, RegisterState>(
listener: (context, state) {
if ( state is RegisterLoading ) {
_isLoading = true;
} else {
_isLoading = false;
}
},
),
BlocListener<LoginBloc, LoginState>(
listener: (context, state) {
if ( state is LoginLoading ) {
_isLoading = true;
} else {
_isLoading = false;
}
},
)
],
child: BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
return Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'مرحبا',
style: TextStyle(
fontSize: 45,
fontWeight: FontWeight.w900
),
),
Text(
'بك',
style: TextStyle(
fontSize: 40,
),
),
Container(
width: double.infinity,
child: RaisedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FaIcon(
FontAwesomeIcons.facebookF,
color: Colors.white,
),
SizedBox(width: 10,),
Text(
'تسحيل دخول بالفيسبوك',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: Colors.white
),
),
],
),
color: Color(0xff029487d),
onPressed: _isLoading ? null : () {
BlocProvider.of<LoginBloc>(context).add(
LoginByFacebook()
);
}
),
),
TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'هذا الحقل لا يمكن ان يكون خالي';
}
return null;
},
controller: _usernameController,
enabled: _isLoading ? false : true,
focusNode: _userFocusNode,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
icon: FaIcon(
FontAwesomeIcons.user,
),
labelText: 'اسم المستخدم',
labelStyle: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black
),
alignLabelWithHint: false,
hintText: 'مثال: ahmed mohamed'
),
onFieldSubmitted: (String data) {
_fieldFocusChange(context, _userFocusNode, _passwordFocusNode);
},
),
TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'هذا الحقل لا يمكن ان يكون خالي';
}
return null;
},
controller: _passwordController,
enabled: _isLoading ? false : true,
focusNode: _passwordFocusNode,
textInputAction: TextInputAction.none,
obscureText: true,
decoration: InputDecoration(
icon: FaIcon(
FontAwesomeIcons.lockOpen,
),
labelText: 'الرقم السري',
labelStyle: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold
),
alignLabelWithHint: false,
hintText: 'مثال: ********'
),
onFieldSubmitted: (String data) {
_passwordFocusNode.unfocus();
_loginByEmail(context);
},
),
SizedBox(height: 20,),
Container(
width: double.infinity,
child: RaisedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FaIcon(
FontAwesomeIcons.signInAlt,
color: Colors.white,
),
SizedBox(width: 10),
Text(
'تسحيل دخول',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white
),
),
],
),
onPressed: _isLoading ? null : () {
_loginByEmail(context);
},
color: Colors.red,
),
),
Container(
width: double.infinity,
child: RaisedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FaIcon(
FontAwesomeIcons.userPlus,
),
SizedBox(width: 10),
Text(
'تسحيل حساب جديد',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
onPressed: state is LoginLoading ? null : () {
BlocProvider.of<RegisterBloc>(context).add(
RegisterByUsername(
username: _usernameController.text,
password: _passwordController.text
)
);
}
),
),
Center(
child: Container(
child: _isLoading
? CircularProgressIndicator()
: null,
),
),
],
)
);
},
),
)
I made a one large bloc containing user registration, login, and fetching logic, but still wanna know what if I'm in a situation need to access the UI with other states
I'm assuming your _isLoading variable is declared as a field in the state class, and that you're using a StatefulWidget. In this case, you should call
setState(() => _isLoading = value);
or else the changes won't affect the UI.
You could also drop the listeners and just use the builders, as they're rebuilt each time the state changes just as often as the listeners.
@arnemolland thanks for the detailed answers!
@heshaShawky closing for now but feel free to comment with additional questions and we can continue the conversation 👍