I'm building an app with Flutter and Bloc like architecture. I'm trying to call submit func with not only login button but also keyboard done button with password; only when email and password are valid.
I could implement login button version with combineLatest, but I'm not sure keyboard version. I need to validate both email and password when keyboard done button pressed before calling submit. I could nest streamBuilder, but I feel it is not good practice.
Is there any way to get the latest value from combineLatest? BehaviorSubject<bool>().value Or any possible advice to implement this.
sample code:
final _emailController = BehaviorSubject<String>();
final _passwordController = BehaviorSubject<String>();
// Add data to stream
Stream<String> get email => _emailController.stream.transform(validateEmail);
Stream<String> get password =>
_passwordController.stream.transform(validatePassword);
Stream<bool> get submitValid =>
Observable.combineLatest2(email, password, (e, p) => true);
// change data
Function(String) get changeEmail => _emailController.sink.add;
Function(String) get changePassword => _passwordController.sink.add;
submit() {
final validEmail = _emailController.value;
final validPassword = _passwordController.value;
print('Email is $validEmail, and password is $validPassword');
}
import 'package:flutter/material.dart';
import '../blocs/bloc.dart';
import '../blocs/provider.dart';
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final bloc = Provider.of(context);
return Container(
margin: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
emailField(bloc),
passwordField(bloc),
Container(
margin: EdgeInsets.only(top: 25.0),
),
submitButton(bloc),
],
),
);
}
Widget emailField(Bloc bloc) {
return StreamBuilder(
stream: bloc.email,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changeEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'ypu#example.com',
labelText: 'Email Address',
errorText: snapshot.error,
),
);
},
);
}
Widget passwordField(Bloc bloc) {
return StreamBuilder(
stream: bloc.password,
builder: (context, snapshot) {
return TextField(
obscureText: true,
onChanged: bloc.changePassword,
decoration: InputDecoration(
hintText: 'Password',
labelText: 'Password',
errorText: snapshot.error,
),
textInputAction: TextInputAction.done,
onSubmitted: () {}, // <- here
);
});
}
Widget submitButton(Bloc bloc) {
return StreamBuilder(
stream: bloc.submitValid,
builder: (context, snapshot) {
return RaisedButton(
child: Text('Login'),
color: Colors.blue,
onPressed: snapshot.hasData ? bloc.submit : null,
);
},
);
}
}
You can wrap Your Password streamBuilder with another streamBilder and onSubmitted call submit method.
Widget passwordField(Bloc bloc) {
return StreamBuilder(
stream: bloc.submitValid,
builder: (context, snap) {
return StreamBuilder(
stream: bloc.password,
builder: (context, snapshot) {
return TextField(
obscureText: true,
onChanged: bloc.changePassword,
decoration: InputDecoration(
hintText: 'Password',
labelText: 'Password',
errorText: snapshot.error,
),
textInputAction: TextInputAction.done,
onSubmitted: snap.hasData ? bloc.submit : null,
);
});
});
}
Related
Screenshot
I'm trying to send the data 'area name' from textfield at the side of map to google map,
and here's my code
// sideBar.dart
class StreamTitle {
StreamTitle({required this.getTitle});
final String getTitle;
Stream<String> get showTitle async* {
yield getTitle;
}
.....
Container(
child: Consumer<String>(
builder: (context, String showTitle, child)
=> googleMap(showTitle)
),
),
TextField(
controller: Controller,
onSubmitted: (value) {
StreamProvider<String>(
create: (_) =>
StreamTitle(getTitle:Controller.text).showTitle,
initialData: Controller.text);
},
),
googleMap.dart
I have a TextFormField widget wrapped inside a StreamBuilder, in TextFormField widget, inside the decoration, when is pass snapshot.error to the errorText argument, it gives an error:
The argument type 'Object?' can't be assigned to the parameter type 'String?'
Here is the code for state class with form and TextFormField
class LoginScreen extends StatefulWidget{
State<StatefulWidget> createState() {
return _LoginScreen();
}
}
class _LoginScreen extends State<LoginScreen>{
final form_key = GlobalKey<FormState>();
Widget build(context){
return Container(
margin: EdgeInsets.all(20),
child: Form(
key: form_key,
child: Column(
children: [
emailField(),
passwordField(),
Padding(
padding: EdgeInsets.all(7),
child: submitButton(),
),
Padding(
padding: EdgeInsets.all(7),
child: ResetButton(),
)
],
),
),
);
}
Widget emailField(){
return StreamBuilder(
stream: bloc.email,
builder: (context, snapshot){
return TextFormField(
decoration: const InputDecoration(
labelText: 'Email',
errorText: snapshot.error,
),
keyboardType: TextInputType.emailAddress,
onChanged: bloc.changeEmail,
);
},
);
}
Widget passwordField(){
return StreamBuilder(
stream: bloc.pass,
builder: (context, snapshot){
return TextFormField(
decoration: const InputDecoration(
labelText: 'Password'
errorText: snapshot.error,
),
obscureText: true,
);
},
);
}
Widget submitButton(){
return ElevatedButton(
child: Text('SUBMIT'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith(getColor),
),
onPressed: (){},
);
}
Widget ResetButton(){
return ElevatedButton(
child: Text('RESET'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith(getColor),
),
onPressed: (){
form_key.currentState!.reset();
}
);
}
Color getColor(Set<MaterialState> states) {
const Set<MaterialState> interactiveStates = <MaterialState>{
MaterialState.pressed,
MaterialState.hovered,
MaterialState.focused,
};
if (states.any(interactiveStates.contains)) {
return Colors.orange.shade600;
}
return Colors.blue.shade400;
}
}
The code of my bloc class:
class Bloc with Validators{
final _email = StreamController<String?>();
final _pass = StreamController<String?>();
//get access to stream
Stream<String?> get email => _email.stream.transform(validate_email);
Stream<String?> get pass => _pass.stream.transform(validate_password);
//change new data
Function(String?) get changeEmail => _email.sink.add;
Function(String?) get changePass => _pass.sink.add;
dispose(){
_email.close();
_pass.close();
}
}
And here is validator class:
class Validators{
final validate_email = StreamTransformer<String?, String?>.fromHandlers(
handleData: (String? email, sink){
if(email!.contains('#')){
sink.add(email);
}else{
sink.addError('Enter valid email');
}
}
);
final validate_password = StreamTransformer<String?, String?>.fromHandlers(
handleData: (String? pass, sink){
if(pass!.length < 4){
sink.addError('Enter valid password');
}else{
sink.add(pass);
}
}
);
}
You need to use
snapshot.error?.toString()
errorText: snapshot.hasError ? snapshot.error.toString() : "",
This will check if there is an error before converting it to a string. If there is no error, it'll prevent runtime null exceptions as well.
Interestingly no one knows the solution as they didn't face the problem.
You've to just remove the const keyword before InputDecoration(),
The below code will not work as it has the const keyword -
TextFormField(
decoration: const InputDecoration(
labelText: 'Email', errorText: snapshot.error),
keyboardType: TextInputType.emailAddress,
onChanged: bloc.changeEmail,
);
But this piece of code will work as it does not have the const keyword -
TextFormField(
decoration: InputDecoration(
labelText: 'Email', errorText: snapshot.error),
keyboardType: TextInputType.emailAddress,
onChanged: bloc.changeEmail,
);
I have submit button, but when i press the button it gives this error:
BlocProvider.of() called with a context that does not contain a
CreatePostCubit.
No ancestor could be found starting from the
context that was passed to BlocProvider.of(). This
can happen if the context you used comes from a widget above the
BlocProvider.
The context used was: Builder
I suppose contexts get mixed. how can i solve this error?
my code:
Widget createPostButton(BuildContext context) {
final TextEditingController _postTitleController = TextEditingController();
final TextEditingController _postDetailsController = TextEditingController();
final TextEditingController _priceController = TextEditingController();
final _formKey = GlobalKey<FormState>(debugLabel: '_formKey');
return BlocProvider<CreatePostCubit>(
create: (context) => CreatePostCubit(),
child: Padding(
padding: const EdgeInsets.only(right: 13.0, bottom: 13.0),
child: FloatingActionButton(
child: FaIcon(FontAwesomeIcons.plus),
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
autocorrect: true,
controller: _postTitleController,
textCapitalization: TextCapitalization.words,
enableSuggestions: false,
validator: (value) {
if (value.isEmpty || value.length <= 4) {
return 'Please enter at least 4 characters';
} else {
return null;
}
},
decoration:
InputDecoration(labelText: 'Post Title'),
)),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
controller: _postDetailsController,
autocorrect: true,
textCapitalization: TextCapitalization.words,
enableSuggestions: false,
validator: (value) {
if (value.isEmpty || value.length <= 25) {
return 'Please enter at least 25 characters';
} else {
return null;
}
},
decoration: InputDecoration(
labelText: 'Write a post details'),
)),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
controller: _priceController,
enableSuggestions: false,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
validator: (value) {
if (value.isEmpty || value.length >= 4) {
return 'Please enter a valid value';
} else {
return null;
}
},
decoration:
InputDecoration(labelText: 'Enter the Price'),
)),
OutlinedButton(
style: OutlinedButton.styleFrom(
primary: Colors.white,
backgroundColor: Colors.blue,
),
child: Text("Submit"),
onPressed: () => {
BlocProvider.of<CreatePostCubit>(context)
.createNewPost(
postTitle: _postTitleController.text,
postDetails: _postDetailsController.text,
price: _priceController.text)
},
),
],
),
),
),
);
});
},
),
),
);
}
Your showDialog builder is using new context. The widget returned by the builder does not share a context with the location that showDialog is originally called from.
Just rename builder parameter into something else
showDialog(
context: context,
barrierDismissible: false,
builder: (dialog_context) { // instead of context
return AlertDialog(
....
Also you need to wrap BlocProvide child with builder(so it can access inherited BlocProvider)
BlocProvider<CreatePostCubit>(
create: (context) => CreatePostCubit(),
child: Builder(
builder: (BuildContext context) => Padding(
...
Use BlocProvider.of<CreatePostCubit>(context,listen:false) instead of BlocProvider.of<CreatePostCubit>(context) in you submit button.
I think the problem is that context in
BlocProvider.of<CreatePostCubit>(context) refer to the orginal context which doesn't have cubit so try to rename the context into sth else like _context :
create: (_context) => CreatePostCubit(),
and when calling like this
BlocProvider.of<CreatePostCubit>(_context)
I have a TextField rendered with the help of a StreamBuilder, following the BLoC pattern with a sink and stream.
Widget field(SignUpBloc signUpBloc) {
return StreamBuilder(
stream: signUpBloc.outFirstName,
builder: (context, snapshot) {
return TextField(
style: TextStyle(fontSize: 15.0),
onChanged: signUpBloc.inFirstName,
decoration: InputDecoration(
errorStyle: TextStyle(fontSize: 15.0),
errorText: snapshot.error
),
);
},
);
}
My question is how do I set up an initial value? I've tried with the StreamBuilder's initialData property but no text appears in the TextField.
TextEditingController _controller = TextEditingController(); // make a controller,
Widget field(SignUpBloc signUpBloc) {
return StreamBuilder(
stream: signUpBloc.outFirstName,
initialData: YourData, // provide initial data
builder: (context, snapshot) {
_controller.value = TextEditingValue(text: "${snapshot.data}"); // assign value to controller this way
return TextField(
controller: _controller,
style: TextStyle(fontSize: 15.0),
onChanged: signUpBloc.inFirstName,
decoration: InputDecoration(
errorStyle: TextStyle(fontSize: 15.0),
errorText: snapshot.error
),
);
},
);
}
Edit: To put the cursor at the end of the line, you can use
var cursorPos = _controller.selection;
if (cursorPos.start > _controller.text.length) {
cursorPos = TextSelection.fromPosition(TextPosition(offset: _controller.text.length));
}
_controller.selection = cursorPos;
Source
You will have to have a TextEditingController when you need to pass initial values. Using the value.copyWith strategy there is no need to deal with the cursor, and to make the widget cleaner you can pass the text controller as a parameter.
// Stream widget
Widget field(SignUpBloc signUpBloc, TextEditingController _txtController) {
return StreamBuilder(
stream: signUpBloc.outFirstName,
builder: (context, snapshot) {
_txtController.value =
_txtController.value.copyWith(text: snapshot.data);
return TextField(
controller: _txtController,
onChanged: signUpBloc.inFirstName;
decoration: InputDecoration(
errorStyle: TextStyle(fontSize: 15.0),
errorText: snapshot.error
),
});
}
I have a Registration page with TextField Form Validators in my App with a button. The text field will display form validation error messages if business rules haven't been met and the "next" button will be tap-able once all criteria has been met. This is currently all working well in my app but I find that once I leave the page and return to it, the validation error messages stops displaying and the button stops working as well. Looking at the console logs in my IDE (android studio) the only relevant error message I am getting is
[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: Bad state:
Stream is already closed
#0 _SinkTransformerStreamSubscription._add
(dart:async/stream_transformers.dart:66:7)
#1 _EventSinkWrapper.add
(dart:async/stream_transformers.dart:15:11)
I'm not exactly sure what this means, does the stream close and not reopen once the page has been reloaded? If not, is there a way i could fix this or is there something i'm missing? This is what i'm experiencing
Stream Builder Code:
Widget emailField(authBloc) {
return StreamBuilder(
stream: authBloc.emailStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updateEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
border: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.deepOrange
)
),
hintText: 'Enter Email',
labelText: 'Email Address',
errorText: snapshot.error
),
);
},
);
}
Widget passwordField( authBloc) {
return StreamBuilder(
stream: authBloc.passwordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updatePassword,
obscureText: true,
decoration: InputDecoration(
hintText: 'Enter password',
labelText: 'Password',
errorText: snapshot.error,
),
);
},
);
}
Widget checkPasswordField( authBloc) {
return StreamBuilder(
stream: authBloc.validatePasswordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updateValidatePassword,
obscureText: true,
decoration: InputDecoration(
hintText: 'Re-enter password',
labelText: 'Confirm Password',
errorText: snapshot.error,
),
);
},
);
}
Widget nextBtn(authBloc) {
return StreamBuilder(
stream: authBloc.submitValid,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return RaisedButton(
child: Text('Next'),
color: Colors.deepOrange,
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7.0))
),
onPressed: snapshot.hasData
? () => Navigator.pushNamed(context, '/register')
: null,
);
}
);
}
Streams:
/// REGISTER VARIABLES
static final _emailController = BehaviorSubject<
String>(); //RxDart's implementation of StreamController. Broadcast stream by default
static final _passwordController = BehaviorSubject<String>();
static final _validatePasswordController = BehaviorSubject<
String>(); // Will check that the password entered the 2nd time is correct
/// REGISTER STREAM & METHODS
//Retrieve data from the stream
Stream<String> get emailStream => _emailController.stream
.transform(performEmailValidation); //Return the transformed stream
Stream<String> get passwordStream =>
_passwordController.stream.transform(performPasswordValidation);
Stream<String> get validatePasswordStream =>
_validatePasswordController.stream.transform(performIsPasswordSame);
//Merging email, password and validate password
Stream<bool> get submitValid => Observable.combineLatest3(
emailStream, passwordStream, validatePasswordStream, (e, p1, p2) => true);
//Add data to the stream
Function(String) get updateEmail => _emailController.sink.add;
Function(String) get updatePassword => _passwordController.sink.add;
Function(String) get updateValidatePassword =>
_validatePasswordController.sink.add;
// performing user input validations
final performEmailValidation = StreamTransformer<String, String>.fromHandlers(
handleData: (email, sink) async {
String emailValidationRule =
r'^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = new RegExp(emailValidationRule);
if (await doesNameAlreadyExist("email", _emailController.value) == true)
sink.addError("That email already exists");
else if (regExp.hasMatch(email)) {
sink.add(email);
} else {
sink.addError(StringConstant.emailErrorMessage);
}
});
final performPasswordValidation =
StreamTransformer<String, String>.fromHandlers(
handleData: (_passwordController, sink) {
if (_passwordController.length >= 6) {
sink.add(_passwordController);
} else {
sink.addError(StringConstant.passwordErrorMessage);
}
});
final performIsPasswordSame = StreamTransformer<String, String>.fromHandlers(
handleData: (password, sink) {
if (password != _passwordController.value)
sink.addError(StringConstant.invalidPasswordMessage);
else
sink.add(password);
});
Entire Screen Code:
class SignUp extends StatefulWidget {
#override
_SignUpState createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
AuthBloc _authBloc;
#override
void didChangeDependencies() {
super.didChangeDependencies();
_authBloc = AuthBlocProvider.of(context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
SizedBox(height: 80,),
Text("Register", style: Style.appTextStyle),
SizedBox(height: 100,),
emailField(_authBloc),
SizedBox(height: 30),
passwordField(_authBloc),
SizedBox(height: 30),
checkPasswordField(_authBloc),
SizedBox(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
cancelBtn(),
nextBtn(_authBloc),
],
)
// checkPasswordField(authBloc),
],
),
)
),
);
}
Widget emailField(authBloc) {
return StreamBuilder(
stream: authBloc.emailStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updateEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
border: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.deepOrange
)
),
hintText: 'Enter Email',
labelText: 'Email Address',
errorText: snapshot.error
),
);
},
);
}
Widget passwordField( authBloc) {
return StreamBuilder(
stream: authBloc.passwordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updatePassword,
obscureText: true,
decoration: InputDecoration(
hintText: 'Enter password',
labelText: 'Password',
errorText: snapshot.error,
),
);
},
);
}
Widget checkPasswordField( authBloc) {
return StreamBuilder(
stream: authBloc.validatePasswordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: authBloc.updateValidatePassword,
obscureText: true,
decoration: InputDecoration(
hintText: 'Re-enter password',
labelText: 'Confirm Password',
errorText: snapshot.error,
),
);
},
);
}
Widget nextBtn(authBloc) {
return StreamBuilder(
stream: authBloc.submitValid,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return RaisedButton(
child: Text('Next'),
color: Colors.deepOrange,
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7.0))
),
onPressed: snapshot.hasData
? () => Navigator.pushNamed(context, '/register')
: null,
);
}
);
}
Widget cancelBtn(){
return RaisedButton(
child: Text('Cancel'),
color: Colors.white30,
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(7.0))
),
onPressed: () => Navigator.pop(context),
);
}
#override
void dispose() {
super.dispose();
_authBloc.dispose();
}
Bloc Code:
/// REGISTER VARIABLES
static final _emailController = BehaviorSubject<
String>(); //RxDart's implementation of StreamController. Broadcast stream by default
static final _passwordController = BehaviorSubject<String>();
static final _validatePasswordController = BehaviorSubject<
String>(); // Will check that the password entered the 2nd time is correct
/// REGISTER STREAM & METHODS
//Retrieve data from the stream
Stream<String> get emailStream => _emailController.stream
.transform(performEmailValidation); //Return the transformed stream
Stream<String> get passwordStream =>
_passwordController.stream.transform(performPasswordValidation);
Stream<String> get validatePasswordStream =>
_validatePasswordController.stream.transform(performIsPasswordSame);
//Merging email, password and validate password
Stream<bool> get submitValid => Observable.combineLatest3(
emailStream, passwordStream, validatePasswordStream, (e, p1, p2) => true);
//Add data to the stream
Function(String) get updateEmail => _emailController.sink.add;
Function(String) get updatePassword => _passwordController.sink.add;
Function(String) get updateValidatePassword =>
_validatePasswordController.sink.add;
// performing user input validations
final performEmailValidation = StreamTransformer<String, String>.fromHandlers(
handleData: (email, sink) async {
String emailValidationRule =
r'^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = new RegExp(emailValidationRule);
if (await doesNameAlreadyExist("email", _emailController.value) == true)
sink.addError("That email already exists");
else if (regExp.hasMatch(email)) {
sink.add(email);
} else {
sink.addError(StringConstant.emailErrorMessage);
}
});
final performPasswordValidation =
StreamTransformer<String, String>.fromHandlers(
handleData: (_passwordController, sink) {
if (_passwordController.length >= 6) {
sink.add(_passwordController);
} else {
sink.addError(StringConstant.passwordErrorMessage);
}
});
final performIsPasswordSame = StreamTransformer<String, String>.fromHandlers(
handleData: (password, sink) {
if (password != _passwordController.value)
sink.addError(StringConstant.invalidPasswordMessage);
else
sink.add(password);
});
dispose() {
_emailController.close();
_passwordController.close();
_validatePasswordController.close();
}
Well, looking to full source and the GIF that you show I can see what is causing this problem.
Your mistake is call dispose() BLoC instance method in dispose() SingUp widget class method.
Why is a mistake?
In your specific case when you're in SingUp screen and go to a next route/screen dispose method from SingUp is called and in this moment the streams of your BLoC instance is going to be closed. But the next allows the user go back to SingUp screen and when this happen SingUp instance gets the same BLoC instance that was used before but this BLoC instance has the streams already closed.
How can I solve this in a simple way?
In SingUp class :
#override
void dispose() {
super.dispose();
/// DON'T CALL BLoC dispose here
/// _authBloc.dispose();
}
Don't dispose you BloC here because the user can back to this screen any moment. Since you're using InheritedWidget to get BLoC instance and this gives you access the same BLoC instance in different places I advise you call yourBloc.dispose() in the moment where the user is ending all the sing up process.