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

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.

Related

Flutter - widget variables are always null

I have a Flutter app connected to firebase, and I'm using it to receive push notifications from Firebase Cloud Messaging, using this code. Whenever I call the variables widget.title, widget.body, they are null, but when the method receives a notification, they have the value that came from FCM. What should I do?
class PushList extends StatefulWidget {
Map<dynamic, dynamic> notific;
String title, body;
Future<dynamic> fcmMessageReceiver() async {
FirebaseMessaging.instance.getInitialMessage().then((value) {
if (value != null) {}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.notification != null) {
notificacao = {
'title': message.notification.title,
'body': message.notification.body
};
title = message.notification.title;
body = message.notification.body;
print('MENSAGEM: $notific');
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {});
}
PushList() {
}
#override
_PushListState createState() => _PushListState();
}
class _PushListState extends State<PushList> {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
backgroundColor: Colors.white,
appBar: null,
body: ListView(
children: [
widget.notific != null
? Card(
margin: EdgeInsets.all(10),
elevation: 4,
child: ListTile(
title: Text(
widget.title,
),
subtitle: Text(
widget.body,
),
),
)
: Container(
child: Text("U don't have new notifcs."),
),
],
),
);
}
}
PushList should be immutable. Define the properties inside the state and call fcmMessageReceiver inside initState. Also you need to call setState to trigger rebuild after you set title and body:
class PushList extends StatefulWidget {
#override
_PushListState createState() => _PushListState();
}
class _PushListState extends State<PushList> {
Map<dynamic, dynamic> notific;
String title, body;
#override
void initState() {
super.initState();
fcmMessageReceiver();
}
Future<dynamic> fcmMessageReceiver() async {
FirebaseMessaging.instance.getInitialMessage().then((value) {
if (value != null) {}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.notification != null) {
if (mounted) {
setState(() {
notificacao = {
'title': message.notification.title,
'body': message.notification.body
};
title = message.notification.title;
body = message.notification.body;
});
}
print('MENSAGEM: $notific');
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
backgroundColor: Colors.white,
appBar: null,
body: ListView(
children: [
notific != null
? Card(
margin: EdgeInsets.all(10),
elevation: 4,
child: ListTile(
title: Text(
title ?? '',
),
subtitle: Text(
body ?? '',
),
),
)
: Container(
child: Text("U don't have new notifcs."),
),
],
),
);
}
}

Cannot convert Future<String> to String and use in Dart (Flutter)

I try to implement Firebase authentication in my mobile app. (I am very new to this..)
I have the following code which attempts to create the user for the first time:
class WpAuthService {
FirebaseAuth _auth = FirebaseAuth.instance;
Stream<WpUser> get wpUser {
return _auth.authStateChanges().map((User firebaseUser) =>
(firebaseUser != null) ? WpUser(uid: firebaseUser.uid) : null);
}
Future<String> createUserWithEmail(email, password) async {
UserCredential userCredential;
try {
userCredential = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
} on FirebaseAuthException catch (e) {
print(e.code + " - " + e.message);
return e.message;
}
return 'SUCCESS';
}
}
And in another file, I am trying to call the createUserWithEmail function as following:
class SignupForm extends StatefulWidget {
#override
_SignupFormState createState() => _SignupFormState();
}
class _SignupFormState extends State<SignupForm> {
final _formKey = GlobalKey<FormState>();
var _userEmail = "";
var _userPassword = "";
String _opResult;
void _trySubmit() {
final isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState.save();
WpAuthService()
.createUserWithEmail(_userEmail, _userPassword)
.then((value) {
setState(() {
_opResult = value;
});
});
print('MESSAGE:');
print(_opResult);
if (_opResult != 'SUCCESS') {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(_opResult),
backgroundColor: Theme.of(context).errorColor,
),
);
}
}
}
#override
Widget build(BuildContext context) {
return Center(
child: Card(
margin: EdgeInsets.all(20),
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
key: ValueKey('email'),
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Please enter a valid email address.';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(labelText: 'Email address'),
onSaved: (value) {
_userEmail = value;
},
),
TextFormField(
key: ValueKey('password'),
validator: (value) {
if (value.isEmpty || value.length < 7) {
return 'Password must be at least 7 characters long.';
}
return null;
},
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onSaved: (value) {
_userPassword = value;
},
),
SizedBox(height: 12),
RaisedButton(
child: Text('Sign up',
style: Theme.of(context).textTheme.headline6),
onPressed: _trySubmit,
),
],
),
),
),
),
),
);
}
}
When I run the above piece of code, it prints out the following:
I/flutter ( 4032): MESSAGE:
I/flutter ( 4032): null
════════ Exception caught by gesture ═══════════════════════════════════════════
The following assertion was thrown while handling a gesture:
A non-null String must be provided to a Text widget.
'package:flutter/src/widgets/text.dart':
Failed assertion: line 370 pos 10: 'data != null'
Looking at some other examples, I expect my implementation to work but something is seemingly wrong. How can I use the return value as String from the createUserWithEmail function?
by default your _opResult variable is null and your passing it to the Text widget of the Snackbar which throws that assertion.
You need either to first wait for the response to return or change your code to be inside the then method.
void _trySubmit() {
final isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState.save();
WpAuthService()
.createUserWithEmail(_userEmail, _userPassword)
.then((value) {
setState(() {
_opResult = value;
});
print('MESSAGE:');
print(_opResult);
if (_opResult != 'SUCCESS') {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(_opResult),
backgroundColor: Theme.of(context).errorColor,
),
);
}
});
}
}
Your _opResult is null. And it is not equal to 'SUCCESS'. And you are trying to set it into Text() widget. Text widget requires a string parameter, not null.
You can set a default string when initializing the _opResult. Like this:
String _opResult = "";
print('MESSAGE:');
print(_opResult);
if (_opResult != 'SUCCESS') {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(_opResult),
backgroundColor: Theme.of(context).errorColor,
),
);
}
Solved.
In the createUserWithEmail function, I was returning String values as the following:
return e.message;
return 'SUCCESS';
I updated it so that the function now returns as stated below:
return Future.value(e.message);
return Future.value('SUCCESS');
Everything else is the same and it worked.
But I saw some other examples where people were just returning String values from their functions. My problem is solved but is this behavior really expected? You are more than welcome to educate me.

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;} });

Another exception was thrown: NoSuchMethodError: The setter "wastedate=" was called on null

I am received the following error on my form, I am trying to submit a form page to a Firebase database. The error is "Another exception was thrown: NoSuchMethodError: The setter 'wastedate=' was called on null." I am thinking it must relate to the _saveWaste function as the print shows "saveWaste called" but not "form saved".
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:async';
import 'package:wasteagram/model/waste.dart';
import 'package:path/path.dart' as path;
import 'package:uuid/uuid.dart';
class CameraScreen extends StatefulWidget {
final bool isUpdating;
CameraScreen({#required this.isUpdating});
#override
_CameraScreenState createState() => _CameraScreenState();
}
class _CameraScreenState extends State<CameraScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Waste _currentWaste;
Widget _buildDateField() {
return Form(
key: _formKey,
child: TextFormField(
decoration: InputDecoration(labelText: 'Date'),
keyboardType: TextInputType.text,
style: TextStyle(fontSize: 20),
validator: (String value) {
if(value.isEmpty){
return 'Date required';
}
if(value.length < 3 || value.length > 20) {
return 'Name must be more than 3 or less than 20';
}
return null;
},
onSaved: (String value) {
_currentWaste.wastedate = value;
},
),
);
}
Widget _buildWasteNumber() {
return Form(
child: TextFormField(
decoration: InputDecoration(labelText: 'Number'),
keyboardType: TextInputType.number,
style: TextStyle(fontSize: 20),
validator: (value) {
if(value.isEmpty){
return 'Number required';
}
return null;
},
onSaved: (String value) {
String wasteNum = _currentWaste.wastenumber.toString();
wasteNum = value;
},
),
);
}
_saveWaste(context) {
print("saveWaste Called");
if(!_formKey.currentState.validate()) {
return "FALSE";
}
_formKey.currentState.save();
print("form saved");
uploadItems(_currentWaste, widget.isUpdating, image);
print("date ${_currentWaste.wastedate}");
print("number ${_currentWaste.wastenumber.toString()}");
print("_imageFile ${image.toString()}");
}
File image;
void getImage() async {
image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState( () {});
}
#override
Widget build(BuildContext context) {
if (image == null) {
return Scaffold(
appBar: AppBar(
title: Text('Wasteagram')
),
body: Center(
child: RaisedButton(
child: Text('Select Photo'),
onPressed: () {
getImage();
},
),
),
);
} else {
return Scaffold(
appBar: AppBar(
title: Text('Wasteagram')
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.file(image),
SizedBox(height: 40),
RaisedButton(
child: Text('Select Photo'),
onPressed: () {
getImage();
}
),
_buildDateField(),
_buildWasteNumber(),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => _saveWaste(context),
child: Icon(Icons.save),
foregroundColor: Colors.white,
),
);
}
}
}
uploadItems(Waste waste, bool isUpdating, File localFile) async {
if (localFile != null) {
print("uploading image");
var fileExtension = path.extension(localFile.path);
print(fileExtension);
var uuid = Uuid().v4();
final StorageReference firebaseStorageRef =
FirebaseStorage.instance.ref().child('/$uuid$fileExtension');
await firebaseStorageRef.putFile(localFile).onComplete.catchError(
(onError){
print(onError);
return false;
}
);
String url = await firebaseStorageRef.getDownloadURL();
print("download url: $url");
_uploadWaste(waste, isUpdating, imageUrl: url);
} else {
print("skipping image upload");
_uploadWaste(waste, isUpdating);
}
}
_uploadWaste(Waste waste, bool isUpdating, {String imageUrl}) async {
CollectionReference wasteRef = Firestore.instance.collection('todolist');
if(imageUrl != null) {
waste.image = imageUrl;
}
if(isUpdating) {
waste.updatedAt = Timestamp.now();
await wasteRef.document(waste.id).updateData(waste.toMap());
print("updated waste with id: ${waste.id}");
} else {
DocumentReference documentRef = await wasteRef.add(waste.toMap());
waste.id = documentRef.documentID;
print("uploaded waste successfully: ${waste.toString()}");
await documentRef.setData(waste.toMap(), merge: true);
}
}
You must init _currentWaste , it is null
Please change from
Waste _currentWaste;
To
Waste _currentWaste = Waste();

How to restore data in Listview

My goal is restoring data from sql database to Listview. Firstly, I started with creating database and model class. Secondly I realized that I have to use FutureBuilder. But I can't understood, how to use this stuff in my case. In addition I have known, that have to use GlobalKey.
This is my code. In this version of my code, Alert dialog doesn't work
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:path/path.dart';
import 'dart:ui';
import 'package:samuraigym/program_training_handler.dart';
import 'package:samuraigym/my_icons_icons.dart' as custicon;
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:io' as io;
import 'package:fluttertoast/fluttertoast.dart';
class MeasurementsScreen extends StatefulWidget {
#override
_MeasurementsScreenState createState() => _MeasurementsScreenState();
}
class _MeasurementsScreenState extends State<MeasurementsScreen> {
List<ListItem> listItems;
final scaffoldKey = new GlobalKey<ScaffoldState>();
final formKey = new GlobalKey<FormState>();
String typeOfMuscle;
String numberOfMuscle;
var nameItem = ["Рост","Вес","Шея","Плечевой пояс","Грудь","Бицепс",
"Предплечье","Запястье","Живот","Бедро","Голень","Лодыжка"];
#override
void initState() {
super.initState();
initListItems();
}
void initListItems() {
listItems = [
new ListItem(
detail: nameItem[0],
index: 0,
data: " "),
new ListItem(
detail: nameItem[1],
index: 1,
data: " "),
new ListItem(
detail: nameItem[2],
index: 2,
data: " "),
new ListItem(
detail: nameItem[3],
index: 3,
data: " "),
new ListItem(
detail: nameItem[4],
index: 4,
data: " "),
new ListItem(
detail: nameItem[5],
index: 5,
data: " "),
new ListItem(
detail: nameItem[6],
index: 6,
data: " "),
new ListItem(
detail: nameItem[7],
index: 7,
data: " "),
new ListItem(
detail: nameItem[8],
index: 8,
data: " "),
new ListItem(
detail: nameItem[9],
index: 9,
data: " "),
new ListItem(
detail: nameItem[10],
index: 10,
data: " "),
new ListItem(
detail: nameItem[11],
index: 11,
data: " ")
];
}
void sumbitContact(int index, String numberOfMuscle) {
if(this.formKey.currentState.validate())
formKey.currentState.save();
else
return null;
var measurementsDatabaseModel = MeasurementsDatabaseModel();
measurementsDatabaseModel.numberOfMuscle = numberOfMuscle;
measurementsDatabaseModel.typeOfMuscle = index as String;
var dbHelper = DatabaseHelperForMeasurements();
dbHelper.addNewMeasurementsDatabaseModel(measurementsDatabaseModel);
Fluttertoast.showToast(msg: 'Contact was saved',
toastLength: Toast.LENGTH_SHORT);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: scaffoldKey,
backgroundColor: Color(0xff2b2b2b),
appBar: AppBar(
backgroundColor: Colors.lightGreen[400],
title: Text(
'Замеры',
style: new TextStyle(
color: Colors.white
),),
leading: IconButton(
icon:Icon(Icons.arrow_back),
color: Colors.white ,
onPressed:() => Navigator.of(context).pop(),
),
),
body: FutureBuilder<List<MeasurementsDatabaseModel>>(
future: getMeasurementsDatabaseModelFromDB(),
builder: (context, snapshot){
if(snapshot.data != null && snapshot.hasData){
return ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: listItems.length,
itemBuilder: (BuildContext ctxt, int index) => listItems[index],
);
} else {
return ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: listItems.length,
itemBuilder: (BuildContext ctxt, int index) => listItems[index],
);
}
}
)
);
}
}
Future<List<MeasurementsDatabaseModel>> getMeasurementsDatabaseModelFromDB() async {
var dbHelper = DatabaseHelperForMeasurements();
Future<List<MeasurementsDatabaseModel>> contacts = dbHelper.getMeasurementsDatabaseModel();
return contacts;
}
class ListItem extends StatefulWidget {
String detail;
int index;
String data;
_MeasurementsScreenState measurementsScreen;
ListItem({Key key, this.detail, this.index, this.data}) : super(key: key);
#override
_ListItem createState() => _ListItem(measurementsScreen);
}
class _ListItem extends State<ListItem> {
bool isAppear = false;
final _MeasurementsScreenState measurementsScreen;
DatabaseHelperForMeasurements db = DatabaseHelperForMeasurements();
_ListItem(this.measurementsScreen);
MeasurementsDatabaseModel measurementsDatabaseModel = new MeasurementsDatabaseModel();
String typeOfMuscle;
String numberOfMuscle;
String lastSelectedValue;
var name = ["Рост","Вес","Шея","Плечевой пояс","Грудь","Бицепс",
"Предплечье","Запястье","Живот","Бедро","Голень","Лодыжка"];
var indication = ["Ваш рост","Ваш вес","Ваша шея","Ваш плечевой пояс","Ваша грудь","Ваш бицепс",
"Ваше предплечье","Ваше запястье","Ваш живот","Ваше бедро","Ваша голень","Ваша лодыжка"];
var prefix = ["см: ","кг: ","см: ","см: ","см: ","см: ","см: ","см: ","см: ","см: ","см: ","см: "];
var prefixAlert = ["см","кг","см","см","см","см","см","см","см","см","см","см"];
TextEditingController customcintroller;
Future<String> createAlertDialog(BuildContext context, int indexAl) async{
customcintroller = TextEditingController();
String returnVal = await showDialog(
context: context, builder: (context){
return AlertDialog(
title: Text(name[indexAl]),
content: TextFormField(
textDirection: TextDirection.ltr,
controller: customcintroller,
style: TextStyle(
color: Colors.lightGreen[400],
fontSize: 18.5),
decoration: InputDecoration(
contentPadding: EdgeInsets.only(bottom: 4.0),
labelText: indication[indexAl],
suffixText: prefixAlert[widget.index],
alignLabelWithHint: false,
),
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.done,
onSaved: (val) => this.numberOfMuscle = val,
),
actions: <Widget>[
FlatButton(
child: const Text('ОТМЕНА'),
onPressed: () {
Navigator.of(context).pop();
},
),
FlatButton(
child: const Text('ОК'),
onPressed: () {
setState(() {
widget.data = customcintroller.text.toString();
isAppear = !isAppear;
measurementsScreen.sumbitContact(widget.index, widget.data);
getMeasurementsDatabaseModelFromDB();
Navigator.of(context).pop();
});
},
),
],
);
});
return returnVal;
}
#override
Widget build(BuildContext context) {
return Container(
child: GestureDetector(
onTap: () {
createAlertDialog(context, widget.index);
},
child: Container(
color: Color(0xff2b2b2b),
height: 55.0,
margin: const EdgeInsets.symmetric(
vertical: 1.0,
),
child: new Stack(
children: <Widget>[
new Container(
child: new SizedBox.expand(
child: Container(
alignment: Alignment.center,
child: new Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
// Padding(
// padding: const EdgeInsets.all(8.0),
// child: Icon(
// custicon.MyIcons.bathroom_scale,
// color: Colors.lightGreen[400],
// size: 40.0)),
Padding(
padding: const EdgeInsets.all(16.0),
child: new Text(
widget.detail,
style:
new TextStyle(fontSize: 16.0, color: Colors.white),
),
),
Container(
alignment: Alignment.centerRight,
child: isAppear ? Padding(
padding: EdgeInsets.all(8.0),
child: Container(
decoration: ShapeDecoration(
color: Colors.lightGreen[400],
shape: RoundedRectangleBorder(
side: BorderSide(width: 1.0, style: BorderStyle.solid, color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
),
child: Padding(
padding: EdgeInsets.all(4.0),
child: new Text(
prefix[widget.index] + widget.data,
style: new TextStyle(
fontSize: 16.0,
color: Colors.white
),
)))) : SizedBox(),
)
],
),
),
),
),
],
),
)));
}
}
class MeasurementsDatabaseModel{
int id;
String typeOfMuscle;
String numberOfMuscle;
MeasurementsDatabaseModel();
}
class DatabaseHelperForMeasurements{
static Database db_instance;
final String TABLE_NAME = "Measurements";
Future<Database> get db async{
if(db_instance == null)
db_instance = await initDB();
return db_instance;
}
initDB() async {
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path,"Measurements_db.db");
var db = await openDatabase(path,version: 1, onCreate: onCreateFunc);
return db;
}
void onCreateFunc(Database db, int version) async{
await db.execute('CREATE TABLE $TABLE_NAME(id INTEGER PRIMARY KEY AUTOINCREMENT, typeOfMuscle TEXT, numberOfMuscle TEXT);');
}
Future<List<MeasurementsDatabaseModel>> getMeasurementsDatabaseModel() async{
var db_connection = await db;
List<Map> list = await db_connection.rawQuery('SELECT * FROM $TABLE_NAME');
List<MeasurementsDatabaseModel> modelList = new List();
for(int i = 0; i < list.length; i++){
MeasurementsDatabaseModel measurementsDatabaseModel = new MeasurementsDatabaseModel();
measurementsDatabaseModel.id = list[i]['id'];
measurementsDatabaseModel.typeOfMuscle = list[i]['typeOfMuscle'];
measurementsDatabaseModel.numberOfMuscle = list[i]['numberOfMuscle'];
modelList.add(measurementsDatabaseModel);
}
return modelList;
}
void addNewMeasurementsDatabaseModel(MeasurementsDatabaseModel measurementsDatabaseModel) async {
var db_connection = await db;
String query =
'INSERT INTO $TABLE_NAME(name,phone) VALUES( \'${measurementsDatabaseModel.typeOfMuscle}\',\'${measurementsDatabaseModel.numberOfMuscle}\')';
await db_connection.transaction((transition) async{
return await transition.rawInsert(query);
});
}
void updateMeasurementsDatabaseModel(MeasurementsDatabaseModel measurementsDatabaseModel) async {
var db_connection = await db;
String query =
'UPDATE $TABLE_NAME SET name =\'${measurementsDatabaseModel.typeOfMuscle}\',phone =\'${measurementsDatabaseModel.typeOfMuscle}\' WHERE id =${measurementsDatabaseModel.id}';
await db_connection.transaction((transition) async{
return await transition.rawQuery(query);
});
}
void deleteMeasurementsDatabaseModel(MeasurementsDatabaseModel measurementsDatabaseModel) async {
var db_connection = await db;
String query = 'DELETE FROM $TABLE_NAME WHERE id = ${measurementsDatabaseModel.id}';
await db_connection.transaction((transition) async{
return await transition.rawQuery(query);
});
}
}
This is gif, where I want to save data. How can you see, there is container with text near right part of screen. There I want to save data, which I put in AlertDialog.
Firstly, we are going to discuss its simplified implementation. At the MainScreen,
we won't connect it to SQLite. But later on, We can discuss more complex
implementation by calling SQLite queries.
Should we use FutureBuilder ?
In many tutorials spread accross internet, app screen can shows Loading indicator, and later on, it shows ListView and its ListTile after fetching to Database.
It works well if there are no further interaction with ListView later on.
As we using future, the builder will only triggered twice.
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: queryDatabase(), // Calling Database
builder: (context, snapshot) {
if (snapshot.hasData) { // After Callback, it may triggers this Part
return ListView.builder(
itemBuilder: (_, index) {
return ListTile(
title: Text("$index"),
);
},
);
}
return Center( // First Triggering this Part
child: CircularProgressIndicator(),
);
},
),
);
}
}
The solution is Stateful Widget !
By using Stateful Widget, we can store our products, in variable. Therefore,
each time MainScreen's build method called, the app will display updated List.
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
List<Map<String, dynamic>> products = []; // Store Item, Here
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(...),
body: Container(
child: renderProducts(), // Render stored Item, here
),
);
}
We can see at the demo below, as we interact with buttons, the build method
always being re-called, and fortunately, we can have our Expected Screen
Identified Problems
class _ListItem extends State<ListItem> {
String typeOfMuscle;
String numberOfMuscle;
TextEditingController customcintroller;
...
AlertDialog(
title: Text(name[indexAl]),
content: TextFormField(
controller: customcintroller,
onSaved: (val) => this.numberOfMuscle = val,
),
);
),
the problem is, in onSaved method, the app will only update local variable that
resides on each of ListItem. Therefore, the app does not know that the new value
should be displayed.
How to make the widget rerenders ?
By using setState((){}) as the code below
class _ListItem extends State<ListItem> {
String typeOfMuscle;
String numberOfMuscle;
TextEditingController customcintroller;
...
void updateAndRerender(val){
this.numberOfMuscle = val;
setState((){});
}
AlertDialog(
title: Text(name[indexAl]),
content: TextFormField(
controller: customcintroller,
onSaved: (val) {
updateAndRerender(val);
},
),
);
),
Working Example-App Repo
you may look into this repository. Github

Resources