Flutter: Change state depending on validation - mobile

I am building a simple forgot password form for a demo app which consists of one TextFormFields and a FloatingActionButton to submit the data. I have realised that the FloatingActionButton doesn't have disabled boolean state as such, so I wanted to try and replicate it by change the state to _isValid: true/ false depending on the TextFormField validation functions, which I can then put some ternary operators on FloatingActionButton to change the color and the functionality, depending on the state of this widget.
You will be able to see that I have the _autoValidate set to true on mounting of the widget then I try and trigger a UI reload in the _validateForgetEmail function. When I trigger these state changes I get a big UI error saying
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building Form-[LabeledGlobalKey<FormState>#0a40e](dirty, state:
flutter: FormState#59216):
flutter: setState() or markNeedsBuild() called during build.
flutter: This ForgotPasswordForm widget cannot be marked as needing to build because the framework is already
flutter: in the process of building widgets. A widget can be marked as needing to be built during the build
flutter: phase only if one of its ancestors is currently building. This exception is allowed because the
flutter: framework builds parent widgets before children, which means a dirty descendant will always be
flutter: built. Otherwise, the framework might not visit this widget during this build phase.
Code is below:
class ForgotPasswordForm extends StatefulWidget {
#override
_ForgotPasswordFormState createState() => _ForgotPasswordFormState();
}
Class _ForgotPasswordFormState extends State<ForgotPasswordForm> {
final _emailController = TextEditingController();
final _formKey = GlobalKey<FormState>();
final bool _autoValidate = true;
bool _isLoading = false;
bool _isValid = false;
String email;
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: _isLoading
? _buildLoadingSpinner(context)
: _buildPasswordForm(context),
autovalidate: _autoValidate,
);
}
Widget _buildLoadingSpinner(BuildContext context) {
return (Center(child: CircularProgressIndicator()));
}
Widget _buildPasswordForm(BuildContext context) {
print('isValid: ' + _isValid.toString());
return Column(
children: <Widget>[
Text(
'Please enter your email address.',
style: TextStyle(fontSize: 14.0),
textAlign: TextAlign.center,
),
Text(
'You will recieve a link to reset your password.',
style: TextStyle(fontSize: 14.0),
textAlign: TextAlign.center,
),
SizedBox(height: 32.0),
TextFormField(
controller: _emailController,
validator: _validateForgetEmail,
keyboardType: TextInputType.emailAddress,
autovalidate: _autoValidate,
style: TextStyle(fontSize: 14.0),
onSaved: (String val) {
email = val;
},
decoration: InputDecoration(
filled: true,
contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 8),
labelText: 'Email',
border: InputBorder.none,
labelStyle: TextStyle(fontSize: 14.0, color: Colors.lightBlueAccent),
errorStyle: TextStyle(fontSize: 10.0, height: 0.5),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.lightGreenAccent, width: 2.0),
),
),
),
SizedBox(height: 24.0),
FloatingActionButton(
backgroundColor: _isValid ? Colors.lightBlue : Colors.grey,
onPressed: () {
_submitPasswordReset();
},
child: Icon(Icons.arrow_forward_ios, size: 14.0),
)
],
mainAxisAlignment: MainAxisAlignment.center,
);
}
void _submitPasswordReset() async {
if (_formKey.currentState.validate()) {
setState(() {
_isLoading = true;
});
UserPasswordResetRequest newPasswordRequest =
new UserPasswordResetRequest(email: _emailController.text);
http.Response response = await ApiService.queryPost(
'/api/users/password-forgot',
body: newPasswordRequest.toJson());
final int statusCode = response.statusCode;
if (statusCode == 400) {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Wrong email or password'),
duration: Duration(seconds: 3),
backgroundColor: Colors.red));
setState(() {
_isLoading = false;
});
}
if (statusCode == 200) {
// setState(() {
// _isLoading = false;
// });
Navigator.push(
context,
MaterialPageRoute(builder: (context) => UserBackToLogin()),
);
}
setState(() {
_isLoading = false;
});
}
}
String _validateForgetEmail(String value) {
String patttern =
r"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))#((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$";
RegExp regExp = new RegExp(patttern);
if (value.length == 0) {
return "Email is Required";
} else if (!regExp.hasMatch(value)) {
setState(() {
_isValid = false;
});
return "Must be a valid email address";
}
print('value' + value);
setState(() {
_isValid = true;
});
return null;
}
}
Any insight would be great to see what I am doing wrong - very new to flutter. If you need any more info, then I can provide.
Cheers Sam

You can do it like this:
Split _validateForgetEmail method in two:
String _validateForgetEmail(String value) {
if (value.length == 0) {
return "Email is Required";
} else if (!_isEmailValid(value)) {
return "Must be a valid email address";
}
print('value' + value);
return null;
}
bool _isEmailValid(String value) {
String pattern =
r"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))#((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$";
RegExp regExp = new RegExp(pattern);
return regExp.hasMatch(value);
}
Now these methods only validate values without affecting any state.
Listen to _emailController changes
#override
void initState() {
super.initState();
_emailController.addListener(() {
final isEmailValid = _isEmailValid(_emailController.value.text);
if(isEmailValid != _isValid) {
setState(() {
_isValid = isEmailValid;
});
}
});
}
Also don't forget to dispose _emailController
#override
void dispose() {
_emailController.dispose();
super.dispose();
}
Exception explanation:
TextFormField extends FormField class. If autovalidate is turned on, then function passed as validator will be called in FormFieldState.build method to update error text.
So it leads to setState being called from build which is not allowed by framework

A simpler way to achieve this would be to validate in the onChanged callback.
class FormPage extends StatefulWidget {
#override
_FormPageState createState() => _FormPageState();
}
class _FormPageState extends State<FormPage> {
final _formKey = GlobalKey<FormState>();
bool _isValid = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Form(
key: _formKey,
onChanged: () {
final isValid = _formKey.currentState.validate();
if (_isValid != isValid) {
setState(() {
_isValid = isValid;
});
}
},
child: Column(
children: <Widget>[
TextFormField(validator: (x) => x.length > 2 ? null : 'Too short'),
],
),
),
floatingActionButton: Opacity(
opacity: _isValid ? 1 : 0.5,
child: FloatingActionButton(
child: Icon(Icons.send),
onPressed: () {},
),
),
);
}
}

Simple use
Form(
key: _key,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
//All FormTextFields Here as you went....
],
),
),

Bit late to the party, but you can use setState directly inside the validator by adding a post frame callback in the validator as follows:
validator: (val) {
if (val == '') {
SchedulerBinding.instance.addPostFrameCallback((duration) {
setState(() {
someErrorVariableYouWantToChange = true;
});
});
} else {
SchedulerBinding.instance.addPostFrameCallback((duration) {
setState(() {
someErrorVariableYouWantToChange = false;
});
});
}
return null;
}
This will ensure that setState is called after the build process is complete due to the validator function.

Related

I face this issue with firebase streamabuilder

I face this issue with firebase streamabuilder
NoSuchMethodError: The getter 'docs' was called on null, Receiver: null Tried calling: docs See also: https://flutter.dev/docs/testing/errors.
class streamBuilder extends StatelessWidget {
const streamBuilder({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _FireData.collection('Text').snapshots(),
builder: (
context,
AsyncSnapshot snapshot,
) {
if (true) {
final Messages = snapshot.data.docs; // here
List<BubbleText> MessagesWeigets = [];
for (var message in Messages) {
final TextMessage = message.data()['Text'];
final TextUser = message.data()['User'];
final currentuser = loggedInUser.email;
if (currentuser == TextUser) {}
final MessageWeiget = BubbleText(
TextMessage: TextMessage,
TextUser: TextUser,
itsMe: currentuser == TextUser,
);
MessagesWeigets.add(MessageWeiget);
}
return Expanded(
child: Padding(
padding: const EdgeInsets.all(2.0),
child: ListView(
reverse: true,
children: MessagesWeigets,
),
),
);
}
},
);
}
}

Flutter/Dart - How to process Json data which contains a comma separated list?

I've got a PageViewBuilder which uses a Map<String> of json data containing a String called name and another called tagname. The problem is that the tagname string is actually a comma-separated list of three tags which print out as "boat,grass,toads"; But I'd like to separate this String so that I can make each item a clickable button. IE:
FlatButton( onPressed: () => boat(),
text: "boat",
);
FlatButton( onPressed: () => grass(),
text: "grass",
);
FlatButton( onPressed: () => toads(),
text: "toads",
);
I tried to using Dart's split function like this;
var splitag = tagname.split(",");
var splitag1 = splitag[0];
var splitag2 = splitag[1];
var splitag3 = splitag[2];
But this gives an error if any of the three tag names are void.
So I tried this;
String splitagone = splitag1 ?? "";
String splitagtwo = splitag2 ?? "";
String splitagthree = splitag3 ?? "";
But this still gave me an error. So can anyone suggest a different way to accomplish what I need? Here's the full code;
class SpeakContent {
final String name, tagname;
SpeakContent({
this.name,
this.tagname
});
factory SpeakContent.fromJson(Map<String, dynamic> jsonData) {
return SpeakContent(
name: jsonData['name'],
tagname: jsonData['tagname'],
);
}
}
class StageBuilder extends StatelessWidget {
final List<SpeakContent> speakcrafts;
StageBuilder(this.speakcrafts);
#override
Widget build(context) {
return PageView.builder(
itemCount: speakcrafts.length,
itemBuilder: (context, int currentIndex) {
return createViewItem(speakcrafts[currentIndex], context);
},
);
}
Widget createViewItem(SpeakContent speakcraft, BuildContext context) {
return Column(
children: <Widget>[
Container(
child: Text(
speakcraft.name,
),
),
Container(child:
FlatButton( onPressed: () => boat(),
child: Text('boat'),
),
),
Container(child:
FlatButton( onPressed: () => grass(),
child: Text('grass'),
),
),
Container(child:
FlatButton( onPressed: () => toads(),
child: Text('toads'),
)
),
],
);
}
}
You can do something like this..
Widget createViewItem(SpeakContent speakcraft, BuildContext context) {
List<Widget> columnChildren = [];
Text name = Text(speakcraft.name);
columnChildren.add(name);
speakcraft.tagname.split(',').forEach((String tag) {
FlatButton tagButton = FlatButton(
child: Text(tag),
onPressed: (){},
);
columnChildren.add(tagButton);
});
return Column(
children: columnChildren,
);
}
You can try:
var response = await http.get(
Uri.encodeFull("...."),
headers: {
...
}
);
this.setState(() {
testList = json.decode (response.body) as List;
**testList[0];
testList[1];**

Error When Parsing JSON Array In Flutter With Provider

I want to use json array on my flutter project, but it's always an error like this
_TypeError (type 'List<dynamic>' is not a subtype of type 'FutureOr<ProvinsiModel>')
what I expect from this project is that when I click on a widget I mean below it will open a new page on flutter and display a list taken from the json array.
in my code there is no error but just when I click and switch to another page, debugging mode in VSCode suddenly stops and displays an error like the above in a PopUp.
and this is the code that I have
provider
class IndonesianProvinsiProvider with ChangeNotifier {
var api = ApiServices();
ProvinsiModel indonesia;
Future<ProvinsiModel> getIndonesianProvinsiProvider() async {
final response = await api.client.get("${api.baseUrl}/indonesia/");
if (response.statusCode == 200) {
notifyListeners();
final data = jsonDecode(response.body);
return data;
} else {
return null;
}
}
}
page for data that i will use
class ProvinsiPage extends StatelessWidget {
final List<ProvinsiModel> data;
const ProvinsiPage({Key key, this.data}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return Card(
child: Column(
children: <Widget>[
Card(child: Text(data[index].attributes.provinsi))
],
),
);
},
)
);
}
}
and I call that data with future and future.builder ()
GestureDetector(
onTap: () {
Navigator.push(
context, MaterialPageRoute(
builder: (context) => FutureBuilder (
future: Provider.of<IndonesianProvinsiProvider>(context, listen: false).getIndonesianProvinsiProvider(),
builder: (context, snapshot) {
if (snapshot.data == null) { // Cek jika snapshot tidak menerima data atau null
return Center( // maka
child: CircularProgressIndicator(), // menampilkan loading
);
} else { // maka
return ProvinsiPage( // data dikirim ke class PostinganList
data: snapshot.data,
);
}
}
)
),
);
},
child: IndonesiaStatsWidget (
bgColor: Colors.redAccent,
indo: Provider.of<IndonesianProvider>(context).indonesia,
widgetBgColor: Colors.white,
),
),
for json that I got from the provider requests like this
[
{
"attributes": {
"FID": 11,
"Kode_Provi": 31,
"Provinsi": "DKI Jakarta",
"Kasus_Posi": 6895,
"Kasus_Semb": 1682,
"Kasus_Meni": 509
}
},
{
"attributes": {
"FID": 15,
"Kode_Provi": 35,
"Provinsi": "Jawa Timur",
"Kasus_Posi": 4142,
"Kasus_Semb": 522,
"Kasus_Meni": 320
}
},
....
]

Flutter Countdown Timer

How can I do to put the value passed in the construction, to make a timer that rounds to the first decimal and shows at the child text of my RaisedButton? I've tried but without luck. I manage to make work the callback function with a simple Timer but no periodic and with no update of value in real time in the text...
import 'package:flutter/material.dart';
import 'dart:ui';
import 'dart:async';
class TimerButton extends StatefulWidget {
final Duration timerTastoPremuto;
TimerButton(this.timerTastoPremuto);
#override
_TimerButtonState createState() => _TimerButtonState();
}
class _TimerButtonState extends State<TimerButton> {
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(5.0),
height: 135.0,
width: 135.0,
child: new RaisedButton(
elevation: 100.0,
color: Colors.white.withOpacity(.8),
highlightElevation: 0.0,
onPressed: () {
int _start = widget.timerTastoPremuto.inMilliseconds;
const oneDecimal = const Duration(milliseconds: 100);
Timer _timer = new Timer.periodic(
oneDecimal,
(Timer timer) =>
setState(() {
if (_start < 100) {
_timer.cancel();
} else {
_start = _start - 100;
}
}));
},
splashColor: Colors.red,
highlightColor: Colors.red,
//shape: RoundedRectangleBorder e tutto il resto uguale
shape: BeveledRectangleBorder(
side: BorderSide(color: Colors.black, width: 2.5),
borderRadius: new BorderRadius.circular(15.0)),
child: new Text(
"$_start",
style: new TextStyle(fontFamily: "Minim", fontSize: 50.0),
),
),
);
}
}
Here is an example using Timer.periodic :
Countdown starts from 10 to 0 on button click :
import 'dart:async';
[...]
Timer _timer;
int _start = 10;
void startTimer() {
const oneSec = const Duration(seconds: 1);
_timer = new Timer.periodic(
oneSec,
(Timer timer) {
if (_start == 0) {
setState(() {
timer.cancel();
});
} else {
setState(() {
_start--;
});
}
},
);
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(title: Text("Timer test")),
body: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
startTimer();
},
child: Text("start"),
),
Text("$_start")
],
),
);
}
Result :
You can also use the CountdownTimer class from the quiver.async library, usage is even simpler :
import 'package:quiver/async.dart';
[...]
int _start = 10;
int _current = 10;
void startTimer() {
CountdownTimer countDownTimer = new CountdownTimer(
new Duration(seconds: _start),
new Duration(seconds: 1),
);
var sub = countDownTimer.listen(null);
sub.onData((duration) {
setState(() { _current = _start - duration.elapsed.inSeconds; });
});
sub.onDone(() {
print("Done");
sub.cancel();
});
}
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(title: Text("Timer test")),
body: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
startTimer();
},
child: Text("start"),
),
Text("$_current")
],
),
);
}
EDIT : For the question in comments about button click behavior
With the above code which uses Timer.periodic, a new timer will indeed be started on each button click, and all these timers will update the same _start variable, resulting in a faster decreasing counter.
There are multiple solutions to change this behavior, depending on what you want to achieve :
disable the button once clicked so that the user could not disturb the countdown anymore (maybe enable it back once timer is cancelled)
wrap the Timer.periodic creation with a non null condition so that clicking the button multiple times has no effect
if (_timer != null) {
_timer = new Timer.periodic(...);
}
cancel the timer and reset the countdown if you want to restart the timer on each click :
if (_timer != null) {
_timer.cancel();
_start = 10;
}
_timer = new Timer.periodic(...);
if you want the button to act like a play/pause button :
if (_timer != null) {
_timer.cancel();
_timer = null;
} else {
_timer = new Timer.periodic(...);
}
You could also use this official async package which provides a RestartableTimer class which extends from Timer and adds the reset method.
So just call _timer.reset(); on each button click.
Finally, Codepen now supports Flutter ! So here is a live example so that everyone can play with it : https://codepen.io/Yann39/pen/oNjrVOb
I have created a Generic Timer Widget which can be used to display any kind of timer and its flexible as well.
This Widget takes following properties
secondsRemaining: duration for which timer needs to run in seconds
whenTimeExpires: what action needs to be performed if timer finished
countDownStyle: any kind of style which you want to give to timer
countDownFormatter: the way user wants to display the count down timer e.g
hh mm ss string like 01 hours: 20 minutes: 45 seconds
you can provide a default formatter ( formatHHMMSS ) in case you don't want to supply it from every place.
// provide implementation for this - formatHHMMSS(duration.inSeconds); or use below one which I have provided.
import 'package:flutter/material.dart';
class CountDownTimer extends StatefulWidget {
const CountDownTimer({
Key key,
int secondsRemaining,
this.countDownTimerStyle,
this.whenTimeExpires,
this.countDownFormatter,
}) : secondsRemaining = secondsRemaining,
super(key: key);
final int secondsRemaining;
final Function whenTimeExpires;
final Function countDownFormatter;
final TextStyle countDownTimerStyle;
State createState() => new _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
AnimationController _controller;
Duration duration;
String get timerDisplayString {
Duration duration = _controller.duration * _controller.value;
return widget.countDownFormatter != null
? widget.countDownFormatter(duration.inSeconds)
: formatHHMMSS(duration.inSeconds);
// In case user doesn't provide formatter use the default one
// for that create a method which will be called formatHHMMSS or whatever you like
}
#override
void initState() {
super.initState();
duration = new Duration(seconds: widget.secondsRemaining);
_controller = new AnimationController(
vsync: this,
duration: duration,
);
_controller.reverse(from: widget.secondsRemaining.toDouble());
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) {
widget.whenTimeExpires();
}
});
}
#override
void didUpdateWidget(CountDownTimer oldWidget) {
if (widget.secondsRemaining != oldWidget.secondsRemaining) {
setState(() {
duration = new Duration(seconds: widget.secondsRemaining);
_controller.dispose();
_controller = new AnimationController(
vsync: this,
duration: duration,
);
_controller.reverse(from: widget.secondsRemaining.toDouble());
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
widget.whenTimeExpires();
} else if (status == AnimationStatus.dismissed) {
print("Animation Complete");
}
});
});
}
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Center(
child: AnimatedBuilder(
animation: _controller,
builder: (_, Widget child) {
return Text(
timerDisplayString,
style: widget.countDownTimerStyle,
);
}));
}
}
Usage:
Container(
width: 60.0,
padding: EdgeInsets.only(top: 3.0, right: 4.0),
child: CountDownTimer(
secondsRemaining: 30,
whenTimeExpires: () {
setState(() {
hasTimerStopped = true;
});
},
countDownTimerStyle: TextStyle(
color: Color(0XFFf5a623),
fontSize: 17.0,
height: 1.2,
),
),
)
example for formatHHMMSS:
String formatHHMMSS(int seconds) {
int hours = (seconds / 3600).truncate();
seconds = (seconds % 3600).truncate();
int minutes = (seconds / 60).truncate();
String hoursStr = (hours).toString().padLeft(2, '0');
String minutesStr = (minutes).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
if (hours == 0) {
return "$minutesStr:$secondsStr";
}
return "$hoursStr:$minutesStr:$secondsStr";
}
Null Safe Version of the Above Code
import 'package:flutter/material.dart';
class CountDownTimer extends StatefulWidget {
const CountDownTimer({
Key? key,
required this.secondsRemaining,
required this.whenTimeExpires,
this.countDownFormatter,
this.countDownTimerStyle,
}) : super(key: key);
final int secondsRemaining;
final VoidCallback whenTimeExpires;
final TextStyle? countDownTimerStyle;
final Function(int seconds)? countDownFormatter;
#override
State createState() => _CountDownTimerState();
}
class _CountDownTimerState extends State<CountDownTimer>
with TickerProviderStateMixin {
late final AnimationController _controller;
late final Duration duration;
String get timerDisplayString {
final duration = _controller.duration! * _controller.value;
if (widget.countDownFormatter != null) {
return widget.countDownFormatter!(duration.inSeconds) as String;
} else {
return formatHHMMSS(duration.inSeconds);
}
}
String formatHHMMSS(int seconds) {
final hours = (seconds / 3600).truncate();
seconds = (seconds % 3600).truncate();
final minutes = (seconds / 60).truncate();
final hoursStr = (hours).toString().padLeft(2, '0');
final minutesStr = (minutes).toString().padLeft(2, '0');
final secondsStr = (seconds % 60).toString().padLeft(2, '0');
if (hours == 0) {
return '$minutesStr:$secondsStr';
}
return '$hoursStr:$minutesStr:$secondsStr';
}
#override
void initState() {
super.initState();
duration = Duration(seconds: widget.secondsRemaining);
_controller = AnimationController(
vsync: this,
duration: duration,
);
_controller
..reverse(from: widget.secondsRemaining.toDouble())
..addStatusListener((status) {
if (status == AnimationStatus.completed ||
status == AnimationStatus.dismissed) {
widget.whenTimeExpires();
}
});
}
#override
void didUpdateWidget(CountDownTimer oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.secondsRemaining != oldWidget.secondsRemaining) {
setState(() {
duration = Duration(seconds: widget.secondsRemaining);
_controller.dispose();
_controller = AnimationController(
vsync: this,
duration: duration,
);
_controller
..reverse(from: widget.secondsRemaining.toDouble())
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
widget.whenTimeExpires();
}
});
});
}
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _controller,
builder: (_, Widget? child) {
return Text(
timerDisplayString,
style: widget.countDownTimerStyle,
);
},
),
);
}
}
Little late to the party but why don't you guys try animation.No I am not telling you to manage animation controllers and disposing them off and all that stuff, there's a built-in widget for that called TweenAnimationBuilder. You can animate between values of any type, here's an example with a Duration class
TweenAnimationBuilder<Duration>(
duration: Duration(minutes: 3),
tween: Tween(begin: Duration(minutes: 3), end: Duration.zero),
onEnd: () {
print('Timer ended');
},
builder: (BuildContext context, Duration value, Widget? child) {
final minutes = value.inMinutes;
final seconds = value.inSeconds % 60;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Text('$minutes:$seconds',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 30)));
}),
and You also get onEnd call back which notifies you when the animation completes;
here's the output
Here is my Timer widget, not related to the Question but may help someone.
import 'dart:async';
import 'package:flutter/material.dart';
class OtpTimer extends StatefulWidget {
#override
_OtpTimerState createState() => _OtpTimerState();
}
class _OtpTimerState extends State<OtpTimer> {
final interval = const Duration(seconds: 1);
final int timerMaxSeconds = 60;
int currentSeconds = 0;
String get timerText =>
'${((timerMaxSeconds - currentSeconds) ~/ 60).toString().padLeft(2, '0')}: ${((timerMaxSeconds - currentSeconds) % 60).toString().padLeft(2, '0')}';
startTimeout([int milliseconds]) {
var duration = interval;
Timer.periodic(duration, (timer) {
setState(() {
print(timer.tick);
currentSeconds = timer.tick;
if (timer.tick >= timerMaxSeconds) timer.cancel();
});
});
}
#override
void initState() {
startTimeout();
super.initState();
}
#override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.timer),
SizedBox(
width: 5,
),
Text(timerText)
],
);
}
}
You will get something like this
doesnt directly answer your question. But helpful for those who want to start something after some time.
Future.delayed(Duration(seconds: 1), () {
print('yo hey');
});
If all you need is a simple countdown timer, this is a good alternative instead of installing a package. Happy coding!
countDownTimer() async {
int timerCount;
for (int x = 5; x > 0; x--) {
await Future.delayed(Duration(seconds: 1)).then((_) {
setState(() {
timerCount -= 1;
});
});
}
}
I've Created a amazing timer without any plugin, here you can also get count down timer.
And don't forget to stop the timer on back pressed.
Here is the link of my timer full Project.
*Hope this will help someone. Thank you.
*
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AttendanceScreen(),
);
}
}
class AttendanceScreen extends StatefulWidget {
AttendanceScreen();
#override
_AttendanceScreenState createState() => _AttendanceScreenState();
}
class _AttendanceScreenState extends State<AttendanceScreen> {
static var countdownDuration = Duration(minutes: 10);
static var countdownDuration1 = Duration(minutes: 10);
Duration duration = Duration();
Duration duration1 = Duration();
Timer? timer;
Timer? timer1;
bool countDown = true;
bool countDown1 = true;
#override
void initState() {
var hours;
var mints;
var secs;
hours = int.parse("00");
mints = int.parse("00");
secs = int.parse("00");
countdownDuration = Duration(hours: hours, minutes: mints, seconds: secs);
startTimer();
reset();
var hours1;
var mints1;
var secs1;
hours1 = int.parse("10");
mints1 = int.parse("00");
secs1 = int.parse("00");
countdownDuration1 =
Duration(hours: hours1, minutes: mints1, seconds: secs1);
startTimer1();
reset1();
super.initState();
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
appBar: AppBar(
title: Text("Timer Example"),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
color: Colors.white,
onPressed: () {
_onWillPop();
},
),
),
body: Container(
color: Colors.black12,
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 20,
),
Text(
"Timer",
style: TextStyle(fontSize: 25),
),
Container(
margin: EdgeInsets.only(top: 30, bottom: 30),
child: buildTime()),
SizedBox(
height: 20,
),
Text(
"Count down timer",
style: TextStyle(fontSize: 25),
),
Container(
margin: EdgeInsets.only(top: 30, bottom: 30),
child: buildTime1()),
]),
),
),
);
}
Future<bool> _onWillPop() async {
final isRunning = timer == null ? false : timer!.isActive;
if (isRunning) {
timer!.cancel();
}
Navigator.of(context, rootNavigator: true).pop(context);
return true;
}
void reset() {
if (countDown) {
setState(() => duration = countdownDuration);
} else {
setState(() => duration = Duration());
}
}
void reset1() {
if (countDown) {
setState(() => duration1 = countdownDuration1);
} else {
setState(() => duration1 = Duration());
}
}
void startTimer() {
timer = Timer.periodic(Duration(seconds: 1), (_) => addTime());
}
void startTimer1() {
timer = Timer.periodic(Duration(seconds: 1), (_) => addTime1());
}
void addTime() {
final addSeconds = 1;
setState(() {
final seconds = duration.inSeconds + addSeconds;
if (seconds < 0) {
timer?.cancel();
} else {
duration = Duration(seconds: seconds);
}
});
}
void addTime1() {
final addSeconds = 1;
setState(() {
final seconds = duration1.inSeconds - addSeconds;
if (seconds < 0) {
timer1?.cancel();
} else {
duration1 = Duration(seconds: seconds);
}
});
}
Widget buildTime() {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final hours = twoDigits(duration.inHours);
final minutes = twoDigits(duration.inMinutes.remainder(60));
final seconds = twoDigits(duration.inSeconds.remainder(60));
return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
buildTimeCard(time: hours, header: 'HOURS'),
SizedBox(
width: 8,
),
buildTimeCard(time: minutes, header: 'MINUTES'),
SizedBox(
width: 8,
),
buildTimeCard(time: seconds, header: 'SECONDS'),
]);
}
Widget buildTime1() {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final hours = twoDigits(duration1.inHours);
final minutes = twoDigits(duration1.inMinutes.remainder(60));
final seconds = twoDigits(duration1.inSeconds.remainder(60));
return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
buildTimeCard(time: hours, header: 'HOURS'),
SizedBox(
width: 8,
),
buildTimeCard(time: minutes, header: 'MINUTES'),
SizedBox(
width: 8,
),
buildTimeCard(time: seconds, header: 'SECONDS'),
]);
}
Widget buildTimeCard({required String time, required String header}) =>
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(20)),
child: Text(
time,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 50),
),
),
SizedBox(
height: 24,
),
Text(header, style: TextStyle(color: Colors.black45)),
],
);
}
import 'dart:async';
import 'package:flutter/material.dart';
class CustomTimer extends StatefulWidget {
#override
_CustomTimerState createState() => _CustomTimerState();
}
class _CustomTimerState extends State<CustomTimer> {
final _maxSeconds = 61;
int _currentSecond = 0;
Timer _timer;
String get _timerText {
final secondsPerMinute = 60;
final secondsLeft = _maxSeconds - _currentSecond;
final formattedMinutesLeft =
(secondsLeft ~/ secondsPerMinute).toString().padLeft(2, '0');
final formattedSecondsLeft =
(secondsLeft % secondsPerMinute).toString().padLeft(2, '0');
print('$formattedMinutesLeft : $formattedSecondsLeft');
return '$formattedMinutesLeft : $formattedSecondsLeft';
}
#override
void initState() {
super.initState();
_startTimer();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.timer),
Text(_timerText),
],
),
),
);
}
void _startTimer() {
final duration = Duration(seconds: 1);
_timer = Timer.periodic(duration, (Timer timer) {
setState(() {
_currentSecond = timer.tick;
if (timer.tick >= _maxSeconds) timer.cancel();
});
});
}
}
I'm using https://pub.dev/packages/flutter_countdown_timer
dependencies:
flutter_countdown_timer: ^1.0.0
$ flutter pub get
CountdownTimer(endTime: 1594829147719)
1594829147719 is your timestamp in milliseconds
For showing your total seconds in this format hh:mm:ss, you can use below method:
String getDuration(int totalSeconds) {
String seconds = (totalSeconds % 60).toInt().toString().padLeft(2, '0');
String minutes =
((totalSeconds / 60) % 60).toInt().toString().padLeft(2, '0');
String hours = (totalSeconds ~/ 3600).toString().padLeft(2, '0');
return "$hours\:$minutes\:$seconds";
}
Many answer already provided. I suggest a shortcut way-
Use this package Custom_timer
Add this to your package's pubspec.yaml file:
dependencies:
custom_timer: ^0.0.3
(use latest version)
and very simple to implement
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("CustomTimer example"),
),
body: Center(
child: CustomTimer(
from: Duration(hours: 12),
to: Duration(hours: 0),
onBuildAction: CustomTimerAction.auto_start,
builder: (CustomTimerRemainingTime remaining) {
return Text(
"${remaining.hours}:${remaining.minutes}:${remaining.seconds}",
style: TextStyle(fontSize: 30.0),
);
},
),
),
),
);
}
import 'package:rxdart/rxdart.dart';
final BehaviorSubject<int> resendTimeController = BehaviorSubject<int>();
static const timerDuration = 90;
int resendTimer = 0;
Timer? timer;
void startTimer() {
timer?.cancel();
resendTimer = timerDuration;
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (resendTimer > 0) {
resendTimer--;
resendTimeController.add(resendTimer);
if (resendTimer == 0) {
timer.cancel();
}
}
});
}
and use intl this
StreamBuilder<int>(
stream: _bloc.resendTimeController,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container();
}
final DateTime date = DateTime.fromMillisecondsSinceEpoch(snapshot.data! * 1000);
return GestureDetector(
onTap: () {
if (snapshot.data == 0) {
_bloc.resendCode();
}
},
child: Text(
snapshot.data! > 0 ? 'Resend code ${DateFormat('mm:ss').format(date)}' : 'Resend SMS',
),
);
},
),
import 'package:async/async.dart';
late CancelableOperation? cancellableOperation;
int counter = 5;
StatefulBuilder(
builder: (context, setState) {
if (counter > 0) {
cancellableOperation = CancelableOperation.fromFuture(
Future.delayed(const Duration(seconds: 1)),
onCancel: () => {},
);
cancellableOperation?.value.whenComplete(() => setState(() => counter--));
return buildButton(
text: 'Wait($counter)',
textColor: getTextColor,
backgroundColor: Colors.grey.withOpacity(0.5),
onPressed: () {},
);
}
return buildButton(
text: 'Complete',
textColor: Colors.green,
backgroundColor: Colors.greenAccent.withOpacity(0.5),
onPressed: () {},
);
},
)
I did something like this for my dialog's complete button. It counts for 5 seconds before to show complete button. But do not forget to call cancellableOperation?.cancel() if you close the dialog or page before counting complete.
Countdown timer in one line
CountdownTimer(Duration(seconds: 5), Duration(seconds: 1)).listen((data){
})..onData((data){
print('data $data');
})..onDone((){
print('onDone.........');
});

Getting an error using sqflite plugin in my CRUD Flutter app

I'm super noob in Flutter and I'm trying a "trivial" (for everyone else)
CRUD app so I followed some tutorial
(https://grokonez.com/android/flutter-sqlite-example-listview-crud-operations-sqflite-plugin)
his "version" obviously works, of course mine doesn't.
I got this error: https://i.stack.imgur.com/TM893.png
here's "my" code, i guess the parts involved are DBHelper and TicketScreen:
class DatabaseHelper {
static final DatabaseHelper _instance = new DatabaseHelper.internal();
factory DatabaseHelper() => _instance;
final String ticketTable = 'ticketTable';
final String columnId = 'id';
final String columnDay = 'day';
final String columnMonth = 'month';
final String columnYear = 'year';
final String columnFrom = 'from';
final String columnTo = 'to';
final String columnAircraft = 'aircraft';
final String columnAirline = 'airline';
static Database _db;
DatabaseHelper.internal();
Future<Database> get db async {
if (_db != null) {
return _db;
}
_db = await initDb();
return _db;
}
initDb() async {
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'ticket.db');
var db = await openDatabase(path, version: 1, onCreate: _onCreate);
return db;
}
void _onCreate(Database db, int newVersion) async {
await db.execute(
'CREATE TABLE $ticketTable($columnId INTEGER PRIMARY KEY, $columnDay TEXT, $columnMonth TEXT, $columnYear TEXT, $columnFrom TEXT, $columnTo TEXT, $columnAircraft TEXT, $columnAirline TEXT)');
}
Future<int> saveTicket(Ticket ticket) async {
var dbClient = await db;
var result = await dbClient.insert(ticketTable, ticket.toMap());
return result;
}
Future<List> getAllTickets() async {
var dbClient = await db;
var result = await dbClient.query(ticketTable, columns: [
columnId,
columnDay,
columnMonth,
columnYear,
columnFrom,
columnTo,
columnAircraft,
columnAirline
]);
return result.toList();
}
Future<int> getCount() async {
var dbClient = await db;
return Sqflite.firstIntValue(
await dbClient.rawQuery('SELECT COUNT(*) FROM $ticketTable'));
}
Future<Ticket> getTicket(int id) async {
var dbClient = await db;
List<Map> result = await dbClient.query(ticketTable,
columns: [
columnId,
columnDay,
columnMonth,
columnYear,
columnFrom,
columnTo,
columnAircraft,
columnAirline
],
where: '$columnId = ?',
whereArgs: [id]);
if (result.length > 0) {
return new Ticket.fromMap(result.first);
}
return null;
}
Future<int> deleteTicket(int id) async {
var dbClient = await db;
return await dbClient
.delete(ticketTable, where: '$columnId = ?', whereArgs: [id]);
}
Future<int> updateTicket(Ticket ticket) async {
var dbClient = await db;
return await dbClient.update(ticketTable, ticket.toMap(),
where: "$columnId = ?", whereArgs: [ticket.id]);
}
Future close() async {
var dbClient = await db;
return dbClient.close();
}
}
class TicketScreen extends StatefulWidget {
final Ticket ticket;
TicketScreen(this.ticket);
#override
State<StatefulWidget> createState() => new _TicketScreenState();
}
class _TicketScreenState extends State<TicketScreen> {
DatabaseHelper db = new DatabaseHelper();
TextEditingController _dayController;
TextEditingController _monthController;
TextEditingController _yearController;
TextEditingController _fromController;
TextEditingController _toController;
TextEditingController _aircraftController;
TextEditingController _airlineController;
#override
void initState() {
super.initState();
_dayController = new TextEditingController(text: widget.ticket.day);
_monthController = new TextEditingController(text: widget.ticket.month);
_yearController = new TextEditingController(text: widget.ticket.year);
_fromController = new TextEditingController(text: widget.ticket.from);
_toController = new TextEditingController(text: widget.ticket.to);
_aircraftController = new TextEditingController(text: widget.ticket.aircraft);
_airlineController = new TextEditingController(text: widget.ticket.airline);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Ticket')),
body: Container(
margin: EdgeInsets.all(15.0),
alignment: Alignment.center,
child: Column(
children: <Widget>[
Text('Travel Date',
style: new TextStyle(fontWeight: FontWeight.bold),
),
Row(
children: <Widget>[
Container(
alignment: Alignment.center,
width: 80.0,
child: TextField(
maxLength: 2,
keyboardType: TextInputType.number,
controller: _dayController,
decoration: InputDecoration(labelText: 'Day'),
),
),
Text(' / '),
Container(
width: 80.0,
child: TextField(
maxLength: 2,
keyboardType: TextInputType.number,
controller: _monthController,
decoration: InputDecoration(labelText: 'Month'),
),
),
Text(' / '),
Container(
width: 160.0,
child: TextField(
maxLength: 4,
keyboardType: TextInputType.number,
controller: _yearController,
decoration: InputDecoration(labelText: 'Year'),
),
),
],
),
Padding(padding: new EdgeInsets.all(5.0)),
Row(
children: <Widget>[
Container(
width: 160.0,
child: TextField(
controller: _fromController,
decoration: InputDecoration(labelText: 'From'),
),
),
Container(
width: 160.0,
child: TextField(
controller: _toController,
decoration: InputDecoration(labelText: 'To'),
),
),
],
),
Padding(padding: new EdgeInsets.all(5.0)),
Row(
children: <Widget>[
Container(
width: 160.0,
child: TextField(
controller: _aircraftController,
decoration: InputDecoration(labelText: 'Aircraft'),
),
),
Container(
width: 160.0,
child: TextField(
controller: _airlineController,
decoration: InputDecoration(labelText: 'Airline'),
),
),
],
),
Padding(padding: new EdgeInsets.all(5.0)),
RaisedButton(
child: (widget.ticket.id != null) ? Text('Update') : Text('Add'),
onPressed: () {
if (widget.ticket.id != null) {
db
.updateTicket(Ticket.fromMap({
'id': widget.ticket.id,
'day': _dayController.text,
'year': _yearController.text,
'from': _fromController.text,
'to': _toController.text,
'aircraft': _aircraftController.text,
'airline': _airlineController.text
}))
.then((_) {
Navigator.pop(context, 'update');
});
} else {
db
.saveTicket(Ticket(
_dayController.text,
_monthController.text,
_yearController.text,
_fromController.text,
_toController.text,
_aircraftController.text,
_airlineController.text))
.then((_) {
Navigator.pop(context, 'save');
});
}
},
),
],
),
),
);
}
}
and then some other stuff, but i tend to rule them out as the source of the problem
class MyListView extends StatefulWidget {
#override
_ListViewState createState() => new _ListViewState();
}
class _ListViewState extends State<MyListView> {
List<Ticket> items = new List();
DatabaseHelper db = new DatabaseHelper();
#override
void initState() {
super.initState();
db.getAllTickets().then((tickets) {
setState(() {
tickets.forEach((ticket) {
items.add(Ticket.fromMap(ticket));
});
});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Ticket List',
home: Scaffold(
appBar: AppBar(
title: Text('Ticket List'),
centerTitle: true,
backgroundColor: Colors.blue,
),
body: Center(
child: ListView.builder(
itemCount: items.length,
padding: const EdgeInsets.all(15.0),
itemBuilder: (context, position) {
return Column(
children: <Widget>[
Divider(height: 5.0),
ListTile(
title: Text(
'From ${items[position].from} to ${items[position].to}',
style: TextStyle(
fontSize: 22.0,
color: Colors.deepOrangeAccent,
),
),
subtitle:
Text(
'Operated by ${items[position].airline} with ${items[position].aircraft} ',
style: new TextStyle(
fontSize: 18.0,
fontStyle: FontStyle.italic,
),
),
leading: Column(
children: <Widget>[
Padding(padding: EdgeInsets.all(10.0)),
CircleAvatar(
backgroundColor: Colors.blueAccent,
radius: 20.0,
child: Text(
'${items[position].day}/${items[position].month}/${items[position].year}',
style: TextStyle(
fontSize: 22.0,
color: Colors.white,
),
),
),
IconButton(
icon: const Icon(Icons.remove_circle_outline),
onPressed: () => _deleteTicket(context, items[position], position)
),
],
),
onTap: () => _navigateToTicket(context, items[position]),
),
],
);
}),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => _createNewTicket(context),
),
),
);
}
void _deleteTicket(BuildContext context, Ticket ticket, int position) async {
db.deleteTicket(ticket.id).then((tickets) {
setState(() {
items.removeAt(position);
});
});
}
void _navigateToTicket(BuildContext context, Ticket ticket) async {
String result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => TicketScreen(ticket)),
);
if (result == 'update') {
db.getAllTickets().then((tickets) {
setState(() {
items.clear();
tickets.forEach((ticket) {
items.add(Ticket.fromMap(ticket));
});
});
});
}
}
void _createNewTicket(BuildContext context) async {
String result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => TicketScreen(Ticket('', '', '', '', '', '', ''))),
);
if (result == 'save') {
db.getAllTickets().then((tickets) {
setState(() {
items.clear();
tickets.forEach((ticket) {
items.add(Ticket.fromMap(ticket));
});
});
});
}
}
}
class Ticket {
int _id;
String _day;
String _month;
String _year;
String _from;
String _to;
String _aircraft;
String _airline;
Ticket(this._day, this._month, this._year, this._from, this._to, this._aircraft, this._airline);
Ticket.map(dynamic obj) {
this._id = obj['id'];
this._day = obj['day'];
this._month = obj['month'];
this._year = obj['year'];
this._from = obj['form'];
this._to = obj['to'];
this._aircraft = obj['aircraft'];
this._airline = obj['airline'];
}
int get id => _id;
String get day => _day;
String get month => _month;
String get year => _year;
String get from => _from;
String get to =>_to;
String get aircraft =>_aircraft;
String get airline => _airline;
Map<String, dynamic> toMap() {
var map = new Map<String, dynamic>();
if (_id != null) {
map['id'] = _id;
}
map['day'] = _day;
map['month'] = _month;
map['year'] = _year;
map['from'] = _from;
map['to'] = _to;
map['aircraft'] = _aircraft;
map['airline'] = _airline;
return map;
}
Ticket.fromMap(Map<String, dynamic> map) {
this._id = map['id'];
this._day = map['day'];
this._month = map['month'];
this._year = map['year'];
this._from = map['from'];
this._to = map['to'];
this._aircraft = map['aircraft'];
this._airline = map['airline'];
}
}
void main() => runApp(
MaterialApp(
title: 'Returning Data',
home: MyListView(),
),
);
so, can you tell me what I did wrong?
thank you in advance
bonus question:
I feel no love for SQL,
is there any alternative db for flutter?
I mean real DB, not saving data in sharedpref or using firebase,
I like realm, but looks like there's not a flutter.
thanks again
The problem is you are using a reserved keyword 'From' in your column name, you also use 'To'
'CREATE TABLE $ticketTable($columnId INTEGER PRIMARY KEY, $columnDay TEXT, $columnMonth TEXT, $columnYear TEXT, $columnFrom TEXT, $columnTo TEXT, $columnAircraft TEXT, $columnAirline TEXT)');
So essentially you are asking it to, Create a table called TicketTable with the following column names -id,day,month,year.. that hold these specific data types. But when it gets to the column called 'from' it thinks you want to create a null (since column name doesn't exist) FROM the table called 'Text', which makes no sense.
Here is a list of keywords you should avoid - https://docs.oracle.com/database/121/SQLRF/ap_keywd001.htm#SQLRF55621
You might be able to get around this by surrounding the variable with double quotes but you'll have to test that out for yourself.

Resources