Flutter: Start and Stop Timer Using Same Button - asynchronous

I have a stopwatch and would like to have a single button that both pauses and starts it. I am struggling with the logic around this. When printing to the console, the boolean is stuck on false, and won't let me re-click the button.
stopwatch.dart:
class NewStopWatch extends StatefulWidget {
#override
_NewStopWatchState createState() => new _NewStopWatchState();
}
class _NewStopWatchState extends State<NewStopWatch> {
Stopwatch watch = new Stopwatch();
Timer timer;
bool startStop = true;
String elapsedTime = '';
updateTime(Timer timer) {
if (watch.isRunning) {
setState(() {
startStop = false;
print("startstop Inside=$startStop");
elapsedTime = transformMilliSeconds(watch.elapsedMilliseconds);
});
}
}
#override
Widget build(BuildContext context) {
return new Container(
padding: EdgeInsets.all(20.0),
child: new Column(
children: <Widget>[
new Text(elapsedTime, style: new TextStyle(fontSize: 25.0)),
SizedBox(height: 20.0),
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new FloatingActionButton(
heroTag: "btn1",
backgroundColor: Colors.red,
onPressed: startOrStop(),
child: new Icon(Icons.pause)),
SizedBox(width: 20.0),
new FloatingActionButton(
heroTag: "btn2",
backgroundColor: Colors.green,
onPressed: resetWatch,
child: new Icon(Icons.check)),
],
)
],
));
}
startOrStop() {
print("startstop=$startStop");
if(startStop == true) {
startWatch();
} else {
stopWatch();
}
}
startWatch() {
startStop = true;
watch.start();
timer = new Timer.periodic(new Duration(milliseconds: 100), updateTime);
}
stopWatch() {
startStop = false;
watch.stop();
setTime();
startStop = true;
}
setTime() {
var timeSoFar = watch.elapsedMilliseconds;
setState(() {
elapsedTime = transformMilliSeconds(timeSoFar);
});
}
transformMilliSeconds(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
int hours = (minutes / 60).truncate();
String hoursStr = (hours % 60).toString().padLeft(2, '0');
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
return "$hoursStr:$minutesStr:$secondsStr";
}
}
When the first button is clicked the first time, the stopwatch should start running. When it is clicked the second time, it should pause it.

I solved your code by adding setState() in your start and stop watch methods, flipped the logic in said methods, and added () => before startOrStop in the onPressed callback (This was the dealbreaker).
Furthermore, I removed startStop = false; from updateTimer(). I simplified your startOrStop() if statement as you do not need to write == true when checking the boolean value, you can simply write if(startStop) when evaluating booleans.
Working example:
import 'dart:async';
import 'package:flutter/material.dart';
class NewStopWatch extends StatefulWidget {
#override
_NewStopWatchState createState() => _NewStopWatchState();
}
class _NewStopWatchState extends State<NewStopWatch> {
Stopwatch watch = Stopwatch();
Timer timer;
bool startStop = true;
String elapsedTime = '';
updateTime(Timer timer) {
if (watch.isRunning) {
setState(() {
print("startstop Inside=$startStop");
elapsedTime = transformMilliSeconds(watch.elapsedMilliseconds);
});
}
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Text(elapsedTime, style: TextStyle(fontSize: 25.0)),
SizedBox(height: 20.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
heroTag: "btn1",
backgroundColor: Colors.red,
onPressed: () => startOrStop(),
child: Icon(Icons.pause)),
SizedBox(width: 20.0),
FloatingActionButton(
heroTag: "btn2",
backgroundColor: Colors.green,
onPressed: null, //resetWatch,
child: Icon(Icons.check)),
],
)
],
),
);
}
startOrStop() {
if(startStop) {
startWatch();
} else {
stopWatch();
}
}
startWatch() {
setState(() {
startStop = false;
watch.start();
timer = Timer.periodic(Duration(milliseconds: 100), updateTime);
});
}
stopWatch() {
setState(() {
startStop = true;
watch.stop();
setTime();
});
}
setTime() {
var timeSoFar = watch.elapsedMilliseconds;
setState(() {
elapsedTime = transformMilliSeconds(timeSoFar);
});
}
transformMilliSeconds(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
int hours = (minutes / 60).truncate();
String hoursStr = (hours % 60).toString().padLeft(2, '0');
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
return "$hoursStr:$minutesStr:$secondsStr";
}
}

Thanks for the working example. That helped me solve a similar problem.
In case it helps anyone, I added a few bits to flarkmarup's code so that the Icon's more relate to the flow.
At the top I added a variable:
IconData btnPlayStatus = Icons.play_arrow;
In FloatingActionButton (btn1) I replaced the Icon with the variable like:
child: Icon(btnPlayStatus)),
Then added SetState to startOrStop like:
startOrStop() {
if(startStop) {
setState(() {
btnPlayStatus = Icons.pause;
});
startWatch();
} else {
setState(() {
btnPlayStatus = Icons.play_arrow;
});
stopWatch();
}
}

Related

How to iterate through every document and sum properties in Firestore with Flutter

I'm creating an app in which you put the amount of crypto you have and then with an API it retrieves the amount with live data.
Thing is I have this ListView with all the cards for all the cryptos but I'd like a Container at the bottom of the screen with the total of all the amounts.
I don't know how to create a function that goes through every "Coins" of a user and then retrieve the amount of the total.
Thing is in Firestore I only have the quantity and then in my HomePage with the cards there's the function with the Api that calculates the amount in usd.
Could you help me to create some function which does that?
Here's my code of my HomePage view :
class HomeView extends StatefulWidget {
const HomeView({ Key? key }) : super(key: key);
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
double bitcoin = 0.0;
double ethereum = 0.0;
double tether = 0.0;
double ultra = 0.0;
double ternoa = 0.0;
double dai = 0.0;
double litecoin = 0.0;
double cardano = 0.0;
double stellar = 0.0;
double tezos = 0.0;
double elrond = 0.0;
double dogecoin = 0.0;
double solana = 0.0;
#override
initState() {
updateValues();
}
updateValues() async {
bitcoin = await getPrice("bitcoin");
ethereum = await getPrice("ethereum");
tether = await getPrice("tether");
ultra = await getPrice("ultra");
ternoa = await getPrice("coin-capsule");
dai = await getPrice("dai");
litecoin = await getPrice("litecoin");
cardano = await getPrice("cardano");
stellar = await getPrice("stellar");
tezos = await getPrice("tezos");
elrond = await getPrice("elrond-erd-2");
dogecoin = await getPrice("dogecoin");
solana = await getPrice("solana");
setState(() {});
}
#override
Widget build(BuildContext context) {
getValue(String id, double amount) {
if (id == "bitcoin") {
return (bitcoin * amount).toStringAsFixed(2);
} else if (id == "ethereum") {
return (ethereum * (amount)).toStringAsFixed(2);
} else if (id == "ultra"){
return(ultra*amount).toStringAsFixed(2);
}else if(id == "coin-capsule"){
return(ternoa*amount).toStringAsFixed(2);
}else if (id == "dai"){
return(dai*amount).toStringAsFixed(2);
}else if (id == "litecoin"){
return(litecoin*amount).toStringAsFixed(2);
}else if (id == "cardano"){
return(cardano*amount).toStringAsFixed(2);
}else if (id == "stellar"){
return(stellar*amount).toStringAsFixed(2);
}else if (id == "tezos"){
return(tezos*amount).toStringAsFixed(2);
}else if (id == "elrond-erd-2"){
return(elrond*amount).toStringAsFixed(2);
}else if (id == "dogecoin"){
return(dogecoin*amount).toStringAsFixed(2);
}else if (id == "solana"){
return(solana*amount).toStringAsFixed(2);
}
else {
return (tether * amount).toStringAsFixed(2);
}
}
return Scaffold(
body : Row(
children: [
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(color: Colors.white),
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection('Coins')
.snapshots(),
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot){
if(!snapshot.hasData){
return Center(child: CircularProgressIndicator(),);
}
return ListView(
children: snapshot.data!.docs.map((document) {
return Padding(
padding: const EdgeInsets.only(top: 8, left: 15, right :15),
child: Container(
width: MediaQuery.of(context).size.width/1.3,
height: MediaQuery.of(context).size.height/12,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
color: Colors.blue,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 5,),
Text("Coin : ${document.id}", style: TextStyle(color: Colors.white, fontSize: 18),),
Text("\$${getValue(document.id, document['Amount'])}",style: TextStyle(fontSize: 18,color: Colors.white)),
IconButton(
onPressed: ()async{
await removeCoin(document.id);
},
icon: Icon(Icons.close, color: Colors.red)
)
],
),
),
);
}).toList(),
);
}
,
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => AddView()));
},
child: Icon(Icons.add, color: Colors.white,),
backgroundColor: Colors.blue,
),
);
}
}
And here's the function for the api:
Future<double> getPrice(String id) async{
try{
var url = "https://api.coingecko.com/api/v3/coins/" + id;
var response = await http.get(Uri.parse(url));
var json = jsonDecode(response.body);
var value = json['market_data']['current_price']['usd'].toString();
return double.parse(value);
}catch(e){
print(e.toString());
return 0.0;
}
}
Any idea would be super helpul!
Thank you guys!
If you want total of amunt for user this works:
FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection('Coins')
.snapshots()
.listen((snapshot) {
double tempTotal = snapshot.documents.fold(0, (tot, doc) => tot + doc.data['amount']);
print(tempTotal.toString());
});

How can I add 3 widgets in one?

I am trying to show 3 widgets with 3 different on tap functions, but it is not working.
So what I want is the whole widget split in 3 different widgets so I can call widget 1 widget 2 or widget 3. But this is not working and I don't know why exactly.
I have this videos collection where users videos are uploaded with 3 hashtags and what I want is that the user can search for one hashtag no matter which one, but it always shows all 3 hashtags instead of the one which the user searched for. And that is what I mean with 3 different widgets.
Here is my code:
class Openalldocs extends StatefulWidget {
final TextEditingController searchinginput;
static const route = '/openalldocs';
const Openalldocs({Key key, this.searchinginput}) : super(key: key);
#override
_OpenalldocsState createState() => _OpenalldocsState();
}
class _OpenalldocsState extends State<Openalldocs> {
List _allResults = [];
List _resultsList = [];
Future resultsLoaded;
bool nosuerfound = false;
String searchresult;
#override
void initState() {
super.initState();
widget.searchinginput.addListener(_onsearchChanged);
setState(() {
nosuerfound = true;
});
}
#override
void dispose() {
widget.searchinginput.removeListener(_onsearchChanged());
super.dispose();
}
#override
void didChangeDependencies() {
widget.searchinginput.text;
resultsLoaded = getusers();
super.didChangeDependencies();
}
_onsearchChanged() {
setState(() {
nosuerfound = false;
});
searchResults();
}
searchResults() {
var showResults = [];
if (widget.searchinginput.text != "") {
for (var tripsnapshot in _allResults) {
var title = DatbaseService.instance
.videosfromsnapshot(tripsnapshot)
.hashtag1
.toLowerCase();
print(title);
var title2 = DatbaseService.instance
.videosfromsnapshot(tripsnapshot)
.hashtag3
.toLowerCase();
print(title);
var title3 = DatbaseService.instance
.videosfromsnapshot(tripsnapshot)
.hashtag2
.toLowerCase();
print(title);
if (title.contains(widget.searchinginput.text.toLowerCase()) ||
title2.contains(widget.searchinginput.text.toLowerCase()) ||
title3.contains(widget.searchinginput.text.toLowerCase())) {
setState(() {
nosuerfound = true;
});
showResults.add(tripsnapshot);
}
}
} else {
setState(() {
nosuerfound = true;
});
showResults = List.from(_allResults);
}
setState(() {
_resultsList = showResults;
});
}
getusers() async {
var firestore = FirebaseFirestore.instance;
QuerySnapshot qn = await firestore.collection('videos').get();
if (!mounted) return;
setState(() {
_allResults = qn.docs;
});
searchResults();
return "Complete";
}
#override
Widget build(BuildContext context) {
final user = Provider.of<Userforid>(context);
if (nosuerfound == true) {
return ListView.builder(
itemCount: _resultsList.length,
itemBuilder: (BuildContext context, int index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InkWell(
onTap: () {
},
child: Column(
children: <Widget>[
Column(
children: [
HighlightedMatchesText(
searchString: widget.searchinginput.text,
content: _resultsList[index].data()['hashtag1'],
),
],
),
SizedBox(height: 3),
],
),
),
SizedBox(height: 6),
InkWell(
onTap: () {
},
child: Column(
children: <Widget>[
Column(
children: [
HighlightedMatchesText(
searchString: widget.searchinginput.text,
content: _resultsList[index].data()['hashtag2'],
),
],
),
],
),
),
SizedBox(height: 6),
InkWell(
onTap: () {
},
child: Column(
children: <Widget>[
Column(
children: [
HighlightedMatchesText(
searchString: widget.searchinginput.text,
content: _resultsList[index].data()['hashtag3'],
),
],
),
SizedBox(height: 3),
],
),
),
SizedBox(height: 6),
]);
},
);
} else {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 30, 0, 0),
child: Center(
child: Container(
child: Text(
"No Hashtag found",
style: TextStyle(fontSize: 16),
)),
),
);
}
}
}
Your onTap handlers are empty, so nothing will happen actually when tapping.
To achieve what you are trying to, it is better to instead of creating widgets one by one in the Column children, create a for loop, and make the onTap and everything relative to it.
Here is how to achieve it (I took only a subsection of the code, the Column part):
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// the AMOUNT is how many hashtags you want to show
for (var i = 0; i < AMOUNT; i += 1) ...[
// the SizedBox will only exist between the elements in the list
// as before
if (i != 0) SizedBox(height: 6),
// create a builder to allow declaring a variable
Builder(
builder: (context) {
// declare the hashtag variable
final hashtag = 'hashtag$i';
return InkWell(
onTap: () {
// do something with the hashtag stored in the variable
// this will make it relative to the element in the list
},
child: Column(
children: <Widget>[
// why is there a Column inside another with only one child?
// I would recommend to remove it
Column(
children: [
HighlightedMatchesText(
searchString: widget.searchinginput.text,
// notice how I am using the hashtag variable here
// instead of a constant? ('hashtag1'), by the way
// the for loop will make the hashtag start at 0
// you can change it by increment in the declaration
// `final hashtag = 'hashtag${i+1}'`, if you want
// the existing behavior
content: _resultsList[index].data()[hashtag],
),
],
),
// what is this? if it is to add more space between the items
// in the list, I recommend removing it from here, and add it
// to the first `SizedBox` in the for loop
// in case you do that, the Column that this widget belong
// would also only now contain one widget, so, there is no
// need to have it
SizedBox(height: 3),
],
),
),
},
);
],
],
);
I added a lot of comments, I hope they help you to achieve what you are trying to.

Flutter ListView is not updating when the list items are changed

I started learning Flutter. I am developing a simple application using it. Now, I am developing a feature where my application will display the records from the SQLite database and where the user adds the new records into the SQLite database. But my ListView is displaying the blank screen.
I have a class called DatabaseHelper with the following code.
class DatabaseHelper {
static DatabaseHelper _databaseHelper;
Database _database;
String noteTable = 'note_table';
String colId = 'id';
String colTitle = 'title';
String colDescription = 'description';
String colPriority = 'priority';
String colDate = 'date';
DatabaseHelper._createInstance();
factory DatabaseHelper() {
if (_databaseHelper == null) {
_databaseHelper = DatabaseHelper._createInstance();
}
return _databaseHelper;
}
Future<Database> get database async {
if (_database == null) {
_database = await initializeDatabase();
}
return _database;
}
Future<Database> initializeDatabase() async {
Directory directory = await getApplicationDocumentsDirectory();
String path = directory.path + 'notes.db';
var notesDatabase = await openDatabase(path, version: 1, onCreate: _createDB);
return notesDatabase;
}
void _createDB(Database db, int newVersion) async {
await db.execute('CREATE TABLE $noteTable($colId INTEGER PRIMARY KEY AUTOINCREMENT, $colTitle TEXT, $colDescription TEXT, $colPriority INTEGER, $colDate TEXT)');
}
Future<List<Map<String, dynamic>>> getNoteMapList() async {
Database db = await this.database;
return await db.query(noteTable, orderBy: '$colPriority ASC');
}
Future<int> insertNote(Note note) async {
Database db = await this.database;
return await db.insert(noteTable, note.toMap());
}
Future<int> updateNote(Note note) async {
var db = await this.database;
return await db.update(noteTable, note.toMap(), where: '$colId = ?', whereArgs: [note.id]);
}
Future<int> deleteNote(int id) async {
var db = await this.database;
return await db.rawDelete('DELETE FROM $noteTable WHERE $colId = $id');
}
Future<int> getCount() async {
Database db = await this.database;
List<Map<String, dynamic>> x = await db.rawQuery('SELECT COUNT(*) FROM $noteTable');
return Sqflite.firstIntValue(x);
}
}
Then I have a widget called NoteList with the following code where the list of items are displayed.
class NoteList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _NoteListState();
}
}
class _NoteListState extends State<NoteList> {
List<Note> _notes = [];
int _count = 0;
DatabaseHelper _databaseHelper = DatabaseHelper();
_NoteListState() {
this._notes = getNotes();
this._count = _notes.length;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Notes"),),
body: Container(
child: getListView(context),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
navigateToNoteForm("Add Note");
},
),
);
}
Widget getListView(BuildContext context) {
return ListView.builder(
itemCount: _count,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
backgroundColor: _notes[index].priority == 1? Colors.yellow: Colors.red,
child: Icon(_notes[index].priority == 1 ? Icons.arrow_right : Icons.add),
),
title: Text(_notes[index].title),
subtitle: Text(_notes[index].date),
trailing: Icon(Icons.delete),
onTap: () {
navigateToNoteForm("Edit Note", _notes[index]);
},
);
});
}
void navigateToNoteForm(String pageTitle, [Note note]) async {
bool result = await Navigator.push(context, MaterialPageRoute(builder: (context) {
return NoteForm(pageTitle, note);
}));
if (result) {
setState(() {
debugPrint("Updating list");
_notes = getNotes();
_count = _notes.length;
});
}
}
List<Note> getNotes() {
List<Note> notes = List<Note>();
Future<List<Map<String, dynamic>>> notesFuture = _databaseHelper.getNoteMapList();
notesFuture.then((notesMap) {
debugPrint("Total notes found in the database ${notesMap.length}");
notesMap.forEach((map) {
notes.add(Note.fromMapObject(map));
});
});
return notes;
}
}
Then I also have another widget class called NoteForm with the following code.
class NoteForm extends StatefulWidget {
String _title = "";
Note _note = null;
NoteForm(String title, [Note note]) {
this._title = title;
this._note = note;
}
#override
State<StatefulWidget> createState() {
return _NoteFormState();
}
}
class _NoteFormState extends State<NoteForm> {
double _minimumPadding = 15.0;
var _priorities = [ 1, 2 ];
var _titleController = TextEditingController();
var _descriptionController = TextEditingController();
var _dateController = TextEditingController();
DatabaseHelper _databaseHelper = DatabaseHelper();
var _selectedPriority = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget._title),),
body: Builder(
builder: (scaffoldContext) => Form(
child: Column(
children: <Widget>[
Container(
child: Padding(
padding: EdgeInsets.all(_minimumPadding),
child: TextFormField(
controller: _titleController,
decoration: InputDecoration(
labelText: "Title",
hintText: "Enter title"
),
),
),
),
Container(
child: Padding(
padding: EdgeInsets.all(_minimumPadding),
child: TextFormField(
controller: _descriptionController,
decoration: InputDecoration(
labelText: "Description",
hintText: "Enter description"
),
),
)
),
Container(
child: Padding(
padding: EdgeInsets.all(_minimumPadding),
child: TextFormField(
controller: _dateController,
decoration: InputDecoration(
labelText: "Date",
hintText: "Enter date"
),
),
),
),
Container(
child: Padding(
padding: EdgeInsets.all(_minimumPadding),
child: DropdownButton<int>(
value: _selectedPriority,
items: _priorities.map((dropdownItem) {
return DropdownMenuItem<int>(
value: dropdownItem,
child: Text(dropdownItem == 1? "Low": "High"),
);
}).toList(),
onChanged: (int newSelectedValue) {
setState(() {
_selectedPriority = newSelectedValue;
});
},
),
),
),
Container(
child: Padding(
padding: EdgeInsets.all(_minimumPadding),
child: RaisedButton(
child: Text(
"Save"
),
onPressed: () {
_save(scaffoldContext);
},
),
),
)
],
),
),
)
);
}
void _save(BuildContext context) async {
Note note = Note();
note.title = _titleController.text;
note.description = _descriptionController.text;
note.date = _dateController.text;
note.priority = _selectedPriority;
if (widget._note != null && widget._note.id!=null) {
//update
_databaseHelper.updateNote(note);
this.showSnackBar(context, "Note has been updated.");
} else {
//create
_databaseHelper.insertNote(note);
this.showSnackBar(context, "Note has been added.");
}
closeForm(context);
}
void showSnackBar(BuildContext context, String message) {
var snackBar = SnackBar(
content: Text(message),
action: SnackBarAction(
label: "UNDO",
onPressed: () {
},
),
);
Scaffold.of(context).showSnackBar(snackBar);
}
void closeForm(BuildContext context) {
Navigator.pop(context, true);
}
}
When I run my application, it is just displaying the blank screen as follows.
As you can see I am logging out the number of records returned from the database using debugPrint method. It is saying that there are 6 records within the database. It is just not displaying the records. What is wrong with my code and how can I fix it?
As i mention in comment that was happening because of async task take some time to perform and if you do not keep it async then setState function execute before actual data load or set.
So Following changes solve your issue.
make getNotes async method And
getNotes().then((noteresponce){ setState((){ _notes=noteresponce; _count = _notes.length;} });

SQLite/ SQFlite updating is not working in a Flutter app [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I am building an application using Flutter and Dart programming language. I am using SQLite as the local database. Now I am developing a feature where I pass the data to a new form page when one of the items of the ListView is tapped. But updating the data is not working.
Following is my database helper class. Pay attention to the updateNote method
class DatabaseHelper {
static DatabaseHelper _databaseHelper;
Database _database;
String noteTable = 'note_table';
String colId = 'id';
String colTitle = 'title';
String colDescription = 'description';
String colPriority = 'priority';
String colDate = 'date';
DatabaseHelper._createInstance();
factory DatabaseHelper() {
if (_databaseHelper == null) {
_databaseHelper = DatabaseHelper._createInstance();
}
return _databaseHelper;
}
Future<Database> get database async {
if (_database == null) {
_database = await initializeDatabase();
}
return _database;
}
Future<Database> initializeDatabase() async {
Directory directory = await getApplicationDocumentsDirectory();
String path = directory.path + 'notes.db';
var notesDatabase = await openDatabase(path, version: 1, onCreate: _createDB);
return notesDatabase;
}
void _createDB(Database db, int newVersion) async {
await db.execute('CREATE TABLE $noteTable($colId INTEGER PRIMARY KEY AUTOINCREMENT, $colTitle TEXT, $colDescription TEXT, $colPriority INTEGER, $colDate TEXT)');
}
Future<List<Map<String, dynamic>>> getNoteMapList() async {
Database db = await this.database;
return await db.query(noteTable, orderBy: '$colPriority ASC');
}
Future<int> updateNote(Note note) async {
var db = await this.database;
return await db.update(noteTable, note.toMap(), where: '$colId = ?', whereArgs: [note.id]);
}
//there are other methods hidden here
}
I have a widget class that displays the ListView with the following code
class NoteList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _NoteListState();
}
}
class _NoteListState extends State<NoteList> {
List<Note> _notes = [];
int _count = 0;
DatabaseHelper _databaseHelper = DatabaseHelper();
_NoteListState() {
getNotes();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Notes"),),
body: Container(
child: getListView(context),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
navigateToNoteForm("Add Note");
},
),
);
}
Widget getListView(BuildContext context) {
return ListView.builder(
itemCount: _count,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
backgroundColor: _notes[index].priority == 1? Colors.yellow: Colors.red,
child: Icon(_notes[index].priority == 1 ? Icons.arrow_right : Icons.add),
),
title: Text(_notes[index].title),
subtitle: Text(_notes[index].date),
trailing: Icon(Icons.delete),
onTap: () {
navigateToNoteForm("Edit Note", _notes[index]);
},
);
});
}
void navigateToNoteForm(String pageTitle, [Note note]) async {
bool result = await Navigator.push(context, MaterialPageRoute(builder: (context) {
return NoteForm(pageTitle, note);
}));
if (result) {
getNotes();
}
}
void getNotes() {
List<Note> notes = List<Note>();
Future<List<Map<String, dynamic>>> notesFuture = _databaseHelper.getNoteMapList();
notesFuture.then((notesMap) {
debugPrint("Total notes found in the database ${notesMap.length}");
notesMap.forEach((map) {
notes.add(Note.fromMapObject(map));
});
setState(() {
_notes = notes;
_count = notes.length;
});
});
}
}
As you can see I am passing the data to the next widget, NoteForm when one of the ListView's items is clicked.
This is my NoteForm class's implementation.
class NoteForm extends StatefulWidget {
String _title = "";
Note _note = null;
NoteForm(String title, [Note note]) {
this._title = title;
this._note = note;
}
#override
State<StatefulWidget> createState() {
return _NoteFormState(this._title, this._note);
}
}
class _NoteFormState extends State<NoteForm> {
double _minimumPadding = 15.0;
var _priorities = [ 1, 2 ];
var _titleController = TextEditingController();
var _descriptionController = TextEditingController();
var _dateController = TextEditingController();
DatabaseHelper _databaseHelper = DatabaseHelper();
var _selectedPriority = 1;
String _title;
Note _note;
_NoteFormState(String title, [Note note]) {
this._title = title;
this._note = note;
if (this._note != null && this._note.id != null) {
_titleController.text = this._note.title;
_descriptionController.text = this._note.description;
_dateController.text = this._note.date;
_selectedPriority = this._note.priority;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(this._title),),
body: Builder(
builder: (scaffoldContext) => Form(
child: Column(
children: <Widget>[
Container(
child: Padding(
padding: EdgeInsets.all(_minimumPadding),
child: TextFormField(
controller: _titleController,
decoration: InputDecoration(
labelText: "Title",
hintText: "Enter title"
),
),
),
),
Container(
child: Padding(
padding: EdgeInsets.all(_minimumPadding),
child: TextFormField(
controller: _descriptionController,
decoration: InputDecoration(
labelText: "Description",
hintText: "Enter description"
),
),
)
),
Container(
child: Padding(
padding: EdgeInsets.all(_minimumPadding),
child: TextFormField(
controller: _dateController,
decoration: InputDecoration(
labelText: "Date",
hintText: "Enter date"
),
),
),
),
Container(
child: Padding(
padding: EdgeInsets.all(_minimumPadding),
child: DropdownButton<int>(
value: _selectedPriority,
items: _priorities.map((dropdownItem) {
return DropdownMenuItem<int>(
value: dropdownItem,
child: Text(dropdownItem == 1? "Low": "High"),
);
}).toList(),
onChanged: (int newSelectedValue) {
setState(() {
_selectedPriority = newSelectedValue;
});
},
),
),
),
Container(
child: Padding(
padding: EdgeInsets.all(_minimumPadding),
child: RaisedButton(
child: Text(
"Save"
),
onPressed: () {
_save(scaffoldContext);
},
),
),
)
],
),
),
)
);
}
void _save(BuildContext context) async {
Note note = Note();
note.title = _titleController.text;
note.description = _descriptionController.text;
note.date = _dateController.text;
note.priority = _selectedPriority;
if (widget._note != null && widget._note.id!=null) {
//update
_databaseHelper.updateNote(note);
debugPrint("Note title is ${note.title}");
debugPrint("Note description is ${note.description}");
debugPrint("Note date is ${note.date}");
debugPrint("Note priority is ${note.priority}");
debugPrint("Note has been updated.");
this.showSnackBar(context, "Note has been updated.");
} else {
//create
_databaseHelper.insertNote(note);
debugPrint("Note has been created.");
this.showSnackBar(context, "Note has been added.");
}
closeForm(context);
}
void showSnackBar(BuildContext context, String message) {
var snackBar = SnackBar(
content: Text(message),
action: SnackBarAction(
label: "UNDO",
onPressed: () {
},
),
);
Scaffold.of(context).showSnackBar(snackBar);
}
void closeForm(BuildContext context) {
Navigator.pop(context, true);
}
}
When I update the note, it does not update it. The list view is still displaying the same data. What is wrong with my code and how can I fix it?
When you are making your call to _databaseHelper.updateNote(note); I believe it you should be awaiting this so await _databaseHelper.updateNote(note); because it returns a future so right now you are not waiting for your data to return. You have a couple other functions that return futures too.
maybe you need a await or future then, where you call
also give a print in the _databaseHelper.updateNote
you can see which one comes first
void _save(BuildContext context) async {
Note note = Note();
note.title = _titleController.text;
note.description = _descriptionController.text;
note.date = _dateController.text;
note.priority = _selectedPriority;
if (widget._note != null && widget._note.id!=null) {
//update
await _databaseHelper.updateNote(note);
debugPrint("Note title is ${note.title}");
debugPrint("Note description is ${note.description}");
debugPrint("Note date is ${note.date}");
debugPrint("Note priority is ${note.priority}");
debugPrint("Note has been updated.");
this.showSnackBar(context, "Note has been updated.");
} else {
//create
await _databaseHelper.insertNote(note);
debugPrint("Note has been created.");
this.showSnackBar(context, "Note has been added.");
}
closeForm(context);
}
I found the issue. It is with my code. Have a look at the following snippet.
Note note = Note();
note.title = _titleController.text;
note.description = _descriptionController.text;
note.date = _dateController.text;
note.priority = _selectedPriority;
I am not assigning the id value. So that id is always null for updating. Thanks for all your help.

flutter - how to best practice make a countdown widget

Im developing a soccer match schedule application .
I want to add a countdown when kick off the match.
How to best practice make a countdown widget with the format hh : mm : ss ?
Both in the comment will have good results. It is also best to rely on Flutter documentation for guidance.
With that, I've made a little sample of a countdown timer based on your requirements.
First, I've tried to define what kind of input I'm going to use. Decided to implement the input this way:
//Update the time in 'YYYY-MM-DD HH:MM:SS' format
final eventTime = DateTime.parse('2021-01-09 03:41:00');
So that I can supply the exact date and time I needed.
Then get the difference from the current date and time and convert it to seconds:
int timeDiff = eventTime.difference(DateTime.now()).inSeconds;
Then created a function that would handle the clocking of the timer:
void handleTick() {
if (timeDiff > 0) {
if (isActive) {
setState(() {
if (eventTime != DateTime.now()) {
timeDiff = timeDiff - 1;
} else {
print('Times up!');
//Do something
}
});
}
}
}
So when the timer is working as expected, I've just used mathematical operation to define the remaining days, hours, minutes and seconds:
int days = timeDiff ~/ (24 * 60 * 60) % 24;
int hours = timeDiff ~/ (60 * 60) % 24;
int minutes = (timeDiff ~/ 60) % 60;
int seconds = timeDiff % 60;
If you just need the HH:MM:SS format you can just play around and omit that section, check the working code:
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(TimerApp());
class TimerApp extends StatefulWidget {
#override
_TimerAppState createState() => _TimerAppState();
}
//Update the time in 'YYYY-MM-DD HH:MM:SS' format
final eventTime = DateTime.parse('2021-01-09 03:41:00');
class _TimerAppState extends State<TimerApp> {
static const duration = const Duration(seconds: 1);
int timeDiff = eventTime.difference(DateTime.now()).inSeconds;
bool isActive = false;
Timer timer;
void handleTick() {
if (timeDiff > 0) {
if (isActive) {
setState(() {
if (eventTime != DateTime.now()) {
timeDiff = timeDiff - 1;
} else {
print('Times up!');
//Do something
}
});
}
}
}
#override
Widget build(BuildContext context) {
if (timer == null) {
timer = Timer.periodic(duration, (Timer t) {
handleTick();
});
}
int days = timeDiff ~/ (24 * 60 * 60) % 24;
int hours = timeDiff ~/ (60 * 60) % 24;
int minutes = (timeDiff ~/ 60) % 60;
int seconds = timeDiff % 60;
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.grey[700],
title: Center(
child: Text('Countdown Timer'),
),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
LabelText(
label: 'DAYS', value: days.toString().padLeft(2, '0')),
LabelText(
label: 'HRS', value: hours.toString().padLeft(2, '0')),
LabelText(
label: 'MIN', value: minutes.toString().padLeft(2, '0')),
LabelText(
label: 'SEC', value: seconds.toString().padLeft(2, '0')),
],
),
SizedBox(height: 60),
Container(
width: 200,
height: 47,
margin: EdgeInsets.only(top: 30),
child: RaisedButton(
color: isActive ? Colors.grey : Colors.green,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25)),
child: Text(isActive ? 'STOP' : 'START'),
onPressed: () {
setState(() {
isActive = !isActive;
});
},
),
)
],
),
),
),
);
}
}
class LabelText extends StatelessWidget {
LabelText({this.label, this.value});
final String label;
final String value;
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 5),
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: Colors.grey,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'$value',
style: TextStyle(
color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
),
Text(
'$label',
style: TextStyle(
color: Colors.white70,
),
),
],
),
);
}
}
Here is the output of the countdown timer I've created:

Resources