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
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?
I want to change the value of segment control when button is clicked in parent widget.
If button and segment control are in same widget I can easily change the value of segment control but if they are in different widgets how can I change?
This is my main.dart
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
children: [
const TabView(),
ElevatedButton(onPressed: () {//go to next segment on click},
child: const Text('Next'))
],
)),
),
);
}
}
This is my tab_view.dart
class TabView extends StatefulWidget {
const TabView({Key? key}) : super(key: key);
#override
State<TabView> createState() => _TabViewState();
}
class _TabViewState extends State<TabView> {
FormGroup formGroup = FormGroup({
'name': FormControl<String>(validators: [Validators.required]),
'note': FormControl<String>()
});
final FormControl<String> segmentControl =
FormControl<String>(validators: [Validators.required]);
#override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: formGroup,
child: Column(
children: [
ReactiveSlidingSegmentedControl<String, String>(
formControl: segmentControl,
children: const {
'name': Text('Name'),
'note': Text('Note'),
},
),
const SizedBox(height: 16),
LayoutBuilder(builder: (context, constraints) {
return ReactiveValueListenableBuilder(
formControl: segmentControl,
builder: (context, field, child) {
return _buildView(field as FormControl<String>, formGroup);
});
}),
],
),
);
}
Widget _buildView(FormControl<String> control, FormGroup formGroup) {
if ((control.value != 'name') && formGroup.control('name').invalid) {
formGroup.control('name').markAsTouched();
control.value = 'name';
}
switch (control.value) {
case 'name':
return ReactiveTextField(
formControlName: 'name',
decoration: const InputDecoration(labelText: 'Name'),
);
case 'note':
return ReactiveTextField(
formControlName: 'note',
decoration: const InputDecoration(labelText: 'Note'),
);
default:
return Container();
}
}
}
Is there any method so that the child Widget knows button has been clicked in Parent widget and change the value of segment control.
create two final variables next and prev, and ask inside the constructor, set to false initially in TabView. Override didUpdateWidget and put the logic of changing the segmentControl to reflect next/prev value by finding the current value and next value from map given inside the children param.
Make MyApp Stateful widget declare two state variables _next and _prev alter their value on prev/next button pass these value as param to TabView(prev:_prev,next:_next) and you are good to go.
A detailed example:
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
///HERE
bool _next = false;
bool _prev = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
children: [
TabView(
///HERE
next: _next,
prev: _prev,
),
const SizedBox(
height: 20,
),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
ElevatedButton(
onPressed: () {
///HERE
setState(() {
_next = false;
_prev = true;
});
},
child: const Text('Prev')),
const SizedBox(
width: 20,
),
ElevatedButton(
onPressed: () {
///HERE
setState(() {
_next = true;
_prev = false;
});
},
child: const Text('Next'))
])
],
)),
),
);
}
}
class TabView extends StatefulWidget {
const TabView({Key? key, this.prev = false, this.next = false})
: super(key: key);
final bool prev;
final bool next;
#override
State<TabView> createState() => _TabViewState();
}
class _TabViewState extends State<TabView> {
FormGroup formGroup = FormGroup({
'name': FormControl<String>(validators: [Validators.required]),
'note': FormControl<String>()
});
final List<Widget> control = [const Text('Name'), const Text('Note')];
final FormControl<int> segmentControl = FormControl<int>(value: 0);
#override
void didUpdateWidget(TabView oldWidget) {
if (widget.next) {
final nextIndex =
min((segmentControl.value ?? -1) + 1, control.length - 1);
segmentControl.value = nextIndex;
}
if (widget.prev) {
final prevIndex = max((segmentControl.value ?? -1) - 1, 0);
segmentControl.value = prevIndex;
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return ReactiveForm(
formGroup: formGroup,
child: Column(
children: [
ReactiveSlidingSegmentedControl<int, int>(
formControl: segmentControl,
children: control.asMap(),
),
const SizedBox(height: 16),
ReactiveValueListenableBuilder(
formControl: segmentControl,
builder: (context, control, child) {
return _buildView(segmentControl.value!, formGroup);
})
],
),
);
}
Widget _buildView(int index, FormGroup formGroup) {
switch (index) {
case 0:
return ReactiveTextField(
formControlName: 'name',
decoration: const InputDecoration(labelText: 'Name'),
);
case 1:
return ReactiveTextField(
formControlName: 'note',
decoration: const InputDecoration(labelText: 'Note'));
default:
return Container();
}
}
}
A more flexible approach would be to add callback function onTraversalFallback instead of hardcoding.
One usecase may be: in stepper form you want next to trigger tabview next tab until last tab is reached. if the last tab is reached move to stepCount+1.
For that a little tweak is needed. Hope one finds useful
class TabView extends StatefulWidget {
/*...*/
final VoidCallback? onTraversalFallback;
/*...*/
}
class _TabViewState extends State<TabView> {
/*...*/
#override
void didUpdateWidget(SaveElearningCourseDetailsForm oldWidget) {
if (widget.next) {
final nextIndex =
min((segmentControl.value ?? -1) + 1, control.length - 1);
if (nextIndex == segmentControl.value) {
widget.onTraversalFallback?.call();
} else {
segmentControl.value = nextIndex;
}
}
if (widget.prev) {
final prevIndex = max((segmentControl.value ?? -1) - 1, 0);
if (prevIndex == segmentControl.value) {
widget.onTraversalFallback?.call();
} else {
segmentControl.value = prevIndex;
}
}
super.didUpdateWidget(oldWidget);
}
/*...*/
}
on caller side you can do something like:
TabView(
///HERE
next: _next,
prev: _prev,
onTraversalFallback: () {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
if (_prev) {
_currentStep -= 1;
_prev = false;
}
if (_next) {
_currentStep + 1;
_next = false;
}
});
});
},
),
I have taken the below code from How to create a checkbox using listview which checks all the items when one item is checked. how do i fix the code to not to check all the items?
class CheckBoxInListView extends StatefulWidget {
#override
_CheckBoxInListViewState createState() => _CheckBoxInListViewState();
}
class _CheckBoxInListViewState extends State<CheckBoxInListView> {
bool _isChecked = false;
List<String> _texts = [
"InduceSmile.com",
"Flutter.io",
"google.com",
"youtube.com",
"yahoo.com",
"gmail.com"
];
#override
Widget build(BuildContext context) {
return ListView(
padding: EdgeInsets.all(8.0),
children: _texts
.map((text) => CheckboxListTile(
title: Text(text),
value: _isChecked,
onChanged: (val) {
setState(() {
_isChecked = val;
});
},
))
.toList(),
);
}
}
Just make a List of '_isChecked' variable and use that.
class CheckBoxInListView extends StatefulWidget {
#override
_CheckBoxInListViewState createState() => _CheckBoxInListViewState();
}
class _CheckBoxInListViewState extends State<CheckBoxInListView> {
List<String> _texts = [
"InduceSmile.com",
"Flutter.io",
"google.com",
"youtube.com",
"yahoo.com",
"gmail.com"
];
List<bool> _isChecked;
#override
void initState() {
super.initState();
_isChecked = List<bool>.filled(_texts.length, false);
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _texts.length,
itemBuilder: (context, index) {
return CheckboxListTile(
title: Text(_texts[index]),
value: _isChecked[index],
onChanged: (val) {
setState(
() {
_isChecked[index] = val;
},
);
},
);
},
);
}
}
you should have a list for is checked, and assign them individually to each item.
class CheckBoxInListView extends StatefulWidget {
#override
_CheckBoxInListViewState createState() => _CheckBoxInListViewState();
}
class _CheckBoxInListViewState extends State<CheckBoxInListView> {
final List<SimpleModel> _items = <SimpleModel>[
SimpleModel('InduceSmile.com', false),
SimpleModel('Flutter.io', false),
SimpleModel('google.com', false),
SimpleModel('youtube.com', false),
SimpleModel('yahoo.com', false),
SimpleModel('gmail.com', false),
];
#override
Widget build(BuildContext context) => ListView(
padding: const EdgeInsets.all(8),
children: _items
.map(
(SimpleModel item) => CheckboxListTile(
title: Text(item.title),
value: item.isChecked,
onChanged: (bool val) {
setState(() => item.isChecked = val);
},
),
)
.toList(),
);
}
class SimpleModel {
String title;
bool isChecked;
SimpleModel(this.title, this.isChecked);
}
The answer with the most votes works with correction, reports two errors, errors and lines that have been corrected:
A value of type 'bool?' can't be assigned to a variable of type 'bool'.
correction: _isChecked[index] = val!;
Non-nullable instance field '_isChecked' must be initialized.
correction: late List _isChecked;
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);
});
},
),
],
),
);
}
}
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.