Flutter show datepicker with keypress - flutter

this is my first question, I'm sorry if I'm not clear for what I'm written (I'm French and don't work my english..)
So I'm newby in Flutter and World of Dev, I work on a project who display forms. Here we have a input who onTap return a DateTime to select a date. No problems. But the input don't accept focus so we add FocusNode. I modify the code to add visually feedback on focus input. But it's not possible to open the date picker without tap the input. We want to open it when space or enter while pressed and preserve the existant way.
So i had a method KeyEventResult for these keyboard keys. But i don't know how to return the datepicker when we press them and to return to the page. I try to set a bool when we pressed these keys to show the date picker but the build function returned null and after press tab show the datepicker but i can't leave it. I don't know how I really need to use it..
I show you the initial code I have and the second with my last modifications.. I hope your help, I think it's not hard but I miss competences.... Thanks for your help !
Initial Code with first implementation of focus
/// Displays a Material date picker.
class DateInput extends StatefulWidget {
final Map<String, dynamic> properties;
final int lines;
final TCell templateCell;
final modelFormField.FormField<DateTime> formField;
final String fieldName;
final bool isFormReadonly;
//-----------------------------------------------------------------------------------------------
DateInput(
{#required this.properties,
#required this.lines,
#required this.templateCell,
#required this.formField,
#required this.fieldName,
#required this.isFormReadonly,
Key key})
: super(key: key);
//-----------------------------------------------------------------------------------------------
#override
_AfiDatePickerState createState() => _AfiDatePickerState(formField);
}
//#################################################################################################
class _AfiDatePickerState extends ElementState<DateInput> {
TextEditingController _dateController;
String _selectedValue;
FocusNode _fieldFocusNode;
FocusAttachment _nodeAttachment;
//-----------------------------------------------------------------------------------------------
_AfiDatePickerState(modelFormField.FormField formField) : super(formField);
//-----------------------------------------------------------------------------------------------
#override
void initState() {
super.initState();
_dateController = TextEditingController(
text: widget.formField.valueAccessor.modelToViewValue(widget.formField.value));
_selectedValue = _dateController.text;
initStreamListeners(widget.formField);
_dateController.addListener(_onUserUpdateValue);
_fieldFocusNode = FocusNode();
_fieldFocusNode.addListener(_onFocusChanges);
_nodeAttachment = _fieldFocusNode.attach(context);
}
//-----------------------------------------------------------------------------------------------
void _onUserUpdateValue() {
String newDate = _dateController.text;
// This is a necessary check. If we don't do this, the rules will be executed everytime the
// date picker is opened.
if (newDate != _selectedValue) {
setState(() {
_selectedValue = newDate;
});
widget.formField.updateValue(widget.formField.valueAccessor.viewToModelValue(newDate));
formField.markAsDirty();
formField.markAsTouched();
}
}
//-----------------------------------------------------------------------------------------------
#override
void onValueChanges(value) {
if (mounted) {
setState(() {
_selectedValue = widget.formField.valueAccessor.modelToViewValue(value);
_dateController.text = _selectedValue;
});
}
}
//-----------------------------------------------------------------------------------------------
void _onFocusChanges() {}
//-----------------------------------------------------------------------------------------------
#override
void dispose() {
_dateController.removeListener(_onUserUpdateValue);
_dateController.dispose();
_fieldFocusNode.dispose();
super.dispose();
}
//-----------------------------------------------------------------------------------------------
#override
void refreshValidationMessages() {
validationMessages = '';
Set<String> validationKeys = {};
RegExp dateRegex = RegExp(r'^(\d{2}/\d{2}/\d{4})?$');
if (!dateRegex.hasMatch(_dateController.text)) {
validationKeys.add(ValidationMessage.pattern);
}
validationKeys.forEach((key) {
if (modelFormField.FormField.validatorMessagesI18N.containsKey(key)) {
validationMessages += translate(modelFormField.FormField.validatorMessagesI18N[key]);
validationMessages += ' ';
}
});
}
//-----------------------------------------------------------------------------------------------
#override
Widget build(BuildContext context) {
refreshValidationMessages();
final formProvider = Provider.of<FormProvider>(context);
final alerts = formProvider.getFieldAlerts(
widget.fieldName,
);
bool isReadonly = isFieldReadonly(
formProvider, widget.fieldName, widget.templateCell.control, widget.isFormReadonly);
InputDecoration inputDecoration =
buildInputDecoration(alerts, isReadonly, isValid: validationMessages.isEmpty);
addAlertsToValidationMessage(alerts, formProvider.form.template);
_nodeAttachment.reparent();
return wrapAlertTooltip(
Container(
height: 30,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: InputDecorator(
decoration: inputDecoration,
child: Text(
_dateController.text,
overflow: TextOverflow.visible,
maxLines: 1,
style: TextStyle(fontSize: 16.0),
),
),
onTap: () async {
if (!isReadonly) {
DateTime initialDate = widget.formField.value ?? DateTime.now();
final DateTime pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
cancelText: translate(I18n.CORE_ERASE),
confirmText: translate(I18n.CORE_OK),
builder: (BuildContext context, Widget child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true, accessibleNavigation: true),
child: child,
);
}
);
if (pickedDate == null) {
setState(() {
_dateController.text = '';
});
} else {
setState(() {
_dateController.text = widget.formField.valueAccessor.modelToViewValue(pickedDate);
});
}
}
},
),
),
),
alerts,
validationMessages,
);
}
}
My modif'
import 'package:afi_dto/template/t_cell.dart';
import 'package:afi_flutter_client/config/i18n/i18n.dart';
import 'package:afi_flutter_client/models/form/form_field.dart' as modelFormField;
import 'package:afi_flutter_client/services/providers/form_provider.dart';
import 'package:afi_flutter_client/ui/forms/input/element_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_translate/global.dart';
import 'package:provider/provider.dart';
import 'package:reactive_forms/reactive_forms.dart';
//#################################################################################################
/// Displays a Material date picker.
class DateInput extends StatefulWidget {
final Map<String, dynamic> properties;
final int lines;
final TCell templateCell;
final modelFormField.FormField<DateTime> formField;
final String fieldName;
final bool isFormReadonly;
//-----------------------------------------------------------------------------------------------
DateInput(
{#required this.properties,
#required this.lines,
#required this.templateCell,
#required this.formField,
#required this.fieldName,
#required this.isFormReadonly,
Key key})
: super(key: key);
//-----------------------------------------------------------------------------------------------
#override
_AfiDatePickerState createState() => _AfiDatePickerState(formField);
}
//#################################################################################################
class _AfiDatePickerState extends ElementState<DateInput> {
TextEditingController _dateController;
String _selectedValue;
FocusNode _fieldFocusNode;
FocusAttachment _nodeAttachment;
bool _focused = false;
bool _setDatePicker = false;
//-----------------------------------------------------------------------------------------------
_AfiDatePickerState(modelFormField.FormField formField) : super(formField);
//-----------------------------------------------------------------------------------------------
#override
void initState() {
super.initState();
_dateController = TextEditingController(
text: widget.formField.valueAccessor.modelToViewValue(widget.formField.value));
_selectedValue = _dateController.text;
initStreamListeners(widget.formField);
_dateController.addListener(_onUserUpdateValue);
_fieldFocusNode = FocusNode();
_fieldFocusNode.addListener(_onFocusChanges);
_nodeAttachment = _fieldFocusNode.attach(context, onKey: _handleKeyPress);
}
//-----------------------------------------------------------------------------------------------
void _onUserUpdateValue() {
String newDate = _dateController.text;
// This is a necessary check. If we don't do this, the rules will be executed everytime the
// date picker is opened.
if (newDate != _selectedValue) {
setState(() {
_selectedValue = newDate;
});
widget.formField.updateValue(widget.formField.valueAccessor.viewToModelValue(newDate));
formField.markAsDirty();
formField.markAsTouched();
}
}
//-----------------------------------------------------------------------------------------------
#override
void onValueChanges(value) {
if (mounted) {
setState(() {
_selectedValue = widget.formField.valueAccessor.modelToViewValue(value);
_dateController.text = _selectedValue;
});
}
}
//-----------------------------------------------------------------------------------------------
Future<Null> _onFocusChanges() async {
if (_fieldFocusNode != _focused) {
setState(() {
_focused = _fieldFocusNode.hasFocus;
});
}
}
//-----------------------------------------------------------------------------------------------
KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.space || event.logicalKey == LogicalKeyboardKey.enter) {
setState(() {
_setDatePicker = true;
});
print("key pressed: $event.logicalKey");
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
//-----------------------------------------------------------------------------------------------
#override
void dispose() {
_dateController.removeListener(_onUserUpdateValue);
_dateController.dispose();
_fieldFocusNode.dispose();
super.dispose();
}
//-----------------------------------------------------------------------------------------------
#override
void refreshValidationMessages() {
validationMessages = '';
Set<String> validationKeys = {};
RegExp dateRegex = RegExp(r'^(\d{2}/\d{2}/\d{4})?$');
if (!dateRegex.hasMatch(_dateController.text)) {
validationKeys.add(ValidationMessage.pattern);
}
validationKeys.forEach((key) {
if (modelFormField.FormField.validatorMessagesI18N.containsKey(key)) {
validationMessages += translate(modelFormField.FormField.validatorMessagesI18N[key]);
validationMessages += ' ';
}
});
}
//-----------------------------------------------------------------------------------------------
#override
Widget build(BuildContext context) {
refreshValidationMessages();
final formProvider = Provider.of<FormProvider>(context);
final alerts = formProvider.getFieldAlerts(
widget.fieldName,
);
bool isReadonly = isFieldReadonly(
formProvider, widget.fieldName, widget.templateCell.control, widget.isFormReadonly);
InputDecoration inputDecoration =
buildInputDecoration(alerts, isReadonly, isValid: validationMessages.isEmpty);
addAlertsToValidationMessage(alerts, formProvider.form.template);
_nodeAttachment.reparent();
//-----------------------------------------------------------------------------------------------
Future _datePicker() async {
if (!isReadonly) {
DateTime initialDate = widget.formField.value ?? DateTime.now();
final DateTime pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
cancelText: translate(I18n.CORE_ERASE),
confirmText: translate(I18n.CORE_OK),
builder: (BuildContext context, Widget child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true, accessibleNavigation: true),
child: child,
);
}
);
if (pickedDate == null) {
setState(() {
_dateController.text = '';
_focused = true;
});
} else {
setState(() {
_dateController.text = widget.formField.valueAccessor.modelToViewValue(pickedDate);
_focused = true;
});
}
}
}
//-----------------------------------------------------------------------------------------------
if ( _setDatePicker) {
DateTime initialDate = widget.formField.value ?? DateTime.now();
showDatePicker(
context: context,
initialDate: initialDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
cancelText: translate(I18n.CORE_ERASE),
confirmText: translate(I18n.CORE_OK),
builder: (BuildContext context, Widget child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true, accessibleNavigation: true),
child: child,
);
}
);
} else {
return wrapAlertTooltip(
Container(
height: 30,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: InputDecorator(
decoration: inputDecoration,
isFocused: _focused,
child: Text(
_dateController.text,
overflow: TextOverflow.visible,
maxLines: 1,
style: TextStyle(fontSize: 16.0),
),
),
onTap: _datePicker ,
),
),
),
alerts,
validationMessages,
);
}
}
}

So I resolved my problem. But i don't know how to edit my first message or closing it..
I just extract the method in the onTap to create an other method, and use it in the ontap and in the _handleKeyPress method.
Also I adjust an other problem to the focus when the new methode is call.
In fact it was simple...
I post the code if it can be helpful for someone :)
//#################################################################################################
/// Displays a Material date picker.
class DateInput extends StatefulWidget {
final Map<String, dynamic> properties;
final int lines;
final TCell templateCell;
final modelFormField.FormField<DateTime> formField;
final String fieldName;
final bool isFormReadonly;
//-----------------------------------------------------------------------------------------------
DateInput(
{#required this.properties,
#required this.lines,
#required this.templateCell,
#required this.formField,
#required this.fieldName,
#required this.isFormReadonly,
Key key})
: super(key: key);
//-----------------------------------------------------------------------------------------------
#override
_AfiDatePickerState createState() => _AfiDatePickerState(formField);
}
//#################################################################################################
class _AfiDatePickerState extends ElementState<DateInput> {
TextEditingController _dateController;
String _selectedValue;
FocusNode _fieldFocusNode;
FocusAttachment _nodeAttachment;
bool _focused = false;
//-----------------------------------------------------------------------------------------------
_AfiDatePickerState(modelFormField.FormField formField) : super(formField);
//-----------------------------------------------------------------------------------------------
#override
void initState() {
super.initState();
_dateController = TextEditingController(
text: widget.formField.valueAccessor.modelToViewValue(widget.formField.value));
_selectedValue = _dateController.text;
initStreamListeners(widget.formField);
_dateController.addListener(_onUserUpdateValue);
_fieldFocusNode = FocusNode();
_fieldFocusNode.addListener(_onFocusChanges);
_nodeAttachment = _fieldFocusNode.attach(context, onKey: _handleKeyPress);
}
//-----------------------------------------------------------------------------------------------
void _onUserUpdateValue() {
String newDate = _dateController.text;
// This is a necessary check. If we don't do this, the rules will be executed everytime the
// date picker is opened.
if (newDate != _selectedValue) {
setState(() {
_selectedValue = newDate;
});
widget.formField.updateValue(widget.formField.valueAccessor.viewToModelValue(newDate));
formField.markAsDirty();
formField.markAsTouched();
}
}
//-----------------------------------------------------------------------------------------------
#override
void onValueChanges(value) {
if (mounted) {
setState(() {
_selectedValue = widget.formField.valueAccessor.modelToViewValue(value);
_dateController.text = _selectedValue;
});
}
}
//-----------------------------------------------------------------------------------------------
Future<Null> _onFocusChanges() async {
if (_fieldFocusNode.hasFocus != _focused) {
setState(() {
_focused = _fieldFocusNode.hasFocus;
});
}
}
//-----------------------------------------------------------------------------------------------
void activeDatePicker() async {
DateTime initialDate = widget.formField.value ?? DateTime.now();
final DateTime pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: DateTime(2000),
lastDate: DateTime(2101),
cancelText: translate(I18n.CORE_ERASE),
confirmText: translate(I18n.CORE_OK),
builder: (BuildContext context, Widget child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true, accessibleNavigation: true),
child: child,
);
}
);
if (_focused) {
_fieldFocusNode.unfocus();
} else {
_fieldFocusNode.requestFocus();
}
if (pickedDate == null) {
setState(() {
_dateController.text = '';
});
} else {
setState(() {
_dateController.text = widget.formField.valueAccessor.modelToViewValue(pickedDate);
});
}
}
//-----------------------------------------------------------------------------------------------
KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.space || event.logicalKey == LogicalKeyboardKey.enter) {
activeDatePicker();
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}
//-----------------------------------------------------------------------------------------------
#override
void dispose() {
_dateController.removeListener(_onUserUpdateValue);
_dateController.dispose();
_fieldFocusNode.dispose();
super.dispose();
}
//-----------------------------------------------------------------------------------------------
#override
void refreshValidationMessages() {
validationMessages = '';
Set<String> validationKeys = {};
RegExp dateRegex = RegExp(r'^(\d{2}/\d{2}/\d{4})?$');
if (!dateRegex.hasMatch(_dateController.text)) {
validationKeys.add(ValidationMessage.pattern);
}
validationKeys.forEach((key) {
if (modelFormField.FormField.validatorMessagesI18N.containsKey(key)) {
validationMessages += translate(modelFormField.FormField.validatorMessagesI18N[key]);
validationMessages += ' ';
}
});
}
//-----------------------------------------------------------------------------------------------
#override
Widget build(BuildContext context) {
refreshValidationMessages();
final formProvider = Provider.of<FormProvider>(context);
final alerts = formProvider.getFieldAlerts(
widget.fieldName,
);
bool isReadonly = isFieldReadonly(
formProvider, widget.fieldName, widget.templateCell.control, widget.isFormReadonly);
InputDecoration inputDecoration =
buildInputDecoration(alerts, isReadonly, isValid: validationMessages.isEmpty);
addAlertsToValidationMessage(alerts, formProvider.form.template);
_nodeAttachment.reparent();
//-----------------------------------------------------------------------------------------------
return wrapAlertTooltip(
Container(
height: 30,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: InputDecorator(
decoration: inputDecoration,
isFocused: _focused,
child: Text(
_dateController.text,
overflow: TextOverflow.visible,
maxLines: 1,
style: TextStyle(fontSize: 16.0),
),
),
onTap: () {
if (!isReadonly) {
activeDatePicker();
}
} ,
),
),
),
alerts,
validationMessages,
);
}
}

Related

how to Enable button if "All textfield" is not empty flutter

my app has two textformfield. I want to enable button if all textfield are not empty.
In the internet, there are a way to enable button if "one" textfield are not empty.
I try to make isFilledTitle true if title of textfield is not empty. and isFilledContent true if content of textfield is not empty. and then if they are all true, isButtonActive is true. but it doesn't work.
late TextEditingController _titleEditingController;
late TextEditingController _contentEditingController;
bool isButtonActive = true;
bool isFilledContent = false;
bool isFilledTitle = false;
#override
void initState() {
super.initState();
_titleEditingController = TextEditingController();
_contentEditingController = TextEditingController();
_titleEditingController.addListener(() {
final isFilledTitle = _titleEditingController.text.isNotEmpty;
setState(() {
this.isFilledTitle = isFilledTitle;
});
});
_contentEditingController.addListener(() {
final isFilledContent = _contentEditingController.text.isNotEmpty;
setState(() {
this.isFilledContent = isFilledContent;
});
});
if(isFilledContent && isFilledTitle){
setState(() {
isButtonActive = true;
});
} else {
setState(() {
isButtonActive = false;
});
}
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Container(
child: SafeArea(
child: Scaffold(
appBar: AppBarWriteContainer(
onButtonPressed: isButtonActive
? (widget.postNo != null)
? () => revisePost()
: () => newPost()
: null,
),
Yeah your answer is correct but the user needs validation on two text field so i will modify the answer like
class _TempDialogState extends State<TempDialog> {
final TextEditingController _inputController = TextEditingController();
final TextEditingController _inputController2 = TextEditingController();
bool enable = false;
#override
void initState() {
super.initState();
}
#override
void dispose() {
_inputController.dispose();
_inputController2.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Center(
child: Column(children: [
TextField(
controller: _inputController,
onChanged: (data) {
if (_inputController.text.isEmpty ||
_inputController2.text.isEmpty) {
enable = false;
} else {
enable = true;
}
setState(() {});
},
),
TextField(
controller: _inputController2,
onChanged: (data) {
if (_inputController.text.isEmpty ||
_inputController2.text.isEmpty) {
enable = false;
} else {
enable = true;
}
setState(() {});
},
),
ElevatedButton(
onPressed: enable ? () {} : null,
child: Text('${enable}'),
)
])),
),
);
}
}
You don't need any additional libraries to do that. Flutter has it out-of-the-box and you can make sure you're not going to rebuild the whole tree on each keystroke.
TextEditingController extends ValueNotifier<TextEditingValue> which means you can utilize ValueListenableBuilder from material package to listen to text changes.
class MyWidget extends StatelessWidget {
final TextEditingController _inputController = TextEditingController();
#override
void dispose() {
_inputController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(children: [
TextField(controller: _inputController),
ValueListenableBuilder<TextEditingValue>(
valueListenable: _inputController,
builder: (context, value, child) {
return ElevatedButton(
onPressed: value.text.isNotEmpty ? () {} : null,
child: Text('I am disabled when text is empty'),
);
},
),
]);
}
}
You only set the isButtonActive in the initState. Try updating it in the controller listeners. Maybe something like:
#override
void initState() {
super.initState();
_titleEditingController = TextEditingController();
_contentEditingController = TextEditingController();
_titleEditingController.addListener(() {
final isFilledTitle = _titleEditingController.text.isNotEmpty;
setState(() {
this.isFilledTitle = isFilledTitle;
updateIsButtonActive();
});
});
_contentEditingController.addListener(() {
final isFilledContent = _contentEditingController.text.isNotEmpty;
setState(() {
this.isFilledContent = isFilledContent;
updateIsButtonActive();
});
});
}
void updateIsButtonActive() {
if(isFilledContent && isFilledTitle){
isButtonActive = true;
} else {
isButtonActive = false;
}
}

Multiple showDialog for validation

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.

How can I setup simple audio player with background and notification support in flutter

I am using Flutter sound package to play a single audio from a URL and also show notification media controller.
I have tried the demo app on their documentation but its not clear to understand.
The background audio player somehow worked fine but I want to remove the recording system and make it to play audio from URL
Here is the demo app
Here is the example of the how to do the above question.
you will have many errors if you haven't enabled null safety which is just changing your flutter sdk in the Pub.yaml
environment:
sdk: ">=2.12.0 <3.0.0"
to the following if you are using flutter above 2.12 otherwise check on how to migrate flutter project to null safety
import 'dart:async';
import 'dart:math';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'package:flutter/foundation.dart';
import 'package:just_audio/just_audio.dart';
import 'package:rxdart/rxdart.dart';
// You might want to provide this using dependency injection rather than a
// global variable.
late AudioHandler _audioHandler;
Future<void> main() async {
_audioHandler = await AudioService.init(
builder: () => AudioPlayerHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
),
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Audio Service Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: MainScreen(),
);
}
}
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Audio Service Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Show media item title
StreamBuilder<MediaItem?>(
stream: _audioHandler.mediaItem,
builder: (context, snapshot) {
final mediaItem = snapshot.data;
return Text(mediaItem?.title ?? '');
},
),
// Play/pause/stop buttons.
StreamBuilder<bool>(
stream: _audioHandler.playbackState
.map((state) => state.playing)
.distinct(),
builder: (context, snapshot) {
final playing = snapshot.data ?? false;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_button(Icons.fast_rewind, _audioHandler.rewind),
if (playing)
_button(Icons.pause, _audioHandler.pause)
else
_button(Icons.play_arrow, _audioHandler.play),
_button(Icons.stop, _audioHandler.stop),
_button(Icons.fast_forward, _audioHandler.fastForward),
],
);
},
),
// A seek bar.
StreamBuilder<MediaState>(
stream: _mediaStateStream,
builder: (context, snapshot) {
final mediaState = snapshot.data;
return SeekBar(
duration: mediaState?.mediaItem?.duration ?? Duration.zero,
position: mediaState?.position ?? Duration.zero,
onChangeEnd: (newPosition) {
_audioHandler.seek(newPosition);
},
);
},
),
// Display the processing state.
StreamBuilder<AudioProcessingState>(
stream: _audioHandler.playbackState
.map((state) => state.processingState)
.distinct(),
builder: (context, snapshot) {
final processingState =
snapshot.data ?? AudioProcessingState.idle;
return Text(
"Processing state: ${describeEnum(processingState)}");
},
),
],
),
),
);
}
/// A stream reporting the combined state of the current media item and its
/// current position.
Stream<MediaState> get _mediaStateStream =>
Rx.combineLatest2<MediaItem?, Duration, MediaState>(
_audioHandler.mediaItem,
AudioService.position,
(mediaItem, position) => MediaState(mediaItem, position));
IconButton _button(IconData iconData, VoidCallback onPressed) => IconButton(
icon: Icon(iconData),
iconSize: 64.0,
onPressed: onPressed,
);
}
class MediaState {
final MediaItem? mediaItem;
final Duration position;
MediaState(this.mediaItem, this.position);
}
/// An [AudioHandler] for playing a single item.
class AudioPlayerHandler extends BaseAudioHandler with SeekHandler {
static final _item = MediaItem(
id: 'https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3',
album: "Science Friday",
title: "A Salute To Head-Scratching Science",
artist: "Science Friday and WNYC Studios",
duration: const Duration(milliseconds: 5739820),
artUri: Uri.parse(
'https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg'),
);
final _player = AudioPlayer();
/// Initialise our audio handler.
AudioPlayerHandler() {
// So that our clients (the Flutter UI and the system notification) know
// what state to display, here we set up our audio handler to broadcast all
// playback state changes as they happen via playbackState...
_player.playbackEventStream.map(_transformEvent).pipe(playbackState);
// ... and also the current media item via mediaItem.
mediaItem.add(_item);
// Load the player.
_player.setAudioSource(AudioSource.uri(Uri.parse(_item.id)));
}
// In this simple example, we handle only 4 actions: play, pause, seek and
// stop. Any button press from the Flutter UI, notification, lock screen or
// headset will be routed through to these 4 methods so that you can handle
// your audio playback logic in one place.
#override
Future<void> play() => _player.play();
#override
Future<void> pause() => _player.pause();
#override
Future<void> seek(Duration position) => _player.seek(position);
#override
Future<void> stop() => _player.stop();
/// Transform a just_audio event into an audio_service state.
///
/// This method is used from the constructor. Every event received from the
/// just_audio player will be transformed into an audio_service state so that
/// it can be broadcast to audio_service clients.
PlaybackState _transformEvent(PlaybackEvent event) {
return PlaybackState(
controls: [
MediaControl.rewind,
if (_player.playing) MediaControl.pause else MediaControl.play,
MediaControl.stop,
MediaControl.fastForward,
],
systemActions: const {
MediaAction.seek,
MediaAction.seekForward,
MediaAction.seekBackward,
},
androidCompactActionIndices: const [0, 1, 3],
processingState: const {
ProcessingState.idle: AudioProcessingState.idle,
ProcessingState.loading: AudioProcessingState.loading,
ProcessingState.buffering: AudioProcessingState.buffering,
ProcessingState.ready: AudioProcessingState.ready,
ProcessingState.completed: AudioProcessingState.completed,
}[_player.processingState]!,
playing: _player.playing,
updatePosition: _player.position,
bufferedPosition: _player.bufferedPosition,
speed: _player.speed,
queueIndex: event.currentIndex,
);
}
}
class PositionData {
final Duration position;
final Duration bufferedPosition;
final Duration duration;
PositionData(this.position, this.bufferedPosition, this.duration);
}
class SeekBar extends StatefulWidget {
final Duration duration;
final Duration position;
final Duration bufferedPosition;
final ValueChanged<Duration>? onChanged;
final ValueChanged<Duration>? onChangeEnd;
SeekBar({
required this.duration,
required this.position,
this.bufferedPosition = Duration.zero,
this.onChanged,
this.onChangeEnd,
});
#override
_SeekBarState createState() => _SeekBarState();
}
class _SeekBarState extends State<SeekBar> {
double? _dragValue;
bool _dragging = false;
late SliderThemeData _sliderThemeData;
#override
void didChangeDependencies() {
super.didChangeDependencies();
_sliderThemeData = SliderTheme.of(context).copyWith(
trackHeight: 2.0,
);
}
#override
Widget build(BuildContext context) {
final value = min(
_dragValue ?? widget.position.inMilliseconds.toDouble(),
widget.duration.inMilliseconds.toDouble(),
);
if (_dragValue != null && !_dragging) {
_dragValue = null;
}
return Stack(
children: [
SliderTheme(
data: _sliderThemeData.copyWith(
thumbShape: HiddenThumbComponentShape(),
activeTrackColor: Colors.blue.shade100,
inactiveTrackColor: Colors.grey.shade300,
),
child: ExcludeSemantics(
child: Slider(
min: 0.0,
max: widget.duration.inMilliseconds.toDouble(),
value: min(widget.bufferedPosition.inMilliseconds.toDouble(),
widget.duration.inMilliseconds.toDouble()),
onChanged: (value) {},
),
),
),
SliderTheme(
data: _sliderThemeData.copyWith(
inactiveTrackColor: Colors.transparent,
),
child: Slider(
min: 0.0,
max: widget.duration.inMilliseconds.toDouble(),
value: value,
onChanged: (value) {
if (!_dragging) {
_dragging = true;
}
setState(() {
_dragValue = value;
});
if (widget.onChanged != null) {
widget.onChanged!(Duration(milliseconds: value.round()));
}
},
onChangeEnd: (value) {
if (widget.onChangeEnd != null) {
widget.onChangeEnd!(Duration(milliseconds: value.round()));
}
_dragging = false;
},
),
),
Positioned(
right: 16.0,
bottom: 0.0,
child: Text(
RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$')
.firstMatch("$_remaining")
?.group(1) ??
'$_remaining',
style: Theme.of(context).textTheme.caption),
),
],
);
}
Duration get _remaining => widget.duration - widget.position;
}
class HiddenThumbComponentShape extends SliderComponentShape {
#override
Size getPreferredSize(bool isEnabled, bool isDiscrete) => Size.zero;
#override
void paint(
PaintingContext context,
Offset center, {
required Animation<double> activationAnimation,
required Animation<double> enableAnimation,
required bool isDiscrete,
required TextPainter labelPainter,
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required TextDirection textDirection,
required double value,
required double textScaleFactor,
required Size sizeWithOverflow,
}) {}
}
class LoggingAudioHandler extends CompositeAudioHandler {
LoggingAudioHandler(AudioHandler inner) : super(inner) {
playbackState.listen((state) {
_log('playbackState changed: $state');
});
queue.listen((queue) {
_log('queue changed: $queue');
});
queueTitle.listen((queueTitle) {
_log('queueTitle changed: $queueTitle');
});
mediaItem.listen((mediaItem) {
_log('mediaItem changed: $mediaItem');
});
ratingStyle.listen((ratingStyle) {
_log('ratingStyle changed: $ratingStyle');
});
androidPlaybackInfo.listen((androidPlaybackInfo) {
_log('androidPlaybackInfo changed: $androidPlaybackInfo');
});
customEvent.listen((dynamic customEventStream) {
_log('customEvent changed: $customEventStream');
});
customState.listen((dynamic customState) {
_log('customState changed: $customState');
});
}
// TODO: Use logger. Use different log levels.
void _log(String s) => print('----- LOG: $s');
#override
Future<void> prepare() {
_log('prepare()');
return super.prepare();
}
#override
Future<void> prepareFromMediaId(String mediaId,
[Map<String, dynamic>? extras]) {
_log('prepareFromMediaId($mediaId, $extras)');
return super.prepareFromMediaId(mediaId, extras);
}
#override
Future<void> prepareFromSearch(String query, [Map<String, dynamic>? extras]) {
_log('prepareFromSearch($query, $extras)');
return super.prepareFromSearch(query, extras);
}
#override
Future<void> prepareFromUri(Uri uri, [Map<String, dynamic>? extras]) {
_log('prepareFromSearch($uri, $extras)');
return super.prepareFromUri(uri, extras);
}
#override
Future<void> play() {
_log('play()');
return super.play();
}
#override
Future<void> playFromMediaId(String mediaId, [Map<String, dynamic>? extras]) {
_log('playFromMediaId($mediaId, $extras)');
return super.playFromMediaId(mediaId, extras);
}
#override
Future<void> playFromSearch(String query, [Map<String, dynamic>? extras]) {
_log('playFromSearch($query, $extras)');
return super.playFromSearch(query, extras);
}
#override
Future<void> playFromUri(Uri uri, [Map<String, dynamic>? extras]) {
_log('playFromUri($uri, $extras)');
return super.playFromUri(uri, extras);
}
#override
Future<void> playMediaItem(MediaItem mediaItem) {
_log('playMediaItem($mediaItem)');
return super.playMediaItem(mediaItem);
}
#override
Future<void> pause() {
_log('pause()');
return super.pause();
}
#override
Future<void> click([MediaButton button = MediaButton.media]) {
_log('click($button)');
return super.click(button);
}
#override
Future<void> stop() {
_log('stop()');
return super.stop();
}
#override
Future<void> addQueueItem(MediaItem mediaItem) {
_log('addQueueItem($mediaItem)');
return super.addQueueItem(mediaItem);
}
#override
Future<void> addQueueItems(List<MediaItem> mediaItems) {
_log('addQueueItems($mediaItems)');
return super.addQueueItems(mediaItems);
}
#override
Future<void> insertQueueItem(int index, MediaItem mediaItem) {
_log('insertQueueItem($index, $mediaItem)');
return super.insertQueueItem(index, mediaItem);
}
#override
Future<void> updateQueue(List<MediaItem> queue) {
_log('updateQueue($queue)');
return super.updateQueue(queue);
}
#override
Future<void> updateMediaItem(MediaItem mediaItem) {
_log('updateMediaItem($mediaItem)');
return super.updateMediaItem(mediaItem);
}
#override
Future<void> removeQueueItem(MediaItem mediaItem) {
_log('removeQueueItem($mediaItem)');
return super.removeQueueItem(mediaItem);
}
#override
Future<void> removeQueueItemAt(int index) {
_log('removeQueueItemAt($index)');
return super.removeQueueItemAt(index);
}
#override
Future<void> skipToNext() {
_log('skipToNext()');
return super.skipToNext();
}
#override
Future<void> skipToPrevious() {
_log('skipToPrevious()');
return super.skipToPrevious();
}
#override
Future<void> fastForward() {
_log('fastForward()');
return super.fastForward();
}
#override
Future<void> rewind() {
_log('rewind()');
return super.rewind();
}
#override
Future<void> skipToQueueItem(int index) {
_log('skipToQueueItem($index)');
return super.skipToQueueItem(index);
}
#override
Future<void> seek(Duration position) {
_log('seek($position)');
return super.seek(position);
}
#override
Future<void> setRating(Rating rating, [Map<String, dynamic>? extras]) {
_log('setRating($rating, $extras)');
return super.setRating(rating, extras);
}
#override
Future<void> setCaptioningEnabled(bool enabled) {
_log('setCaptioningEnabled($enabled)');
return super.setCaptioningEnabled(enabled);
}
#override
Future<void> setRepeatMode(AudioServiceRepeatMode repeatMode) {
_log('setRepeatMode($repeatMode)');
return super.setRepeatMode(repeatMode);
}
#override
Future<void> setShuffleMode(AudioServiceShuffleMode shuffleMode) {
_log('setShuffleMode($shuffleMode)');
return super.setShuffleMode(shuffleMode);
}
#override
Future<void> seekBackward(bool begin) {
_log('seekBackward($begin)');
return super.seekBackward(begin);
}
#override
Future<void> seekForward(bool begin) {
_log('seekForward($begin)');
return super.seekForward(begin);
}
#override
Future<void> setSpeed(double speed) {
_log('setSpeed($speed)');
return super.setSpeed(speed);
}
#override
Future<dynamic> customAction(String name,
[Map<String, dynamic>? extras]) async {
_log('customAction($name, extras)');
final dynamic result = await super.customAction(name, extras);
_log('customAction -> $result');
return result;
}
#override
Future<void> onTaskRemoved() {
_log('onTaskRemoved()');
return super.onTaskRemoved();
}
#override
Future<void> onNotificationDeleted() {
_log('onNotificationDeleted()');
return super.onNotificationDeleted();
}
#override
Future<List<MediaItem>> getChildren(String parentMediaId,
[Map<String, dynamic>? options]) async {
_log('getChildren($parentMediaId, $options)');
final result = await super.getChildren(parentMediaId, options);
_log('getChildren -> $result');
return result;
}
#override
ValueStream<Map<String, dynamic>> subscribeToChildren(String parentMediaId) {
_log('subscribeToChildren($parentMediaId)');
final result = super.subscribeToChildren(parentMediaId);
result.listen((options) {
_log('$parentMediaId children changed with options $options');
});
return result;
}
#override
Future<MediaItem?> getMediaItem(String mediaId) async {
_log('getMediaItem($mediaId)');
final result = await super.getMediaItem(mediaId);
_log('getMediaItem -> $result');
return result;
}
#override
Future<List<MediaItem>> search(String query,
[Map<String, dynamic>? extras]) async {
_log('search($query, $extras)');
final result = await super.search(query, extras);
_log('search -> $result');
return result;
}
#override
Future<void> androidSetRemoteVolume(int volumeIndex) {
_log('androidSetRemoteVolume($volumeIndex)');
return super.androidSetRemoteVolume(volumeIndex);
}
#override
Future<void> androidAdjustRemoteVolume(AndroidVolumeDirection direction) {
_log('androidAdjustRemoteVolume($direction)');
return super.androidAdjustRemoteVolume(direction);
}
}
void showSliderDialog({
required BuildContext context,
required String title,
required int divisions,
required double min,
required double max,
String valueSuffix = '',
// TODO: Replace these two by ValueStream.
required double value,
required Stream<double> stream,
required ValueChanged<double> onChanged,
}) {
showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: Text(title, textAlign: TextAlign.center),
content: StreamBuilder<double>(
stream: stream,
builder: (context, snapshot) => Container(
height: 100.0,
child: Column(
children: [
Text('${snapshot.data?.toStringAsFixed(1)}$valueSuffix',
style: const TextStyle(
fontFamily: 'Fixed',
fontWeight: FontWeight.bold,
fontSize: 24.0)),
Slider(
divisions: divisions,
min: min,
max: max,
value: snapshot.data ?? value,
onChanged: onChanged,
),
],
),
),
),
),
);
}
I recommend just_audio with audio_service.
As the plugin name says just_audio is just for audio and to create notification you have to use audio_service.

Flutter setting variable of CupertinoPicker after showModalBottomSheet

I'm referencing this answer:https://stackoverflow.com/a/57592970/8616895
Trying to get selectedValue when the bottommodal is closed with WhenComplete.
The function in WhenComplete() is called, but when I print the selectedValue, it only responds as the default, 0. I have checked and SelectedValue does change as I rotate, but the correct value is not retrieved when I close the modal, I assume it has something to do with async or scope. Please share any suggestions.
My class is as follows:
class PopupButton extends StatefulWidget {
final String initialValue;
final List<dynamic> options;
final Function callback;
int initialIndex;
PopupButton(
{#required this.initialValue,
#required this.options,
this.callback,
this.initialIndex = 0});
#override
_PopupButtonState createState() => _PopupButtonState();
}
class _PopupButtonState extends State<PopupButton> {
int selectedValue;
String text = "";
void showPicker(
BuildContext context,
) {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
child: CupertinoPicker(
itemExtent: 32,
onSelectedItemChanged: (value) {
if (mounted) {
setState(() {
selectedValue = value;
});
}
},
children: (widget.options.map((option) => Text(
option,
style: TextStyles.headerLight,
))).toList()),
);
}).whenComplete(() {
// THIS ALWAYS PRINTS 0
print("Selected: $selectedValue");
if (widget.callback != null) {
widget.callback(selectedValue);
}
TextEditingController();
return;
});
}
#override
Widget build(BuildContext context) {
selectedValue = widget.initialIndex;
text = widget.initialValue;
return GestureDetector(
child: Text(text),
onTap: () {
showPicker(context);
},
);
}
}
your mistake is initialization selectedValue in build method.
Every time you call
setState(() {
selectedValue = value;
});
the selectedValue initiated by widget.initialIndex.
Please move selectedValue = widget.initialIndex; to initState method.
#override
void initState() {
super.initState();
selectedValue = widget.initialIndex;
text = widget.initialValue;
}
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Text(text),
onTap: () {
showPicker(context);
},
);
}

Flutter bool name = String its possible?

Hey i make a Favorite System with a bool to say if is favorite or not.
But if the name of the bool is always the same it applies to all my entries!
but each entry has its own name (widget.name), and i thought maybe something like that could work
bool widget.name;
but this not work :(
how can i solve that each entry has its own bool?
by the way i use this plugin for that
https://pub.dev/packages/shared_preferences/example
SharedPreferences sharedPreferences;
bool isfavorit;
#override
void initState() {
super.initState();
SharedPreferences.getInstance().then((SharedPreferences sp) {
sharedPreferences = sp;
isfavorit = sharedPreferences.getBool(spKey);
// will be null if never previously saved
if (isfavorit == null) {
isfavorit = false;
persist(isfavorit); // set an initial value
}
setState(() {});
});
}
void persist(bool value) {
setState(() {
isfavorit = value;
});
sharedPreferences?.setBool(spKey, value);
}
Complete Code
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Details extends StatefulWidget {
final String name;
Details(
this.name,
);
#override
_DetailsState createState() => _DetailsState();
}
const String spKey = 'myBool';
class _DetailsState extends State<Details> {
SharedPreferences sharedPreferences;
bool isfavorit;
#override
void initState() {
super.initState();
SharedPreferences.getInstance().then((SharedPreferences sp) {
sharedPreferences = sp;
isfavorit = sharedPreferences.getBool(spKey);
// will be null if never previously saved
if (isfavorit == null) {
isfavorit = false;
persist(isfavorit); // set an initial value
}
setState(() {});
});
}
void persist(bool value) {
setState(() {
isfavorit = value;
});
sharedPreferences?.setBool(spKey, value);
}
// ignore: missing_return
IconData favicon() {
if (isfavorit == true) {
return Icons.favorite;
} else if (isfavorit == false) {
return Icons.favorite_border;
}
}
// ignore: missing_return
Color favicolor() {
if (isfavorit == true) {
return Colors.red;
} else if (isfavorit == false) {
return Colors.white;
}
}
void changefav() {
if (isfavorit == true) {
return persist(false);
} else if (isfavorit == false) {
return persist(true);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(
favicon(),
color: favicolor(),
),
onPressed: () => changefav(),
),
],
title: Text(widget.name),
),
body: Container(
child: Text(widget.name),
),
);
}
}
You are always saving the isFavorite to the same key in shared preferences, instead of using a constant key use one that is based on the widget.name
So for instance:
sharedPreferences.getBool('details_favorite_${widget.name}');