Flutter Dynamic Textfield Autocompletion - flutter

I use the library autocomplete_textfield to create a textfield with autocompletion. Every time the text is changed I make a request to fetch a specific list of users and then set them to the suggestion of my AutocompleteTextfield. The list seems to be updated (when I print(list.length)) but visually it isn't. Any idea?
My AutocompletionTextfield:
AutoCompleteTextField<User>(
textChanged: (item) async {
await model.searchRecommendation(item);
},
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(0)
),
labelText: 'Recommendations',
labelStyle: TextStyle(color: AppColors.blackColor)
),
key: autocompleteUserSearchTextFieldKey,
suggestionsAmount: 3,
controller: _userSearchController,
itemSubmitted: (item) {},
suggestions: model.userRecommendations,
itemBuilder: (context, suggestion) => new Padding(
padding: EdgeInsets.all(16),
child: ListTile(
title: Text(suggestion.email)
),
),
itemSorter: (a, b) => a.email.compareTo(b.email),
itemFilter: (suggestion, input) => suggestion.email.toLowerCase().startsWith(input.toLowerCase()),
),
my viewmodel:
List<User> userRecommendations = [];
Future searchRecommendation(String filter) async {
var token = await SharedPreferenceUtils().getStringValue('jwt');
final response = await _api.filterUserSearch(filter, currentUser, token);
if (response is SuccessState) {
List<dynamic> tmp = response.value.payload;
tmp ??= [];
userRecommendations = List<User>.from(tmp.map((x) => User.fromJson(x)));
notifyListeners();
} else if (response is ErrorState) {
String error = response.msg;
print('Error $error');
} else {
print('Error');
}
}

Nvm I found the answer! AutocompleteTextfield has a method called updateSuggestions(). I just had to use it.

I don't see all of your code, but I assume the widget tree isn't being rebuilt. You have to wrap AutoCompleteTextField with FutureBuilder or StreamBuilder (depending on your implementation, anyway StreamBuilder seems more appropriate there) that listens to recommendations result.
You could also implement StatefulWidget or use StatefulBuilder.
Example with StatefulBuilder (it's dirty but should work, ideally you'd use StreamBuilder):
return StatefulBuilder(
builder: (context, setState) => AutoCompleteTextField<User>(
textChanged: (item) async {
await model.searchRecommendation(item);
setState(() {});
},
decoration: InputDecoration(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(0)),
labelText: 'Recommendations',
labelStyle: TextStyle(color: AppColors.blackColor)),
key: autocompleteUserSearchTextFieldKey,
suggestionsAmount: 3,
controller: _userSearchController,
itemSubmitted: (item) {},
suggestions: model.userRecommendations,
itemBuilder: (context, suggestion) => new Padding(
padding: EdgeInsets.all(16),
child: ListTile(title: Text(suggestion.email)),
),
itemSorter: (a, b) => a.email.compareTo(b.email),
itemFilter: (suggestion, input) => suggestion.email.toLowerCase().startsWith(input.toLowerCase()),
),
);

Related

How to add search history functionality in flutter app

I have recently add search functionality in flutter app and I want to add search history functionality in my flutter app. I am stuck in this I have no idea that how to add it.
Please give me instuction and also provide suitable code for it .
My code
TextFormField(
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
controller: searchcontroller,
focusNode: fousnode,
onChanged: (query) {
setState(() {
FirebaseFirestore.instance
.collectionGroup("Add_product")
.where("product_name", isGreaterThanOrEqualTo: query)
.where("product_name", isLessThan: query + 'z')
.snapshots();
});
},
decoration: InputDecoration(
hintText: "Search Product",
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(11)),
),
),
)
StreamBuilder(
stream:
FirebaseFirestore.instance.collectionGroup("Add_product").snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
List<QueryDocumentSnapshot> data = snapshot.data!.docs;
if (searchcontroller.text.isNotEmpty) {
data = data.where((doc) => doc["product_name"]
.toLowerCase()
.contains(searchcontroller.text.toLowerCase()))
.toList();
}
return GridView.builder(
itemCount:data.length,
itemBuilder: (itemBuilder, index) {
return Container(
child: InkWell(
onTap: (){
Navigator.push(context, MaterialPageRoute(builder: (builder)=>detail(
url: snapshot.data!.docs[index]["url"],
productName: snapshot.data!.docs[index]["product_name"],
productPrice: snapshot.data!.docs[index]["product_price"],
)));
},
child: ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
snapshot.data!.docs[index]["url"]
),
),
title: Text(snapshot.data!.docs[index]["product_name"])
),
),
),
);
},
);
}),
every time a search is performed you can create a list or a map and save it locally or in the database according to your needs. To then make the data that you are going to recover appear under the search bar, I recommend using existing plugins such as material_floating_search_bar
example with shared preferences (locally):
final prefs = await SharedPreferences.getInstance();
List<String> _currentResearch = prefs.getStringList("searches") ?? [];
_currentResearch.add(searchcontroller.text);
await prefs.setStringList("searches", _ricercheAttuali);

RawAutoComplete initialValue not setting in Flutter

I was able to get initialValue to work in AutoComplete. But there is a bug where the drop down goes off screen. So I found a workaround on slack and I am not using RawAutoComplete and trying to get the initial Value to work. I tried to set it in RawAutoComplete with:
child: RawAutocomplete<String>(
initialValue: TextEditingValue(text: itemTypeController.text),
When I look at the documentation I see:
This parameter is ignored if [textEditingController] is defined
But I am not sure how to set it otherwise.
I initially tried to set it in the TextFormField like so:
child: TextFormField(
controller: itemTypeController,
initialValue: "test",
focusNode: focusNode,
onEditingComplete: onEditingComplete,
decoration: const InputDecoration(
labelText: "Item type*",
hintText: 'What is the item?',
),
),
But that throws this error:
'initialValue == null || controller == null': is not true.
Which I assume is because if controller is present it woudl take the initial value from there. If both are not null then it doesnt know what to pic. I need the controller because I need to retrieve the value in the form to submit to my database.
Full code below:
LayoutBuilder(
builder: (context, constraints) => InputDecorator(
decoration: const InputDecoration(
icon: Icon(Icons.style),
border: InputBorder.none,
),
child: RawAutocomplete<String>(
initialValue: TextEditingValue(text: itemTypeController.text),
// first property
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return itemTypeList;
}
return itemTypeList.where((String option) {
return option
.toLowerCase()
.contains(textEditingValue.text.toLowerCase());
});
},
//second property where you can limit the overlay pop up suggestion
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<String> onSelected,
Iterable<String> options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: SizedBox(
height: 200.0,
// set width based on you need
width: constraints.biggest.width * 0.8,
child: ListView.builder(
padding: const EdgeInsets.all(8.0),
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final String option = options.elementAt(index);
return GestureDetector(
onTap: () {
onSelected(option);
},
child: ListTile(
title: Text(option),
),
);
},
),
),
),
);
},
// third property
fieldViewBuilder:
(context, controller, focusNode, onEditingComplete) {
itemTypeController = controller;
return Focus(
onFocusChange: (hasFocus) {
if (temperatureItemTypes
.contains(itemTypeController.text.trim())) {
//show temperature field
setState(() {
temperatureField = true;
});
} else {
setState(() {
temperatureField = false;
});
}
if (volumeItemTypes
.contains(itemTypeController.text.trim())) {
//show temperature field
setState(() {
volumeField = true;
});
} else {
setState(() {
volumeField = false;
});
}
},
child: TextFormField(
controller: itemTypeController,
focusNode: focusNode,
onEditingComplete: onEditingComplete,
decoration: const InputDecoration(
labelText: "Item type*",
hintText: 'What is the item?',
),
),
);
}),
),
);

Flutter - How to reset Autocomplete list after fetching data from server?

I have Autocomplete list:
List<CompanyName> companyNames = <CompanyName>[
const CompanyName(name: 'No Data'),
];
And this works, only one item No Data is on the array, but that array is filled by data from the server, and the problem is when you press autocomplete on start you will see the No Data item on the list, after server fetching data, the list will not be updated by data from the server.
My idea is to create a local variable that will be updated by the async call, and that variable should hide autocomplete list before the server responds, or refresh the (re-render) widget after fetching...
Autocomplete<CompanyName>(
optionsBuilder:
(TextEditingValue textEditingValue) {
return companyNames.where((CompanyName companyName) {
return companyName.name.toLowerCase().contains(textEditingValue.text.toLowerCase());
}).toList();
},
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<CompanyName>
onSelected,
Iterable<CompanyName> options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
child: ConstrainedBox(constraints: const BoxConstraints(maxHeight: 280,),
child: SizedBox(width: 280,
height: companyNames.length <= 1 ? 80 : 280,
child: ListView.builder(padding: const EdgeInsets.all(10.0),
itemCount: options.length, itemBuilder: (BuildContext context, int index) { final CompanyName option = options.elementAt(index);
return GestureDetector(
onTap: () { onSelected(option); },
child: ListTile( title: Text(option.name, style: TextStyle(color: isDarkMode ? Colors.white : Colors.black)),
),
);
})))));
},
fieldViewBuilder:
(context,
controller,
focusNode,
onEditingComplete) {
return TextFormField(
controller:
controller,
focusNode:
focusNode,
onEditingComplete:
onEditingComplete,
keyboardType:
TextInputType
.text,
autocorrect:
false,
decoration: InputDecoration(
isDense: true,
hintText: "Company Name",
border: OutlineInputBorder(
borderRadius:
BorderRadius.circular(10.0),
),
fillColor: isDarkMode ? const Color(0XFF212124) : Colors.white,
filled: true),
validator: (value) {
if (value ==
null ||
value.isEmpty) {
return 'Please enter company name';
} else {
setState(
() {
client =
value;
});
}
return null;
});
},
onSelected:
(CompanyName
selection) {
setState(() {
brokerCompany =
selection
.name;
});
},
displayStringForOption:
(CompanyName
option) =>
option
.name,
),
What is the best option and where is the best option to put the variable and re-render Autocomplete()?

How to implement validation with keyboard done button Flutter, observable, streamBuilder

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,
);
});
});
}

Flutter StreamBuilder not working after Page Reload

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.