Flutter: The method [] was called on null - firebase

I know this question is asked and answered many times but my problem is a bit different.
I am working with firebase realtime database in flutter. I upload a JSON file in the database for the existing data I have. It looks like this
Each child has 4 properties (Name, Location, Quality, Size)
The children have IDs of 0,1,2.....
When I retrieve this database in my app, it works and it is displayed perfectly.
When I create a new entry from my app, the child has a random ID and it looks like
this.
After that when I try to retrieve the values, the values are printed in the console but on the screen I get:-
The method [] was called on null error. Receiver: null. Tried Calling: []("Name")
.
The Error screen looks like this.
Error in console looks like this
My code for retrieving (I fetch and pass the variable to another screen):-
ref.once().then((DataSnapshot data) {
datatosend = data;
print(datatosend.value);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DisplayList(
text: datatosend,
title: CategoryItemsitems[index].name),
));
});
My code for displaying listview:
itemCount: widget.text.value.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
leading: IconButton(
icon: Icon(Icons.edit),
onPressed: () {
print("Edit Pressed!");
}),
title: Text(widget.text.value[index]["Name"]),
subtitle: Text("Quality: " +
widget.text.value[index]["Quality"] +
"\nSize: " +
widget.text.value[index]["Size"] +
"\nLocation: " +
widget.text.value[index]["Location"]),
trailing: IconButton(
icon: Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () {
print("Delete Pressed!");
}),
),
);
}),
My code for creating a new entry:
databaseReference.child(_category).push().set({
"Name": _name,
"Quality": _quality,
"Size": _size,
"Location": _location
}).then((_) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('Successfully Added')));
});
Where am I going Wrong?

Hard to know without having access to the code, but this is usually what I do when I run into this type of issues:
run the code in debug mode and put a breaking right after this line:
itemBuilder: (BuildContext context, int index) {. This way you can introspect widget.text.value and see if it's really the array of objects you expected.
otherwise, use a good old print and start printing widget.text.value[index] in your itemBuilder (or even start by printing widget.text, then widget.text.value). You would be surprised how man bugs got figured out with a low-tech print function over the years ;-)
Good luck

After about 10 days, my problem is finally solved.
Some changes I made:
Instead of using, realtime database I am using Cloud Firestore.
Instead of importing the JSON file, I will enter the data manually. Fortunately, it is not very big and earlier also I was writing in the JSON file manually only.
Code to add the data to Firestore. All of this is inside the "onpressed" parameter of a button. All the inputs are taken by a simple form.
firestoreInstance.collection("cars").add({
"Name": _name,
"Quality": _quality,
"Size": _size,
"Location": _location,
}).then((value) {
print("Added Successfully")});
For fetching the data and access its different fields I am using a stream-builder:
StreamBuilder(
stream: Firestore.instance
.collection("cars")
.orderBy("Name") //To display the list by Name
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
else {
if(snapshot.data.documents.length>0)
{
return ListView.builder(
itemCount: snapshot.data.documents.length, //no. of entries fetched
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
snapshot.data.documents[index].data()["Name"]), //Accessing the Name property
subtitle: Text("Quality: " +snapshot.data.documents[index].data()["Quality"] + //Accessing the Quality property
"\nSize: " +snapshot.data.documents[index].data()["Size"] + //Accessing the Size property
"\nLocation: " +snapshot.data.documents[index].data()["Location"]) //Accessing the Location property
);
}
)}
}
}
}
)
I apologize if some of the closing parenthesis does not match. I pulled these important snippets from my very messy code.

Related

Firebase does not retrieve fields in flutter

I'm trying to retrieve from my collection "Courses" 2 field.
Course code and Course name. My issue when retrieving is that I only managed to retrieve the first field.
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.separated(
itemCount: snapshot.data!.docs.length,
separatorBuilder: (BuildContext context, int index) =>
Divider(height: 1),
// ignore: dead_code
itemBuilder: (context, index) {
DocumentSnapshot doc = snapshot.data!.docs[index];
return ListTile(
contentPadding:
EdgeInsets.symmetric(horizontal: 30, vertical: 10),
selectedTileColor: Color(0xffE5E5E5),
title: Text.rich(
TextSpan(
text: doc["Course code"],
children: <TextSpan>[
TextSpan(text: ":"),
TextSpan(text: doc["Course name"]),
and it shows me this error:
Exception has occurred.
StateError (Bad state: field does not exist within the DocumentSnapshotPlatform)
this is my firebase
I only can retrieve course code I don't know why.
It seems like you have a problem with how Flutter and Firebase interacts with each other. I would like to suggest to check the following threadand especially the comment posted by jschnip which could solve your problem
This is probably related to recent changes in DocumentSnapshot, your
factory is looking for the data map, which used to be in doc, but nows
its in doc.data() - except DocumentID
So for your factory, one way to change it would be to adjust to:
Userr( id: doc.id, email: docdata['email'], username:
docdata['username'], url: docdata['url'], profileName:
docdata['profileName'], bio: docdata['bio'], subject1:
docdata['subject1'], subject2: docdata['subject2']
and when you call
it, you'll need to do something like:
doc.data();
newUserrr = Userr.fromDocument(doc, _docdata);

Does Streambuilder fetches all the data from a firestore document or only fields that are needed?

In my App I use a Streambuilder to show a list of plans that are stored in Firestore (every plan has its own document) on a screen:
final CollectionReference planCol = FirebaseFirestore.instance.collection('users').doc(FirebaseAuth.instance.currentUser.uid).collection('plans');
body: StreamBuilder(
stream: planCol.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final planDocs = snapshot.data.docs;
return ListView.builder(
itemCount: planDocs.length,
itemBuilder: (context, index) => MyListTile(
title: Text(planDocs[index]['name']),
trailing: IconButton(
icon: Icon(Icons.edit),
onPressed: () {
edit(planDocs[index]);
},
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return ExerciseTable(
plan: UserPlan(
name: planDocs[index]['name'],
exerciseNames: planDocs[index]
['exerciseNames'],
rows: planDocs[index]['rows'],
planId: planDocs[index].id,
),
);
},
),
);
}),
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}));
On this screen, I only want to show the name of each document and not all the other data which is stored in the document(because they are very big). Now I was asking myself how efficient Streambuilder works when each document is very big. Does it load the whole document or only the field of the document, that is needed?
On this screen I only want to show the name of each document and not all the other data which is stored in the document(because they are very big).
All Cloud Firestore listeners fire on the document level. So there is no way you can get only the value of a single field in a document. That's the way Firestore works.
Now I was asking myself how efficient Streambuilder works when each document is very big.
It's more about the time that it takes to download those documents.
Does it load the whole document or only the field of the document, that is needed?
It loads the entire document.
If you need only some names, you should consider storing those names in a single document. In this case, you can read only one document and you have to pay only one read.

Error showing when document is deleted from firestore with streambuilder (flutter)

Hello everybody first of all I'm sorry for my English if its not clear, I'm working on a personal project. so, I'm using StreamBuilder on firestore document with the userID of the user from 'Users' Collections. So, I have retrieved the "imageUrl" field and display it in Image Network in my application, so, I have 'Delete Account' Button, this button will delete the account from firebase auth and also delete the document that the streambuilder listens to it.
So, the error happens because the streambuilder will build ImageNetwork and retrieve the URL from the document field.
Any ideas to handle the error?
this is the code for the streamBuilder that will return NetworkImage
StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('Users')
.document(user.getID())
.snapshots(),
builder:
(context, AsyncSnapshot<DocumentSnapshot> snapshot) {
print(snapshot.connectionState);
var userDocument = snapshot.data;
if (userDocument.data.length == 0) {
return const Center(
child: Text(
"Not Available",
style:
TextStyle(fontSize: 30.0, color: Colors.grey),
),
);
} else
return AvatarGlow(
glowColor: Colors.redAccent,
endRadius: 90,
child: Material(
elevation: 8.0,
shape: CircleBorder(),
child: CircleAvatar(
backgroundColor: Colors.grey[100],
child: ClipOval(
child: FadeInImage(
image: NetworkImage(
userDocument['imageUrl'] ??
'https://picsum.photos/250?image=9'),
placeholder: AssetImage('assets/noImage.png'),
),
),
radius: 70,
),
),
);
},
),
Debug error
The getter 'length' was called on null.
Receiver: null
Tried calling: length
The relevant error-causing widget was
StreamBuilder<DocumentSnapshot>
The solution was on if else blocks
StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('Users')
.document(user.getID())
.snapshots(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.data != null && snapshot.data.exists) {
var userDocument = snapshot.data;
// return something
}
}
For a non-existing document userDocument.data will return null, so userDocument.data.length throws the error you get.
My guess is you want to check if the document exists, which you'd do with:
if (userDocument.exists) {
Also see the reference documentation on DocumentSnapshot class, which is the type of object your userDocument is.
Ok. So, how these StreamBuilder(s) and FutureBuilder(s) are supposed be used are as follows:
Note: the following code should be inside your Builder function.
if(snapshot.hasData){
// Your normal functioning of the app will follow here.
// Now that you know that your snapshot contains data,
// then you access the data.
var userDocument = snapshot.data;
// Now, you can check if the userDocument exists or not.
}
else if(snapshot.hasError){
// return an error message or do something else.
}
// return any default Widget while the data is being loaded.
return CircularProgressIndicator();
Also, I would recommend that once the user requests to delete his/her account, you should navigate back to the home screen...

In Dart/Flutter, how can I find out if there is no Collection in the Firestore database?

I'd like to check if a collection (the users collection) exists in the Firestore database or not. But I cannot find any means of doing that. If the collection exists, I would like to stream its documents, otherwise an empty stream as you see in the following method
- Is there any way to find a collection exists without getting its snapshots?
- Why break; or yield* Stream.empty() hangs the stream, like an endless stream!
Stream<userInfo> getCurrentUserInfos() async* {
final String usersCollectionPath = "users";
Stream<QuerySnapshot> snapshots = Firestore.instance.collection(usersCollectionPath).snapshots();
snapshots.isEmpty.then((hasNoUserCollection) {
// Although I don't have 'users' collection
// in my database but I never reach this point!
// i.e. hasNoUserCollection is always FALSE!
if(hasNoUserCollection) {
print("users collection doesn't exist, create it!");
// next line (i.e. `break;`) hangs the tool!
// And sometimes hangs the VSCode's IDE as well, if I have a breakpoint on it!!!
break;
// I tried yielding an empty stream instead of break, again another hang!
// yield* Stream<userInfo>.empty();
} else {
// previous stream is dead, so create it again!
snapshots = Firestore.instance.collection(usersCollectionPath ).snapshots();
await for (QuerySnapshot snap in snapshots) {
for (DocumentSnapshot userDoc in snap.documents) {
yield (new userInfo.fromQuerySnapshot(userDoc));
}
}
});
}
Now even a try-catch block cannot catch what's gone wrong, when the stream is empty!
try{
getCurrentUserInfos().last.then((userInfolastOne) {
print("last one: $lastOne.name");
});
// the following line (i.e. await ...) at least doesn't hang and
// `catch` block is able to catch the error `Bad state: No element`,
// when the stream is empty
//
// userInfo lastOne = await stream.last;
} catch (ex) {
print ("ex: $ex");
}
There is no API to detect if a collection exists. In fact: a collection in Firestore only exists if there are documents in it.
The cheapest check I can think of is doing a query for a single document, and then checking if that query has any results.
Okay, maybe I figured it out
final snapshot = await Firestore.instance.collection(collectionName).getDocuments();
if (snapshot.documents.length == 0) {
//Doesn't exist
}
This worked for me
As stated by #Frank, a collection in Firestore gets deleted if no Documents exist in it.
However, I understand that there might be cases where you want to keep a history of the collection modification/ creation events, or let's say for some reason prevent Collections from being deleted.
Or for example, you want to know when a collection was created in the first place. Normally, if the Documents are deleted, and then the Collection gets created again, you will not know the initial creation date.
A workaround I can think of is the following:
Initialize each collection you want with a Document that will be specifically for keeping generic info about that collection.
For example:
This way, even if all other Documents in the Collection are deleted, you'll still keep the Collection in addition to some info that might be handy if In the future you need to get some history info about the Collection.
So to know if a Collection exists of no, you can run a query that checks for a field in Info Documents (eg CollectionInfo.exists) to know which Collections have been already created.
This is a sample from one of my projects. You can use snapshot.data!.docs.isEmpty to check if a collection has data or not.
StreamBuilder(
stream: _billGroupStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('Something went wrong'),
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Column(
children: const [
LinearProgressIndicator(),
Text('Loading data, please wait...'),
],
),
);
} else if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.data!.docs.isEmpty) {
return const Center(
child: Text(
'Huh!! Looks like you have no transactions yet!' ,
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
);
} else if (snapshot.connectionState == ConnectionState.active) {
final List<DocumentSnapshot> docs = snapshot.data!.docs;
return ListView.builder(
shrinkWrap: true,
restorationId: 'billModeList',
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
///This document snapshot is used for determining the unique id for update and delete methods
final DocumentSnapshot doc = docs[index];
//final DocumentSnapshot doc = snapshot.data!.docs[index];
///This [BillModel] converted data is used to build widgets
final BillModel billModel = doc.data()! as BillModel;
return Dismissible(
onDismissed: (direction) {
_remoteStorageService.deleteItemFromGroup(
widget.uri, doc.id);
setState(() {
docs.removeAt(index);
});
},
background: Container(
color: Colors.red,
child: const Icon(Icons.delete_forever_sharp),
),
key: Key(doc.id),
child: Card(
elevation: 3,
shadowColor: Colors.teal.withOpacity(.5),
child: ListTile(
leading:
const CircleAvatar(child: Icon(Icons.attach_money)),
title: Text(billModel.name),
subtitle: Text(billModel.category ?? ''),
trailing: Text(billModel.amount.toString()),
),
),
);
},
);
}
return const CircularProgressIndicator();
},
),

Grabbing Primary Key of listed values from sqlite database, dart with flutter

Currently my code is grabbing the order of the listed values by how it is indexed and i'm passing it to my raw sql query. I think i may be going about this the wrong way buy using listviewbuilder, but I want to grab the primary key (from sqlite database) of the clicked on value and pass that to the query on the next page
body: !loading ? new ListView.builder(
itemCount: sectorList.length,
itemBuilder: (BuildContext context, int index) {
return new Card(
color: Colors.cyan[800],
elevation: 2.0,
child: new ListTile(
title: new Text("${sectorList[index]}"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) =>
DocumentPage(id: index),
),
);
}),
);
},
) : CircularProgressIndicator(),
);
}
}
any help or guidance would be greatly appreciated.
Comment if you need to see more of the code.
You must be using some type of query like:
List<Map<String, dynamic>> categories =
await db.rawQuery('SELECT * FROM tbl_categories order by category_name');
This is going to give you a List with each of the categories ordered by name. If the table also contains another column (maybe category_id) that will also be available in the map.
Now each ListTile looks like:
child: new ListTile(
title: new Text("${categories[index]['category_name']}"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) =>
OtherPage(id: categories[index]['category_id']),
),
);
},
),
You display the name, but pass the id to the next page. Assuming then that there's another table with a foreign key of category_id you could:
await db.rawQuery('SELECT * FROM tbl_other WHERE category_id=${widget.id}');
This would give you another List<Map<String, dynamic>> of all the other things in the relevant category. Use this to build this next page.

Resources