Skip to content

Instantly share code, notes, and snippets.

@roipeker
Last active June 18, 2020 15:52
Show Gist options
  • Save roipeker/594004689007946672431568a0092803 to your computer and use it in GitHub Desktop.
Save roipeker/594004689007946672431568a0092803 to your computer and use it in GitHub Desktop.
A StatefulBuilder with a persistent value in State + some basic dropdown demo.
// roipeker 2020
// sample using ValueBuilder. Generic Dropdown wrapper with basic styling.
import 'package:flutter/material.dart';
import 'valuebuilder.dart';
class CommonDropDown<T> extends StatelessWidget {
final double height;
final List<T> listData;
final T initialValue;
final String hintText;
final String Function(T) itemLabelBuilder;
final Widget Function(T) itemWidgetBuilder;
final void Function(T) onChanged;
const CommonDropDown({
Key key,
@required this.listData,
this.height,
this.onChanged,
this.hintText,
this.initialValue,
this.itemWidgetBuilder,
this.itemLabelBuilder,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final list =
listData.map<DropdownMenuItem>(_buildItem).toList(growable: false);
Widget child = ValueBuilder<T>(
initialValue: initialValue,
onValue: onChanged,
builder: (ctx, value, updater) {
// style as you want.
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey, width: 1)),
),
alignment: Alignment.center,
child: DropdownButton(
isExpanded: true,
hint: hintText != null ? Text(hintText) : null,
isDense: true,
underline: Container(),
onChanged: updater,
items: list,
value: value,
),
);
},
);
if (height != null) {
child = SizedBox(width: double.infinity, height: height, child: child);
}
return child;
}
DropdownMenuItem _buildItem(T data) {
Widget child;
if (itemWidgetBuilder != null) {
child = itemWidgetBuilder(data);
} else {
final text = (itemLabelBuilder == null)
? data?.toString()
: itemLabelBuilder(data);
child = Text(text);
}
return DropdownMenuItem<T>(
child: child,
value: data,
);
}
}
// roipeker 2020
// demo models
import 'package:flutter/material.dart';
// option model List<String>.
final hotelTypesStrings = <String>[
'Hotel',
'Hostel',
'Motel',
'Resort',
'Apartment',
'Studios',
'BnB',
'Villa',
'Camping',
'Pet Hotel',
];
// option model List<HotelType>.
final hotelTypesModels = <HotelType>[
HotelType('Hotel', Icons.business),
HotelType('Hostel', Icons.directions_car),
HotelType('Motel', Icons.golf_course),
HotelType('Resort', Icons.more_vert),
HotelType('Apartment', Icons.portrait),
HotelType('Studios', Icons.shopping_basket),
HotelType('BnB', Icons.replay_30),
HotelType('Villa', Icons.fiber_dvr),
HotelType('Camping', Icons.local_grocery_store),
HotelType('Pet Hotel', Icons.subscriptions),
];
class HotelType {
final IconData icon;
final String name;
final int index;
HotelType(this.name, this.icon, [this.index]);
// if doing this, no need to set a CommonDropDown::itemLabelBuilder...
@override
String toString() => name ;
}
//... some widget class.
Widget buildDropdowns() {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
CommonDropDown<HotelType>(
listData: hotelTypesModels,
hintText: 'Select a type',
// 1 - only return String for the Text.
// itemLabelBuilder: (model) => model.name,
// 2 - generate the actual item's child widget, overrides itemLabelBuilder.
itemWidgetBuilder: (model) =>
Row(
children: [
Icon(model.icon),
SizedBox(width: 10),
Text(model.name),
],
),
onChanged: (value) => print("dropdown changed: $value"),
height: 50,
),
CommonDropDown<String>(
listData: hotelTypesStrings,
initialValue: hotelTypesStrings.first,
// When no itemLabelBuilder, takes directly from .toString()
// itemLabelBuilder: (model) => model,
onChanged: (value) => print("dropdown changed: $value"),
height: 40,
),
],
);
}
//... rest of implementation
// SAMPLE code:
// return ValueBuilder<bool>(
// initialData: false,
// builder: (_, value, updater) =>
// Switch(
// onChanged: updater,
// value: value,
// ),
// utility callbacks
// onValue: print,
// onDispose: () => print("Disposing widget"),
// );
class ValueBuilder<T> extends StatefulWidget {
final T initialData;
final ValueUpdateBuilder builder;
final void Function() onDispose;
final void Function(T) onValue;
const ValueBuilder({
Key key,
this.initialData,
this.onDispose,
this.onValue,
@required this.builder,
}) : super(key: key);
@override
_ValueBuilderState<T> createState() => _ValueBuilderState<T>();
}
class _ValueBuilderState<T> extends State<ValueBuilder<T>> {
T value;
@override
void initState() {
super.initState();
value = widget.initialData;
}
@override
Widget build(BuildContext context) => widget.builder(context, value, updater);
void updater(T newValue) {
if (widget.onValue != null) {
widget.onValue(newValue);
}
setState(() {
value = newValue;
});
}
@override
void dispose() {
super.dispose();
if (widget.onDispose != null) {
widget.onDispose();
}
if (value is ChangeNotifier) {
(value as ChangeNotifier)?.dispose();
} else if (value is StreamController) {
(value as StreamController)?.close();
}
value = null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment