I have a ListView.builder that builds a certain amount of widgets depending on user input. Each widget has their own specific name and has a DropDownMenu. I save this value with the corresponding name of the widget. It saves it correctly. However, when I try and read the data and create a new list from it, this error appears: [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The method '[]' was called on null.
'course' is a list. I am using the shared preferences import. When you tap the flat button, it should build the new list, but it is not. Could you explain to me why this is not working please?
This is code in the main app.
void main() {
runApp(Hemis());
}
class Hemis extends StatefulWidget {
#override
_HemisState createState() => _HemisState();
}
class _HemisState extends State<Hemis> {
_read() async {
final prefs = await SharedPreferences.getInstance();
for(int i = 0; i < course.length; i++) {
listMarks[i].name = course[i].name;
listMarks[i].mark = prefs.getInt(course[i].name) ?? 0;
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
ListView.builder(
itemCount: course.length,
itemBuilder: (context, index) {
return ModuleListItem(
name: '${course[index].name}',
credits: course[index].credits,
);
},
),
FlatButton(
onPressed: () {
_read();
for(int i = 0; i < course.length; i++) {
print('${listMarks[i].name}: ${listMarks[i].mark}');
}
},
),
],
),
)
)
);
}
}
The widget that is being built.
final percentage = List<String>.generate(100, (i) => "$i");
class ModuleListItem extends StatefulWidget {
const ModuleListItem ({ Key key, this.name, this.credits }): super(key: key);
final String name;
final int credits;
#override
_ModuleListItemState createState() => _ModuleListItemState();
}
class _ModuleListItemState extends State<ModuleListItem> {
String dropdownValue;
bool isSwitched = false;
_save() async {
final prefs = await SharedPreferences.getInstance();
final key = '${widget.name}';
final value = int.parse(dropdownValue);
prefs.setInt(key, value);
print('saved $value');
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
DropdownButton<String>(
value: dropdownValue,
icon: Icon(Icons.keyboard_arrow_down),
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: percentage.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
Switch(
value: isSwitched,
onChanged: (value) {
setState(() {
isSwitched = value;
if(isSwitched == true) {
_save();
}
print(isSwitched);
});
},
),
],
),
);
}
}
Related
How can I select/check only one checkbox to be checked at time?
And below is my code
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Checkbox(
checkColor: Colors.white,
value: isChecked,
onChanged: (bool value) {
setState(() {
isChecked = value;
// ignore: unnecessary_statements
passData(certId);
});
},
),
],
)),
Option1 - Using a map to maintain the state
Create a map:
final Map<int, bool> _state = {};
then, check if the value for that index is true/false:
return ListView.builder(itemBuilder: (context, index) {
return CheckboxListTile(
value: _state[index] ?? false,
onChanged: (value) {
setState(() {
_state[index] = value!;
});
},
title: Text(_data[index].text),
);
});
Option 2 - using a model:
class CheckBoxModel {
bool isChecked = false;
String text = "";
CheckBoxModel({required this.isChecked, required this.text});
}
and then, generate a List of 30 widgets:
final _data = List.generate(
30, (index) => CheckBoxModel(isChecked: false, text: "Item $index"));
Now, use a ListView.builder and based on the index, to update the corresponding value:
class Testing extends StatefulWidget {
const Testing({Key? key}) : super(key: key);
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
#override
Widget build(BuildContext context) {
return ListView.builder(itemBuilder: (context, index) {
return CheckboxListTile(
value: _data[index].isChecked,
onChanged: (value) {
setState(() {
_data[index].isChecked = value!;
});
},
title: Text(_data[index].text),
);
});
}
}
See also
Expansion tile trailing icon updates all in list on interaction with one tile. How can I only change the icon for the expanded tile?
On my project I need to use several showDialog one after the other.
For user creation, I use a SearchField widget to retrieve info from a table related to the user.
If the SearchField value does not exist I would like to propose the creation. Depending on the choice either the form is in error or I propose to register the user.
For this I use a showDialog in the validator of the SearchField and an if validator is correct.
My problem is that my second dialog box is displayed before validating the first one and even above that of the SearchField.
What is the correct way to do this?
Thank you,
class InformationsPage extends StatefulWidget {
const InformationsPage({
required Key key,
required this.user,
required this.type,
}) : super(key: key);
final User user;
final FenType type;
#override
InformationsPageState createState() => InformationsPageState();
}
class InformationsPageState extends State<InformationsPage>
with AutomaticKeepAliveClientMixin {
InformationsPageState({this.user});
final User? user;
late UserApi _api;
#override
bool get wantKeepAlive => true;
bool _familyIsCreated = false;
late User userSaved;
late FenType type;
//Info Form
var _pseudoController = TextEditingController();
var _familyController = TextEditingController();
#override
void initState() {
super.initState();
_api = UserApi();
_pseudoController = TextEditingController(text: widget.user.pseudo);
_familyController = TextEditingController(text: widget.user.familyName);
userSaved = User.fromUser();
type = widget.type;
}
#override
void dispose() {
_pseudoController.dispose();
_familyController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Column(
children: <Widget>[
FutureBuilder(
future: _api.getFamilies(),
builder: (context, AsyncSnapshot<List<Family>> snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(
"Something wrong with message: ${snapshot.error.toString()}"));
} else if (snapshot.connectionState == ConnectionState.done) {
List<Family> _list = snapshot.data!;
return _buildDropdownSearchFamilies(_list);
} else {
return const Center(child: CircularProgressIndicator());
}
}),
TextFormField(
readOnly: type == FenType.read ? true : false,
inputFormatters: [LowerCaseTextFormatter()],
controller: _pseudoController,
onSaved: (value) => userSaved.pseudo = value,
decoration: const InputDecoration(
icon: Icon(Icons.person),
hintText: 'Pseudo',
labelText: 'Pseudo',
),
validator: (value) =>
value!.isEmpty ? 'Obligatory' : null),
],
);
}
int? _contains(List<Family> list, String? name) {
int? res = -1;
for (Family element in list) {
if (element.name == name) {
res = element.id;
break;
}
}
return res;
}
Widget _buildDropdownSearchFamilies(List<Family> _list) {
return SearchField(
controller: _familyController,
suggestions: _list
.map((e) =>
SearchFieldListItem(e.name!, child: Text(e.name!), item: e.id))
.toList(),
hint: 'Family',
validator: (x) {
if (x!.isEmpty) {
userSaved.familyId = null;
userSaved.familyName = null;
return null;
}
int? id = _contains(_list, x);
if (id == -1) {
userSaved.familyId == null;
showDiaglog(x);
if (userSaved.familyId != null) {
return null;
} else {
return 'Family not exist';
}
} else {
userSaved.familyId = id;
userSaved.familyName = x;
return null;
}
},
searchInputDecoration: const InputDecoration(
labelText: 'Family', icon: Icon(Icons.groups)),
itemHeight: 50,
onTap: (x) {
userSaved.familyId = x.item as int?;
userSaved.familyName = x.child.toString();
});
}
showDiaglog(String family) async {
String title = "Family";
String message =
"Family $family not exist. Create ?";
String textKoButton = "no";
String textOkButton = "yes";
MyDialog alert = MyDialog(
title: title,
message: message,
onPressedKo: koButtonPressed(),
onPressedOk: okButtonPressed(family),
textKoButton: textKoButton,
textOkButton: textOkButton);
await showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
void Function() koButtonPressed() => () {
_familyIsCreated = false;
Navigator.of(context).pop(false);
};
void Function() okButtonPressed(family) => () {
_save(family);
Navigator.of(context).pop();
};
void _save(family) async {
UserApi apiUser = UserApi();
Family oldF = Family.empty();
Family newF = Family.empty();
newF.name = family;
newF.createdAt = oldF.createdAt;
newF.deletedAt = newF.deletedAt;
Map<String, dynamic> data = oldF.toJson(newF);
int res = -1;
res = await apiUser.createFamily(data);
SnackBar snackBar;
if (res != -1) {
snackBar = MyWidget.okSnackBar('Family created');
userSaved.familyId = res;
userSaved.familyName = family;
} else {
snackBar = MyWidget.koSnackBar(
'Family not created');
userSaved.familyId = null;
userSaved.familyName = null;
}
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
My form :
class UserFormPage extends StatefulWidget {
static const String routeName = '/admin/user-form';
final User? user;
final FenType fenType;
const UserFormPage({Key? key, required this.user, required this.fenType})
: super(key: key);
#override
_UserFormPageState createState() => _UserFormPageState();
}
class _UserFormPageState extends State<UserFormPage>
with SingleTickerProviderStateMixin {
static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_appState');
static final GlobalKey<InformationsPageState> _infoKey =
GlobalKey<InformationsPageState>();
late TabController _controller;
late User _user;
late User _userSaved;
#override
void initState() {
super.initState();
_controller = TabController(vsync: this, length: 2);
_user = widget.user!;
_userSaved = widget.user!;
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () =>
Navigator.pushReplacementNamed(context, Routes.admUserList),
),
title: const Text('Member'),
actions: <Widget>[
Visibility(
visible: widget.fenType != FenType.read ? true : false,
child: IconButton(
icon: const Icon(Icons.save),
onPressed: () {
if (!_formKey.currentState!.validate()) {
return;
}
showDiaglog();
},
))
],
bottom: TabBar(
controller: _controller,
tabs: const [
Tab(text: 'Info'),
Tab(text: 'Others'),
],
),
),
body: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Flexible(
child: TabBarView(
controller: _controller,
children: <Widget>[
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InformationsPage(
user: _user,
key: _infoKey,
type: widget.fenType),
])),
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DetailsPage(
user: _user,
key: _detailsKey,
type: widget.fenType)
],
)),
],
))
],
))),
);
}
void _save() async {
final infoState = _infoKey.currentState;
_userSaved = infoState?.userSaved ?? _user;
_userSaved.pseudo = infoState?.userSaved.pseudo ?? _user.pseudo;
Map<String, dynamic> data = _user.userToJsonClean(_userSaved);
if (!_userSaved.userIsUpdated()) {
final outSnackBar = MyWidget.okSnackBar('Not update');
ScaffoldMessenger.of(context).showSnackBar(outSnackBar);
} else {
UserApi apiUser = UserApi();
bool res = false;
res = widget.fenType == FenType.update
? await apiUser.update(data)
: await apiUser.create(data);
SnackBar snackBar;
res
? snackBar = MyWidget.okSnackBar('Member saved')
: snackBar = MyWidget.koSnackBar(
'Member not saved');
ScaffoldMessenger.of(context).showSnackBar(snackBar);
_user = _userSaved;
if (widget.fenType == FenType.create) {
Navigator.of(context).popAndPushNamed(Routes.admUserList);
}
}
}
void showDiaglog() {
String pseudo = _userSaved.pseudo!;
String title = "Save";
String message = widget.fenType == FenType.create
? "Create member $pseudo ?"
: "Save meber $pseudo ?";
String textKoButton = "no";
String textOkButton = "yes";
MyDialog alert = MyDialog(
title: title,
message: message,
onPressedKo: koButtonPressed(),
onPressedOk: okButtonPressed(),
textKoButton: textKoButton,
textOkButton: textOkButton);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
void Function() koButtonPressed() => () {
Navigator.of(context).pop(false);
};
void Function() okButtonPressed() => () {
_formKey.currentState!.save();
_save();
Navigator.of(context).pop();
};
}
I resolve this problem to modified the widget SearchField to a DropdownSearch.
I want to create a DropdownButton that unfolds when I hover over the Button. So basically I don't have to click to unfold the DropdownButton. Does anyone has a code sample or could help me with that?
By using GlobalKey we can open DropdownButton. To open on Hover, I'm using Inkwell.
Result
FullWidget
import 'package:flutter/material.dart';
class StraggedExample extends StatefulWidget {
const StraggedExample({Key? key}) : super(key: key);
#override
_StraggedExampleState createState() => _StraggedExampleState();
}
class _StraggedExampleState extends State<StraggedExample> {
final fromAPi = ["a", "e", "f", "a"];
late final dropitems;
late String initValue;
#override
void initState() {
super.initState();
final values = fromAPi.toSet().toList();
dropitems = List.generate(
values.length,
(index) => DropdownMenuItem(
child: Text("item $index"),
value: values[index],
),
);
initValue = values[0];
}
GlobalKey _dropdownButtonKey = GlobalKey();
openDropdown() {
GestureDetector? detector;
searchForGestureDetector(BuildContext element) {
element.visitChildElements((element) {
if (element.widget != null && element.widget is GestureDetector) {
detector = element.widget as GestureDetector;
} else {
searchForGestureDetector(element);
}
});
}
searchForGestureDetector(_dropdownButtonKey.currentContext!);
detector!.onTap!();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: InkWell(
onHover: (value) {
if (value) openDropdown();
},
onTap: () {},
child: DropdownButton(
key: _dropdownButtonKey,
value: initValue,
items: dropitems,
onChanged: (value) {
setState(() {
initValue = value as String;
});
},
),
),
),
);
}
}
ref: more details
i have a DropDownButton, which is filled from an SQLite DB which is ok for my app for now. But after choosing an entry, the DropDownButton didnt show the choosen entry, just the hint. To check my entry i try to fill a textfield also with the choosen entry, but this isnt changed too. Here is my code for the DropDownButton:
List<DropdownMenuItem<String>> teamList;
DropdownMenuItem selectedTeam;
DropdownButton(
hint: Text("Choose"),
value: selectedTeam,
onChanged: (value) {
setState(() {
_teamController.text = value.name;
selectedTeam = value;
});
},
items: teamList,
),
actually i fill my teamList with a codesnippet inside the initstate:
super.initState();
teamList = [];
db.getData().then((listMap) {
listMap.map((map) {
print(map.toString());
return getDropDownWidget(map);
}).forEach((dropDownMenuItem) {
teamList.add(dropDownMenuItem);
});
setState(() {});
});
and with this:
DropdownMenuItem<String> getDropDownWidget(Map<String, dynamic> map) {
return DropdownMenuItem<String>(
value: map['team'],
child: Text(map['team']),
);
}
in my dbhelper-file i have this code:
Future<List<Map<String, dynamic>>> getData() async {
var dbClient = await db;
return await dbClient.rawQuery('SELECT team FROM teamTable');
}
Hey Thomas Check out this example :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SampleApp(),
debugShowCheckedModeBanner: false,
);
}
}
class SampleApp extends StatefulWidget {
#override
_SampleAppState createState() => _SampleAppState();
}
class _SampleAppState extends State<SampleApp> {
List<String> teamList = ['Sample', 'Sample2', 'Sample3', 'Sample4'];
String selectedTeam;
TextEditingController _teamController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Your heading'),
),
body: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: _teamController,
),
new DropdownButton<String>(
items: teamList.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
value: selectedTeam,
hint: Text('Choose'),
onChanged: (value) {
setState(() {
_teamController.text = value;
selectedTeam = value;
print('This is the selected value: $selectedTeam');
});
},
),
],
)));
}
}
Let me know if it works.
I am having issues with setting state of variable because i am using other class outside stateful widget. On line 115 inside buildActions method i want to set _selectedStores = selectedStores;. How can i set the state?
I tried using callback but got no luck.
import 'package:flutter/material.dart';
class SearchDemo extends StatefulWidget {
#override
_SearchDemoState createState() => _SearchDemoState();
}
class _SearchDemoState extends State<SearchDemo> {
final _SearchDemoSearchDelegate _delegate = _SearchDemoSearchDelegate();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String _lastSearchSelected;
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Search Demo'),
actions: <Widget>[
IconButton(
tooltip: 'Search',
icon: const Icon(Icons.search),
onPressed: () async {
final String selected = await showSearch<String>(
context: context,
delegate: _delegate,
);
if (selected != null && selected != _lastSearchSelected) {
setState(() {
_lastSearchSelected = selected;
});
}
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Last search: ${_lastSearchSelected ?? 'NONE'}.'),
],
),
),
);
}
}
class Stores {
int id;
String name;
Stores(this.id, this.name);
static List<Stores> getStores() {
return <Stores>[
Stores(1, 'Amazon'),
Stores(2, 'Flipkart'),
Stores(3, 'Snapdeal'),
];
}
}
class _SearchDemoSearchDelegate extends SearchDelegate<String> {
List<Stores> _stores = Stores.getStores();
List<DropdownMenuItem<Stores>> _dropdownMenuItems;
Stores _selectedStores;
List<DropdownMenuItem<Stores>> buildDropdownMenuItems(List stores) {
List<DropdownMenuItem<Stores>> items = List();
for (Stores stores in stores) {
items.add(
DropdownMenuItem(
value: stores,
child: Text(stores.name),
),
);
}
return items;
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
tooltip: 'Back',
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
},
);
}
#override
Widget buildSuggestions(BuildContext context) {
return _SuggestionList(
query: query,
onSelected: (String suggestion) {
print(suggestion);
},
);
}
#override
Widget buildResults(BuildContext context) {}
#override
List<Widget> buildActions(BuildContext context) {
_dropdownMenuItems = buildDropdownMenuItems(_stores);
_selectedStores = _dropdownMenuItems[0].value;
void onChangeDropdownItem(Stores selectedStores) {
setState(() {
_selectedStores = selectedStores;
});
}
return <Widget>[
query.isEmpty
? Container(
padding: const EdgeInsets.only(right: 5.0, top: 5.0),
child: DropdownButtonHideUnderline(
child: DropdownButton(
elevation: 0,
value: _selectedStores,
items: _dropdownMenuItems,
onChanged: onChangeDropdownItem,
),
),
)
: IconButton(
tooltip: 'Clear',
icon: const Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
}
List<String> getHistory() {
//Get Last Searched products from device storage *Pending*
final List<String> _history = <String>[
"iPhone X 64GB Silver",
"Galaxy S10+ White",
"Apple Watch Series 3",
"Samson C01UPRO",
"Cooler Master masterbox 5"
];
return _history;
}
class _SuggestionList extends StatelessWidget {
const _SuggestionList({this.query, this.onSelected});
final String query;
final ValueChanged<String> onSelected;
#override
Widget build(BuildContext context) {
//Get Data From API *Pending*
final List<String> _data = <String>[
"iPhone X 64GB Silver",
"Galaxy S10+ White",
"Apple Watch Series 3",
"Samson C01UPRO",
"Cooler Master Masterbox 5"
];
final List<String> suggestions = query.isEmpty
? getHistory()
: _data
.where((p) => p.toLowerCase().contains(query.toLowerCase()))
.toList();
return ListView.builder(
itemCount: suggestions.length,
itemBuilder: (BuildContext context, int i) {
final String suggestion = suggestions[i];
return ListTile(
leading: query.isEmpty ? const Icon(Icons.history) : const Icon(null),
title: Text(suggestion),
onTap: () {
onSelected(suggestion);
},
);
},
);
}
}
The method setState is only part of StatefulWidgets and that information shouldn't be passed around. It's not recommended and is not a good development practice. Can you do it? Yes, like this:
class OtherClass {
final State state;
OtherClass(this.state);
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
super.initState();
OtherClass(this);
}
}
But, again, I do not recommend this at all. You should be using some kind of Future or Stream to send your data to your StatefulWidget and then use your setState there, where it should be.