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)
Related
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?',
),
),
);
}),
),
);
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()?
This is my current code. The "Field required" does not show up when the input is null.
class _HomeState extends State<Home> {
final _formkey = GlobalKey<FormState>();
final FocusNode _nameFocusNode = FocusNode();
final TextEditingController _nameController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
//padding: const EdgeInsets.all(15.0),
key: _formkey,
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(20.0)
),
),
),
TextFormField(
keyboardType: TextInputType.name,
textInputAction: TextInputAction.next,
focusNode: _nameFocusNode,
onFieldSubmitted: (String value) {
_nextFocus(_addressFocusNode);
},
controller: _nameController,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Field required';
}
return null;
},
And this is the submit button code:
ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all<Color>(Colors.deepOrange),
),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: [
Text(
'Name:',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(_nameController.text),
),
onPressed: () => Navigator.pop(context, 'Confirm'),
child: const Text('Confirm'),
),
],
)),
);
},
);
},
child: Text('Submit'),
),
Furthermore, according to flutter docs (https://docs.flutter.dev/cookbook/forms/validation), I'm sure I have to insert this portion of code on my submit button but I can't find the right way to integrate it.
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
}
I've inserted the
if (_formKey.currentState!.validate())
after "onPressed" and before "showDialog" but I get the error "Undefined name '_formKey'"
You need to wrap your widget with a Form and pass your key to that widget (because you want a FormState from that GloabaaKey)
return Form(
key: _formKey,
child: Scaffold(...),
);
That way your GlobalKey will actually reference a FormState and you can pass that to any onPressed method
onPressed: () {
// Validate returns true if the form is valid, or false otherwise.
if (_formKey.currentState!.validate()) {
showDialod(...);
} else {
/// show error message or do something
}
I have a function in flutter that opens a dialogue box with a list of text fields. The size of the list is determined by an argument to the function. I want to move focus to the next item as each is entered. I have tried creating a list of focus nodes to do this, but it doesn't work. Can someone point out what I am doing wrong. The code I have is shown below.
Future<bool> getPositions(BuildContext context, List<String> positions) async
{
int numPositions = positions.length ;
final List<FocusNode> _fNodes = new List<FocusNode>.generate(numPositions, (_) => new FocusNode()) ;
List<Widget> _widgets = new List<Widget>(numPositions) ;
for (int i = 0 ; i < numPositions ; ++i)
{
int p = i+1;
_widgets[i] = new TextField(
style: TextStyle(fontSize: 15.0),
autofocus: true,
focusNode: _fNodes[i],
controller: TextEditingController(text: positions[i]),
textInputAction: p < numPositions ? TextInputAction.next : TextInputAction.done,
onChanged: (value)
{
if (value.trim().isNotEmpty)
positions[i] = value.trim();
},
onSubmitted: (value)
{
if (value.trim().isNotEmpty)
positions[i] = value.trim();
if (p >= numPositions)
Navigator.pop(context, true);
else
_fieldFocusChange(context, _fNodes[i], _fNodes[p]);
},
);
}
return showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (context)
{
return AlertDialog(
title: Text("${Config.position} names", style: TextStyle(fontSize: 15.0)),
titlePadding: EdgeInsets.all(10.0),
contentPadding: EdgeInsets.all(0.0),
content: new Container(
width: double.maxFinite,
height: 300.0,
child: ListView(
padding: const EdgeInsets.all(8.0),
children: _widgets,
),
),
actions: <Widget>[
new FlatButton(
child: const Text('CANCEL'),
onPressed: ()
{
Navigator.of(context).pop(false);
}),
new FlatButton(
child: const Text("OK"),
onPressed: ()
{
Navigator.of(context).pop(true);
}),
],
);
},
);
}
void _fieldFocusChange(BuildContext context, FocusNode currentFocus,FocusNode nextFocus)
{
currentFocus.unfocus();
FocusScope.of(context).requestFocus(nextFocus);
}
I suggest adding autofocus: i == 0, else every node is requesting focus on build.
Minimal Viable Example:
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
int listLength = 4;
List<FocusNode> _focusNodes = List<FocusNode>.generate(listLength, (int index) => FocusNode());
List<Widget> textFields = List<Widget>.generate(listLength, (int index) => TextField(
controller: TextEditingController(),
focusNode: _focusNodes[index],
onSubmitted: (String value){
if (index < listLength){
_focusNodes[index+1].requestFocus();
}
},
));
return Scaffold(
body: Container(
child: ListView(
padding: const EdgeInsets.all(8.0),
children: textFields
),
),
);
}
}
I'm new at Provider package. and Just making demo app for learning purpose.
Here is my code of simple Form Widget.
1) RegistrationPage (Where my app is start)
class RegistrationPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("Title"),
),
body: MultiProvider(providers: [
ChangeNotifierProvider<UserProfileProvider>.value(value: UserProfileProvider()),
ChangeNotifierProvider<RegiFormProvider>.value(value: RegiFormProvider()),
], child: AllRegistrationWidgets()),
);
}
}
class AllRegistrationWidgets extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SetProfilePicWidget(),
RegistrationForm(),
],
),
),
),
BottomSaveButtonWidget()
],
),
),
);
}
}
class BottomSaveButtonWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final _userPicProvider =
Provider.of<UserProfileProvider>(context, listen: false);
final _formProvider =
Provider.of<RegiFormProvider>(context, listen: false);
return SafeArea(
bottom: true,
child: Container(
margin: EdgeInsets.all(15),
child: FloatingActionButton.extended(
heroTag: 'saveform',
icon: null,
label: Text('SUBMIT',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
)),
onPressed: () {
print(_userPicProvider.strImageFileName);
_formProvider.globalFormKey.currentState.validate();
print(_formProvider.firstName);
print(_formProvider.lastName);
},
)),
);
}
}
2) RegistrationForm
class RegistrationForm extends StatefulWidget {
#override
_RegistrationFormState createState() => _RegistrationFormState();
}
class _RegistrationFormState extends State<RegistrationForm> {
TextEditingController _editingControllerFname;
TextEditingController _editingControllerLname;
#override
void initState() {
_editingControllerFname = TextEditingController();
_editingControllerLname = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
final formProvider = Provider.of<RegiFormProvider>(context);
return _setupOtherWidget(formProvider);
}
_setupOtherWidget(RegiFormProvider _formProvider) {
return Container(
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SizedBox(height: 20),
Text(
'Fields with (*) are required.',
style: TextStyle(fontStyle: FontStyle.italic),
textAlign: TextAlign.left,
),
SizedBox(height: 20),
_formSetup(_formProvider)
],
),
);
}
_formSetup(RegiFormProvider _formProvider) {
return Form(
key: _formProvider.globalFormKey,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: _editingControllerFname,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
labelText: "First Name *",
hintText: "First Name *",
),
onSaved: (value) {},
validator: (String value) =>
_formProvider.validateFirstName(value)),
SizedBox(height: 15),
TextFormField(
controller: _editingControllerLname,
textCapitalization: TextCapitalization.sentences,
validator: (String value) =>
_formProvider.validateLastName(value),
onSaved: (value) {},
decoration: InputDecoration(
labelText: "Last Name *",
hintText: "Last Name *",
),
)
],
),
),
);
}
#override
void dispose() {
_editingControllerFname.dispose();
_editingControllerLname.dispose();
super.dispose();
}
}
3) RegiFormProvider
class RegiFormProvider with ChangeNotifier {
final GlobalKey<FormState> globalFormKey = GlobalKey<FormState>();
String _strFirstName;
String _strLasttName;
String get firstName => _strFirstName;
String get lastName => _strLasttName;
String validateFirstName(String value) {
if (value.trim().length == 0)
return 'Please enter first name';
else {
_strFirstName = value;
return null;
}
}
String validateLastName(String value) {
if (value.trim().length == 0)
return 'Please enter last name';
else {
_strLasttName = value;
return null;
}
}
}
Here you can see, RegiFormProvider is my first page where other is children widgets in widget tree. I'm using final GlobalKey<FormState> globalFormKey = GlobalKey<FormState>(); in the RegiFormProvider provider, Because I want to access this in the 1st RegistrationPage to check my firstName and lastName is valid or not.
I'm using a builder widget to get form level context like below , and then easily we can get the form instance by using that context. by this way we don't need global key anymore.
Form(
child: Builder(
builder: (ctx) {
return ListView(
padding: EdgeInsets.all(12),
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: "Title"),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
initialValue: formProduct.title,
validator: validateTitle,
onSaved: (value) {
formProduct.title = value;
},
),
TextFormField(
decoration: InputDecoration(labelText: "Price"),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
initialValue: formProduct.price == null
? ""
: formProduct.price.toString(),
keyboardType: TextInputType.number,
validator: validatePrice,
onSaved: (value) {
formProduct.price = double.parse(value);
},
),
TextFormField(
decoration: InputDecoration(labelText: "Description"),
textInputAction: TextInputAction.next,
initialValue: formProduct.description,
maxLines: 3,
validator: validateDescription,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
onSaved: (value) {
formProduct.description = value;
},
),
TextFormField(
decoration: InputDecoration(labelText: "Image Url"),
textInputAction: TextInputAction.done,
onFieldSubmitted: (_) => FocusScope.of(context).unfocus(),
initialValue: formProduct.imageUrl,
validator: validateImageUrl,
onSaved: (value) {
formProduct.imageUrl = value;
},
),
Padding(
padding: EdgeInsets.all(10),
child: FlatButton(
color: Colors.amberAccent,
onPressed: () {
if (Form.of(ctx).validate()) {
Form.of(ctx).save();
formProduct.id =
Random.secure().nextDouble().toString();
ProductsProvider provider =
Provider.of<ProductsProvider>(context,
listen: false);
editing
? provider.setProduct(formProduct)
: provider.addProduct(formProduct);
Router.back(context);
}
},
child: Text("Save"),
),
)
],
);
},
),
)
you can see the Form.of(ctx) gives us the current level form.