I am trying to create form.
I managed to create every widget in it, but every time I try to open TextFormField I get redirected back to my MainMenuScreen without any error.
I am using BLoC and routes. I think that issue might be related with using named routes.
Issue was not spotted before changing to named routes
MainMenuScreen fragment:
CategoryCard(
categoryName: 'Main dishes',
assetPath: 'assets/images/main_dish.png',
onPressed: () => Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext context) {
return BlocProvider.value(
value: BlocProvider.of<RecipesBloc>(context)
..add(LoadRecipesEvent())
..category = 'main_dish',
child: RecipesScreen(),
);
})),
),
From MainMenuScreen I redirect to RecipesScreen
Fragment of RecipesScreen with redirect to RecipeCreateForm:
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (BuildContext context) {
return RecipeCreateForm();
}),
),
and then I redirect to RecipeCreateForm where I'm using TextFormFields.
Whenever I try to use TextFormField I get redirected back to MainMenuScreen.
class RecipeCreateForm extends StatefulWidget {
#override
_RecipeCreateFormState createState() => _RecipeCreateFormState();
}
class _RecipeCreateFormState extends State<RecipeCreateForm> {
final _recipeNameController = TextEditingController();
final _imageUrl = TextEditingController();
String? _difficultyValue;
late int _ingredientsQuantity;
late int _preparationStepsQuantity;
late List<Ingredient> _ingredientsValues;
late List<PreparationStep> _preparationStepsValues;
late double _preparationTime;
String? _portions;
#override
void initState() {
_ingredientsQuantity = 1;
_preparationStepsQuantity = 1;
_ingredientsValues = [];
_preparationStepsValues = [];
_preparationTime = 0;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
leading: IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: Icon(
Icons.arrow_back,
color: Colors.white,
),
),
),
body: Scrollbar(
thickness: 10,
hoverThickness: 2,
child: SingleChildScrollView(
child: Container(
color: Colors.lightGreen.shade100,
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Recipe name',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
TextFormField(
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
controller: _recipeNameController,
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Image',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
TextFormField(
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
controller: _imageUrl,
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Difficulty',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
DropdownButton(
hint: _difficultyValue == null
? Text(
'Select difficulty',
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
)
: Text(
_difficultyValue!,
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
items: ['Easy', 'Medium', 'Hard'].map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
_difficultyValue = val as String;
},
);
},
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Preparation time',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
Slider(
value: _preparationTime,
onChanged: (newPreparationTime) {
setState(() => _preparationTime = newPreparationTime);
},
label: _preparationTime.toStringAsFixed(0),
min: 0,
max: 360,
divisions: 24,
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Ingredients',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
SizedBox(
height: 175,
child: Scrollbar(
child: ListView.builder(
itemCount: _ingredientsQuantity,
itemBuilder: (context, index) {
return _ingredientRow(index);
}),
),
),
Row(
children: [
IconButton(
icon: Icon(Icons.add),
onPressed: () async {
setState(() {
_ingredientsQuantity++;
});
}),
IconButton(
icon: Icon(Icons.delete),
onPressed: () async {
setState(() {
_ingredientsQuantity = 1;
_ingredientsValues.clear();
});
})
],
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Preparation steps',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
Scrollbar(
child: SizedBox(
height: 100,
child: ListView.builder(
shrinkWrap: true,
itemCount: _preparationStepsQuantity,
itemBuilder: (context, index) {
return _preparationStepRow(index);
}),
),
),
Row(
children: [
IconButton(
icon: Icon(Icons.add),
onPressed: () async {
setState(() {
_preparationStepsQuantity++;
});
}),
IconButton(
icon: Icon(Icons.delete),
onPressed: () async {
setState(() {
_preparationStepsQuantity = 1;
_preparationStepsValues.clear();
});
}),
],
),
Padding(
padding: EdgeInsets.only(top: 15),
),
Text(
'Portions',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
DropdownButton(
hint: _portions == null
? Text(
'Select number of portions',
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
)
: Text(
_portions!,
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
),
isExpanded: true,
iconSize: 30.0,
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontStyle: FontStyle.italic),
items: ['1', '2', '3', '4', '5', '6', '7'].map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(
() {
_portions = val as String;
},
);
},
),
ElevatedButton(
onPressed: () {
BlocProvider.of<RecipesBloc>(context).add(
AddRecipeEvent(
Recipe(
name: _recipeNameController.text,
image:
'https://www.thespruceeats.com/thmb/dA8o8EZpjJyeocYZNpzfknoKh2s=/4351x3263/smart/filters:no_upscale()/baked-stuffed-potatoes-482217-hero-01-850f2d87fe80403f923e140dbf5f1bf3.jpg',
ingredients: _ingredientsValues,
difficulty: _difficultyValue,
preparationTime: _preparationTime,
preparationSteps: _preparationStepsValues,
type: BlocProvider.of<RecipesBloc>(context)
.category
.toString(),
portions: _portions,
),
),
);
Navigator.of(context).pop();
},
child: Text('Submit'),
),
],
),
),
),
),
);
}
_ingredientRow(int key) {
return IntrinsicHeight(
child: Row(
children: [
Padding(padding: EdgeInsets.only(left: 10)),
SizedBox(
width: 225,
child: TextFormField(
maxLength: 35,
onChanged: (val) {
setState(() {
_onIngredientUpdate(key,name: val);
});
},
),
),
VerticalDivider(
width: 20,
thickness: 1,
color: Colors.black,
indent: 30,
endIndent: 10,
),
SizedBox(
width: 55,
child: TextFormField(
maxLength: 7,
initialValue: '0',
onChanged: (val) {
setState(() {
_onIngredientUpdate(key, quantity: val);
});
},
),
),
Padding(padding: EdgeInsets.only(left: 10)),
DropdownButton(
hint: Text('pcs'),
items: ['pcs', 'ml', 'g'].map(
(val) {
return DropdownMenuItem<String>(
value: val,
child: Text(val),
);
},
).toList(),
onChanged: (val) {
setState(() {
_onIngredientUpdate(key,measurement: val.toString());
});
},
)
],
),
);
}
_onIngredientUpdate(int key, {String? name, String? measurement, String? quantity}) {
int foundKey = -1;
_ingredientsValues.forEach((element) {
if (element.id.contains(key.toString())) {
foundKey = key;
}
});
if (-1 != foundKey) {
_ingredientsValues.removeWhere((map) {
return map.id == foundKey.toString();
});
}
Map<String, dynamic> json = {'id': key, 'name': name, 'measurement': measurement, 'quantity':quantity};
_ingredientsValues.add(json as Ingredient);
}
_preparationStepRow(int key) {
return IntrinsicHeight(
child: Row(
children: [
Padding(padding: EdgeInsets.only(left: 10)),
SizedBox(
width: 225,
height: 50,
child: TextFormField(
maxLength: 35,
onChanged: (val) => {
_onPreparationUpdate(key,val)
},
),
),
],
),
);
}
_onPreparationUpdate(int key, String val) {
int foundKey = -1;
_preparationStepsValues.forEach((element) {
if (element.id.contains(key.toString())) {
foundKey = key;
}
});
if (-1 != foundKey) {
_preparationStepsValues.removeWhere((map) {
return map.id == foundKey.toString();
});
}
Map<String, dynamic> json = {'id': key, 'step': val};
_preparationStepsValues.add(json as PreparationStep);
}
}
Issue GIF:
EDIT:
Issue is not related with form. I have replaced whole form with only one field without any logic and issue remains.
It is probably related to named routes.
As I was thinking, issue was related with usage of named routes.
I managed to bypass this issue with using Future.delayed and pushNamedAndRemoveUntil
In main_menu_screen I have created method which I later used to redirect to categories.
void redirectToCategory(BuildContext context, String categoryName) {
Future.delayed(Duration.zero, () {
Navigator.pushNamedAndRemoveUntil(
context,
'/recipeScreen',
(_) => false,
arguments: BlocProvider.value(
value: BlocProvider.of<RecipesBloc>(context)
..add(LoadRecipesEvent())
..category = categoryName,
child: RecipesScreen(),
),
);
});
how to make dropdown list when on tap on button with out using dropdown button #flutterenter image description here
new DropdownButton(
value: _selectedLocation,
onChanged: (String newValue) {
setState(() {
_selectedLocation = newValue;
});
},
items: _locations.map((String location) {
return new DropdownMenuItem<String>(
child: new Text(location),
);
}).toList(),
Try below code hope its helpful to you . you must used dropdown_below from here, Refer my answer here also for same
Create your list:
List mobileList = [
{'no': 1, 'mobile': 'Apple'},
{'no': 2, 'mobile': 'Google'},
{'no': 3, 'mobile': 'Samsung'},
{'no': 4, 'mobile': 'Sony'},
{'no': 5, 'mobile': 'LG'},
];
One varibale and list our value
List<DropdownMenuItem<Object?>> _dropdownTestItems = [];
var selectedmobile;
Create initState() and dispose() method:
#override
void initState() {
_dropdownTestItems = buildDropdownTestItems(mobileList);
super.initState();
}
#override
void dispose() {
super.dispose();
}
Add your selected gender value in dropdown
List<DropdownMenuItem<Object?>> buildDropdownTestItems(List mobileList) {
List<DropdownMenuItem<Object?>> items = [];
for (var i in mobileList) {
items.add(
DropdownMenuItem(
value: i,
child: Text(
i['mobile'],
style: TextStyle(color: Colors.black),
),
),
);
}
return items;
}
Your Widget:
Padding(
padding: const EdgeInsets.all(8.0),
child: DropdownBelow(
itemWidth: 150,
itemTextstyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black),
boxTextstyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.white54),
boxPadding: EdgeInsets.fromLTRB(13, 12, 13, 12),
boxWidth: 150,
boxHeight: 45,
boxDecoration: BoxDecoration(
color: Colors.transparent,
border: Border.all(
width: 1,
color: Colors.black,
),
),
icon: Icon(
Icons.arrow_downward,
color: Colors.black,
),
hint: Text(
'Select Mobile',
style: TextStyle(
color: Colors.black,
),
),
value: selectedmobile,
items: _dropdownTestItems,
onChanged: (selectedTest) {
setState(() {
selectedmobile = selectedTest;
});
},
),
),
Your Result Screen->
I want to show PopupMenu with dynamic data which will I got from API response. I tried with listview.builder inside PopupMenu child but it not works.
My code of showmenu()
void showMemberMenu() async {
await showMenu(
context: context,
position: RelativeRect.fromLTRB(200, 150, 100, 100),
items: [
PopupMenuItem(
value: 1,
child: Text(
"ROHIT",
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.bold,
fontFamily: 'Roboto',
color: green3),
),
),
PopupMenuItem(
value: 2,
child: Text(
"REKHA",
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.bold,
fontFamily: 'Roboto',
color: green3),
),
),
PopupMenuItem(
value: 3,
child: Text(
"DHRUV",
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.bold,
fontFamily: 'Roboto',
color: green3),
),
),
],
elevation: 8.0,
).then((value) {
if (value != null) print(value);
});
}
Please help to get out from this.
You can use List.generate method to generate the dynamic length of the list using the existing list you get from the API response.
Below is an example of how you can achieve it.
void showMemberMenu() async {
final List<String> popList = ['ROHIT', 'REKHA', 'DHRUV'];
await showMenu(
context: context,
position: RelativeRect.fromLTRB(200, 150, 100, 100),
items: List.generate(
popList.length,
(index) => PopupMenuItem(
value: 1,
child: Text(
popList[index],
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
fontFamily: 'Roboto',
),
),
),
),
elevation: 8.0,
).then((value) {
if (value != null) print(value);
});
}
I've added final List<String> popList = ['ROHIT', 'REKHA', 'DHRUV'];
just for the testing purpose, and you can replace it with your list
you get from API response.
Try this:
call the api and put the value in PopupMenuItem
class PopupMenu {
PopupMenu({#required this.title, #required this.onTap});
final String title;
final VoidCallback onTap;
static PopupMenuButton<String> createPopup(List<PopupMenu> popupItems) {
return PopupMenuButton<String>(
onSelected: (value) {
popupItems.firstWhere((e) => e.title == value).onTap();
},
itemBuilder: (context) => popupItems
.map((item) => PopupMenuItem<String>(
value: item.title,
child: Text(
item.title,
),
))
.toList(),
);
}
}
you can try to do something like this:
https://dartpad.dev/?id=a3f9002a37cbacc2cfae46174cbd2eba
you can add any state management to replace the FutureBuilder but this is the logical approach.
I hope it is helpful.
I want to put an information button that works just like an AlertDialog() but I'm having problems to put it next to the checkbox text since they're organized inside a Column() and I cannot just put a Row() inside of it.
Here is the code of the checkboxes:
Column (
// ...
RadioListTile(
title: Text('Pelagens de camundongos',
style: TextStyle(
fontSize: 17.0, color: Colors.white)),
value: 1,
groupValue: id,
onChanged: (val) {
setState(() {
predominant = 'recessiva_aa';
id = 1;
});
},
),
const SizedBox(
height: 5.0,
),
RadioListTile(
title: Text('Pelagem em cães labradores',
style: TextStyle(
fontSize: 17.0, color: Colors.white)),
value: 2,
groupValue: id,
onChanged: (val) {
setState(() {
predominant = 'recessiva_ee';
id = 2;
});
},
),
),
Inside of the title of your RadioListTile instead of putting a text widget you can put a row and inside of that row have Text and an IconButton that will pop up your alert dialog when pressed like this
Column(
children: [
RadioListTile(
title: Row(
children: [
Text('Pelagens de camundongos',
style: TextStyle(fontSize: 17.0, color: Colors.white)),
IconButton(
icon: Icon(
Icons.info_outline,
),
onPressed: () {
// code to pop up alert dialog
},
),
],
),
value: 1,
groupValue: id,
onChanged: (val) {
setState(() {
predominant = 'recessiva_aa';
id = 1;
});
},
),
const SizedBox(
height: 5.0,
),
RadioListTile(
title: Row(
children: [
Text('Pelagem em cães labradores',
style: TextStyle(fontSize: 17.0, color: Colors.white)),
IconButton(
icon: Icon(
Icons.info_outline,
),
onPressed: () {
// code to pop up alert dialog
},
),
],
),
value: 2,
groupValue: id,
onChanged: (val) {
setState(() {
predominant = 'recessiva_ee';
id = 2;
});
},
),
],
)
In PageViewBuilder, instead of swipe gesture I want to move to the next page using onTap on an ArgonTimerButton which I found on pub.dev. I created a controller and used controller.nextPage() but it gives me this error:
'positions.isNotEmpty': PageController.page cannot be accessed before a PageView is built with it.
My code:
final controller = PageController();
#override
Widget build(BuildContext context) {
MediaQueryData size = MediaQuery.of(context).size;
return PageView.builder(
physics: NeverScrollableScrollPhysics(),
controller: PageController(),
itemCount: widget.workoutExcercises.length,
itemBuilder: (context, index) {
return
//other widgets
ArgonTimerButton(
initialTimer: 3, // Optional
height: 50,
width: size.width * 0.55,
minWidth: width * 0.40,
color: Colors.white,
borderRadius: 5.0,
curve: Curves.easeInToLinear,
child: Text(
"Done?",
style: TextStyle(
color: Colors.black,
fontSize: 24,
fontWeight: FontWeight.w700),
),
loader: (timeLeft) {
return Text(
"Rest Time | $timeLeft",
style: TextStyle(
color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.w700),
);
},
onTap: (startTimer, btnState) {
if (btnState == ButtonState.Idle) {
controller.nextPage(duration: Duration(milliseconds:1000), curve: Curves.easeInToLinear);
}
},
),
],
),
);
},
);
How do I solve this?
You are creating the controller but you are not using it.
Instead of :
controller: PageController(),
Do:
controller: controller,