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.
Related
My problem is with Futures, because they should be obtained before build() method executed, as the documentation states:
The future must be obtained earlier, because if the future is created
at the same time as the FutureBuilder, then every time the
FutureBuilder's parent is rebuilt, the asynchronous task will be
restarted.
I know that Futures should be called in initstate() function before the build method executed, but my case is different.
I want to get data from api as a Future, but the request I am sending to the api needs some parameters that user should select inside the screen's build() method.
And I don't know what the parameter of the request will be until user selects in build() method, and I have to call the api in the build() method and use FutureBuilder there, but that makes FutureBuilder to get constantly called, and I don't want that.
basically, I don't want to call FutureBuilder indefinetely, and I can't put my Future inside initState() because the Future needs some parameters that user later selects when the screen is shown inside build() method.
inside the build method:
FutureBuilder<List<LatLng>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
return PolylineLayer(
polylines: [
Polyline(
points: snapshot.data!,
strokeWidth: 4,
color: Colors.purple),
],
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
} else {
return Container();
}
},
future: Provider.of<NavigationProvider>(context)
.getNavigationPoints(pointToGoTo!),
),
now if you look at the code, at the final lines, I am sending the parameter pointToGoTo to the function which calls the backend.
simply, I want to get rid of calling api and getting data back as a Future inside build method, I want to do it in initState or somewhere else that prevents the build methods calling backend indefinitely.
is there any way to fix this problem?
Thanks in advance.
Firstly, create future state variable and a nullable params and use it with conditional if while using FutureBuilder.
I will recommend checking Fixing a common FutureBuilder and StreamBuilder problem
Now you can follow this example. It is missing progressBar on API recall, StreamBuilder might be better option in cases like this.
class Foo extends StatefulWidget {
const Foo({super.key});
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int? params;
Future<int> fetch(int? data) async {
await Future.delayed(Duration(seconds: 1));
return (params ?? 0) * 2;
}
late Future<int> future = fetch(params);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
DropdownButton<int?>(
value: params,
items: List.generate(
12,
(index) => DropdownMenuItem(
value: index,
child: Text("$index"),
)).toList(),
onChanged: (value) {
future =
fetch(params); // this will only call api with update data
setState(() {
params = value;
});
},
),
if (params != null)
FutureBuilder<int>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) return Text("${snapshot.data}");
return CircularProgressIndicator();
},
)
],
),
);
}
}
class Testing extends StatefulWidget {
const Testing({super.key});
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
bool isFetched = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<SomethingProvider>(
builder: (context, prov, child) {
if (!isFetched) {
prov.getData("a", "b");
Future.delayed(const Duration(milliseconds: 200), () {
isFetched = true;
});
}
if (prov.newData.isNotEmpty) {
return Column(
// make widget tree from here
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
class SomethingProvider extends ChangeNotifier {
List newData = [];
Future getData(param1, param2) async {
newData = ["testingdata"];
}
}
I get a list of items in home of my app.
In each one of this items I show the distance from the user and the item.
But in some item I have the distance and in others get null
This is my partial code:
class ProductHorizontalListItem extends StatelessWidget {
const ProductHorizontalListItem({
Key? key,
required this.product,
required this.coreTagKey,
this.onTap,
}) : super(key: key);
final Product product;
final Function? onTap;
final String coreTagKey;
#override
Widget build(BuildContext context) {
// print('***Tag*** $coreTagKey${PsConst.HERO_TAG__IMAGE}');
final PsValueHolder valueHolder =
Provider.of<PsValueHolder>(context, listen: false);
Future<double> getCurrentLocation() async {
Position position = await Geolocator.getCurrentPosition();
double lat = position.latitude;
double long = position.longitude;
final double distanceInMeters = Geolocator.distanceBetween(
double.parse(position.latitude.toString()),
double.parse(position.longitude.toString()),
double.parse(product.itemLocation!.lat.toString()),
double.parse(product.itemLocation!.lng.toString()),
);
return Future.value(distanceInMeters);
}
return FutureBuilder<double>(
future: getCurrentLocation(),
builder: (BuildContext context, AsyncSnapshot<double> snapshot) {
return InkWell(
onTap: onTap as void Function()?,
child: Container(
margin: const EdgeInsets.only(
left: PsDimens.space4, right: PsDimens.space4,
bottom: PsDimens.space12),
child: Text(
'${snapshot.data}',
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.caption!.copyWith(
color: PsColors.textColor3
)))
);});
}
}
Somebody can tell me why?
Thank you.
In the FutureBuilder you need to handle the different statuses of the future. With your current code you are building the widget on the very first snapshot. The reason of the null values is likely this, in most of the cases (if not every time) the first snapshot will not contain valid value.
Look at the following code about how to handle the different cases in a FutureBuilder:
return FutureBuilder<double>(
future: getCurrentLocation(),
builder: (BuildContext context, AsyncSnapshot<double> snapshot) {
// the future is not completed yet, so show a progress indicator
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
// the future is completed, but with an error, you have to handle it
if (snapshot.hasError) {
return...;
}
// the future is completed without error, but still you need
// to check whether it contains data, this is basically a check
// against null
if (snapshot.hasData) {
return...;
}
// if your code arrives here, it means the future is already
// completed, there was no error but the data returned is null
return...;
});
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"]
},
);
},
),
},
},
);
),
),
);
}
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;
},
);
}
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.