Check if Field Already exists in Flutter Firestore - firebase

I have a collection called company.
All the companies are going to be stored like in my screenshot.
When I add another company, I want to check if the name already exists or not.
How to perform that?
Here, "Nova" and "Tradetech" are two companies.
When I try to add "Nova" with the field name: "nova" again, I want to show a notice: "Company already exists!".

I have solved this issue with the follwoing code, thanks for helping me!
IN THE FOLLOWING CODE I USED TO FIND
1)A DOCUMENT IS EXISTING OR NOT?
2)A KEY IS EXISTING OR NOT?
3)A VALUE IS EXISTING OR NOT?
SIMPLE METHOD
//////////////////////////////////////////////////////////////////////
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
String myText1 = 'temp1';
String myText2 = 'temp2';
String myText3 = 'temp3';
String myText4 = 'temp4';
String myText5 = 'temp5';
String myText6 = 'temp6';
StreamSubscription<DocumentSnapshot> subscription;
final DocumentReference documentReference =
Firestore.instance.document("company/Nova");
class Clean extends StatefulWidget {
#override
_CleanState createState() => _CleanState();
}
class _CleanState extends State<Clean> {
#override
void initState() {
super.initState();
subscription = documentReference.snapshots().listen((datasnapshot) {
//FINDING A SPECIFICDOCUMENT IS EXISTING INSIDE A COLLECTION
if (datasnapshot.exists) {
setState(() {
myText1 = "Document exist";
});
} else if (!datasnapshot.exists) {
setState(() {
myText2 = "Document not exist";
});
}
//FINDING A SPECIFIC KEY IS EXISTING INSIDE A DOCUMENT
if (datasnapshot.data.containsKey("name")) {
setState(() {
myText3 = "key exists";
});
} else if (!datasnapshot.data.containsKey("name")) {
setState(() {
myText4 = "key not exists";
});
}
//FINDING A SPECIFIC VALUE IS EXISTING INSIDE A DOCUMENT
if (datasnapshot.data.containsValue("nova")) {
setState(() {
myText5 = "value exists";
});
} else if (!datasnapshot.data.containsValue("nova")) {
setState(() {
myText6 = "value not exists";
});
}
});
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
new Text(myText1),
new Text(myText2),
new Text(myText3),
new Text(myText4),
new Text(myText5),
new Text(myText6),
],
);
}
}
MY OLD COMPLEX METHOD BASED ON MY EXISTING CODE
////////////////////////////////////////////////////////
Concept
it has a search bar,when you type it will show the company name ie existing or not in
A Card and a RaisedButton. I am using lower case in Firestore in order to avoid the search error. I have forced the TextFormField output to be lower case with toLowercase(). You can change it to your own text format.
Code
//if the name is not existing it will show a raised button so u can clcik on that to
//go to a COMPANY ADDING PAGE,otherwise it will only show a **CARD** so that you
//can't go to the next page to add your company
//code:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
import './fullscreen.dart';
const blue = 0xFF3b78e7;
String filter = '';
StreamSubscription<DocumentSnapshot> subscription;
final TextEditingController _usercontroller = new TextEditingController();
class CheckAvail extends StatefulWidget {
#override
HomeState createState() => HomeState();
}
class HomeState extends State<CheckAvail> {
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
// CHILD1
new Flexible(
child: StreamBuilder(
stream: Firestore.instance
.collection('company')
.where('name', isGreaterThanOrEqualTo: filter.toLowerCase())
.limit(1)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return new Column(
children: <Widget>[
new Card(
elevation: 5.0,
child: new Image.asset('assets/progress.gif'),
)
],
);
} else {
return FirestoreListView1(documents: snapshot.data.documents);
}
},
),
),
new Card(
elevation: 0.0,
color: Colors.white,
shape: new RoundedRectangleBorder(
borderRadius: BorderRadius.circular(60.0)),
child: Container(
padding: new EdgeInsets.only(left: 8.0),
child: new TextField(
controller: _usercontroller,
onChanged: (String z) {
setState(() {
filter = z;
});
},
decoration: const InputDecoration(
hintText: "Search...",
hintStyle: TextStyle(
fontFamily: 'roboto',
color: Colors.black38,
fontSize: 16.0,
letterSpacing: -0.500),
fillColor: Colors.white,
border: InputBorder.none,
),
),
),
),
],
),
backgroundColor: Color(blue),
);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class FirestoreListView1 extends StatelessWidget {
final List<DocumentSnapshot> documents;
FirestoreListView1({this.documents});
#override
Widget build(BuildContext context1) {
return ListView.builder(
itemCount: documents.length,
padding: new EdgeInsets.all(1.0),
itemBuilder: (BuildContext context1, int index) {
String name = documents[index].data['name'];
if (name.contains(filter.toLowerCase()) &&
name.length == filter.length) {
return new Container(
padding: new EdgeInsets.only(top: 45.0),
child: new Card(
child: new Text(
"Error:Already a Company Exists with this name\nTry another name")),
);
} else {
return (filter.length >= 1)
? new Container(
padding: new EdgeInsets.only(top: 15.0),
child: new RaisedButton(
onPressed: () => Navigator.push(
context1,
new MaterialPageRoute(
builder: (context1) => new NextPage(
value1: name,
))),
disabledColor: Colors.white,
child: new Text(
"Good!You can use this company name",
),
),
)
: new Container(padding: new EdgeInsets.only(top: 250.0),
child: new Card(child: new Text("CHECK IF YOUR COMPANY NAME \n AVAILABLE OR NOT",style: new TextStyle(fontSize: 20.0),)),
);
}
});
}
}

You can simply use a where query to only receive documents that have that name and then check whether you get documents. Here is an async example method that would perform what you want to know.
Example method
Future<bool> doesNameAlreadyExist(String name) async {
final QuerySnapshot result = await Firestore.instance
.collection('company')
.where('name', isEqualTo: name)
.limit(1)
.getDocuments();
final List<DocumentSnapshot> documents = result.documents;
return documents.length == 1;
}
As you can see, I am only receiving documents, where the name field matches the given name. I also add limit(1) to make sure that I do not unnecessarily retrieve more than 1 document (which would never happen in theory) and then I just check if the length of all documents in the company collection is equal to 1 or not. If it is equal to 1, there already is a company that has that name and otherwise not.
You could also remove the limit(1) and make the check documents.length > 1 and that would work too, but might retrieve unnecessary documents.
Example implementation
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: doesNameAlreadyExist('nova'),
builder: (context, AsyncSnapshot<bool> result) {
if (!result.hasData)
return Container(); // future still needs to be finished (loading)
if (result.data) // result.data is the returned bool from doesNameAlreadyExists
return Text('A company called "Nova" already exists.');
else
return Text('No company called "Nova" exists yet.');
},
);
}
Here, I am not displaying an error message, which would be easily possible with the example method as well. However, the build method of some widget is used. This would e.g. work in a dialog, but I decided to do it to keep it simple and understandable. The FutureBuilder takes in doesNameAlreadyExist, in this case with the name "Nova" from your question and will, in the end, return a Text widget stating whether the name already exists.
Be careful
The where query is case-sensitive. This means that the check would not work if you typed e.g. "noVa" instead of "nova". As this might be important to you, you can make use of this nice method, where you would create an extra field that is insensitive, e.g. all letters are small and then you would simple query like this:
.where('name_insensitive', isEqualTo: name.toLowerCase())

final QuerySnapshot result =
await Firestore.instance.collection('users').where('nickname', isEqualTo:
nickname).getDocuments();
final List < DocumentSnapshot > documents = result.documents;
if (documents.length > 0) {
//exists
} else {
//not exists
}

Use the function:
snapshot.data!.data()!.containsKey('key_name')
to check if a field exists in your document.
PS. I just used this in my code RN and it works

I know I am late.
Posting for future users.
Try this:
DocumentReference datab = db.collection("Company").document("Nova");
datab.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if(documentSnapshot.contains("name"))
{
Toast.makeText(YourActivity.this, "Child exixts.", Toast.LENGTH_SHORT).show();
}
else
Toast.makeText(YourActivity.this, "Doesnt exits.", Toast.LENGTH_SHORT).show();
}
});

Related

How can I check if value exists in Firebase using Flutter? [duplicate]

I have a collection called company.
All the companies are going to be stored like in my screenshot.
When I add another company, I want to check if the name already exists or not.
How to perform that?
Here, "Nova" and "Tradetech" are two companies.
When I try to add "Nova" with the field name: "nova" again, I want to show a notice: "Company already exists!".
I have solved this issue with the follwoing code, thanks for helping me!
IN THE FOLLOWING CODE I USED TO FIND
1)A DOCUMENT IS EXISTING OR NOT?
2)A KEY IS EXISTING OR NOT?
3)A VALUE IS EXISTING OR NOT?
SIMPLE METHOD
//////////////////////////////////////////////////////////////////////
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
String myText1 = 'temp1';
String myText2 = 'temp2';
String myText3 = 'temp3';
String myText4 = 'temp4';
String myText5 = 'temp5';
String myText6 = 'temp6';
StreamSubscription<DocumentSnapshot> subscription;
final DocumentReference documentReference =
Firestore.instance.document("company/Nova");
class Clean extends StatefulWidget {
#override
_CleanState createState() => _CleanState();
}
class _CleanState extends State<Clean> {
#override
void initState() {
super.initState();
subscription = documentReference.snapshots().listen((datasnapshot) {
//FINDING A SPECIFICDOCUMENT IS EXISTING INSIDE A COLLECTION
if (datasnapshot.exists) {
setState(() {
myText1 = "Document exist";
});
} else if (!datasnapshot.exists) {
setState(() {
myText2 = "Document not exist";
});
}
//FINDING A SPECIFIC KEY IS EXISTING INSIDE A DOCUMENT
if (datasnapshot.data.containsKey("name")) {
setState(() {
myText3 = "key exists";
});
} else if (!datasnapshot.data.containsKey("name")) {
setState(() {
myText4 = "key not exists";
});
}
//FINDING A SPECIFIC VALUE IS EXISTING INSIDE A DOCUMENT
if (datasnapshot.data.containsValue("nova")) {
setState(() {
myText5 = "value exists";
});
} else if (!datasnapshot.data.containsValue("nova")) {
setState(() {
myText6 = "value not exists";
});
}
});
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
new Text(myText1),
new Text(myText2),
new Text(myText3),
new Text(myText4),
new Text(myText5),
new Text(myText6),
],
);
}
}
MY OLD COMPLEX METHOD BASED ON MY EXISTING CODE
////////////////////////////////////////////////////////
Concept
it has a search bar,when you type it will show the company name ie existing or not in
A Card and a RaisedButton. I am using lower case in Firestore in order to avoid the search error. I have forced the TextFormField output to be lower case with toLowercase(). You can change it to your own text format.
Code
//if the name is not existing it will show a raised button so u can clcik on that to
//go to a COMPANY ADDING PAGE,otherwise it will only show a **CARD** so that you
//can't go to the next page to add your company
//code:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
import './fullscreen.dart';
const blue = 0xFF3b78e7;
String filter = '';
StreamSubscription<DocumentSnapshot> subscription;
final TextEditingController _usercontroller = new TextEditingController();
class CheckAvail extends StatefulWidget {
#override
HomeState createState() => HomeState();
}
class HomeState extends State<CheckAvail> {
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
// CHILD1
new Flexible(
child: StreamBuilder(
stream: Firestore.instance
.collection('company')
.where('name', isGreaterThanOrEqualTo: filter.toLowerCase())
.limit(1)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return new Column(
children: <Widget>[
new Card(
elevation: 5.0,
child: new Image.asset('assets/progress.gif'),
)
],
);
} else {
return FirestoreListView1(documents: snapshot.data.documents);
}
},
),
),
new Card(
elevation: 0.0,
color: Colors.white,
shape: new RoundedRectangleBorder(
borderRadius: BorderRadius.circular(60.0)),
child: Container(
padding: new EdgeInsets.only(left: 8.0),
child: new TextField(
controller: _usercontroller,
onChanged: (String z) {
setState(() {
filter = z;
});
},
decoration: const InputDecoration(
hintText: "Search...",
hintStyle: TextStyle(
fontFamily: 'roboto',
color: Colors.black38,
fontSize: 16.0,
letterSpacing: -0.500),
fillColor: Colors.white,
border: InputBorder.none,
),
),
),
),
],
),
backgroundColor: Color(blue),
);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class FirestoreListView1 extends StatelessWidget {
final List<DocumentSnapshot> documents;
FirestoreListView1({this.documents});
#override
Widget build(BuildContext context1) {
return ListView.builder(
itemCount: documents.length,
padding: new EdgeInsets.all(1.0),
itemBuilder: (BuildContext context1, int index) {
String name = documents[index].data['name'];
if (name.contains(filter.toLowerCase()) &&
name.length == filter.length) {
return new Container(
padding: new EdgeInsets.only(top: 45.0),
child: new Card(
child: new Text(
"Error:Already a Company Exists with this name\nTry another name")),
);
} else {
return (filter.length >= 1)
? new Container(
padding: new EdgeInsets.only(top: 15.0),
child: new RaisedButton(
onPressed: () => Navigator.push(
context1,
new MaterialPageRoute(
builder: (context1) => new NextPage(
value1: name,
))),
disabledColor: Colors.white,
child: new Text(
"Good!You can use this company name",
),
),
)
: new Container(padding: new EdgeInsets.only(top: 250.0),
child: new Card(child: new Text("CHECK IF YOUR COMPANY NAME \n AVAILABLE OR NOT",style: new TextStyle(fontSize: 20.0),)),
);
}
});
}
}
You can simply use a where query to only receive documents that have that name and then check whether you get documents. Here is an async example method that would perform what you want to know.
Example method
Future<bool> doesNameAlreadyExist(String name) async {
final QuerySnapshot result = await Firestore.instance
.collection('company')
.where('name', isEqualTo: name)
.limit(1)
.getDocuments();
final List<DocumentSnapshot> documents = result.documents;
return documents.length == 1;
}
As you can see, I am only receiving documents, where the name field matches the given name. I also add limit(1) to make sure that I do not unnecessarily retrieve more than 1 document (which would never happen in theory) and then I just check if the length of all documents in the company collection is equal to 1 or not. If it is equal to 1, there already is a company that has that name and otherwise not.
You could also remove the limit(1) and make the check documents.length > 1 and that would work too, but might retrieve unnecessary documents.
Example implementation
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: doesNameAlreadyExist('nova'),
builder: (context, AsyncSnapshot<bool> result) {
if (!result.hasData)
return Container(); // future still needs to be finished (loading)
if (result.data) // result.data is the returned bool from doesNameAlreadyExists
return Text('A company called "Nova" already exists.');
else
return Text('No company called "Nova" exists yet.');
},
);
}
Here, I am not displaying an error message, which would be easily possible with the example method as well. However, the build method of some widget is used. This would e.g. work in a dialog, but I decided to do it to keep it simple and understandable. The FutureBuilder takes in doesNameAlreadyExist, in this case with the name "Nova" from your question and will, in the end, return a Text widget stating whether the name already exists.
Be careful
The where query is case-sensitive. This means that the check would not work if you typed e.g. "noVa" instead of "nova". As this might be important to you, you can make use of this nice method, where you would create an extra field that is insensitive, e.g. all letters are small and then you would simple query like this:
.where('name_insensitive', isEqualTo: name.toLowerCase())
final QuerySnapshot result =
await Firestore.instance.collection('users').where('nickname', isEqualTo:
nickname).getDocuments();
final List < DocumentSnapshot > documents = result.documents;
if (documents.length > 0) {
//exists
} else {
//not exists
}
Use the function:
snapshot.data!.data()!.containsKey('key_name')
to check if a field exists in your document.
PS. I just used this in my code RN and it works
I know I am late.
Posting for future users.
Try this:
DocumentReference datab = db.collection("Company").document("Nova");
datab.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if(documentSnapshot.contains("name"))
{
Toast.makeText(YourActivity.this, "Child exixts.", Toast.LENGTH_SHORT).show();
}
else
Toast.makeText(YourActivity.this, "Doesnt exits.", Toast.LENGTH_SHORT).show();
}
});

Can't get actual String download url from Firebase Storage and only returns Instance of 'Future<String>' even using async/await

I am trying to get user avatar from firebase storage, however, my current code only returns Instance of 'Future<String>' even I am using async/await as below. How is it possible to get actual download URL as String, rather Instance of Future so I can access the data from CachedNewtworkImage?
this is the function that calls getAvatarDownloadUrl with current passed firebase user instance.
myViewModel
FutureOr<String> getAvatarUrl(User user) async {
var snapshot = await _ref
.read(firebaseStoreRepositoryProvider)
.getAvatarDownloadUrl(user.code);
if (snapshot != null) {
print("avatar url: $snapshot");
}
return snapshot;
}
getAvatarURL is basically first calling firebase firestore reference then try to access to the downloadURL, if there is no user data, simply returns null.
Future<String> getAvatarDownloadUrl(String code) async {
Reference _ref =
storage.ref().child("users").child(code).child("asset.jpeg");
try {
String url = await _ref.getDownloadURL();
return url;
} on FirebaseException catch (e) {
print(e.code);
return null;
}
}
I am calling these function from HookWidget called ShowAvatar.
To show current user avatar, I use useProvider and useFuture to actually use the data from the database, and this code works with no problem.
However, once I want to get downloardURL from list of users (inside of ListView using index),
class ShowAvatar extends HookWidget {
// some constructors...
#override
Widget build(BuildContext context) {
// get firebase user instance
final user = useProvider(accountProvider.state).user;
// get user avatar data as Future<String>
final userLogo = useProvider(firebaseStoreRepositoryProvider)
.getAvatarDownloadUrl(user.code);
// get actual user data as String
final snapshot = useFuture(userLogo);
// to access above functions inside of ListView
final viewModel = useProvider(myViewModel);
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Container(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 100,
width: 100,
child: Avatar(
avatarUrl: snapshot.data, // **this avatar works!!!** so useProvider & useFuture is working
),
),
SizedBox(height: 32),
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Center(
child: Column(
children: [
SizedBox(
height: 100,
width: 100,
child: Avatar(
avatarUrl: viewModel
.getAvatarUrl(goldWinners[index].user)
.toString(), // ** this avatar data is not String but Instance of Future<String>
),
),
),
],
),
);
},
itemCount: goldWinners.length,
),
Avatar() is simple statelesswidget which returns ClipRRect if avatarURL is not existed (null), it returns simplace placeholder otherwise returns user avatar that we just get from firebase storage.
However, since users from ListView's avatarUrl is Instance of Future<String> I can't correctly show user avatar.
I tried to convert the instance to String multiple times by adding .toString(), but it didn't work.
class Avatar extends StatelessWidget {
final String avatarUrl;
final double radius;
final BoxFit fit;
Avatar({Key key, this.avatarUrl, this.radius = 16, this.fit})
: super(key: key);
#override
Widget build(BuildContext context) {
print('this is avatar url : ' + avatarUrl.toString());
return avatarUrl == null
? ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: Image.asset(
"assets/images/avatar_placeholder.png",
fit: fit,
),
)
: ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: CachedNetworkImage(
imageUrl: avatarUrl.toString(),
placeholder: (_, url) => Skeleton(radius: radius),
errorWidget: (_, url, error) => Icon(Icons.error),
fit: fit,
));
}
}
Since the download URL is asynchronously determined, it is returned as Future<String> from your getAvatarUrl method. To display a value from a Future, use a FutureBuilder widget like this:
child: FutureBuilder<String>(
future: viewModel.getAvatarUrl(goldWinners[index].user),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return snapshot.hashData
? Avatar(avatarUrl: snapshot.data)
: Text("Loading URL...")
}
)
Frank actually you gave an good start but there are some improvements we can do to handle the errors properly,
new FutureBuilder(
future: //future you need to pass,
builder: (context, snapshot) {
if (snapshot.hasData) {
return new ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, i) {
DocumentSnapshot ds = snapshot.data.docs[i];
return //the data you need to return using /*ds.data()['field value of doc']*/
});
} else if (snapshot.hasError) {
// Handle the error and stop rendering
GToast(
message:
'Error while fetching data : ${snapshot.error}',
type: true)
.toast();
return new Center(
child: new CircularProgressIndicator(),
);
} else {
// Wait for the data to fecth
return new Center(
child: new CircularProgressIndicator(),
);
}
}),
Now if you are using a text widget as a return statement in case of errors it will be rendered forever. Incase of Progress Indicators, you will exactly know if it is an error it will show the progress indicator and then stop the widget rendering.
else if (snapshot.hasError) {
}
else {
}
above statement renders until, if there is an error or the builder finished fetching the results and ready to show the result widget.

Flutter FutureBuilder not building with data from sqlite

I'm very new in flutter, and are trying to write the app that will take value from sqlite. From what I tried, it needs to use FutureBuilder widget.
But the following code I wrote, the FutureBuilder widget seems to get data from sqlite, but the "builder" property was never called:
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutterapp/dbHelper.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes:{
"/": (context) =>Test()
}
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
bool nameCheck = false; // Use to check name textfield has correctly be inputed
TextEditingController nameController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children: <Widget>[
SizedBox(height:300),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: nameController,
)
),
IconButton(
icon: Icon(Icons.check_circle),
onPressed:(){
return FutureBuilder(
future: getData(nameController.text),
builder: (BuildContext context, AsyncSnapshot<List<Map>>snapshot) {
print("Start future"); // never get printed
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
Builder(
builder: (BuildContext context) {
print("got data");
final result = snapshot.data;
print(result) ; // never get printed
setState(() {
nameCheck = true;
});
return Container();
})
];
} else {
children = <Widget>[
AlertDialog(
content: SpinKitCircle(
color: Colors.white,
size: 80.0,
))
];
}
return Center(
child: Container(
color: Colors.blue,
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
crossAxisAlignment:
CrossAxisAlignment
.center,
children: children,
)));
});}
)
],
),
Builder(
builder: (BuildContext context){
if (nameCheck == true){
return Text("test");
}
return Container();
}
)
],
)
);
}
}
Future<List<Map>> getData(String input_name) async{
final dbHelper = DBHelper.instance;
await dbHelper.database;
final result = await dbHelper.query("SELECT * FROM Guest WHERE Name = \"$input_name\"");
print(result); // This get printed
return result;
}
The DBHelper code is as follow, basically it just set up a sqlite database and some database operation:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
class DBHelper {
static final _databaseName = "MonetaryDB.db";
static final _databaseVersion = 1;
static final create_table_Test = "CREATE TABLE \"Guest\" (\"Name\" TEXT NOT NULL PRIMARY KEY, \"Money\" INTEGER, \"Person\" INTEGER)";
static final String insert_guest = "INSERT INTO Guest (Name, Money, Person) VALUES (\"testname\", 1000, 1)";
DBHelper._privateConstructor();
static final DBHelper instance = DBHelper._privateConstructor();
static Database _database;
Future<Database> get database async {
if (_database != null) {
return _database;}
else{
_database = await _initDatabase();
return _database;}
}
_initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
return await openDatabase(path,
version: _databaseVersion,
onCreate: _onCreate,
);
}
Future _onCreate(Database db, int version) async {
await db.execute(create_table_Test);
await db.rawInsert(insert_guest);
}
Future<int> insert(String statement) async {
Database db = await instance.database;
return await db.rawInsert(statement);
}
Future<List<Map>> query(String statement) async {
Database db = await instance.database;
return await db.rawQuery(statement);
}
Future<int> update(String statement) async{
Database db = await instance.database;
return await db.rawUpdate(statement);
}
Future<int> delete(String statement) async{
Database db = await instance.database;
return db.rawDelete(statement);
}
}
If I simply change the IconButton onPressed function into setState((){nameCheck = true}), The Text("test") widget will show, so the problem must be the FutureBuilder. Also, the getData() function can get the correct result from the sqlite database
I have no idea why the FutureBuilder doesn't get build, did someone have any idea of it?
Thanks!
So with the help in the comment section, I change the code and it worked:
...
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children: <Widget>[
SizedBox(height:300),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: nameController,
)
),
IconButton(
icon: Icon(Icons.check_circle),
onPressed:(){
return showDialog(
context: context,
builder: (BuildContext context){
return AlertDialog(
actions: <Widget>[
SizedBox(
width: 300,
height: 300,
child: FutureBuilder(
future: getData(nameController.text),
builder: (BuildContext context, AsyncSnapshot<List<Map>>snapshot) {
if (snapshot.hasData) {
final result = snapshot.data;
SchedulerBinding.instance.addPostFrameCallback((_) => setState(() {
nameCheck = true;
}));
Navigator.pop(context, true);
}
else {
return Container(
child: SpinKitCircle(
color: Colors.white,
size: 80.0,
));
}
return Container(color: Colors.blue);
})
),
],
);
}
);
}
)
],
),
Builder(
builder: (BuildContext context){
if (nameCheck == true){
return Text("test");
}
return Container();
}
)
],
)
);
}
...
The other codes are still the same.
Like the comment section above had suggested, the main problem is that there will be no place to build for the widget the FutureBuilder that is going to built. To solve this problem, I place the FutureBuilder widget into the AlertDialog widget, since that I still want to keep the SpinKitCircle widget when loading.
I also gave up the Column widget at the end of the FutureBuilder widget, and deleted the Builder widget at the beginning of the FutureBuilder widget, which it was no longer needed when there was no Column.
The above codes still throw an acceptable exception:"setState() or markNeedsBuild() called during build." But the whole things still can run, so I will try to fix that the other day.
Thanks for the suggestion in the comment section.

Search database (SQLite) using a textform in Flutter

I am trying to search my sqlite database, right now it returns all the members, even when text is input to the text form. I have a ListView builder in the memberList constructor that creates cards for each member. What I want it to do is display just the cards that match the users input.
i.e. if a user inputs J it would show only the members that either have first or last name with the letters J.
I can see the query is working properly as I have it printing the count in the dbHelper class and it updates each time I make a change to the textform's text. What I need it to do is essentially refresh the body of the Scaffold onChange of the textform's text, which is not working.
Any suggestions on how I can do this?
I prefer to have the textform in the appbar if at all possible.
Below is my code:
import 'package:flutter/material.dart';
import 'package:troop_mobile_app/MemberFiles/Member.dart';
import 'package:troop_mobile_app/MemberFiles/MemberList.dart';
import 'package:troop_mobile_app/DatabaseFiles/DBHelper.dart';
Future<List<Member>> search(String search) async {
var dbHelper = DBHelper();
Future<List<Member>> members = dbHelper.searchScouts(search);
return members;
}
class SearchFunction extends StatefulWidget {
#override
_SearchFunctionState createState() => _SearchFunctionState();
}
class _SearchFunctionState extends State<SearchFunction> {
TextEditingController controller = TextEditingController();
String searchText = "";
_searchResults(String text) {
return new FutureBuilder<List<Member>>(
future: search(text),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MemberList(snapshot.data);
}
return Container(
alignment: AlignmentDirectional.center,
child: new CircularProgressIndicator(
strokeWidth: 7,
));
});
}
Widget build(BuildContext context) {
//Page Creation returning the UI Home Page Display
return Scaffold(
//Top 'Menu Bar' (AppBar) Creation
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
padding: EdgeInsets.fromLTRB(
20 /*left*/, 0 /*top*/, 20 /*right*/, 0 /*bottom*/),
),
title: TextField(
//initialValue: 'Search...',
style: TextStyle(color: Colors.black),
decoration: InputDecoration(
//fillColor: Colors.white,
//filled: true,
//border:
//OutlineInputBorder(borderRadius: BorderRadius.circular(12.0)),
labelText: 'Search...',
contentPadding: EdgeInsets.fromLTRB(10, 6, 0, 6),
prefixIcon: Icon(Icons.search),
),
onChanged: (text) async {
_searchResults(text);
searchText = text;
},
controller: controller,
),
),
//End Top 'Menu Bar' Creation
//Main Body Creation
body: Container(
child: new FutureBuilder<List<Member>> (
future: search(searchText),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MemberList(snapshot.data);
}
return Container(
alignment: AlignmentDirectional.center,
child: new CircularProgressIndicator(
strokeWidth: 7,
));
}),
)
//End Main Body Creation
);
}
}
MemberList:
import 'package:flutter/material.dart';
import 'MemberCards.dart';
import 'package:troop_mobile_app/MemberFiles/Member.dart';
class MemberList extends StatelessWidget {
final List<Member> members;
MemberList(this.members);
#override
Widget build(BuildContext context) {
return _buildList(context);
}
ListView _buildList(context) {
return ListView.builder(
itemCount: members.length,
itemBuilder: (context, int) {
return MemberCards(members[int], );
},
);
}
}
DBHelper:
Future<List<Map<String, dynamic>>> searchScoutsMap(String search) async {
Database db = await this.database;
print("This works? $db");
var result = await db.rawQuery("SELECT * FROM $memberTable WHERE adult = 'N' AND ($colFirstName Like '%$search%' OR $colLastName Like '%$search%') ORDER BY $colFirstName ASC, $colLastName ASC");
print("result is working? $result");
print(result.length);
return result;
}
Future<List<Member>> searchScouts(String search) async {
var searchResults = await searchScoutsMap(search); // Get 'Map List' from database
print(searchResults.length);
print(searchResults.toString());
int count = searchResults.length; // Count the number of map entries in db table
List<Member> memberList = List<Member>();
// For loop to create a 'Member List' from a 'Map List'
for (int i = 0; i < count; i++) {
print("for loop working: ${i+1}");
memberList.add(Member.fromMapObject(searchResults[i]));
}
print("completed for loop");
return memberList;
}
I was able to solve my mistake after hours of frustrating work...
Here is was my fix:
In the first code snippet I was missing the setState()
I had to wrap the return new FutureBuilder... with setState()
_searchResults(String text) {
return new FutureBuilder<List<Member>>(
future: search(text),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MemberList(snapshot.data);
}
return Container(
alignment: AlignmentDirectional.center,
child: new CircularProgressIndicator(
strokeWidth: 7,
));
});
}
New code snippet shown below:
I hope this helps anyone else out there that runs into a similar issue.
_searchResults(String text) {
setState(() {
return new FutureBuilder<List<Member>>(
future: search(text),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MemberList(snapshot.data);
}
return Container(
alignment: AlignmentDirectional.center,
child: new CircularProgressIndicator(
strokeWidth: 7,
));
});
});
}

Getter uid being called on null in flutter fire

I am trying to concatenate two UID's in order to create a chatroom. One uid is being read from firebase while the other is read from the FirebaseAuth.instance.
The clientUID is being assigned as it should, as I am passing it to another page on a Text widget. However the chatroom is not being created in the firestore tree so I assume this should be because of the instructor uid.
Maybe I am not calling the FirebaseAuth.instance as it should?
Code:
class ClientiPage extends StatefulWidget {
static const String id = 'CLIENTI';
#override
_ClientiPageState createState() => _ClientiPageState();
}
class _ClientiPageState extends State<ClientiPage> {
String chatRoomID;
String clientUID;
Firestore db = Firestore.instance;
String instructor;
void getInstructorId() async {
instructor = (await FirebaseAuth.instance.currentUser()).uid;
}
void saveChatRoom() {
getInstructorId();
DocumentReference chatroomIdRef = db.collection('instructori').document(instructor).collection("chatrooms").document(chatRoomID);
if (chatroomIdRef == null) {
db.collection('instructori').document(instructor).collection("chatrooms").document(chatRoomID);
}
}
void createChatRoom() {
getInstructorId();
chatRoomID = clientUID + instructor;
if(chatRoomID != null) {
saveChatRoom();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatPage(
chatRoomID: chatRoomID,
clientUID: clientUID,
),
),
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: db.collection('clienti').snapshots(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
clientUID = snapshot.data.documents[index]["UID"];
return Column(
children: <Widget>[
Divider(
height: 10.0,
),
new ListTile(
onTap: createChatRoom,
title: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(
snapshot.data.documents[index]["numar_telefon"],
style: new TextStyle(
fontWeight: FontWeight.bold,
),
),
],
),
),
],
);
},
);
}
},
),
);
}
}
Error
The getter 'uid' was called on null.
Receiver: null
Tried calling: uid
instructor is a instance variable in the class ClientiPage, thats why you can access it using the property widget. But it seems you are not initializing it correctly.
The uid will retrieve the currently logged in user id, you dont have to pass it inside a constructor or from a different screen, therefore you can do the following:
void saveChatRoom() async {
String userId = (await FirebaseAuth.instance.currentUser()).uid;
DocumentReference chatroomIdRef = db.collection('instructori').document(userId).collection("chatrooms").document(chatRoomID);
if (chatroomIdRef == null) {
db.collection('instructori').document(userId).collection("chatrooms").document(chatRoomID);
}
}
As long as the user is logged in, you can retrieve the uid using the following code (await FirebaseAuth.instance.currentUser()).uid. There is no need to pass it from screen to screen.
https://pub.dev/packages/firebase_auth

Resources