How to generate multiple Dropdown dynamically in Flutter? - flutter

I have a Java background and new to Flutter. I have stuck in a scenario where I need to create multiple dropdown dynamically. For instance, There is a Pizza deal offers 2 Large pizza, 2 Small pizza and 1 drink. So, Whenever customer select any pizza He/She must need to select a flavor to it. If there is 2 Large pizza what i need to generate is 2 dropdown list with defined flavor so that customer can select 2 different flavor and want to save them in separate variable so that, I can get the value later on, and the same goes for 2 small pizza. In this deal, I have to create 5 dropdown and the quantity of dropdown varies along the deal they offer. How can I achieve this in Flutter?

You can copy paste run full code below
You can use ListView, when add data to List like List<CartItem>, DropdownButton will show
You can for loop List<CartItem> to summary data you need like quantity
code snippet
class _CartWidgetState extends State<CartWidget> {
#override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Pizza(cartItem: widget.cart[widget.index])),
Expanded(child: Flavor(cartItem: widget.cart[widget.index])),
Expanded(
child: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
print(widget.index);
widget.cart.removeAt(widget.index);
widget.callback();
});
...
ListView.builder(
key: UniqueKey(),
itemCount: cart.length,
itemBuilder: (BuildContext ctxt, int index) {
return CartWidget(
cart: cart, index: index, callback: refresh);
}),
output of working demo when click print button
I/flutter (14508): Pizza 1
I/flutter (14508): Pizza 2
I/flutter (14508): Pizza 4
working demo
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class Flavor extends StatefulWidget {
CartItem cartItem;
Flavor({this.cartItem});
#override
_FlavorState createState() => _FlavorState();
}
class _FlavorState extends State<Flavor> {
String _value = "Flavor 1";
#override
void initState() {
super.initState();
_value = widget.cartItem.flavor;
}
#override
void didUpdateWidget(Flavor oldWidget) {
if (oldWidget.cartItem.flavor != widget.cartItem.flavor) {
_value = widget.cartItem.flavor;
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return Container(
child: DropdownButton(
value: _value,
items: [
DropdownMenuItem(
child: Text("Flavor 1"),
value: "Flavor 1",
),
DropdownMenuItem(
child: Text("Flavor 2"),
value: "Flavor 2",
),
DropdownMenuItem(child: Text("Flavor 3"), value: "Flavor 3"),
DropdownMenuItem(child: Text("Flavor 4"), value: "Flavor 4")
],
onChanged: (value) {
setState(() {
_value = value;
widget.cartItem.flavor = value;
});
}),
);
}
}
class Pizza extends StatefulWidget {
CartItem cartItem;
Pizza({this.cartItem});
#override
_PizzaState createState() => _PizzaState();
}
class _PizzaState extends State<Pizza> {
String _value = "";
#override
void initState() {
super.initState();
_value = widget.cartItem.itemName;
}
#override
void didUpdateWidget(Pizza oldWidget) {
if (oldWidget.cartItem.itemName != widget.cartItem.itemName) {
_value = widget.cartItem.itemName;
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return Container(
child: DropdownButton(
value: _value,
items: [
DropdownMenuItem(
child: Text("Pizza 1"),
value: "Pizza 1",
),
DropdownMenuItem(
child: Text("Pizza 2"),
value: "Pizza 2",
),
DropdownMenuItem(child: Text("Pizza 3"), value: "Pizza 3"),
DropdownMenuItem(child: Text("Pizza 4"), value: "Pizza 4")
],
onChanged: (value) {
setState(() {
_value = value;
widget.cartItem.itemName = value;
});
}),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class CartItem {
String productType;
String itemName;
String flavor;
CartItem({this.productType, this.itemName, this.flavor});
}
class CartWidget extends StatefulWidget {
List<CartItem> cart;
int index;
VoidCallback callback;
CartWidget({this.cart, this.index, this.callback});
#override
_CartWidgetState createState() => _CartWidgetState();
}
class _CartWidgetState extends State<CartWidget> {
#override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Pizza(cartItem: widget.cart[widget.index])),
Expanded(child: Flavor(cartItem: widget.cart[widget.index])),
Expanded(
child: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
print(widget.index);
widget.cart.removeAt(widget.index);
widget.callback();
});
},
),
)
],
);
}
}
class _MyHomePageState extends State<MyHomePage> {
List<CartItem> cart = [];
void refresh() {
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.builder(
key: UniqueKey(),
itemCount: cart.length,
itemBuilder: (BuildContext ctxt, int index) {
return CartWidget(
cart: cart, index: index, callback: refresh);
}),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
onPressed: () {
cart.add(CartItem(
productType: "pizza",
itemName: "Pizza 1",
flavor: "Flavor 1"));
setState(() {});
},
child: Text("add Pizza"),
),
RaisedButton(
onPressed: () {
for (int i = 0; i < cart.length; i++) {
print(cart[i].itemName);
}
},
child: Text("Print Pizza"),
),
],
)
],
),
),
);
}
}

You can use collection-if in your UI code to show those dropdowns when a certain condition is met.
Widget build() {
return Column(
children: [
pizzaSelector(),
if (pizzaIsSelected)
flavorSelector(),
]
);
}
Andrea has a good video explaining collection-if and spread operators which I think will help you.

Related

grouped multi select dropdown in flutter

after long search for create multi select dropdown in flutter i found tow solutions
first one with custom class :
Is there an equivalent widget in flutter to the "select multiple" element in HTML
scound one with the package :
multi_select_flutter
But what I want is how to make a grouped dropdown in either of these two ways Because giving a title to each option group is very important in my case like this:
In the items, list set the type to data to add checkbox or to sep to add a title. The output from the dialog will be a dictionary in the form of {2,3} where value 2 = Cordoba.
Full Code
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
class MultiSelectDialogItem<V> {
V value;
String name;
String type;
MultiSelectDialogItem(
{required this.name, required this.type, required this.value});
}
class MultiSelectDialog<V> extends StatefulWidget {
const MultiSelectDialog({
Key? key,
required this.items,
required this.initialSelectedValues,
}) : super(key: key);
final List<MultiSelectDialogItem<V>> items;
final Set<V> initialSelectedValues;
#override
State<StatefulWidget> createState() => _MultiSelectDialogState<V>();
}
class _MultiSelectDialogState<V> extends State<MultiSelectDialog<V>> {
final _selectedValues = <V>{};
#override
void initState() {
super.initState();
_selectedValues.addAll(widget.initialSelectedValues);
}
void _onItemCheckedChange(V itemValue, bool checked) {
setState(() {
if (checked) {
_selectedValues.add(itemValue);
} else {
_selectedValues.remove(itemValue);
}
});
}
void _onCancelTap() {
Navigator.pop(context);
}
void _onSubmitTap() {
Navigator.pop(context, _selectedValues);
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Select place'),
contentPadding: const EdgeInsets.all(20.0),
content: SingleChildScrollView(
child: ListTileTheme(
contentPadding: const EdgeInsets.fromLTRB(14.0, 0.0, 24.0, 0.0),
child: ListBody(
children: widget.items.map(_buildItem).toList(),
),
),
),
actions: <Widget>[
ElevatedButton(
onPressed: _onCancelTap,
child: const Text('CANCEL'),
),
ElevatedButton(
onPressed: _onSubmitTap,
child: const Text('OK'),
)
],
);
}
Widget _buildItem(MultiSelectDialogItem<V> item) {
final checked = _selectedValues.contains(item.value);
return item.type == "data"
? CheckboxListTile(
value: checked,
title: Text(item.name),
controlAffinity: ListTileControlAffinity.leading,
onChanged: (checked) => _onItemCheckedChange(item.value, checked!),
)
: Container(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
item.name,
style: TextStyle(color: Color.fromARGB(255, 91, 91, 91)),
),
),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
#override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends State<HomeScreen> {
void _showMultiSelect(BuildContext context) async {
final items = <MultiSelectDialogItem<int>>[
MultiSelectDialogItem(name: 'Argentina', type: 'sep', value: 1),
MultiSelectDialogItem(name: 'Cordoba', type: 'data', value: 2),
MultiSelectDialogItem(name: 'Chaco', type: 'data', value: 3),
MultiSelectDialogItem(name: 'Buenos Aires', type: 'data', value: 4),
MultiSelectDialogItem(name: 'USA', type: 'sep', value: 5),
MultiSelectDialogItem(name: 'California', type: 'data', value: 6),
MultiSelectDialogItem(name: 'Florida', type: 'data', value: 7),
];
final selectedValues = await showDialog<Set>(
context: context,
builder: (BuildContext context) {
return MultiSelectDialog(
items: items,
initialSelectedValues: [].toSet(),
);
},
);
print(selectedValues);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: Text("show dialog"),
onPressed: () {
_showMultiSelect(context);
},
),
),
);
}
}
Output
Hope this helps. Happy Coding :)

Access List from statefulwidget to state

I want to pass a List from screen 1 to screen 2 statefulwidget and want to add data to it.
List type Question,
class Question {
String questionText;
String answerText;
Question({this.questionText, this.answerText});
}
I passed the list to 2nd screen
class CardPage extends StatefulWidget {
final List<Question> questionBank;
CardPage({#required this.questionBank});
#override
......
I added the content to the list from state,
TextField(onChanged: (text) {question = text;}),
TextField(onChanged: (text) {answer = text;}),
FlatButton(
child: Text("Create"),
onPressed: () {setState(() {
questionBank.add(Question(questionText: question, answerText: answer));});
}
)
Bt I don't know how to connect the List in stateful widget to the state to access it. I know there is widget for it but don't know how to completely import the list to state with it.
Anyone help me
You can pass a function instead of list to your CardPage. It should be called when you create a new question. I think it is the most simple solution.
You CardPage should be like this:
class CardPage extends StatefulWidget {
final Function(Question) createQuestion;
CardPage({Key key, #required this.createQuestion}) : super(key: key);
#override
State<StatefulWidget> createState() => _CardPageState();
}
class _CardPageState extends State<CardPage> {
String _question = '';
String _answer = '';
#override
Widget build(BuildContext context) {
return Column(children: [
TextField(onChanged: (text) {
_question = text;
}),
TextField(onChanged: (text) {
_answer = text;
}),
FlatButton(
child: Text("Create"),
onPressed: () {
setState(() {
final question =
Question(questionText: _question, answerText: _answer);
widget.createQuestion(question);
});
})
]);
}
}
Question lists owner state should be like this:
class _FirstWidgetState extends State<FirstWidget> {
final List<Question> questionBank = [];
#override
Widget build(BuildContext context) {
return ...
CardPage(createQuestion: _createQuestion);
...
}
void _createQuestion(Question question) {
setState(() {
questionBank.add(question);
});
}
}
Try this
simple demo.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_State createState() => _State();
}
class _State extends State<MyApp> {
final List<String> names = <String>['apple', 'samsung', 'shirsh'];
TextEditingController nameController = TextEditingController();
void addItemToList(){
setState(() {
names.insert(0,nameController.text);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Example'),
),
body: Column(
children: <Widget>[
Row(
children: [
Expanded(
flex: 5,
child: Padding(
padding: EdgeInsets.all(20),
child: TextField(
controller: nameController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Contact Name',
),
),
),
),
Expanded(
flex: 2,
child: RaisedButton(
child: Text('Add Item'),
onPressed: () {
if(nameController.text.toString()!="")
addItemToList();
},
),
)
],
),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: names.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
margin: EdgeInsets.all(2),
color: Colors.cyan,
child: Center(
child: Text('${names[index]}',
style: TextStyle(fontSize: 18),
)
),
);
}
)
)
]
)
);
}
}

flutter - creating dropdown button with data from props

i'm learning flutter for some time and i have a problem that i dont know how to solve.
I'm trying to create a dropdown button with data that I pass from the parent,data is a list of words like ['work','hobby','social'] etc.
My problem is the dropdown button after changing the value is still showing the initial one i think the problem is in the place when i initialize the "dropdownValue" because i do it in a build method but i cant acces properties from outside of that widget.
class TaskSheet extends StatefulWidget {
#override
_TaskSheetState createState() => _TaskSheetState();
final List categories;
TaskSheet(this.categories);
**// HERE I RECIVE THE LIST**
}
class _TaskSheetState extends State<TaskSheet> {
String dropdownValue;
// I CANT ASSIGN VALUE HERE BECAUSE USING widget.categories DONT WORK HERE AND IT MUST BE INSIDE BUILD METHOD
#override
Widget build(BuildContext context) {
var categoriesList = widget.categories
.map(
(category) => DropdownMenuItem(
value: category.title,
child: Text(category.title),
),
)
.toList();
dropdownValue = categoriesList[0].value; // THIS VALUE ALWAYS STAY THE SAME
return BottomSheet(
builder: (context) => Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 16),
child: Column(
children: [
Text('Add new task'),
TextField(
decoration: InputDecoration(labelText: 'What you wanna do?'),
),
DropdownButton(
icon: Icon(Icons.keyboard_arrow_down),
focusColor: Theme.of(context).primaryColor,
value: dropdownValue,
onChanged: (newValue) {
setState(() {
dropdownValue = newValue; // THIS HAVE NO IMPACT ON INITAL VALUE
});
},
items: categoriesList,
),
],
),
),
onClosing: () {},
);
}
}
I think if i find a way to acces widget.props from oustide of build method it going to work but I dont know how to do this
You can copy paste run full code below
You can see working demo below
Step 1: Category extends Equatable
import 'package:equatable/equatable.dart';
class Category extends Equatable {
String title;
Category({this.title});
#override
List<Object> get props => [title];
}
Step 2: dropdownValue is Category not String and use initState()
Category dropdownValue;
#override
void initState() {
dropdownValue = widget.categories[0];
super.initState();
}
Step 3: value is category
DropdownMenuItem<Category>(
value: category,
working demo
full code
import 'package:flutter/material.dart';
import 'package:equatable/equatable.dart';
class Category extends Equatable {
String title;
Category({this.title});
#override
List<Object> get props => [title];
}
class TaskSheet extends StatefulWidget {
#override
_TaskSheetState createState() => _TaskSheetState();
final List<Category> categories;
TaskSheet(this.categories);
}
class _TaskSheetState extends State<TaskSheet> {
Category dropdownValue;
#override
void initState() {
dropdownValue = widget.categories[0];
super.initState();
}
#override
Widget build(BuildContext context) {
var categoriesList = widget.categories
.map(
(category) => DropdownMenuItem<Category>(
value: category,
child: Text(category.title),
),
)
.toList();
return BottomSheet(
builder: (context) => Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 16),
child: Column(
children: [
Text('Add new task'),
TextField(
decoration: InputDecoration(labelText: 'What you wanna do?'),
),
DropdownButton<Category>(
icon: Icon(Icons.keyboard_arrow_down),
focusColor: Theme.of(context).primaryColor,
value: dropdownValue,
onChanged: (newValue) {
setState(() {
dropdownValue =
newValue; // THIS HAVE NO IMPACT ON INITAL VALUE
});
print(dropdownValue.title);
},
items: categoriesList,
),
],
),
),
onClosing: () {},
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TaskSheet([
Category(title: "work"),
Category(title: "hobby"),
Category(title: "social")
]),
],
),
),
);
}
}

List for a class clears out after making new widget

Im trying to learn flutter, but i have stumbled upon a problem i can't solve. I have a class MyApp/MyAppState that has a list of widgets (ovelser), that is used in a listVeiw.builder.
import './barbutton.dart';
import './ovelser.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
// This widget is the root of your application.
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
List<Widget> ovelser = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("progresjon"),
backgroundColor: Colors.blue,
actions: <Widget>[AddButton(nameOvelse)],
),
body: ListView.builder(
itemCount: ovelser.length,
itemBuilder: (context, index) {
final Widget ovelse = ovelser[index]; // lagrer bare ovelse objektet
return Dismissible(
// dismissible gjør det mulig å slette ting i listView
key: UniqueKey(),
onDismissed: (direction) {
//hva som skjer når man skal slette
setState(() {
ovelser.removeAt(index);
});
},
background: Container(
color: Colors.red,
),
//child er hva som skal være objektet som kan slettes
child: ovelse,
);
},
),
);
}
void addOvelse(String name) {
setState(() {
ovelser.add(Ovelser(name));
});
print(ovelser.length);
}
nameOvelse(BuildContext context) {
TextEditingController custumcontroller = TextEditingController();
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("new activity"),
content: TextField(
controller: custumcontroller,
),
actions: <Widget>[
FlatButton(
child: Text("create"),
onPressed: () {
String activityName = " " + custumcontroller.text;
addOvelse(activityName);
Navigator.of(context).pop();
},
)
],
);
},
);
}
}
the list ovelser takes in Ovelser objects. these objects have a class that has a list that takes in integers (progresjonsList) that i can add to via an AlertDialog.
Code for the class with progresjonList in int:
import './ovleseraddbutton.dart';
class Ovelser extends StatefulWidget {
final String name;
Ovelser(this.name);
#override
OvelserState createState() => OvelserState();
}
class OvelserState extends State<Ovelser> {
List<int> progresjonList = [];
#override
Widget build(BuildContext context) {
return Container(
height: 80,
width: double.infinity,
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
top: BorderSide(width: 0.5, color: Colors.grey),
bottom: BorderSide(width: 0.5, color: Colors.grey),
)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: Container(
child: Text(widget.name,
overflow: TextOverflow.fade,
softWrap: false,
maxLines: 1,
style: TextStyle(
fontStyle: FontStyle.italic,
fontSize: 20,
fontWeight: FontWeight.bold)),
)),
OvelserAddbutton(addvalue)
]),
);
}
void insertValue(int value) {
setState(() {
this.progresjonList.add(value);
});
}
addvalue(BuildContext context) {
TextEditingController custumcontroller = TextEditingController();
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("add new value"),
content: TextField(
controller: custumcontroller,
keyboardType: TextInputType.number,
),
actions: <Widget>[
FlatButton(
child: Text("add"),
onPressed: () {
String stringnumber = custumcontroller.text;
int number = int.parse(stringnumber);
insertValue(number);
print(number);
print(progresjonList.length);
print(this.progresjonList);
Navigator.of(context).pop();
},
)
],
);
},
);
}
}
the problem is every time i create a new widget in ovelser (the list that is used in ListView) the lists with integers (progresjonList) clears out so they are empty and dont retain the values previously added by the AlertDialog. I dont understand how i can keep that from happening, so that i keep the integers added. Can anyone help me? thank you in advance:)
there are tow other small files that only have icon widgets in them that i dont think are the problem, but if you need them here they are:)
class AddButton extends StatelessWidget {
final Function setInFunction;
AddButton(this.setInFunction);
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.add),
onPressed: () => setInFunction(context),
);
}
}
import 'package:flutter/material.dart';
class OvelserAddbutton extends StatelessWidget {
final Function setInFunction;
OvelserAddbutton(this.setInFunction);
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.add),
onPressed: () => setInFunction(context),
);
}
}
```
progessjonList is local to Ovelser class. You need to pass overserList to Ovelser class.
class Ovelser extends StatefulWidget {
final String name;
final List<int> list;
Ovelser(this.name, this.list);
#override
OvelserState createState() => OvelserState();
}
Then when you want to add to the list in OvelserState just use
widget.list.add(/*add int here*/);
Which I see is in your insertValue function
void insertValue(int value) {
setState(() {
widget.list.add(value);
});
}
The list you pass in will be a reference to the ovelser list from the original class.

How to populate a form from a listview on tap in flutter

I have a form widget, a list widget, and a "wrapper" widget or in other words, a parent/container widget. So to give an idea of the widget tree, it is as such.
Parent/Container Widget
Form Widget
Button Widget
List Widget
Notice that the form, buttons and list widget are all siblings, inside of the parent/container widget. What I want to happen, is tap on a list item in the list widget, and populate the form widget with the data that gets passed from the list widget.
Here is my parent widget.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:andplus_flutter_7_gui/services/user_service.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'crud_form.dart';
import 'crud_list.dart';
class Crud extends StatefulWidget {
Crud({Key key, this.title}) : super(key: key);
final String title;
_CrudContainerState createState() => _CrudContainerState();
}
class _CrudContainerState extends State<Crud> {
List<User> users;
User user = User();
UserService userService;
#override
void initState() {
super.initState();
if (userService == null) {
userService = UserService(user);
}
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
userService.dispose();
}
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(widget.title),
),
body: Builder(
builder: (context) => Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 2,
child: StreamBuilder(
builder: (context, AsyncSnapshot<User> snapshot) {
return CrudForm(
user: snapshot.data,
onUserAdded: (user) {
userService.addUser(user);
},
);
},
stream: userService.userObservable,
),
),
Expanded(
child: Text("Future button widget"),
),
Expanded(
flex: 3,
child: StreamBuilder(
builder: (ctx, AsyncSnapshot<List<User>> snap) {
return CrudList(
onUserSelected: userService.userSelected,
users: snap.data,
);
},
stream: userService.usersObservable,
),
),
],
),
),
),
);
}
void onEditUser(User user) {
setState(() {
user = user;
});
}
}
The above widget wraps the three widgets I mentioned.
Here are the children widget:
Form:
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:flutter/material.dart';
class CrudForm extends StatefulWidget {
CrudForm({Key key, this.onUserAdded, this.user}) : super(key: key);
final User user;
final void Function(User user) onUserAdded;
_CrudFormState createState() => _CrudFormState(user: user);
}
class _CrudFormState extends State<CrudForm> {
_CrudFormState({this.user});
User user = User();
var _key = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Container(
child: Builder(
builder: (context) => Container(
color: Colors.blueAccent[100],
child: Form(
key: _key,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Text(
"First Name",
style: TextStyle(fontSize: 20),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
initialValue: widget.user?.firstName == null ||
widget.user.firstName.isEmpty
? user.firstName
: widget.user.firstName,
validator: (value) {
if (value.isEmpty) {
return "First name is required";
}
return null;
},
onSaved: (value) {
setState(() {
user.firstName = value;
});
},
),
),
)
],
),
Row(
children: <Widget>[
Text(
"Last Name",
style: TextStyle(fontSize: 20),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (value) {
if (value.isEmpty) {
return "Last name is required";
}
return null;
},
onSaved: (value) {
setState(() {
user.lastName = value;
});
},
),
),
),
],
),
RaisedButton(
child: Text(
"Save",
),
splashColor: Colors.blueGrey,
onPressed: () {
if (!_key.currentState.validate()) {
return;
}
_key.currentState.save();
widget.onUserAdded(
new User(
firstName: user.firstName,
lastName: user.lastName,
),
);
},
)
],
),
),
),
),
),
);
}
}
Here is my list widget.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:flutter/material.dart';
class CrudList extends StatefulWidget {
CrudList({Key key, this.users, this.onUserSelected}) : super(key: key);
final List<User> users;
final SelectUser onUserSelected;
_CrudListState createState() => _CrudListState();
}
class _CrudListState extends State<CrudList> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: ListView.builder(
itemCount: widget.users?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
var user = widget.users[index];
return ListTile(
key: Key(index.toString()),
title: Center(
child: Text(
"${user.firstName} ${user.lastName}",
style: TextStyle(color: Colors.white),
),
),
onTap: () {
print("${widget.users[index]} $index");
widget.onUserSelected(widget.users[index]);
},
);
},
),
);
}
}
typedef void SelectUser(User user);
And just for further context, here is my user service, responsible for adding the objects to the stream, and using the stream builder within rxdart to notify of state changes.
import 'package:andplus_flutter_7_gui/model/user.dart';
import 'package:rxdart/rxdart.dart';
class UserService {
User _editedUser = User();
List<User> _users = <User>[];
BehaviorSubject<User> _userSubject;
BehaviorSubject<List<User>> _usersSubject;
UserService(this._editedUser) {
_userSubject = BehaviorSubject<User>.seeded(_editedUser);
_usersSubject = BehaviorSubject<List<User>>.seeded(_users);
}
Observable<List<User>> get usersObservable => _usersSubject.stream;
Observable<User> get userObservable => _userSubject.stream;
addUser(User user) {
_users.add(user);
_usersSubject.add(_users);
}
dispose() {
_userSubject.close();
_usersSubject.close();
}
void userSelected(User user) {
_editedUser = user;
_userSubject.add(_editedUser);
}
}
What am I missing? It looks like my widget rebuilds, and tries to set the initial value in the form when I tap the user in the list widget. But the actual field doesn't get updated and I'm not sure why.
I'd appreciate any documentation or articles on how to better approach data and state management between sibling widgets within the flutter framework.
Here's a similar use case that I tried to implement locally. What I'm doing here is I generate TextFormFields dynamically and assign TextEditingController.
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
_selectedField = textEditingController;
},
),
);
n--;
}
return Column(children: listForm);
}
Clicking a ListView item updates the text of the currently selected TextFormField.
InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
_selectedField!.value = TextEditingValue(text: 'Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
Complete sample
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
_selectedField = textEditingController;
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
if (_selectedField != null) {
_selectedField!.value = TextEditingValue(text: 'Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}
Demo