I want to change date field value while clicking on a Radio Button named "Yes". One can input date by changing date from datepicker. Another one is is user clicked on "Yes" button the datefield value will be changed. I'm trying it using Provider. But the updated value isn't displaying into datefield instantly.
Code snippet:
DateTimeFormField(
dateFormat: DateFormat('yyyy-MM-dd'),
mode: DateTimeFieldPickerMode.date,
initialValue: DateTime.parse(list[index].endDate!),
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
hintStyle: TextStyle(color: Colors.black45),
errorStyle: TextStyle(color: Colors.redAccent),
border: OutlineInputBorder(),
suffixIcon: Icon(Icons.event_note),
),
onDateSelected: (DateTime value) {
list[index].endDate = value.toString();
},
)
##
class ManipulateDate extends ChangeNotifier {
String date = '';
void setDateToDTNow(String newDate) {
date = newDate;
notifyListeners();
}
}
Inside Button's onPressed function
Provider.of<ManipulateDate>(context, listen: false).setDateToDTNow(DateTime.now().toString());
How could I set the changed value Provider.of<ManipulateDate>(context).date into list[index].endDate and the value will display instantly while clicking on Button.
I guess I got the issue you have set listen: false -
Provider.of<ManipulateDate>(context, listen: false).setDateToDTNow(DateTime.now().toString());
So when you do notifylisteners() the above won't be triggered or updated so try changing that to true -
Provider.of<ManipulateDate>(context, listen: true).setDateToDTNow(DateTime.now().toString());
Or alternatively you can use a consumer widget around the part which you want to update in the UI. Hope these helps now....
Most likely this is because you're not listening to the provider changes in your build method, so the widget doesn't get "refreshed" when the provider is updated. Check ChangeNotifierProvider for some examples.
BTW, I highly recommend using Riverpod over Provider, as it offers a cleaner widget tree, and more flexibility in state management.
For example, you could do this:
// Outside of your class...
// Yes, as a global, but don't worry. That's actually the correct way to do it.
final selectedDateProvider = StateProvider((ref) => DateTime.now());
#override
Widget build(BuildContext context, WidgetRef ref) {
final DateTime selectedDate = ref.watch(selectedDateProvider).state;
return DateTimeFormField(
initialValue: selectedDate,
...
onDateSelected: (DateTime value) {
ref.read(selectedDateProvider).state = value;
},
);
}
Related
How to pass the API data value into the sfslider if I pass the static value to the slider it can be draggable. But if I give the API data value directly it can drag but not update the new value and return to the API response value position.
I saw some of the solutions they said declare a static value outside of a widget, it works fine. But I need to use API values, How to do it? Somebody can help me!
double _value = 40.0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
FutureBuilder(
future: propertycall(),
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return SfSlider(
shouldAlwaysShowTooltip: true,
activeColor: secondarycolor,
min: snapshot.data["set-parameters"]["mortgage_interest_rate"]["min_value"],
max: snapshot.data["set-parameters"]["mortgage_interest_rate"]["max_value"],
value: _value, //issue occur here
// value:snapshot.data["set-parameters"]["mortgage_interest_rate"]
//["default_value"]
interval: snapshot.data["set-parameters"]["mortgage_interest_rate"]
["steps_value"],
showTicks: false,
showLabels: false,
enableTooltip: true,
numberFormat: NumberFormat(),
onChanged: (new_value) {
setState(() {
_value = new_value; // issues!!
//API value working but not able to drag slider,
//if I give static value from outside of a widget it works
// _value=snapshot.data["set-parameters"]["mortgage_interest_rate"]
//["default_value"]
},
);
},
),
},
},
);
),
),
);
}
Make _value nullable and use it when not null.
double? _value = null;
...
value: _value ?? snapshot.data["set-parameters"]["mortgage_interest_rate"]
We have analyzed the code snippet and found that in the sample you set the static value from the API, and it does not get changed. It is always a static one, this is the reason for the point not getting dragged. For this, you just need to update the pointer value in API which is got from the onChanged callback and rebuild the slider control. Then only the point gets moved otherwise it will stay at the initial position and did not get dragged.
In this page I wanted to create a text form field to ask the user to enter the name and the price of the item and then when I click on the bottom button I want to save the data in a map so I can access it from another page/widget even when I close the app or maybe to show history of transactions
What's the right way to do it ?
the UI of the widget
c
lass _IncomeState extends State<Income> {
final amountController = TextEditingController();
#override
void dispose() {
amountController.dispose();
super.dispose();
}
and this is the textfield
onPressed: () {
// //this is the part where i add a new income to my list
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Add income'),
content: TextField(
autofocus: true,
decoration: InputDecoration(
hintText: 'entrez la somme ici'),
controller: amountController,
),
));
}),
You can use a TextEditingController to get the value entered in the TextFormField when the button is clicked. Here is an example from the flutter homepage. Just replace the _printLatestValue method from the example with your saving method and call that method when pressing the button.
I am building a table_calendar widget, on which I want to select multiple days and keep them in firestore. (Selected days are green, everything else white)
I get the values from firestore via stream provider and use ChangeNotifier to read those values where I need to and I use _selectedDays.addAll (from table_calendar) to add all the values onto the calendar. My problem is that sometimes, when I try to deselect a day, it get's removed from firebase, but the ChangeNotifier listener gets refreshed with the old value before it gets refreshed with the new value, meaning that the day remains selected, even though it shouldn't be.
When I am halting a test listener.
final events = watch(todoChangeNotifierProvider).todos;
I can see that 60-70% of the times it gets the new value, but sometimes it refreshes with the old one, screwing up the table_calendar ui.
Why does this happen?
I have the provider declared globally
final todoChangeNotifierProvider = ChangeNotifierProvider.autoDispose<TodosProvider>((ref) {
return TodosProvider();
});
And TodosProvider looks like this
class TodosProvider extends ChangeNotifier {
final FirebaseAuth auth = FirebaseAuth.instance;
List<Todo> _todos = [];
void setTodos(List<Todo> todos) {
_todos = todos;
}
I am setting the Change Notifier value inside the stream, so if something changes it gets updated.
EDIT: More code
Setting the ChangeNotifer with stream value
Widget build(BuildContext context,ScopedReader watch) {
final todoProvider = watch(todoChangeNotifierProvider);
//Todo? currentTodo = Todo();
final firebaseAuth = context.read(firebaseAuthProvider);
final todoStream = watch(todoStreamProvider(firebaseAuth.currentUser!.uid));
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('DASHBOARD'),
),
body: todoStream.when(
data: (data) {
final currentTodo = data;
todoProvider.setTodos(currentTodo);
return SingleChildScrollView(
padding: EdgeInsets.fromLTRB(10, 10, 0, 55),
child: Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center, //Center Column contents horizontally,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center, //Center Row contents horizontally,
children: [Text('${currentUser!.loginStreak}',style: TextStyle(fontSize: 20)), Icon(Icons.local_fire_department, size: 40,color: Color(0xFFFD8787),), Text('DAILY ACTIVITY STREAK', style: TextStyle(fontSize: 20),)],),
],
),
Column(
children: [
TodoListWidget(editAllowed: true, currentUser: currentUser,)
],
)
Reading the provider
class TodoListWidget extends ConsumerWidget {
final UserData? currentUser;
final editAllowed;
TodoListWidget({this.editAllowed, this.currentUser});
#override
Widget build(BuildContext context, ScopedReader watch) {
final todoProvider = watch(todoChangeNotifierProvider);
final todos = todoProvider.todos;
return todos.isEmpty
...
selecting the day, marking it, and updating firebase
void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
setState(() {
if (_selectedDays.contains(selectedDay)) {
docRef.update({
'calendarEvents': FieldValue.arrayRemove([selectedDay])
});
userPointsRef.update({'points': FieldValue.increment(-1)});
habitPointsRef
.update({'daysTrackedThisWeek': FieldValue.increment(-1)});
_selectedDays.remove(selectedDay);
} else {
//INCREMENTS POITNS AND DAYS TRACKED ON SELECTED DAY
docRef.update({
'calendarEvents': FieldValue.arrayUnion([selectedDay])
});
userPointsRef.update({'points': FieldValue.increment(1)});
habitPointsRef.update({'daysTrackedThisWeek': FieldValue.increment(1)});
_selectedDays.add(selectedDay);
}
});
enter code here
To try and paint a better picture.
I get the table_calendar values from firebase via a stream.
I set the values to a ChangeNotifier
I read the ChangeNotifier (But it seems, that 40% of the time, when a new value comes it first refreshes with the old value, before it gets the new one (that messes up the ui)
This is how I set the values for table_calendar selected days
in TodoWidget
Widget build(BuildContext context) {
_selectedDays.addAll(widget.todo!.calendarEvents);
The values come from TodoListWidget
You have to use notifyListeners with ChangeNotifier. When you are watching the provider, it only knows to pick up a new value if notifyListeners is called.
The reason you are getting a runtime error when doing so is that you are triggering an update during the initial build.
In your ChangeNotifier, add the call to notifyListeners:
void setTodos(List<Todo> todos) {
_todos = todos;
notifyListeners();
}
In your widget code:
Widget build(BuildContext context,ScopedReader watch) {
final firebaseAuth = watch(firebaseAuthProvider);
final todoStream = watch(todoStreamProvider(firebaseAuth.currentUser!.uid));
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('DASHBOARD'),
),
body: todoStream.when(
data: (currentTodo) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
context.read(todoChangeNotifierProvider).setTodos(currentTodo);
});
return SingleChildScrollView(...);
}
The PostFrameCallback does exactly what it sounds like - delays the code from being run until the next frame, which will eliminate the runtime error you were facing.
i stored phone numbers in a firebase document(table). What i want is to detect if the number already existed by using validators and display a message under a textbox that the phone number is already exists i had no problem with this , my problem is a have to double tap the button to execute to complete the task.
var _formKey = GlobalKey<FormState>();
var validate;
String validateNumber(String value){
if (value.isEmpty) {
return 'Please enter number';
}if(validate == true){
return "Phone number already exists";
}
return null;
}
addPhoneNumber(phoneNumber) async{
var exist = await db.checkPhoneNumber(phoneNumber); //my query to firestore so far i have no problem with this.
if(exist == true){
validate = true;
print(validate);
}
if(exist == false){
validate = false;
Navigator.of(context).push(_verifyPhone(_phoneNumber.text));
await db.addPhoneNumber(phoneNumber);
}
} //my function to detect if the number exists
#override
Widget build(BuildContext context) {
_get1();
return WillPopScope(
onWillPop: () async {
Navigator.pop(context);
return true;
},
child: Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Form(
key:_formKey,
child:Expanded(
child:Scrollbar(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
TextFormField(
controller: _phoneNumber,
validator: validateNumber, //my validator
),
],
),
),
),
),
FlatButton(
onTap: (){
if(_formKey.currentState.validate()){
addPhoneNumber(phoneNumber)
}
},
)
],
),
),
);
}
}
You need to call the validate() method on your form state. Look here for API reference.
This is not invoked automatically by the framework for you. You need to invoke it inside the onTap() method of your button. Add there a line _formKey.currentState.validate().
When this method is invoked then the form calls the validator for each form field.
---------------------------UPDATE---------------------------------------
Ok, #ppdy, you are one step closer now. It doesn't work yet because your validator only checks if the value is not empty. Just look carefully at what happens when you push the button. It runs the validateNumber and if the value is empty the framework will render your validate message. Then if the value is not empty you run the addPhoneNumber method, but you run it yourself. This is important, notice that it is not get run as part of the text form field validator property function. So you need to handle the output of the await db.checkPhoneNumber(phoneNumber); yourself and render the validation red message in the text form field if the check is false.
For this please note first that the TextFormField class has the decoration property. Type of this property is the InputDecoration. Please read the documentation of this class here. So by setting the errorText property of the decoration you will be able to add the red validation message to the form field manually.
Try to think it all over, play with it, stick this all together and you will have what you want :)
I've used SimpleAutoCompleteTextField in my flutter project but am facing a problem of not being suggestions the right suggestions unless I started to type the beginning of the word not from the middle of it .. example:
When I am looking for the word "Astalavista", if I type "asta" it will be suggested but if I typed "lavis" it won't be suggested, I need to fix this out.
Here is my code :
child: SimpleAutoCompleteTextField(
key: endKey,
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: S.of(context).end_location_hint),
controller: endLocationTextEditingController,
suggestions: suggestions,
textChanged: (text) => currentText = text,
clearOnSubmit: true,
textSubmitted: (text) async {
await movingCameraToLocation(
double.parse(
allStations[suggestions.indexOf(text)]
.stationLatitude),
double.parse(
allStations[suggestions.indexOf(text)]
.stationLongitude));
toLocationName = text;
setState(() {});
},
),
Try adding the filter parameter, im not sure if it can be used with SimpleAutoCompleteTextField, but the official documentation states that
"itemFilter" parameter can be used with AutoCompleteTextField <String>(),
Your filter would be :
itemFilter: (suggestion, input) =>
suggestion.toLowerCase().contains(input.toLowerCase()),```