Displaying Single Document from Firebase - database

I'm having problems getting data from my firebase. I have the data but do not know how to display it.
Here is my Database:
This is my code:
class Home extends StatefulWidget {
final dynamic userUid, nicked;
Home({this.userUid, this.nicked});
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final int delayedAmount = 500;
//final AuthService _auth = AuthService();
void getNick() {
//get nickname of this User
final CollectionReference userNickname = Firestore.instance.collection('NickNames of Users');
var documentNick = userNickname.document(widget.userUid);
documentNick.get()
.then((DocumentSnapshot ds){
var nick = ds.data['MyNickName'];
print(nick);
var nicked = Home(nicked: nick,);
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
backgroundColor: Colors.greenAccent[400],
body: SingleChildScrollView(
physics: ScrollPhysics(),
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.fromLTRB( 0 , 50, 0, 0),
child: Column(
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: DelayedAnimation(
child:
Text(
widget.nicked,//This i where the firebase data nickName code will go
textScaleFactor : 1,
style: TextStyle(
fontFamily: 'Comic',
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 30,
),
),
So i just dont know how to get the single document from firebase and display it.
So this is how i get userUid...
import 'package:firebase_auth/firebase_auth.dart';
//import 'package:flutter/cupertino.dart';
import 'package:fruithero/model/user.dart';
import 'package:fruithero/services/database.dart';
import 'package:fruithero/pages/myHome.dart';
class AuthService {
final dynamic nickNamesofUser;
AuthService({this.nickNamesofUser});
final FirebaseAuth _auth = FirebaseAuth.instance;
void homeTransporter(uid){
dynamic homeTransport = uid;
Home(userUid: homeTransport,);
}
// create user obj based on firebase user
User _userFromFirebaseUser(FirebaseUser user) {
return user != null ? User(uid: user.uid) : null;
}
// auth change user stream
Stream<User> get user {
return _auth.onAuthStateChanged
//.map((FirebaseUser user) => _userFromFirebaseUser(user));
.map(_userFromFirebaseUser);
}
// sign in anon
Future signInAnon() async {
try {
AuthResult result = await _auth.signInAnonymously();
FirebaseUser user = result.user;
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
// sign in with email and password
Future signInWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
return user;
} catch (error) {
print(error.toString());
return null;
}
}
// register with email and password
Future registerWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
//storing user uid
homeTransporter(user.uid);
//create new document for user with the uid
await DataBaseServices(uid: user.uid).createDocumentWithUid(nickNamesofUser);
//await DatabaseService(uid: user.uid).updateUserData(nickNamesofUser);
return _userFromFirebaseUser(user);
} catch (error) {
return null;
}
}
// sign out
Future signOut() async {
try {
return await _auth.signOut();
} catch (error) {
print(error.toString());
return null;
}
}
}
I hope this helps. Sir..............................................................................................................................................................

String nick,userID;
void initState() {
super.initState();
userID = widget.uid;
getNick();
print(userID);
}
void getNick() {
var users;
//get nickname of this User
Firestore.instance.collection('NickNames of Users').document(userID).get().then((data){
setState(() {
users = data;
nick = users['MyNickName'];
print(nick);
});
});
}

Related

Not saving the flutter switch value to sqflite database

I am a completely a beginner to sqlite and flutter. I was trying to create a local database to my flutter to do app. So I watched some youtube videos and started to implementing a database using flutter sqflite plugin. Everything worked fine because all I did was copy typing the you tubers code, until I had to add an extra parameter to the code which is a boolean, in order to track the status of the task (like done the task or not). I used an int value to save the bool, while sqlite does not supports boolean values. I used two functions, one to update the text and the other to update the switch value.
And secondly when I tap on a switch it triggers all the switches in the list. I want to solve that issue as well.
Model class for the Task
class Tasksdb {
final int? id;
final String taskName;
bool isDone;
Tasksdb({
this.id,
required this.taskName,
required this.isDone,
});
factory Tasksdb.fromMap(Map<String, dynamic> json) => Tasksdb(
id: json['id'],
taskName: json['taskName'],
isDone: (json['isDone'] as int) == 0 ? false : true);
Map<String, dynamic> toMap() {
return {
'id': id,
'taskName': taskName,
'isDone': isDone,
};
}
}
DatabaseHelper class
class DatabaseHelper {
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
static Database? _database;
Future<Database> get database async => _database ??= await _initDatabase();
Future<Database> _initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, 'tasks.db');
return await openDatabase(
path,
version: 1,
onCreate: _onCreate,
);
}
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS "taskstable" (
"id" INTEGER,
"taskName" TEXT,
"isDone" INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY("id" AUTOINCREMENT)
);
''');
}
Future<List<Tasksdb>> getTasks() async {
Database db = await instance.database;
var tasksQuery = await db.query(
'taskstable',
);
List<Tasksdb> taskList = tasksQuery.isNotEmpty
? tasksQuery.map((c) => Tasksdb.fromMap(c)).toList()
: [];
return taskList;
}
Future<int> add(Tasksdb task) async {
Database db = await instance.database;
return await db.insert('taskstable', {
'id': task.id,
'taskName': task.taskName,
'isDone': 0,
});
}
Future<int> update(Tasksdb task) async {
Database db = await instance.database;
return await db.update(
'taskstable',
task.toMap(),
where: "id = ?",
whereArgs: [task.id],
);
}
Future<int> updateIsDone(bool isDoneTodb) async {
Database db = await instance.database;
return await db.update(
'taskstable',
{
'isDone': isDoneTodb == true ? 1 : 0,
},
);
}
}
HomeScreen widget
class SqliteApp extends StatefulWidget {
const SqliteApp({Key? key}) : super(key: key);
#override
_SqliteAppState createState() => _SqliteAppState();
}
class _SqliteAppState extends State<SqliteApp> {
int? selectedId;
final textController = TextEditingController();
bool isDone = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: TextField(
controller: textController,
),
),
body: Center(
child: FutureBuilder<List<Tasksdb>>(
future: DatabaseHelper.instance.getTasks(),
builder: (BuildContext context,
AsyncSnapshot<List<Tasksdb>> snapshot) {
if (!snapshot.hasData) {
return const Center(child: Text('Loading...'));
}
return snapshot.data!.isEmpty
? const Center(child: Text('No tasks in List.'))
: ListView(
children: snapshot.data!.map((task) {
return Center(
child: Card(
color: selectedId == task.id
? Colors.green
: Colors.yellow,
child: ListTile(
trailing: Switch( //the problem is here, doesn't save to db
value: isDone,
onChanged: (val) async {
setState(() {
isDone = val;
});
await DatabaseHelper.instance
.updateIsDone(
isDone,
);
}),
title: Text(task.taskName),
onTap: () {
setState(() {
if (selectedId == null) {
textController.text = task.taskName;
selectedId = task.id;
} else {
textController.text = 'add something';
selectedId = null;
}
});
},
),
),
);
}).toList(),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await DatabaseHelper.instance.add(
Tasksdb(
taskName: textController.text,
isDone: false,
),
);
setState(() {
textController.clear();
selectedId = null;
});
},
),
),
);
}
}
I found the answer: (in case of if someone had the same question)
I removed the bool isDone from the material app widget, and instead of assigning the switch val to that bool I assigned it to database's task.isDone value. To avoid switch's auto trigger, I parsed the Taskdb to the updateIsDone function
Future<int> updateIsDone(Tasksdb task, bool isDoneTodb) async {
Database db = await instance.database;
return await db.update(
'taskstable',
{
'isDone': isDoneTodb == true ? 1 : 0,
},
where: "id = ?",
whereArgs: [task.id]);
}
...
Switch(
value: task.isDone,
onChanged: (val) async {
setState(() {
task.isDone = val;
});
await DatabaseHelper.instance
.updateIsDone(task, task.isDone);
});

Flutter initialize data into a table at startup

I can't seem to find how to write a constant data into a database to be loaded every start up. I want to write a set of data into the specializeArea table where it has an id column and area column as seen here from the DbHelper.dart and specializeModel.dart:
import 'package:conferenceApp/Model/UserModel.dart';
import 'package:conferenceApp/Model/specializeModel.dart';
import 'package:conferenceApp/Model/loginModel.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'dart:io' as io;
class DbHelper {
static Database conference;
static const String DB_Name = 'conference.db';
static const String Table_conferenceInfo = 'conferenceinfo';
static const String Table_specializeArea = 'specializearea';
static const String Table_login = 'login';
static const String C_UserID = 'id';
static const String C_Name = 'name';
static const String C_UName = 'username';
static const String C_Email = 'email';
static const String C_Phone = 'phone';
static const String C_Role = 'role';
static const String C_Area = 'area';
static const String C_Password = 'password';
static const int Version = 1;
Future<Database> get db async {
if (conference != null) {
return conference;
}
conference = await initDb();
return conference;
}
initDb() async {
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, DB_Name);
var db = await openDatabase(path, version: Version, onCreate: _onCreate);
return db;
}
_onCreate(Database db, int intVersion) async {
await db.execute("CREATE TABLE $Table_conferenceInfo ("
" $C_UserID INTEGER, "
" $C_Name TEXT, "
" $C_UName TEXT, "
" $C_Email TEXT,"
" $C_Password TEXT, "
" $C_Phone INTEGER, "
" $C_Role TEXT, "
" PRIMARY KEY ($C_UserID)"
")");
await db.execute("CREATE TABLE $Table_specializeArea ("
" $C_UserID INTEGER, "
" $C_Area TEXT )");
await db.execute("CREATE TABLE $Table_login ("
" $C_UserID INTEGER, "
" $C_UName TEXT, "
" $C_Password TEXT )");
}
Future<int> saveData(UserModel user) async {
var dbClient = await db;
var res = await dbClient.insert(Table_conferenceInfo, user.toMap());
return res;
}
Future<int> saveData1(specializeModel user) async {
var dbClient = await db;
var res1 = await dbClient.insert(Table_specializeArea, user.toMap());
return res1;
}
Future<int> saveData2(loginModel user) async {
var dbClient = await db;
var res2 = await dbClient.insert(Table_login, user.toMap());
return res2;
}
Future<UserModel> getLoginUser(String userId, String password) async {
var dbClient = await db;
var res =
await dbClient.rawQuery("SELECT * FROM $Table_conferenceInfo WHERE "
"$C_UName = '$userId' AND "
"$C_Password = '$password'");
if (res.length > 0) {
return UserModel.fromMap(res.first);
}
return null;
}
Future<specializeModel> getSubject(String subject) async {
var dbClient = await db;
var res =
await dbClient.rawQuery("SELECT * FROM $Table_specializeArea WHERE "
"$C_Area = '$subject'");
if (res.length > 0) {
return specializeModel.fromMap(res.first);
}
return null;
}
Future<int> updateUser(UserModel user) async {
var dbClient = await db;
var res = await dbClient.update(Table_conferenceInfo, user.toMap(),
where: '$C_UserID = ?', whereArgs: [user.user_id]);
return res;
}
Future<int> deleteUser(String user_id) async {
var dbClient = await db;
var res = await dbClient.delete(Table_conferenceInfo,
where: '$C_UserID = ?', whereArgs: [user_id]);
return res;
}
}
class specializeModel {
String user_id;
String user_area;
specializeModel(this.user_id, this.user_area);
Map<String, dynamic> toMap() {
var map = <String, dynamic>{
'id': user_id,
'user_area': user_area,
};
return map;
}
specializeModel.fromMap(Map<String, dynamic> map) {
user_id = map['id'];
user_area = map['user_area'];
}
#override
String toString() {
return 'specializeModel{id: $user_id, user_area: $user_area}';
}
}
The data I want to load is around 5 sets.
Here is the main page of the app, HomeForm.dart:
import 'package:conferenceApp/Model/specializeModel.dart';
import 'package:flutter/material.dart';
import 'package:conferenceApp/Comm/comHelper.dart';
import 'package:conferenceApp/Comm/genTextFormField.dart';
import 'package:conferenceApp/DatabaseHandler/DbHelper.dart';
import 'package:conferenceApp/Model/UserModel.dart';
import 'package:conferenceApp/Screens/LoginForm.dart';
import 'package:shared_preferences/shared_preferences.dart';
class HomeForm extends StatefulWidget {
#override
_HomeFormState createState() => _HomeFormState();
}
class _HomeFormState extends State<HomeForm> {
final _formKey = new GlobalKey<FormState>();
Future<SharedPreferences> _pref = SharedPreferences.getInstance();
DbHelper dbHelper;
final _conUserId = TextEditingController();
final _conName = TextEditingController();
final _conDelUserId = TextEditingController();
final _conUserName = TextEditingController();
final _conEmail = TextEditingController();
final _conPassword = TextEditingController();
final _conCPhone = TextEditingController();
final _conRole = TextEditingController();
#override
void initState() {
super.initState();
getUserData();
dbHelper = DbHelper();
}
Future<void> getUserData() async {
final SharedPreferences sp = await _pref;
setState(() {
_conUserId.text = sp.getString("id");
_conDelUserId.text = sp.getString("id");
_conName.text = sp.getString("name");
_conUserName.text = sp.getString("username");
_conEmail.text = sp.getString("email");
_conPassword.text = sp.getString("password");
_conCPhone.text = sp.getString("phone");
_conRole.text = sp.getString("role");
});
}
update() async {
String uid = _conUserId.text;
String Name = _conName.text;
String uname = _conUserName.text;
String email = _conEmail.text;
String passwd = _conPassword.text;
String phone = _conCPhone.text;
String role = _conRole.text;
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
UserModel user = UserModel(uid, Name, uname, email, passwd, phone, role);
await dbHelper.updateUser(user).then((value) {
if (value == 1) {
alertDialog(context, "Successfully Updated");
updateSP(user, true).whenComplete(() {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => LoginForm()),
(Route<dynamic> route) => false);
});
} else {
alertDialog(context, "Error Update");
}
}).catchError((error) {
print(error);
alertDialog(context, "Error");
});
}
}
delete() async {
String delUserID = _conDelUserId.text;
await dbHelper.deleteUser(delUserID).then((value) {
if (value == 1) {
alertDialog(context, "Successfully Deleted");
updateSP(null, false).whenComplete(() {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => LoginForm()),
(Route<dynamic> route) => false);
});
}
});
}
Future updateSP(UserModel user, bool add) async {
final SharedPreferences sp = await _pref;
if (add) {
sp.setString("username", user.user_name);
sp.setString("Name", user.Name);
sp.setString("email", user.email);
sp.setString("password", user.password);
sp.setString("phone", user.phone);
sp.setString("role", user.role);
} else {
sp.remove('id');
sp.remove('name');
sp.remove('username');
sp.remove('email');
sp.remove('password');
sp.remove('phone');
sp.remove('role');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Container(
margin: EdgeInsets.only(top: 20.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//Update
getTextFormField(
controller: _conUserId,
isEnable: false,
icon: Icons.person,
hintName: 'User ID'),
SizedBox(height: 10.0),
getTextFormField(
controller: _conName,
icon: Icons.person,
hintName: 'Name'),
SizedBox(height: 10.0),
getTextFormField(
controller: _conUserName,
icon: Icons.person_outline,
inputType: TextInputType.name,
hintName: 'Username'),
SizedBox(height: 10.0),
getTextFormField(
controller: _conEmail,
icon: Icons.email,
inputType: TextInputType.emailAddress,
hintName: 'Email'),
SizedBox(height: 10.0),
getTextFormField(
controller: _conPassword,
icon: Icons.lock,
hintName: 'Password',
isObscureText: true,
),
SizedBox(height: 10.0),
getTextFormField(
controller: _conCPhone,
icon: Icons.phone,
hintName: 'Phone',
),
SizedBox(height: 10.0),
getTextFormField(
controller: _conRole,
icon: Icons.remove_red_eye,
hintName: 'Role',
),
SizedBox(height: 10.0),
Container(
margin: EdgeInsets.all(30.0),
width: double.infinity,
child: FlatButton(
child: Text(
'Update',
style: TextStyle(color: Colors.white),
),
onPressed: update,
),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(30.0),
),
),
//Specialization area
//Delete
getTextFormField(
controller: _conDelUserId,
isEnable: false,
icon: Icons.person,
hintName: 'User ID'),
SizedBox(height: 10.0),
SizedBox(height: 10.0),
Container(
margin: EdgeInsets.all(30.0),
width: double.infinity,
child: FlatButton(
child: Text(
'Delete',
style: TextStyle(color: Colors.white),
),
onPressed: delete,
),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(30.0),
),
),
],
),
),
),
),
),
);
}
}
If possible, I would also like to display the data stored as buttons or interactables later but storing them comes first.
Thank you

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

"Warning database has been locked" warning with SQFlite and code stops. Why can I not query the table?

I'm having trouble querying a SQFlite db table in Flutter. I get the following warning several times:
I/chatty (32047): uid=10160(com.example.SQFLite_test) 1.ui identical 18498 lines 2
I/flutter (32047): Warning database has been locked for 0:00:10.000000. Make sure you always use the transaction
object for database operations during a transaction
I get the warning when calling the getClients() method to get all clients from the Client table. The main issue though is that it seems to freeze the code as well. Even when I only try to select the top 100, it still gives me the warning and it freezes and doesn't progress.
My db class helping me to init and manage the database:
class LogServiceTwo {
LogServiceTwo._();
static final LogServiceTwo logRepo = LogServiceTwo._();
static Database _database;
Future<Database> get database async {
if (_database != null) {
return _database;
}
var db = await openDb();
// if (db == null) {
// _database = await initDB();
// return _database;
// }
var hasClientTableB = await hasClientTable(db);
if (hasClientTableB) {
_database = db;
return _database;
}
// var backup = await restoreBackup(db);
// if (backup != null) {
// _database = backup;
// return _database;
// }
await createClientTable(db);
_database = db;
return _database;
}
Future createClientTable(Database db) async {
await db.execute("CREATE TABLE Client ("
"id INTEGER PRIMARY KEY,"
"first_name TEXT,"
"last_name TEXT,"
"blocked BIT"
")");
}
Future<bool> hasClientTable(Database db) async {
try {
var table = await db.query("Client");
return table != null;
} catch (e) {
return false;
}
}
Future<Database> openDb() async {
try {
var path = await getPersistentDbPath();
var db = await openDatabase(path, version: 1);
return db;
} catch (e) {
return null;
}
}
Future initDB() async {
var path = await getPersistentDbPath();
return await openDatabase(path, version: 1, onOpen: (db) {}, onCreate: (Database db, int version) async {
await db.execute("CREATE TABLE Client ("
"id INTEGER PRIMARY KEY,"
"first_name TEXT,"
"last_name TEXT,"
"blocked BIT"
")");
});
}
Future newClient(Client newClient) async {
final db = await database;
var res = await db.insert("Client", newClient.toMap());
return res;
}
Future newClients(List<Client> clients) async {
var clientMaps = clients.map((client) => client.toMap()).toList();
final db = await database;
clientMaps.forEach((clientMap) async {
await db.insert("Client", clientMap);
});
}
Future<Client> getClient(int id) async {
final db = await database;
var res = await db.query("Client", where: "id = ?", whereArgs: [id]);
return res.isNotEmpty ? Client.fromMap(res.first) : Null;
}
Future<List<Client>> getAllClients() async {
final db = await database;
var res = await db.query("Client");
List<Client> list = res.isNotEmpty ? res.map((c) => Client.fromMap(c)).toList() : [];
return list;
}
Future<List<Client>> getBlockedClients() async {
final db = await logRepo.database;
var res = await db.rawQuery("SELECT * FROM Client WHERE blocked=1");
List<Client> list = res.isNotEmpty ? res.toList().map((c) => Client.fromMap(c)) : null;
return list;
}
Future<List<String>> getTables() async {
var db = await logRepo.database;
var tableNames = (await db.query('sqlite_master', where: 'type = ?', whereArgs: ['table'])).map((row) => row['name'] as String).toList(growable: false);
return tableNames;
}
Future<String> getPersistentDbPath() async {
return await createPersistentDbDirecotry();
}
Future createPersistentDbDirecotry() async {
var externalDirectoryPath = await ExtStorage.getExternalStorageDirectory();
var persistentDirectory = "$externalDirectoryPath/db_persistent";
await createDirectory(persistentDirectory);
return "$persistentDirectory/persistent.db";
}
Future createDirectory(String path) async {
await (new Directory(path).create());
}
Future<bool> askForWritePermission() async {
var status = await Permission.storage.status;
if (!status.isGranted) {
status = await Permission.storage.request();
return status.isGranted;
}
return status.isGranted;
}
Future mockData() async {
var clients = ClientMocker.createClients();
await newClients(clients);
}
Future deleteAll() async {
var db = await database;
await db.rawDelete("DELETE FROM Client");
}
// Get all clients, throws warnings and stops proceeding in the code.
Future getClients() async {
try {
var db = await database;
return await db.rawQuery("SELECT * FROM Client");
} catch (e) {
print(e);
}
}
}
My main class calling the database service for testing purposes:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
// This makes the visual density adapt to the platform that you run
// the app on. For desktop platforms, the controls will be smaller and
// closer together (more dense) than on mobile platforms.
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
// firstTest();
// secondTest();
thirdTest();
testText = "";
super.initState();
}
String testText;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
children: [
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Text(testText),
)
],
),
),
);
}
Future firstTest() async {
testText = "";
// var client = new Client(blocked: false, firstName: "Tobias", lastName: "Eliasson", id: null);
// await LogRepository.logRepo.newClient(client);
// await LogRepository.logRepo.newClient(client);
// await LogRepository.logRepo.newClient(client);
var clients = await LogRepository.logRepo.getAllClients();
clients.forEach((c) {
setState(() {
testText += "\n${c.toMap()}";
});
print(c.toMap());
});
setState(() {
testText += "Length of found clients: ${clients.length.toString()}";
});
var success = await LogRepository.logRepo.saveBackup();
print(success);
}
Future secondTest() async {
try {
await LogRepository.logRepo.deleteAll();
await LogRepository.logRepo.mockData();
var a = DateTime.now();
print("Saving backup $a");
var backupSuccess = await LogRepository.logRepo.saveBackup();
print("Backup success: $backupSuccess");
var b = DateTime.now();
print("Saved backup:${a.difference(b)}");
} catch (e) {
print("Error!!!");
print(e);
}
}
Future thirdTest() async {
await LogServiceTwo.logRepo.database;
await LogServiceTwo.logRepo.mockData();
var clients = await LogServiceTwo.logRepo.getClients();
print(clients.length);
}
}
As far as I can see, I await all db operations and only use on db object to access it so there shouldn't be any weird parallell access going on. Maybe you find an error somewhere I'm missing though. In case you wonder why I create the database in external memory, is that the database needs to be persistent and saved when uninstalling the app or updating it.
Thanks!
I found the problem and learnt a lesson. In short, follow conventions and best practices when accessing a database. The problem was that I inserted one client at the time inside the method:
Future newClients(List<Client> clients) async {
var clientMaps = clients.map((client) => client.toMap()).toList();
final db = await database;
clientMaps.forEach((clientMap) async {
await db.insert("Client", clientMap);
});
}
When inserting or doing many db operations at the same moment, use batch and commit instead like this:
Future newClients(List<Client> clients) async {
var clientMaps = clients.map((client) => client.toMap()).toList();
final db = await database;
var batch = db.batch();
clientMaps.forEach((clientMap) async {
batch.insert("Client", clientMap);
});
await batch.commit(noResult: true);
}
With the batch and commit solution I could insert 90 000 clients on first try with no errors and could query them all after.

Flutter: Change state depending on validation

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.

Resources