Related
I want when I click on one of the Expansion Tiles, the rest of the items are closed, how can I do that?
Refer to below example code :
import 'package:flutter/material.dart';
class PickPlanPage extends StatefulWidget {
const PickPlanPage({Key? key}) : super(key: key);
#override
State<PickPlanPage> createState() => _PickPlanPageState();
}
class _PickPlanPageState extends State<PickPlanPage> {
bool initiallyExpanded = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.separated(
itemCount: 5,
separatorBuilder: (context, index) => const SizedBox(height: 20),
itemBuilder: (context, index) => ExpansionTile(
initiallyExpanded: false,
title: const Text('title'),
onExpansionChanged: (value) => setState(() => initiallyExpanded = value),
children: const [
Text('Description'),
],
),
),
);
}
}
You can archive in your existing code with small modification.
make sure you need to add key in ListView & ExpansionTile widget. try bellow code.
class _PickPlanPageState extends State<PickPlanPage> {
int expandIndex = -1; // to handle only one expansion at a time.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.separated(
key: Key(expandIndex.toString()), //<-- must add
itemCount: 5,
separatorBuilder: (context, index) => const SizedBox(height: 20),
itemBuilder: (context, index) => ExpansionTile(
key: Key(index.toString()), //<-- must add
initiallyExpanded: (index == expandIndex), //<-- must add
title: const Text('title'),
onExpansionChanged: (value) {
setState(() {
expandIndex = value ? index : -1;
});
},
children: const [
Text('Description'),
],
),
),
);
}
}
You can use ExpansionPanelList and track the selected item tap event.
class PickPlanPage extends StatefulWidget {
const PickPlanPage({Key? key}) : super(key: key);
#override
State<PickPlanPage> createState() => _PickPlanPageState();
}
class _PickPlanPageState extends State<PickPlanPage> {
int? openIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
ExpansionPanelList(
children: [
for (int index = 0; index < 5; index++)
ExpansionPanel(
headerBuilder: (context, isExpanded) => GestureDetector(
onTap: () {
openIndex = index;
setState(() {});
},
child: const Text('title'),
),
isExpanded: index == openIndex,
body: Column(
children: [
Text('Description'),
],
),
),
],
),
],
),
);
}
}
I have a list and I need to set the container's background when clicking on it. However, what I have now does not work. When clicked, the color of the entire list changes, not the selected one. It seems to me that I need to add an index somewhere. I can't put it in a separate widget, because I'm attached to the list. Tell me how to do it?
setState -
Color? _textColor;
Color? _bgColor;
void initState() {
_bgColor = configColors.orange;
_textColor = Colors.white;
super.initState();
}
List
ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: HomeStore.storage.length,
itemBuilder: (BuildContext ctx, index) {
return Row (
// mainAxisAlignment: MainAxisAlignment.start,
children: <Widget> [
InkWell(
onTap: () {
setState(() {
if (_bgColor ==
configColors
.orange) {
_bgColor =
Colors.white;
_textColor =
configColors
.textStorage;
} else {
_bgColor =
configColors.orange;
_textColor =
Colors.white;
}
}
);
},
child: Container(
width: 71.4,
height: 30.3,
decoration: BoxDecoration(
color: _bgColor,
borderRadius: BorderRadius.circular(10)
),
child: Align(
alignment: Alignment.center,
child: Text(HomeStore.storage[index], style: TextStyle(color: _textColor,),),
)
),
),
SizedBox(
width: 18,
),
],
);
}),
For single item selection, you can use a int variable, this snippet will help you to understand the concept.
int? selectedIndex;
onTap: () {
setState(() {
selectedIndex = index;
});
},
And to select color
color:selectedIndex == index ? Colors.red : Colors.blue
Test snippet
class Sg extends StatefulWidget {
Sg({Key? key}) : super(key: key);
#override
State<Sg> createState() => _SgState();
}
class _SgState extends State<Sg> {
int? selectedIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: 4,
itemBuilder: (BuildContext ctx, index) {
return Row(
// mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
InkWell(
onTap: () {
setState(() {
selectedIndex = index;
});
},
child: Container(
width: 71.4,
height: 30.3,
decoration: BoxDecoration(
color:
selectedIndex == index ? Colors.red : Colors.blue,
borderRadius: BorderRadius.circular(10)),
child: Align(
alignment: Alignment.center,
child: Text(
"HomeStore.storage[index]",
),
)),
),
],
);
}),
);
}
}
sharing one of my code demo
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
late int tappedIndex;
#override
void initState() {
super.initState();
tappedIndex = 0;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ListView.builder(
shrinkWrap: true,
itemCount: 4,
itemBuilder: (context, index) {
return Container(
color: tappedIndex == index ? Colors.blue : Colors.grey,
child: ListTile(
title: Center(
child: Text('${index + 1}'),
),onTap:(){
setState((){
tappedIndex=index;
});
}));
})
]));
}
}
taped index will solve problem
I am writing an application on Flutter. I was able to make one filter with multiple selections.
But I want to have additional filters.
I used Multi-Select, but in the case of multiple filters, I don't know how to apply it
With what help can I implement this?
import 'package:flutter/material.dart';
class MainPage extends StatelessWidget {
#override
Widget build(context) => Scaffold(
appBar: AppBar(title: Text("f"),
backgroundColor: Colors.black),
drawer: MediaQuery.of(context).size.width < 500 ? Drawer(
child: HomePage(),
) : null,
body: SafeArea(
child:Center(
child: MediaQuery.of(context).size.width < 500 ? Content() :
Row(
children: [
Container(
width: 200.0,
child: HomePage()
),
Container(
width: MediaQuery.of(context).size.width-200.0,
child: Content()
)
]
)
)
)
);
}
List devices_list = ["First device", "Second device", "Third device", "Fourth device", "Fifth device", "Sixth device", "Seventh device", "Eighth device", "Ninth device"];
class Content extends StatelessWidget{
#override
Widget build(context) =>
Scaffold(
backgroundColor: Colors.white,
body: LayoutBuilder(
builder: (context, constraints){
return AnimatedContainer(
duration: Duration(milliseconds: 500),
color: Colors.white,
child: Center(
child: Container(
constraints: BoxConstraints(
maxWidth: 800,),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5.0),),
child: ListView.builder(
itemCount: devices_list.length,
itemBuilder: (BuildContext context, int index) {
return SizedBox (
height: 60,
key: Key(devices_list[index]),
child: Card(
shape: const RoundedRectangleBorder(
side: BorderSide(color: Colors.black,width: 3),
borderRadius: BorderRadius.all(Radius.circular(15))),
child: TextButton(
onPressed: (){},
child: ListTile(title: Text(devices_list[index]))),
)
);
}
))));
}));
}
class MultiSelect extends StatefulWidget {
final List<String> items;
const MultiSelect({Key? key, required this.items}) : super(key: key);
#override
State<StatefulWidget> createState() => _MultiSelectState();
}
class _MultiSelectState extends State<MultiSelect> {
// this variable holds the selected items
final List<String> _selectedItems = [];
// This function is triggered when a checkbox is checked or unchecked
void _itemChange(String itemValue, bool isSelected) {
setState(() {
if (isSelected) {
_selectedItems.add(itemValue);
} else {
_selectedItems.remove(itemValue);
}
});
}
// this function is called when the Cancel button is pressed
void _cancel() {
Navigator.pop(context);
}
// this function is called when the Submit button is tapped
void _submit() {
Navigator.pop(context, _selectedItems);
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Select Manufactures'),
content: SingleChildScrollView(
child: ListBody(
children: widget.items
.map((item) => CheckboxListTile(
value: _selectedItems.contains(item),
title: Text(item),
controlAffinity: ListTileControlAffinity.leading,
onChanged: (isChecked) => _itemChange(item, isChecked!),
))
.toList(),
),
),
actions: [
TextButton(
child: const Text('Cancel'),
onPressed: _cancel,
),
ElevatedButton(
child: const Text('Submit'),
onPressed: _submit,
),
],
);
}
}
// Implement a multi select on the Home screen
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<String> _selectedItemsManufactures = [];
void _showMultiSelectManufactures() async {
// a list of selectable items
// these items can be hard-coded or dynamically fetched from a database/API
final List<String> _items = [
'Apple',
'Samsung',
'Xiaomi',
'Nokia',
'Huawei',
'Alcatel'
];
final List<String>? results = await showDialog(
context: context,
builder: (BuildContext context) {
return MultiSelect(items: _items);
},
);
// Update UI
if (results != null) {setState(() {_selectedItemsManufactures = results;});}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// use this button to open the multi-select dialog
ElevatedButton(
child: const Text('Manufactures'),
onPressed: _showMultiSelectManufactures,
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.black)
),
),
const Divider(
height: 5,
color: Colors.white,
),
// display selected items
Wrap(
children: _selectedItemsManufactures
.map((e) => Chip(
label: Text(e),
))
.toList(),
)
],
),
),
);
}
}
Addition. Addition. I would like changes in the Menu class to be reflected in the class MainPage.
class DevicesPage extends StatelessWidget {
#override
Widget build(context) => Scaffold(
appBar: AppBar(title: Text("IT"),
backgroundColor: Colors.black),
drawer: MediaQuery.of(context).size.width < 500 ? Drawer(
child: Menu(),
) : null,
body: SafeArea(
child:Center(
child: MediaQuery.of(context).size.width < 500 ? MainPage() :
Row(
children: [
Container(
width: 200.0,
child: Menu()
),
Container(
width: MediaQuery.of(context).size.width-200.0,
child: MainPage()
)
]
)
)
)
);
}
class MainPage extends StatefulWidget {
const MainPage({Key? key}) : super(key: key);
#override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Map<String, List<String>?> filters = {};
List<Phone> filteredPhones = phoneList;
#override
Widget build(BuildContext context) {
return Scaffold(
body: filteredPhones.isEmpty
? const Center(child: Text('No product', style: TextStyle(fontSize: 16),))
: ListView.builder(
itemCount: filteredPhones.length,
itemBuilder: (_, index) {
final currentPhone = filteredPhones[index];
return ListTile(
title: Text(currentPhone.name),
subtitle: Text('${currentPhone.brand}-${currentPhone.color}'),
trailing: Text('${currentPhone.operation_system}'),
);
}
),
);
}
}
class Menu extends StatefulWidget {
const Menu({Key? key}) : super(key: key);
#override
State<Menu> createState() => _MenuState();
}
class _MenuState extends State<Menu> {
Map<String, List<String>?> filters = {};
List<Phone> filteredPhones = phoneList;
void _filter() {
setState(() {
filteredPhones = phoneList;
filters.forEach((key, value) {
if((value ?? []).isNotEmpty) {
filteredPhones = filteredPhones.where((phone) {
switch(key) {
case 'brand':
return value!.contains(phone.brand);
case 'color':
return value!.contains(phone.color);
case 'operation_system':
return value!.contains(phone.operation_system);
return true;
default:
return false;
}
}).toList();
}
});
filters.clear();
Navigator.of(context).pop();
});
}
void _handleCheckFilter(bool checked, String key, String value) {
final currentFilters = filters[key] ?? [];
if(checked) {
currentFilters.add(value);
} else {
currentFilters.remove(value);
}
filters[key] = currentFilters;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('phones List'),
actions: [
IconButton(
icon: const Icon(Icons.filter_alt),
onPressed: () {
showDialog<Filter>(context: context, builder: (_) {
return SimpleDialog(
title: const Text('Filters',textAlign: TextAlign.center,),
contentPadding: const EdgeInsets.all(16),
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('Select a brand'),
...brands.map((el) =>
CustomCheckboxTile(
label: el,
onChange: (check) => _handleCheckFilter(check, 'brand', el),
),
).toList(),
const Text('Select a operation_system'),
...operation_system.map((el) =>
CustomCheckboxTile(
label: el,
onChange: (check) => _handleCheckFilter(check, 'operation_system', el),
)
).toList(),
const Text('Select a colors'),
...colors.map((el) =>
CustomCheckboxTile(
label: el,
onChange: (check) => _handleCheckFilter(check, 'color', el),
),
).toList(),
const SizedBox(height: 24,),
ElevatedButton(onPressed: _filter, child: const Text('APPLY')),
],
),
],
);
});
},
),
],
),
body: filteredPhones.isEmpty
? const Center(child: Text('No product', style: TextStyle(fontSize: 16),))
: ListView.builder(
itemCount: filteredPhones.length,
itemBuilder: (_, index) {
final currentPhone = filteredPhones[index];
return ListTile(
title: Text(currentPhone.name),
subtitle: Text('${currentPhone.brand}-${currentPhone.color}'),
trailing: Text('${currentPhone.operation_system}'),
);
}
),
);
}
}
enter image description here
Well... you have many options. One way is to store all filters selected in a Map, like this:
final allFilters = {
"filterName1": "someValue",
"filterName2": "someOtherValue",
"fitlerName3": ["value1", "value2", "value3"]
}
And create a function that can handle each key of this map:
allFilter.forEach((key, value) {
switch(key) {
case "filterName1":
// ...some code
case "fitlerName2":
// ...more code
case "filterName3":
// ...much more code
}
})
You can check this demo project that I created https://github.com/felipeemidio/ListWithMultipleFilters
I want to be able to spawn a dropdown menu to select values, I've made sure everything is stateful but Setstate doesn't like it still.
Tried wrapping it in different widgets or using different dropdown properties. still doesn't want to work it seems.
class DicePage extends StatefulWidget {
#override
_DicePageState createState() => new _DicePageState();
}
class _DicePageState extends State<DicePage> {
#override
Widget build(BuildContext ctxt) {
return Scaffold(
appBar: AppBar(
),
body: BodyLayout(),
);
}
}
class BodyLayout extends StatefulWidget {
BodyLayout({Key key}) : super(key: key);
#override
_BodyLayoutState createState()=> _BodyLayoutState();
}
class _BodyLayoutState extends State<BodyLayout> {
#override
Widget build(BuildContext ctxt) {
return _myListView(ctxt);
}
}
Widget _myListView(BuildContext ctxt) {
final List<String> entries = <String>['A','B','C'];
final List<int> colorCodes = <int>[600, 500, 100];
return ListView.separated(
padding: const EdgeInsets.all(8.0),
itemCount: entries.length,
itemBuilder: (BuildContext ctxt, int index) {
return Container(
height: 150,
color: Colors.amber[colorCodes[index]],
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new SingleChildScrollView(
child: DropdownButton<String>(
value: dropdownValue,
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: <String>['1','2','3','4','5','6','7','8','9','10']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
new Container(
padding: const EdgeInsets.all(20),
child: new MaterialButton(
onPressed: () {},
child: Text('2'),
),
),
new Container(
padding: const EdgeInsets.all(20),
child: new IconButton(
iconSize: 80,
onPressed: () {},
icon: Icon(Icons.casino),
),
),
],
)
);
},
separatorBuilder: (BuildContext ctxt, int index) => const Divider(),
);
}
Expected it to spawn a listed dropdown menu on button click.
Trying your code, it will actually get the error:
The function 'setState' isn't defined.
If you call setState outside, there is no way for flutter to recognize on which state should be updated.
You should have the _myListView method inside the BodyLayout class, as that has to do with the state.
Here is an updated code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
);
}
}
class DicePage extends StatefulWidget {
#override
_DicePageState createState() => new _DicePageState();
}
class _DicePageState extends State<DicePage> {
#override
Widget build(BuildContext ctxt) {
return Scaffold(
appBar: AppBar(),
body: BodyLayout(),
);
}
}
class BodyLayout extends StatefulWidget {
BodyLayout({Key key}) : super(key: key);
#override
_BodyLayoutState createState() => _BodyLayoutState();
}
class _BodyLayoutState extends State<BodyLayout> {
String dropdownValue;
#override
Widget build(BuildContext ctxt) {
return _myListView(ctxt);
}
Widget _myListView(BuildContext ctxt) {
final List<String> entries = <String>['A', 'B', 'C'];
final List<int> colorCodes = <int>[600, 500, 100];
return ListView.separated(
padding: const EdgeInsets.all(8.0),
itemCount: entries.length,
itemBuilder: (BuildContext ctxt, int index) {
return Container(
height: 150,
color: Colors.amber[colorCodes[index]],
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new SingleChildScrollView(
child: DropdownButton<String>(
value: dropdownValue,
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: <String>[
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10'
].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
new Container(
padding: const EdgeInsets.all(20),
child: new MaterialButton(
onPressed: () {},
child: Text('2'),
),
),
new Container(
padding: const EdgeInsets.all(20),
child: new IconButton(
iconSize: 80,
onPressed: () {},
icon: Icon(Icons.casino),
),
),
],
));
},
separatorBuilder: (BuildContext ctxt, int index) => const Divider(),
);
}
}
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