Using showTimePicker multiple times to assign times to multiple variables - flutter

I am using showTimePicker to get the time from a user. I can get the time as much as I like and assign it to a variable time. The problem is, there will be many instances in my code where I want to get the time but make it a whole new variable, time2 or time3 or whatever. In my code, whenever I call the the function selectTime() it will only ever set the value for the one variable time. I also am not sure how it affects timeOfDay picked;. I could get round these problems by writing a new selectTime() function for each variable but that seems like terrible progrmming.
How can I change my code so that it is possible to assign a new time to any variable without changing the time of others. I am quite new to flutter and will appreciate any help.
Bellow is my code. Of course I'll have another raised button used for changing time2.
Cheers
import 'dart:async';
void main(){
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Troponin Algorithm',
home: CustomForm(),
);
}
}
class CustomForm extends StatefulWidget {
#override
_CustomFormState createState() => _CustomFormState();
}
class _CustomFormState extends State<CustomForm> {
timeFormatter(number) {
//String str;
if (number > 9) {
return number.toString();
}
else {
//str = '0'+number.toString();
return '0'+number.toString();
}
}
TimeOfDay time = TimeOfDay.now();
TimeOfDay time2 = TimeOfDay.now();
TimeOfDay picked;
String hour;
String mins;
String hour2;
String mins2;
Future<Null> selectTime(BuildContext context) async {
picked = await showTimePicker(
context: context,
initialTime: time,
);
if (picked != null) {
setState(() {
time = picked;
});
}
}
#override
Widget build(BuildContext context) {
mins = timeFormatter(time.minute);
hour = timeFormatter(time.hour);
//mins2 = timeFormatter(time2.minute);
return Scaffold(
appBar: AppBar(
title: Text('Troponin Algorthm')
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Text('time is $hour:$mins'), //'${_time.hour}:${_time.minute}'
),
RaisedButton(
child: Text('change time'),
onPressed: () {
setState(() {
selectTime(context);
});
}
),
],
),
Text('time1 is: $time'),
],
)
);
}```

Instead of selectTime returning a Future<Null>, you could modify it to return a Future<TimeOfDay>. The docs specify that showTimePicker will return a Future<TimeOfDay> based on the user's selection or null if the user cancels the dialog.
Future<TimeOfDay> selectTime(BuildContext context) async {
picked = await showTimePicker(
context: context,
initialTime: time,
);
return picked;
}
And change your RaisedButton like this:
RaisedButton(
child: Text('change time'),
onPressed: () async {
// We wait for either a Future<TimeOfDay> or a null
var temp = await selectTime(context) ;
if (temp != null) {
setState(() {
time = temp;
});
}
},
),

Related

Flutter, getting value from showTimePicker not working

I am trying to displayed time selected from showTimePicker dialogue but after getting the value it becomes null again.
after debugging, it seems like the value returned from future function expried or something.
after the widgets onPress function it is back to null.
ElevatedButton(
child: const Text('Time'),
onPressed: () async {
vTimeSelected = await getTimeSelected(context);
setState(() {
// vStrToRndr = vTimeSelected.toString();
//print(vStrToRndr);
});
},
),
ElevatedButton(
child: const Text('Solid'),
onPressed: () {
setState(() {
vDetail.solid = !vDetail.solid;
});
},
),
],
),
),
if (vDetail.wet == true) Text('Nappy wet'),
if (vDetail.solid == true) Text('Nappy Solid'),
Text('Time: $vTimeSelected'),
[7:35 PM]
Future<TimeOfDay?> getTimeSelected(BuildContext context) async {
TimeOfDay vTime = TimeOfDay(hour: 0, minute: 0);
TimeOfDay? newTime =
await showTimePicker(context: context, initialTime: vTime);
return newTime;
//print($newTime);
}
//thanks for any help
Text('Time: $vTimeSelected') //<---- this is displaying null
You can follow this code structure. Make sure to declare variable outside the build method.
class TS extends StatefulWidget {
const TS({super.key});
#override
State<TS> createState() => _TSState();
}
class _TSState extends State<TS> {
TimeOfDay? vTimeSelected;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () async {
vTimeSelected = await getTimeSelected(context);
setState(() {});
}),
body: Text("${vTimeSelected}"),
);
}
}

Waiting for Async Data in InitState

I need to get data from a Future to a Stateful widget before it displays on startup. I have tried async/await, FutureBuilder, and the Sync package implementing a WaitGroup within the initState method; however, nothing I do waits for the data to return from the Future before it renders the screen.
In the below examples, I have a simple String strName that I initialize to "Default Name" that I am using for testing and displaying in the Scaffold. It only displays the initialized "Default Name," and not the name returned from the Future. The closest I got was using a FutureBuilder, at least it updated the screen after the initialized "Default Name" was shown. However, I need to get the data prior to the screen rendering. Does anyone have any ideas?
Here's an example of what I tried with Sync WaitGroup:
class _MyHomePageState extends State<MyHomePage> {
String strName = "Default Name";
Future<String> _getName() async {
var name = await Future<String>.delayed(const Duration(seconds: 5), () => "New Name");
return name;
}
#override
void initState() {
WaitGroup wg = WaitGroup();
wg.add(1);
Future<String> futureName = _getName();
futureName.then(
(value) {
strName = value;
wg.done();
},
);
wg.wait();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(strName),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
strName,
style: Theme.of(context).textTheme.headline4,
),
],
),
),
);
}
}
This is what my async/await method looked like:
class _MyHomePageState extends State<MyHomePage> {
String strName = "Default Name";
Future<String> _getName() async {
var name = await Future<String>.delayed(const Duration(seconds: 5), () => "Jimbo");
return name;
}
#override
void initState() {
Future<String> futureName = _getName();
futureName.then(
(value) {
strName = value;
},
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(strName),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
strName,
style: Theme.of(context).textTheme.headline4,
),
],
),
),
);
}
}
I've never worked with a language where there asynchronous is the default structure of so many parts. How do you deal with making async synchronous in Dart? I haven't even got into the SQLite and HTTP part of it, and it is killing me. I've been at it for four days and got so frustrated I almost broke a keyboard yesterday.
The best is to use a loading screen while fetching your data
and use snapshot.data
full implementation using FutureBuilder:
class _MyHomePageState extends State<MyHomePage> {
String strName = "Default Name";
Future<String> _getName() async {
var name = await Future<String>.delayed(
const Duration(seconds: 5), () => "New Name");
return name;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(strName),
),
body: FutureBuilder<String>(
future: _getName(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
snapshot.data!,
style: Theme.of(context).textTheme.headline4,
),
],
);
}
return Center(
child: CircularProgressIndicator(),
);
}),
);
}
}
This is really a bad practice
but if you really need to resolve some future data before the app renders you can use the void main() method.
void main()async {
Future<String> futureName = _getName();
futureName.then(
(value) {
strName = value;
runApp(MyApp());
},
);
}

How to show loading spinner in GetBuilder

In FutureBuilder when working with an API you can easily show loading spinner when data is not yet available with this code,
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: CircularProgressIndicator(),
);
}
how do I do same for GetBuilder when using getx as state management library?
Here's a basic example of re-building based on the value of an isLoading bool. I'm just changing the value of a String but this should give you the idea of doing a proper API call in a GetX function and displaying an indicator. While I typically default to using GetBuilder whenever possible, showing loading indicators I generally just use Obx so I don't have to call update() twice.
class TestController extends GetxController {
bool isLoading = false;
String data = '';
Future<void> fetchData() async {
isLoading = true;
update(); // triggers the GetBuilder rebuild
await Future.delayed(
const Duration(seconds: 2),
() => data = 'Data Loaded',
);
isLoading = false;
update();
}
}
You can test this by throwing this in a Column. Just make sure the controller is initialized first at some point with Get.put(TestController());
GetBuilder<TestController>(
builder: (controller) => controller.isLoading
? CircularProgressIndicator()
: Text(controller.data)),
ElevatedButton(
onPressed: () => controller.fetchData(),
child: Text('Fetch Data'),
),
If you don't want to have to manually call the function you can also lose the isLoading bool use a FutureBuilder but then just pass a Future function from a GetX class to keep that logic out of your UI.
Update
Here's an example using live dummy data of random Kanye quotes from
https://api.kanye.rest Copy the code below into your IDE and run it and it should make sense.
Basic ApiCaller class
class ApiCaller extends GetConnect {
final url = 'https://api.kanye.rest';
Future<String> fetchData() async {
final response = await httpClient.get(url);
return response.body['quote'] as String;
}
}
Updated TestController class
class TestController extends GetxController {
String data = 'no data';
bool isLoading = false;
Future<void> updateData() async {
_updateIsLoading(true);
final apiCaller = ApiCaller();
await Future.delayed(
const Duration(seconds: 1),
() => data = 'Data Loaded',
); // just to add more visible time with loading indicator
data = await apiCaller.fetchData();
_updateIsLoading(false);
}
void _updateIsLoading(bool currentStatus) {
isLoading = currentStatus;
update();
}
}
Example with GetBuilder and FutureBuilder
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.put(TestController());
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FutureBuilder(
future: ApiCaller().fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('FutureBuilder: ${snapshot.data}');
} else {
return CircularProgressIndicator();
}
},
),
GetBuilder<TestController>(
builder: (_) => controller.isLoading
? CircularProgressIndicator()
: Text('GetBuilder: ${controller.data}'),
),
ElevatedButton(
onPressed: () => controller.updateData(),
child: Text('Update GetBuilder'),
),
],
),
),
),
);
}
}
Example with FutureBuilder with function from GetX class

Flutter add value to another class list

I want to add the date picker value to the List.
When user after select the date, I will calling function [addToDateList] in _AddFieldDynamicItem.
For this function, it will add to list:
List <DateTime> dateList=[];
Which is in _AddFieldDynamicTest
After click "Add another", I don't know why that it only can record the latest picker value.
Did I need to using another storage method such as sql lite or shared preferences?
import 'dart:convert';
import 'package:flutter/material.dart';
class AddFieldDynamicTest extends StatefulWidget {
#override
_AddFieldDynamicTest createState() => _AddFieldDynamicTest();
}
class _AddFieldDynamicTest extends State<AddFieldDynamicTest> {
Map<String, String> _formdata = {};
var _myPets = List<Widget>();
List <DateTime> dateList=[];
int _index = 1;
void _add() {
_myPets = List.from(_myPets)
..add(AddFieldDynamicItem(_index));
setState(() {
_index += 1;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
_add();
}
addToDateList(DateTime d){
dateList.add(d);
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => getDateList(),
child: Text('Save'),
),
appBar: AppBar(
title: Text('Add Test 2'),
actions: <Widget>[
FlatButton(
child: Text('Add another'),
onPressed: _add,
),
],
),
body: ListView(
children: _myPets,
),
);
}
getDateList(){
print(dateList.length);
}
}
class AddFieldDynamicItem extends StatefulWidget {
AddFieldDynamicItem(this._index);
final int _index;
#override
_AddFieldDynamicItem createState() => _AddFieldDynamicItem(_index);
}
class _AddFieldDynamicItem extends State<AddFieldDynamicItem> {
_AddFieldDynamicItem(this._index);
String _value = '';
final int _index;
List<DateTime> d=[];
Future _selectDate() async {
DateTime picked = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime(2000),
lastDate: new DateTime(2100)
);
if(picked != null)
_AddFieldDynamicTest().addToDateList(picked);
setState(() {
_value = picked.toString();
});
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
new Column(
children: <Widget>[
new Text('$_index . Current Date'),
new Text(_value),
new RaisedButton(onPressed: _selectDate, child: new Text('Date picker'),)
],
)
],
);
}
}
"_AddFieldDynamicTest().addToDateList(picked);" is make new instance.
So, fix like this.
from
_myPets = List.from(_myPets)
..add(AddFieldDynamicItem(_index));
to
_myPets = List.from(_myPets)
..add(AddFieldDynamicItem(_index, addToDateList));
from
AddFieldDynamicItem(this._index);
final int _index;
to
AddFieldDynamicItem(this._index, this.addToDateList);
final int _index;
final Function(DateTime) addToDateList;
from
if(picked != null)
_AddFieldDynamicTest().addToDateList(picked);
to
if (picked != null) widget.addToDateList(picked);

What is the proper way to add a value to a list in a map?

I am creating a Flutter app where I want the user to be able to add an event (String) to a List in a Map (Map<DateTime, List<String>>). Then show them on the screen (later on a listView).
This is the class where the Events and DatePicker are located:
class EventBrain {
final Map<DateTime, List<String>> events = {};
Future<Null> addEvent(BuildContext context, String title) async {
final dtPick = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2019),
lastDate: DateTime(2021),
);
if (events.containsKey(dtPick) == true) {
events[dtPick].add(title);
print(events[dtPick]);
} else if (dtPick != null) {
events.putIfAbsent(dtPick, () => <String>[]).add(title);
print(events[dtPick]);
}
}
}
This is the screen where is expect it to print:
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ABC'),
),
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {
EventBrain().addEvent(context, 'NewEvent');
});
}),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text(EventBrain().events.toString())],
),
),
);
}
}
Each time you add an event or try to read the events from EventBrain you're creating a new instance when you'd want to use the same instance in each case.
class _MyHomePageState extends State<MyHomePage> {
final _eventBrain = EventBrain();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ABC'),
),
floatingActionButton: FloatingActionButton(onPressed: () async {
final dtPick = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2019),
lastDate: DateTime(2021),
);
setState(() {
_eventBrain.addEvent('NewEvent', dtPick);
});
}),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text(_eventBrain.events.toString())],
),
),
);
}
}
Also, I think it's good practice to keep context confined to your Widgets, this makes testing and code re-use a lot easier so your EventBrain class would look like this:
class EventBrain {
final Map<DateTime, List<String>> events = {};
Future<Null> addEvent(String title, DateTime dtPick) async {
if (events.containsKey(dtPick) == true) {
events[dtPick].add(title);
print(events[dtPick]);
} else if (dtPick != null) {
events.putIfAbsent(dtPick, () => <String>[]).add(title);
print(events[dtPick]);
}
}
}