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

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.

Related

Incorrect use of parent Widget

I am trying to make a flash Chat App that retrieves the chats from fireBase and displays it on the Screen .I have wrapped it under an Expanded widget .I have give some padding to it .
I am getting the following error
The following assertion was thrown while looking for parent data.:
Incorrect use of ParentDataWidget.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flashchat1/constants.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class ChatScreen extends StatefulWidget {
static String id='Chat_Screen';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _fireStore = FirebaseFirestore.instance;//an instance of fireBase store that stored data created
final _auth = FirebaseAuth.instance;//instance/object of fireBase auth that authorizes users is created
late User loggedInUser;//LoggedInUser is of type FireBase user(now changed to user)
late String messageText;
#override
void initState()
{
super.initState();
getCurrentUser();//calling the getCurrentUser
}
void getCurrentUser()
async{
try
{
final user= await _auth.currentUser;//get the current user id/name/email.Also currentUser return a future so make it async by adding await and async keywords
if(user!=null)
{
loggedInUser=user ;//LoggedInUser = user contains email of the info
print(loggedInUser.email);
}
}
catch(e)
{
print(e);
}
}// Under collection there is documents.Inside documents there are fields like type ,values etc.These fields contain our information
Future<void> messageStream()//Using a stream it becomes very easy .U just need to click once after you run the app .Then u will be done.
async {//The snapShot here is FireBase's Query SnapShot
await for(var snapshot in _fireStore.collection('messages').snapshots()){//make a variable snapshot to store the entire items of the collection in fireBase (Look at the fireBase console there is a collection called messages).This collection takes the snapshot of all the iteams (not literal snapshot .Think it like a snapShot)
for(var message in snapshot.docs)//make a variable message to access the snapShot.docs .(docs stands for Documentation.Look at the fireBase console)
print(message.data());
}
}
void getMessages()//(The problem with this is that we need to keep clicking on the onPressed button every single time the new message is sent .So it is not convinient
async {
final messages = await _fireStore.collection('messages').get();//to retrieve the data from fire base we are creating a variable message
messages.docs;//retreive the data from document section under the collection in firestore
for(var message in messages.docs)//since it is a messages.docs is a list we need to loop through it
{
print(message.data());//print the data its messge.data()
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
messageStream();
//_auth.signOut();
//Navigator.pop(context);
//Implement logout functionality
}),
],
title: Text('⚡️Chat'),
backgroundColor: Colors.lightBlueAccent,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: StreamBuilder(
stream:_fireStore.collection('messages').snapshots(),
builder: (context, AsyncSnapshot snapshot) {
//This is Flutter's Async snapShot
//if(!snapshot.data)
// {
// return Center(
//child: CircularProgressIndicator(
//backgroundColor:Colors.lightBlueAccent,
//),
//);
//}
if(!snapshot.hasData){//flutters async snapshot contains a query snapshot
return Center(
child:CircularProgressIndicator(
backgroundColor:Colors.lightBlueAccent,
),
);
}
final messages = snapshot.data.docs;
List<Text> messageWidgets = [];
for(var message in messages)//Loop through the messages
{
final messageText = message.data()['text'];//retrieve the data under the text field in message collection
final messageSender = message.data()['Sender'];//retrieve the data under the Sender field in message collection
final messageWidget = Text('$messageText from $messageSender',
style:TextStyle(
fontSize:50,
),
);
messageWidgets.add(messageWidget);//add the text to the List messageWidget
}
return Expanded(
flex:2,
child: ListView(//changed from Column to ListView as we want to scroll down .Or else only finite messages can be fit
children: messageWidgets, //if u don't write else with a return it will show an error as null returned and null safety broken
padding: EdgeInsets.symmetric(horizontal: 5,vertical: 5),
),
);
},
),
),
Container(
decoration: kMessageContainerDecoration,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
onChanged: (value) {
messageText=value;//Whatever you chat will be stored in the variable String variable messageText
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () {
_fireStore.collection('messages').add({
'text': messageText,//add the messages sent to fireStore under the messages object that we created manually
'Sender': loggedInUser.email,//add the current users email to the sender field
},);
},//goal is to send the data that we type here to the fireStore cloud
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
),
],
),
),
);
}
}
return Expanded(
flex:2,
child: ListView(//changed from Column to ListView as we want to scroll down .Or else only finite messages can be fit
children: messageWidgets, //if u don't write else with a return it will show an error as null returned and null safety broken
padding: EdgeInsets.symmetric(horizontal: 5,vertical: 5),
),
);
This code block is the issue here. You cannot use Expanded widget anywhere you like. The Expanded widget can only be used inside Row or Column Widget.
Remove the Expanded widget in the above code block. It will works.

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

Flutter: Correct approach to get value from Future

I have a function which returns images directory path, it performs some additional check like if directory exists or not, then it behaves accordingly.
Here is my code:
Future<String> getImagesPath() async {
final Directory appDir = await getApplicationDocumentsDirectory();
final String appDirPath = appDir.path;
final String imgPath = appDirPath + '/data/images';
final imgDir = new Directory(imgPath);
bool dirExists = await imgDir.exists();
if (!dirExists) {
await new Directory(imgPath).create(recursive: true);
}
return imgPath;
}
This piece of code works as expected, but I'm having issue in getting value from Future.
Case Scenario:
I have data stored in local database and trying to display it, inside listview. I'm using FutureBuilder, as explained in this answer. Each data row has an image connected with it (connected means, the image name is stored in db).
Inside Widget build method, I have this code:
#override
Widget build(BuildContext context) {
getImagesPath().then((path){
imagesPath = path;
print(imagesPath); //prints correct path
});
print(imagesPath); //prints null
return Scaffold(
//removed
body: FutureBuilder<List>(
future: databaseHelper.getList(),
initialData: List(),
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, int position) {
final item = snapshot.data[position];
final image = "$imagesPath/${item.row[0]}.jpg";
return Card(
child: ListTile(
leading: Image.asset(image),
title: Text(item.row[1]),
subtitle: Text(item.row[2]),
trailing: Icon(Icons.launch),
));
})
: Center(
child: CircularProgressIndicator(),
);
}));
}
Shifting return Scaffold(.....) inside .then doesn't work. Because widget build returns nothing.
The other option I found is async/await but at the end, same problem, code available below:
_getImagesPath() async {
return await imgPath();
}
Calling _getImagesPath() returns Future, instead of actual data.
I beleive there is very small logical mistake, but unable to find it myself.
I see that you have to build your widget from the output of two futures. You can either use two FutureBuilders or have a helper method to combine them into one simplified code unit.
Also, never compute/invoke async function from build function. It has to be initialized before (either in constructor or initState method), otherwise the widget might end up repainting itself forever.
Coming to the solution: to simplify code, it is better to combine both future outputs into a single class as in the example below:
Data required for build method:
class DataRequiredForBuild {
String imagesPath;
List items;
DataRequiredForBuild({
this.imagesPath,
this.items,
});
}
Function to fetch all required data:
Future<DataRequiredForBuild> _fetchAllData() async {
return DataRequiredForBuild(
imagesPath: await getImagesPath(),
items: await databaseHelperGetList(),
);
}
Now putting everything together in Widget:
Future<DataRequiredForBuild> _dataRequiredForBuild;
#override
void initState() {
super.initState();
// this should not be done in build method.
_dataRequiredForBuild = _fetchAllData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
//removed
body: FutureBuilder<DataRequiredForBuild>(
future: _dataRequiredForBuild,
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data.items.length,
itemBuilder: (_, int position) {
final item = snapshot.data.items[position];
final image = "${snapshot.data.imagesPath}/${item.row[0]}.jpg";
return Card(
child: ListTile(
leading: Image.asset(image),
title: Text(item.row[1]),
subtitle: Text(item.row[2]),
trailing: Icon(Icons.launch),
));
})
: Center(
child: CircularProgressIndicator(),
);
},
),
);
}
Hope it helps.
Moving this piece of code inside FutureBuilder should resolve the issue.
getImagesPath().then((path){
imagesPath = path;
print(imagesPath); //prints correct path
});
So your final code should look like this:
#override
Widget build(BuildContext context) {
return Scaffold(
//removed
body: FutureBuilder<List>(
future: databaseHelper.getList(),
initialData: List(),
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, int position) {
getImagesPath().then((path){
imagesPath = path;
});
final item = snapshot.data[position];
final image = "$imagesPath/${item.row[0]}.jpg";
return Card(
child: ListTile(
leading: Image.file(File(image)),
title: Text(item.row[1]),
subtitle: Text(item.row[2]),
trailing: Icon(Icons.launch),
));
})
: Center(
child: CircularProgressIndicator(),
);
}));
}
Hope it helps!

How to show a dialog with FutureBuilder when press a button?

I upload an image FirebaseStorage with FutureBuilder and when I press upload button I want to show a waiting dialog. Image upload successfully on FireStorage but nothing shows up. Whats wrong my codes? I think FutureBuilder return Widget and press button void. Maybe it is the problem. Or I am calling FutureBuilder wrong way. Do you have any tips or suggestions for my wrong code?
Here is my code;
Widget buildBody(BuildContext context) {
return new SingleChildScrollView(
child: new Column(
children: [
new SizedBox(
width: double.infinity,
child: new RaisedButton(
child: new Text('Upload'),
onPressed: () {
futureBuilder();
},
),
),
],
),
);
}
Future mediaUpload() async {
final fileName = DateTime.now().millisecondsSinceEpoch.toString() + '.jpg';
final StorageReference storage = FirebaseStorage.instance.ref().child(fileName);
final StorageUploadTask task = storage.putFile(_image);
return task.future;
}
Widget futureBuilder() {
return new FutureBuilder(
future: mediaUpload(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
print('ConnectionState.none');
return new Text('Press button to start.');
case ConnectionState.active:
print('ConnectionState.active');
return new Text('');
case ConnectionState.waiting:
waitingDialog(context);
return waitingDialog(context);
case ConnectionState.done:
print('ConnectionState.done');
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
print(snapshot.data.downloadUrl);
Navigator.of(context).pushReplacementNamed('home');
return new Text('Result: ${snapshot.data.downloadUrl}');
}
},
);
}
waitingDialog(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return new Center(
child: new SizedBox(
width: 40.0,
height: 40.0,
child: const CircularProgressIndicator(
value: null,
strokeWidth: 2.0,
),
),
);
},
);
}
I am not 100% sure why FutureBuilder is not working. But I have a guess. In the above example, FutureBuilder is not attached to the widgetTree(screen). Not attached means value returned from FutureBuilder is not displayed in screen. In this case, Text is returned based on snapshot.connectionState which is not attached to screen.
Work around: (I tested and it worked, can you please verify)
futureBuilder(BuildContext context) {
mediaUpload().then((task) { // fire the upload
Navigator.of(context).maybePop(); // remove the dialog on success upload
}); // we can use task(which returned from
// mediaUpload()) to get the values like downloadUrl.
waitingDialog(context); // show the spinner dialog
}

Check if Field Already exists in Flutter Firestore

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

Resources