I am trying to get the list of products by shop from the firestore. However, getting the following error:
I/flutter ( 7294): NoSuchMethodError: The method '[]' was called on null.
I/flutter ( 7294): Receiver: null
I/flutter ( 7294): Tried calling: []("merchantId")
I am using FutureBuilder to get shop data and StreamBuilderWrapper to get the products:
class Shop extends StatelessWidget {
Widget getProductsAndImages(BuildContext context, String outletID) {
return StreamBuilderWrapper(
shrinkWrap: true,
stream: productsRef.doc(outletID).collection('products')
.orderBy('dateTime', descending: true).snapshots(),
itemBuilder: (_, DocumentSnapshot snapshot) {
ProductModel posts = ProductModel.fromJson(snapshot.data() as Map<String, dynamic>);
return posts.productId != null
? Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child:
ProductItem(productName: posts.proName!, productPrice: posts.price!,
count: posts.count!, images: posts.images!,
category: posts.category!, productDescription: posts.productDescription!,
productId: posts.productId!),
)
: Container(
child: Center(
child: Text('No products'),
),
);
});
}
#override
Widget build(BuildContext context) {
final outletData = Provider.of<OutletData>(context);
final outletId = ModalRoute.of(context)!.settings.arguments as String;
print('outlet id--->>> $outletId');
return Scaffold(
appBar: appHeader(context),
backgroundColor: color2,
extendBodyBehindAppBar: true,
body: Stack(
children: <Widget>[
Container(
decoration: const BoxDecoration(
image: DecorationImage(image: AssetImage("assets/images/background.jpg"),
fit: BoxFit.cover,),
),
),
FutureBuilder(
future: outletData.getOutletById(outletId),
builder: (ctx, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting ||
snapshot.data == null) {
return
Center(
child: Text("Юм олдсонгүй"),
);
}
return snapshot.data.length == 0
? Center(
child: Text("Бүтээгдэхүүн байхгүй"),
)
: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ShopHeader(
outletName: snapshot.data()[outletName],
outletImage: snapshot.data()[outletImg],
location: snapshot.data()[outletLocation],
description: snapshot.data()[outletIntro],
phoneNum: snapshot.data()[outletPhone],
merchantId: snapshot.data()[merchantId],
outletId: outletId,
outletRating: snapshot.data()['outletRating'],
),
Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
"Recommended products:",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 17,
color: Colors.black54,
),
),
),
ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (ctx, index) {
return
getProductsAndImages(context, outletId);
},
),
],
),
);
},
)
]));
}
}
my getOutletById method is in the provider class:
Future<Map<String, dynamic>> getOutletById(String outId) async {
var outletsById;
final Map<String, dynamic> idOutlet = {};
_outletMerchant.clear();
try {
outletsById =
await firestore.collection(outletsCollection).doc(outId).get();
var outletResponse = outletsById.data();
var outletMerchantData = await firestore
.collection(allUserCollection)
.doc(outletResponse[merchantId])
.get();
var merchantResponse = outletMerchantData.data();
_outletMerchant.addAll(merchantResponse!);
print(_outletMerchant);
idOutlet.addAll(outletResponse);
print(merchantResponse[merchantId]);
} catch (err) {
print(err);
}
return idOutlet;
}
print() method prints nothing here my firestore database is as follows:
You can see that there is the merchantId for the outlets collection. where is the error here?
Related
I have categories collection with category name and id fields. In foods collection, the category is a reference field. I need to display like this:click to see expected output
where title is coming from categories collection and foods are coming from foods collection.
I tried using nested streambuilder: streambuilder 1: fetch categories in a listview streambuilder 2: fetch foods in a list. Inside streambuilder 2, i have used a futurebuilder to decode the category data. If category name in food and category name from streambuilder 1 is same, the food will be displayed under that category.+
class RestaurantDetails extends StatefulWidget {
final String id;
RestaurantDetails({required this.id, super.key});
#override
State<RestaurantDetails> createState() => _RestaurantDetailsState();
}
class _RestaurantDetailsState extends State<RestaurantDetails> {
List<FoodCategory> categories = [];
List<Food> foods = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: StreamBuilder(
stream: getCategories(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
categories = snapshot.data!.docs
.map((item) => FoodCategory.fromMap(item))
.toList();
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: categories.length,
itemBuilder: ((context, cateindex) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 10),
child: Container(
height: 30,
width: MediaQuery.of(context).size.width * 1,
color: Colors.white,
child: Text(
categories[cateindex].name.toString(),
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(fontSize: 20.0),
),
),
),
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('foods')
.doc(widget.id)
.collection('all')
.snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
foods = snapshot.data!.docs
.map((item) => Food.fromMap(item))
.toList();
return ListView.builder(
itemCount: foods.length,
physics:
const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: ((context, foodindex) {
var catepath = foods[foodindex].cid!.path;
String cateDocumentName = catepath
.substring(11, catepath.length);
return Column(
children: [
FutureBuilder(
future: FirebaseFirestore.instance
.collection('categories')
.doc(cateDocumentName)
.get(),
builder: ((context,
AsyncSnapshot<
DocumentSnapshot>
snapshot) {
if (snapshot.hasData) {
Map<String, dynamic> data =
snapshot.data!.data()
as Map<String,
dynamic>;
if (data['name'] ==
categories[cateindex]
.name) {
return Padding(
padding: const EdgeInsets
.symmetric(
vertical: 10,
horizontal: 10),
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius
.circular(
20),
color:
Colors.white),
height: 100,
width: MediaQuery.of(
context)
.size
.width *
1,
child: Row(
children: [
Image.network(
foods[foodindex]
.cover
.toString(),
height: 100,
width: 100,
errorBuilder:
((context,
error,
stackTrace) {
return Image
.asset(
'assets/images/food1.jpg',
height: 100,
width: 100,
);
}),
),
UIHelper
.horizontalSpaceMedium(),
Column(
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
UIHelper
.verticalSpaceSmall(),
Text(
foods[foodindex]
.name
.toString(),
style: Theme.of(
context)
.textTheme
.bodyText1,
),
UIHelper
.verticalSpaceSmall(),
Text(
'₹${foods[foodindex].price}',
style: Theme.of(
context)
.textTheme
.bodyText1!
.copyWith(
fontSize:
14.0),
),
UIHelper
.verticalSpaceMedium(),
],
)
],
)),
);
} else {
return const SizedBox();
}
} else {
return const CircularProgressIndicator
.adaptive();
}
}))
],
);
}));
} else {
return const CircularProgressIndicator
.adaptive();
}
},
)
],
);
}));
} else {
return const CircularProgressIndicator.adaptive();
}
}),
),
);
}
getCategories() {
return FirebaseFirestore.instance
.collection('categories')
.where('uid', isEqualTo: widget.id)
.snapshots();
}
}
categories data
click to see categories
food data
click to see food data
I get the output.see my output here but when data is large (i.e large number of foods inside a category) the app hangs. Is there anyother way we can achieve this? the data should load seamlessly regardless of data size.
I have a class of complains in which there is a field of status. This status can be pending , inprogress, completed , rejected. In UI I have designed buttons to filter complaints on the basis of status.
The issue that I am facing is that when I switch stream on button action. It still contains the data of previous stream.
Can anyone help me to have 2 streams and not either of it contains data of previous stream.
bool _isSwitched = false;
List<Complains> complains = [];
final Stream<QuerySnapshot> _all = FirebaseFirestore.instance
.collection('Complains').orderBy("startTime", descending: true)
.snapshots();
final Stream<QuerySnapshot> _pending = FirebaseFirestore.instance
.collection('Complains').where('status', isEqualTo: 'Pending').orderBy("startTime", descending: true)
.snapshots();
ElevatedButton(onPressed:(){
setState(() {
_isSwitched = !_isSwitched;
complains.clear();
complains2.clear();
});
} , child: Text("Switch Stream"),),
StreamBuilder<QuerySnapshot>(
initialData: null,
stream: _isSwitched?_all:_pending,
builder: (context, snapshot) {
if (snapshot.hasError) {
Text("Error");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
//length of stream is greater than 0
if (snapshot.data!.docs.length == 0) {
return Center(
child: Text("No Complains"),
);
}
for (var element in snapshot.data!.docs) {
Complains complain = Complains.fromMap(element.data() as Map<String, dynamic>);
_isSwitched?complains.add(complain):complains2.add(complain);
}
return ListView.builder(
itemBuilder: (context, index) {
return InkWell(
onTap: () {
},
child: Card(
margin: EdgeInsets.all(8.0),
elevation: 5,
color: Colors.white70,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12.0))),
child: Container(
padding: EdgeInsets.all(20),
child:
Column(children: [
Text("Title\n ${complains[index].title}",style: TextStyle(fontStyle: FontStyle.italic),),
Table(
children: [
TableRow(children: [
Text("Name: " ),
Text("Address: "+ complains[index].address.toString(),),
]),
TableRow(children: [
Text("Priority: "+ complains[index].priority.toString(),),
Text("Status: "+complains[index].status.toString(),),
]),
TableRow(children: [
Text("Worker: "+ complains[index].worker.toString(),),
Text("Service: "+ complains[index].service.toString(),),
]),
],
),
],)
),
),
);
},
itemCount: _isSwitched?complains2.length:complains.length,
);
},
),
),
I'm going crazy because of this error.
It just appears by opening the screen for a fraction of a second and then is gone.
Every new app installation will have this problem for once only.
I do not know why it can't find the documents fields in this small timeperiod and why it is working after this like it should be working.
My Code Snippet:
getMembers() {
// checkField();
return StreamBuilder<QuerySnapshot> (
///Hier sollte eher aus den User>> Groups>> und dann den Usernamen von dem userid dokument ausgeben
stream: FirebaseFirestore.instance
.collection("Groups")
.doc(widget.groupname)
.collection("Debt")
.snapshots(),
builder: (ctx, streamSnapshot) {
if (!streamSnapshot.hasData || !streamSnapshot.data!.docs.isNotEmpty) {
return Center(
child: CircularProgressIndicator(),
);
}
final documents = streamSnapshot.data!.docs;
return Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: documents.length ,
itemBuilder: (ctx, index) {
String docID = streamSnapshot.data!.docs[index].id;
///Ermitteln der Document ID !!!!
return Container(
padding: EdgeInsets.all(2),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(45),
),
child: ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width / 3,
child: Text(
"${documents[index]["username"]}",
),
),
],
),
),
),
);
},
),
);
},
);
}
EDIT
changing expanded to a column doesn't work either :S
Could you tell me why this one is working without any problems and the one at the top is still showing this error?
I don't understand why it can't find the data, because the data are definetly there...
Here is the one from a different screen, working fine:
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("Chats")
.orderBy("createdAt", descending: true)
.where("group", isEqualTo: widget.groupname)
.snapshots(),
builder: (ctx, streamSnapshot) {
if (!streamSnapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final documents = streamSnapshot.data!.docs;
return ListView.builder(
reverse: true,
itemCount: documents.length,
itemBuilder: (ctx, index) {
String docID = streamSnapshot.data!.docs[index].id;
return Row(
mainAxisAlignment: documents[index]["User"] == userid
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
Container(
width: 300,
padding: EdgeInsets.symmetric(
vertical: 0.2, horizontal: 4),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(45)),
color: documents[index]["User"] == userid
? Colors.grey[400]
: Colors.brown[300],
child: ListTile(
leading: FaIcon(FontAwesomeIcons.comment),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
documents[index]["name"] +
": " +
documents[index]["input"],
),
Text(
"${documents[index]["Datum"]}",
style: TextStyle(fontSize: 10),
)
],
),
trailing: delete
? IconButton(
icon: Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () async {
await chats.doc(docID).delete();
},
)
: null,
),
),
),
],
);
},
);
},
),
EDIT ERROR STACK:
The Error I get
StreamBuilder triggered twice. InitialData can be useful.
I tried to add a better structure. But if (!streamSnapshot.hasData || streamSnapshot.data!.isEmpty) { return const SizedBox(); } is for extra precaution.
getMembers() {
// checkField();
return StreamBuilder<List<Debt>>(
///Hier sollte eher aus den User>> Groups>> und dann den Usernamen von dem userid dokument ausgeben
stream: FirebaseFirestore.instance
.collection("Groups")
.doc(widget.groupname)
.collection("Debt")
.snapshots().map((event) => event.docs.map((map) => Debt.fromMap(map.data())).toList()),
initialData: const [],
builder: (ctx, streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (!streamSnapshot.hasData || streamSnapshot.data!.isEmpty) {
return const SizedBox();
}
final documents = streamSnapshot.data!;
if (documents.isEmpty) {
return const SizedBox();
}
return Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: documents.length,
itemBuilder: (ctx, index) {
String docID = documents[index].id;
final userName = documents[index]["username"];
if(userName == null){
return const SizedBox();
}
///Ermitteln der Document ID !!!!
return Container(
padding: const EdgeInsets.all(2),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(45),
),
child: ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
width: MediaQuery
.of(context)
.size
.width / 3,
child: Text(
"$userName",
),
),
],
),
),
),
);
},
),
);
},
);
}
#immutable
class Debt {
final String id;
final String? username;
const Debt({required this.id, this.username});
Map<String, dynamic> toMap() {
return {
'id': id,
'username': username,
};
}
factory Debt.fromMap(Map<String, dynamic> map) {
return Debt(
id: map['id'] as String,
username: map['username'] as String? ?? '',
);
}
}
What is the purpose of initialData in FutureBuilder? The documentation says:-
The data that will be used to create the snapshots provided until a non-null future has completed
Does that mean the placeholder data that gets shown to the user when the data is awaiting?
If that was the case then why my FutureBuilder always shows the loading state and not the initialData
My FutureBuilder()
FutureBuilder(
future: _getPosts(),
initialData: _getOfflineData(),
builder: (BuildContext context, AsyncSnapshot<List<Post>> snapshot) {
if (!snapshot.hasData ||
snapshot.data == null ||
snapshot.data.isEmpty ||
snapshot.hasError) {
// Still loading || load null data UI
return ListView(
scrollDirection: Axis.horizontal,
children: [for (var i = 0; i < 5; i++) HorizontalPostShimmer()],
);
}
return ListView.builder(
itemCount: snapshot.data.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final titleExists = doesTitleExist(snapshot.data[index].title);
final title = titleExists
? snapshot.data[index].title
: _translate.translate('unknownTitle');
final imgExists =
doesImageExist(snapshot.data[index].featuredMedia);
final img = imgExists ? snapshot.data[index].featuredMedia : null;
return Container(
width: 300.0,
child: Card(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
SAPost(data: snapshot.data[index]),
settings: RouteSettings(name: 'SAPost')));
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
img == null
? Container(
child: SaviorImageShimmer(
height: 100,
width: 100,
))
: CachedNetworkImage(
imageUrl: img,
placeholder: (context, url) =>
SaviorImageShimmer(),
errorWidget: (context, url, error) =>
Icon(Icons.error),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'$title',
style: TextStyle(
fontSize: _isMobile ? 18.0 : 22.0,
),
),
),
)
],
),
),
),
),
);
},
);
},
)
And my _getPosts()
Future<List<Post>> _getPosts() {
final Map<String, dynamic> args = {
'IDs': [1983],
'perPage': 10,
'pageNum': 1,
'from': 'horizontalSlider'
};
final Future<List<Post>> posts =
context.read(fetchSpecificPostsByCategoriesProvider(args).future);
return posts;
}
And my _getOfflineData()
List<Post> _getOfflineData() {
final List<Post> cachedPosts =
Hive.box('posts').get('horizontalSlider', defaultValue: <Post>[]);
return cachedPosts;
}
Am I doing something that my FutureBuilder() always returns the ListView about loading?
Or is initialData used in another way
In case if you would like to provide some data while other data is being fetched, you can use the initialData property to provide that data.
The documentation is as clear as it can be. My guess is your initial data is empty. Try removing the following condition from the build function and check again: snapshot.data.isEmpty.
My method getting the data from db and displaying on the console. I tried several hints given in the other posts as well with no luck.
_getUsers() async {
print("getting");
var data = await http.post("http://10.0.2.2/Flutter/getdata.php", body: {
"date": formattedDate,
});
var jsonData = json.decode(data.body);
print(jsonData);
}
However the future builder not able to display:
new FutureBuilder(
future: _getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Center(
child: new Text('Error ${snapshot.error}'),
);
} else {
return Center(
child: Padding(
padding: const EdgeInsets.fromLTRB(56.0, 8.0, 56.0, 8.0),
//Here I guarded against the null as well:
child: ListView.builder(
itemCount: snapshot.data.length == null // showing error here
? 0
: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: new Text(
'${snapshot.data[index]["branch"]}',
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
trailing: new Text(
'${snapshot.data[index]["count(`branch`)".toString()]}',
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
);
}),
),
);
}
}),
How can I solve the issue?
you should return jsonData in _getUser().
getUsers() async {
print("getting");
var data = await http.post("http://10.0.2.2/Flutter/getdata.php", body: {
"date": formattedDate,
});
var jsonData = json.decode(data.body);
return jsonData;
}
, and change this
itemCount: snapshot.data.length == null // showing error here
? 0
: snapshot.data.length,
to this
itemCount: snapshot.data?.length ?? 0,
snapshot.data? checks whether the data is null or not. ?? executes its successor when the predecessor is null.
Your function doesn't return any Future, therefore the FutureBuilder is unable to get a Future to run on.
_getUsers() {
print("getting");
return http.post("http://10.0.2.2/Flutter/getdata.php", body: {
"date": formattedDate,
});
}
It needs to return a Future, you shouldn't be using await because the FutureBuilder depends on an actual Future, not data. You obtain the data inside the builder and then decode it.
new FutureBuilder(
future: _getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Center(
child: new Text('Error ${snapshot.error}'),
);
} else if (snapshot.hasData) { // checking for data
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 56, vertical: 8),
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: new Text(
'${snapshot.data[index]["branch"]}',
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
trailing: new Text(
'${snapshot.data[index]["count(`branch`)".toString()]}',
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
),
),
);
}),
),
);
}
}),