I have a registration form with many DropDownButton
fields that take enums in the menu. Currently, I'm making a separate widget for every single button. Is there a way to just make a single widget and then using its parameters to change it?
Is there a better way to do this? Currently, I'm just copy-pasting and renaming it for every DropDownButton
I want to create.
class _BloodTypeDropDownFieldState extends State<BloodTypeDropDownField> {
BloodType _currentSelectedValue;
@override
Widget build(BuildContext context) {
return DropdownButtonHideUnderline(
child: DropdownButton<BloodType>(
value: _currentSelectedValue,
hint: Text(
"Blood Group",
style: GoogleFonts.roboto(
textStyle: Theme.of(context).textTheme.headline4,
fontSize: 13,
fontWeight: FontWeight.w700,
color: Color(0xffffffff),
),
),
isDense: true,
onChanged: (BloodType newValue) {
setState(() {
_currentSelectedValue = newValue;
});
},
selectedItemBuilder: (BuildContext context) {
return BloodType.getValues().map((BloodType bloodType) {
return Text(
bloodType.toString(),
style: GoogleFonts.roboto(
textStyle: Theme.of(context).textTheme.headline4,
fontSize: 13,
fontWeight: FontWeight.w700,
color: Color(0xffffffff),
),
);
}).toList();
},
items: BloodType.getValues().map((BloodType bloodType) {
return DropdownMenuItem<BloodType>(
value: bloodType,
child: Text(
bloodType.toString(),
style: GoogleFonts.roboto(
textStyle: Theme.of(context).textTheme.headline4,
fontSize: 15,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
);
}).toList()
),
);
}
}
class BloodType {
final value;
const BloodType._internal(this.value);
toString() => '$value';
static const A_PLUS = const BloodType._internal('A+');
static const A_MINUS = const BloodType._internal('A-');
static const B_PLUS = const BloodType._internal('B+');
static const B_MINUS = const BloodType._internal('B-');
static const AB_PLUS = const BloodType._internal('AB+');
static const AB_MINUS = const BloodType._internal('AB-');
static const O_PLUS = const BloodType._internal('O+');
static const O_MINUS = const BloodType._internal('O-');
static List<BloodType> list = [
A_PLUS,
A_MINUS,
B_PLUS,
B_MINUS,
AB_PLUS,
AB_MINUS,
O_PLUS,
O_MINUS
];
static List<BloodType> getValues() => list;
}
You could define a generic class for your custom DropDown:
class MyDropDown<T> extends StatelessWidget {
final String hint;
final T value;
final List<T> values;
final ValueChanged<T> onChanged;
MyDropDown({
Key key,
this.hint,
this.value,
@required this.values,
this.onChanged,
}) : assert(values != null),
super(key: key);
@override
Widget build(BuildContext context) {
return DropdownButtonHideUnderline(
child: DropdownButton<T>(
value: value,
hint: Text(hint ?? 'Pick one'),
isDense: true,
onChanged: onChanged,
items: values.map((value) => DropdownMenuItem<T>(
value: value,
child: Text(value.toString()),
))
.toList(),
),
);
}
}
That you can easily use wherever you want:
class HomePage extends HookWidget {
@override
Widget build(BuildContext context) {
final _current = useState<BloodType>();
return Scaffold(
body: Center(
child: MyDropDown<BloodType>(
hint: 'Blood Type',
value: _current.value,
values: bloodTypes,
onChanged: (value) => _current.value = value,
),
),
);
}
}
For whatever type T
. Here is an example for your BloodTypes
:
enum BloodGroupEnum {
a,
b,
ab,
o,
}
extension BloodGroupEnumX on BloodGroupEnum {
String get label => describeEnum(this).toUpperCase();
}
enum RhdEnum {
positive,
negative,
}
extension RhdEnumX on RhdEnum {
String get label {
switch (this) {
case RhdEnum.positive:
return '+';
case RhdEnum.negative:
return '-';
default:
return '';
}
}
}
class BloodType {
final BloodGroupEnum group;
final RhdEnum rhd;
String toString() => '${group.label}${rhd.label}';
BloodType({
this.group,
this.rhd,
});
}
final List<BloodType> bloodTypes = BloodGroupEnum.values
.map((group) => RhdEnum.values.map((rhd) {
print('NEW');
print(BloodType(group: group, rhd: rhd).toString());
return BloodType(group: group, rhd: rhd);
}).toList())
.expand((i) => i)
.toList();
The only requirement for the class you want to use within MyDropDown
is to properly implement toString()
that is used as a label for the DropdownMenuItem
.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(AppWidget());
}
class AppWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
textTheme: GoogleFonts.robotoTextTheme(
Theme.of(context).textTheme,
),
),
home: HomePage(),
);
}
}
class HomePage extends HookWidget {
@override
Widget build(BuildContext context) {
final _current = useState<BloodType>();
return Scaffold(
body: Center(
child: MyDropDown<BloodType>(
hint: 'Blood Type',
value: _current.value,
values: bloodTypes,
onChanged: (value) => _current.value = value,
),
),
);
}
}
class MyDropDown<T> extends StatelessWidget {
final String hint;
final T value;
final List<T> values;
final ValueChanged<T> onChanged;
MyDropDown({
Key key,
this.hint,
this.value,
@required this.values,
this.onChanged,
}) : assert(values != null),
super(key: key);
@override
Widget build(BuildContext context) {
return DropdownButtonHideUnderline(
child: DropdownButton<T>(
value: value,
hint: Text(hint ?? 'Pick one'),
isDense: true,
onChanged: onChanged,
items: values
.map((value) => DropdownMenuItem<T>(
value: value,
child: Text(value.toString()),
))
.toList(),
),
);
}
}
enum BloodGroupEnum {
a,
b,
ab,
o,
}
extension BloodGroupEnumX on BloodGroupEnum {
String get label => describeEnum(this).toUpperCase();
}
enum RhdEnum {
positive,
negative,
}
extension RhdEnumX on RhdEnum {
String get label {
switch (this) {
case RhdEnum.positive:
return '+';
case RhdEnum.negative:
return '-';
default:
return '';
}
}
}
class BloodType {
final BloodGroupEnum group;
final RhdEnum rhd;
String toString() => '${group.label}${rhd.label}';
BloodType({
this.group,
this.rhd,
});
}
final List<BloodType> bloodTypes = BloodGroupEnum.values
.map((group) =>
RhdEnum.values.map((rhd) => BloodType(group: group, rhd: rhd)).toList())
.expand((i) => i)
.toList();
So I think what you are looking for is to reuse the dropdownbutton I would recommend to use a callback that will give back the type that is selected in the Widget to it's parent. We can use the ValueChanged.
class CustomDropDown extends StatelessWidget {
final ValueChanged<BloodType> onChanged;
final BloodType currentSelectedValue;
const CustomDropDown({Key key, @required this.onChanged, @required this.currentSelectedValue}) : super(key: key);
@override
Widget build(BuildContext context) {
return DropdownButtonHideUnderline(
child: DropdownButton<BloodType>(
value: currentSelectedValue,
hint: Text(
"Blood Group",
style: GoogleFonts.roboto(
textStyle: Theme.of(context).textTheme.headline4,
fontSize: 13,
fontWeight: FontWeight.w700,
color: Color(0xffffffff),
),
),
isDense: true,
onChanged: this.onChanged,
selectedItemBuilder: (BuildContext context) {
return BloodType.getValues().map((BloodType bloodType) {
return Text(
bloodType.toString(),
style: GoogleFonts.roboto(
textStyle: Theme.of(context).textTheme.headline4,
fontSize: 13,
fontWeight: FontWeight.w700,
color: Color(0xffffffff),
),
);
}).toList();
},
items: BloodType.getValues().map((BloodType bloodType) {
return DropdownMenuItem<BloodType>(
value: bloodType,
child: Text(
bloodType.toString(),
style: GoogleFonts.roboto(
textStyle: Theme.of(context).textTheme.headline4,
fontSize: 15,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
);
}).toList()
Now you can call this widget from anywhere in your app and can be easly reused.
class MyPage extends StatefulWidget {
@override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
BloodType currentSelectedValue;
@override
Widget build(BuildContext context) {
return CustomDropDown(
currentSelectedValue: this.currentSelectedValue,
onChanged: (bloodType){
setState((){
this.currentSelectedValue = bloodType
});
},
);
}
}
User contributions licensed under CC BY-SA 3.0