Flutter Search in Listview from Firebase Realtime Database - flutter

This is the code to show how I get data from Firebase Realtime Database:
List musicList = <Song>[];
_MusicAppState() {
FirebaseDatabase.instance.reference().child("${getCurrentUID()}").once().then((dataSnapshot){
print("Sucessfully loaded the data");
List tmpList = [];
dataSnapshot.value.forEach((k,v){
tmpList.add(v);
});
musicList = tmpList;
setState(() {
});
}).catchError((error) {
print("Failed to loaded the data");
});
}
This is the part of I show the musicList in Listview:
Expanded(
child: ListView.builder(
// List musicList = <Song>[];
itemCount: musicList.length,
itemBuilder: (context, index) => customListTitle(
onTap: () {
playMusic(musicList[index]['url']);
setState(() {
_currentTitle = musicList[index]['title'];
_currentSinger = musicList[index]['singer'];
_currentCover = musicList[index]['cover'];
});
},
title: musicList[index]['title'],
singer: musicList[index]['singer'],
cover: musicList[index]['cover'],
),
),
I'm trying to build a few search function but all of them it didn't work.
Here is one of the search functions that I did:
Widget buildSearch() => SearchWidget(
text: query,
hintText: 'Songs name',
onChanged: searchSong,
);
void searchSong(String query) {
final songs = musicList.where((song) {
final titleLower = song.title.toLowerCase();
final searchLower = query.toLowerCase();
return titleLower.contains(searchLower);
}).toList();
setState(() {
this.query = query;
this.musicList = songs;
});
}
}
This is also my song.dart file:
class Song {
final String title;
final String singer;
final String cover;
final String url;
const Song({
required this.title,
required this.singer,
required this.cover,
required this.url,
});
}
Can someone let me know what's wrong of my code? My search function didn't work

Related

Pagination new data Isn't added to the ListView.builder at the end instead refreshed replacing previous data

I have an API from where I am get 10 data entries on each hit. What I want to do is to add these entries at the end of the ListView.builder List. At the moment it refreshes the ListView.builder instead of populating more entries.
Code for the whole screen is quite longer so I trimmed down only for one type of entry.
class PaginatedScreen extends StatefulWidget {
const PaginatedScreen({Key? key}) : super(key: key);
#override
State<PaginatedScreen> createState() => _PaginatedScreenState();
}
class _PaginatedScreenState extends State<PaginatedScreen> {
final scrollController = ScrollController();
int offSetCurrent = 0;
bool hasMore = true;
bool isLoading = false;
#override
void initState(){
super.initState();
scrollController.addListener(() {
if (scrollController.position.maxScrollExtent == scrollController.offset) {
setState(() {
getDiscussion(offSetCurrent);
print('initial offSet: $offSetCurrent');
offSetCurrent = offSetCurrent + 10;
print('new offset $offSetCurrent');
});
}
});
}
#override
void dispose(){
scrollController.dispose();
super.dispose();
}
Future<PaginatedModel> getDiscussion(offSetCurrent) async {
// if (isLoading) return;
// isLoading = true;
SharedPreferences sp = await SharedPreferences.getInstance();
var parameters = {
'RangeFrom':offSetCurrent.toString(),
'AccessToken':sp.getString('AccessToken'),
'DiscussionID':'22848',
};
var urlfetch = Uri(
scheme: "https",
host: baseUrl,
path: discussionAPIPath,
queryParameters: parameters,
);
try {
http.Response response = await http.get(Uri.parse(urlfetch.toString()));
var data = jsonDecode(response.body.toString());
var snapshot = PaginatedModel.fromJson(data);
var itemsTotal = snapshot.courseLessonDetailsData!.first.discussionDetails!.first.discussionsMessages!.length;
if (itemsTotal < 10){
setState((){
hasMore = false;
});
}
print(data);
print('Discussion API HIT with offset $offSetCurrent');
// isLoading = false;
if (response.statusCode == 200) {
return PaginatedModel.fromJson(data);
}
else {
print(response.statusCode);
return PaginatedModel.fromJson(data);
}
}
catch (e) {
print(e.toString());
rethrow;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: getDiscussion(offSetCurrent),
builder: (dCtx, dSnapshot){
if (dSnapshot.hasData){
var itemCount = dSnapshot.data!.courseLessonDetailsData!.first.discussionDetails!.first.discussionsMessages!.length;
return ListView.builder(
controller: scrollController,
shrinkWrap: true,
itemCount: itemCount + 1,
itemBuilder: (context, idx){
if (idx < itemCount){
return Column(
children: List<Widget>.generate(dSnapshot.data!.courseLessonDetailsData!.first.discussionDetails!.length, (i) => Container(
margin: const EdgeInsets.all(10),
child: Column(
children: [
Html(data: dSnapshot.data!.courseLessonDetailsData!.first.discussionDetails!.first.discussionsMessages![idx].message.toString()),
],
),
))
);
}
else {
return Padding(
padding: EdgeInsets.symmetric(vertical: 32),
child: Center(
child: hasMore? CircularProgressIndicator() : Text('No More Data'),
),
);
}
}
);
}
else {
return const Center(
child: CircularProgressIndicator(),
);
}
}
),
);
}
}
Here is my Model:
class PaginatedModel {
PaginatedModel({
List<CourseLessonDetailsData>? courseLessonDetailsData,}){
_courseLessonDetailsData = courseLessonDetailsData;
}
PaginatedModel.fromJson(dynamic json) {
if (json['CourseLessonDetailsData'] != null) {
_courseLessonDetailsData = [];
json['CourseLessonDetailsData'].forEach((v) {
_courseLessonDetailsData?.add(CourseLessonDetailsData.fromJson(v));
});
}
}
List<CourseLessonDetailsData>? _courseLessonDetailsData;
PaginatedModel copyWith({ List<CourseLessonDetailsData>? courseLessonDetailsData,
}) => PaginatedModel( courseLessonDetailsData: courseLessonDetailsData ?? _courseLessonDetailsData,
);
List<CourseLessonDetailsData>? get courseLessonDetailsData => _courseLessonDetailsData;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
if (_courseLessonDetailsData != null) {
map['CourseLessonDetailsData'] = _courseLessonDetailsData?.map((v) => v.toJson()).toList();
}
return map;
}
}
class CourseLessonDetailsData {
CourseLessonDetailsData({
String? requestStatus,
List<DiscussionDetails>? discussionDetails,}){
_requestStatus = requestStatus;
_discussionDetails = discussionDetails;
}
CourseLessonDetailsData.fromJson(dynamic json) {
_requestStatus = json['request_status'];
if (json['DiscussionDetails'] != null) {
_discussionDetails = [];
json['DiscussionDetails'].forEach((v) {
_discussionDetails?.add(DiscussionDetails.fromJson(v));
});
}
}
String? _requestStatus;
List<DiscussionDetails>? _discussionDetails;
CourseLessonDetailsData copyWith({ String? requestStatus,
List<DiscussionDetails>? discussionDetails,
}) => CourseLessonDetailsData( requestStatus: requestStatus ?? _requestStatus,
discussionDetails: discussionDetails ?? _discussionDetails,
);
String? get requestStatus => _requestStatus;
List<DiscussionDetails>? get discussionDetails => _discussionDetails;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['request_status'] = _requestStatus;
if (_discussionDetails != null) {
map['DiscussionDetails'] = _discussionDetails?.map((v) => v.toJson()).toList();
}
return map;
}
}
class DiscussionDetails {
DiscussionDetails({
List<DiscussionsMessages>? discussionsMessages,}){
_discussionsMessages = discussionsMessages;
}
DiscussionDetails.fromJson(dynamic json) {
if (json['DiscussionsMessages'] != null) {
_discussionsMessages = [];
json['DiscussionsMessages'].forEach((v) {
_discussionsMessages?.add(DiscussionsMessages.fromJson(v));
});
}
}
List<DiscussionsMessages>? _discussionsMessages;
DiscussionDetails copyWith({ List<DiscussionsMessages>? discussionsMessages,
}) => DiscussionDetails( discussionsMessages: discussionsMessages ?? _discussionsMessages,
);
List<DiscussionsMessages>? get discussionsMessages => _discussionsMessages;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
if (_discussionsMessages != null) {
map['DiscussionsMessages'] = _discussionsMessages?.map((v) => v.toJson()).toList();
}
return map;
}
}
class DiscussionsMessages {
DiscussionsMessages({
String? messageID,
String? id,
String? studentName,
String? message,
String? replyMessage,
List<OtherStudentMessages>? otherStudentMessages,}){
_messageID = messageID;
_id = id;
_studentName = studentName;
_message = message;
_replyMessage = replyMessage;
_otherStudentMessages = otherStudentMessages;
}
DiscussionsMessages.fromJson(dynamic json) {
_messageID = json['MessageID'];
_id = json['ID'];
_studentName = json['StudentName'];
_message = json['Message'];
_replyMessage = json['ReplyMessage'];
if (json['OtherStudentMessages'] != null) {
_otherStudentMessages = [];
json['OtherStudentMessages'].forEach((v) {
_otherStudentMessages?.add(OtherStudentMessages.fromJson(v));
});
}
}
String? _messageID;
String? _id;
String? _studentName;
String? _message;
String? _replyMessage;
List<OtherStudentMessages>? _otherStudentMessages;
DiscussionsMessages copyWith({ String? messageID,
String? id,
String? studentName,
String? message,
String? replyMessage,
List<OtherStudentMessages>? otherStudentMessages,
}) => DiscussionsMessages( messageID: messageID ?? _messageID,
id: id ?? _id,
studentName: studentName ?? _studentName,
message: message ?? _message,
replyMessage: replyMessage ?? _replyMessage,
otherStudentMessages: otherStudentMessages ?? _otherStudentMessages,
);
String? get messageID => _messageID;
String? get id => _id;
String? get studentName => _studentName;
String? get message => _message;
String? get replyMessage => _replyMessage;
List<OtherStudentMessages>? get otherStudentMessages => _otherStudentMessages;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['MessageID'] = _messageID;
map['ID'] = _id;
map['StudentName'] = _studentName;
map['Message'] = _message;
map['ReplyMessage'] = _replyMessage;
if (_otherStudentMessages != null) {
map['OtherStudentMessages'] = _otherStudentMessages?.map((v) => v.toJson()).toList();
}
return map;
}
}
class OtherStudentMessages {
OtherStudentMessages({
String? replyID,
String? repliedBy,
String? replyMessage,}){
_replyID = replyID;
_repliedBy = repliedBy;
_replyMessage = replyMessage;
}
OtherStudentMessages.fromJson(dynamic json) {
_replyID = json['ReplyID'];
_repliedBy = json['RepliedBy'];
_replyMessage = json['ReplyMessage'];
}
String? _replyID;
String? _repliedBy;
String? _replyMessage;
OtherStudentMessages copyWith({ String? replyID,
String? repliedBy,
String? replyMessage,
}) => OtherStudentMessages( replyID: replyID ?? _replyID,
repliedBy: repliedBy ?? _repliedBy,
replyMessage: replyMessage ?? _replyMessage,
);
String? get replyID => _replyID;
String? get repliedBy => _repliedBy;
String? get replyMessage => _replyMessage;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['ReplyID'] = _replyID;
map['RepliedBy'] = _repliedBy;
map['ReplyMessage'] = _replyMessage;
return map;
}
}
And here is the JSON response from the API. (with one entry)
{
"CourseLessonDetailsData": [
{
"request_status": "Successful",
"DiscussionDetails": [
{
"DiscussionsMessages": [
{
"MessageID": "301",
"ID": "48",
"StudentName": "Person Name",
"Message": "This is from Message",
"ReplyMessage": "This is a Reply Message",
"OtherStudentMessages": [
{
"ReplyID":"939",
"RepliedBy":"Reply Person",
"ReplyMessage":"resubmit means you can submit again if you want until the deadline is over"
}
]
}
]
}
]
}
]
}
I edited your code but could not test it out, please check it if it works.
You need to make a list variable that will hold all your paginated data. Then call your future and then after result is not null, add it to your list and change your loading status to false. After, use scrolls and scrollListener reaches max extent, change offset and call your api again and after result is not null, again add to your list.
class PaginatedScreen extends StatefulWidget {
const PaginatedScreen({Key? key}) : super(key: key);
#override
State<PaginatedScreen> createState() => _PaginatedScreenState();
}
class _PaginatedScreenState extends State<PaginatedScreen> {
final scrollController = ScrollController();
int offSetCurrent = 0;
bool hasMore = true;
bool isLoading = true;
List<PaginatedModel> listOfData = <PaginatedModel>[];
#override
void initState() async {
super.initState();
var result = await getDiscussion(offSetCurrent);
if (result != null) {
setState(() {
listOfData.add(result);
isLoading = false;
});
}
scrollController.addListener(() async {
if (scrollController.position.maxScrollExtent == scrollController.offset) {
offSetCurrent = offSetCurrent + 10;
setState(() {
isLoading = true;
});
var result = await getDiscussion(offSetCurrent);
setState(() {
listOfData.add(result!);
isLoading = false;
});
}
});
}
#override
void dispose(){
scrollController.dispose();
super.dispose();
}
Future<PaginatedModel?> getDiscussion(offSetCurrent) async {
// if (isLoading) return;
// isLoading = true;
SharedPreferences sp = await SharedPreferences.getInstance();
var parameters = {
'RangeFrom':offSetCurrent.toString(),
'AccessToken':sp.getString('AccessToken'),
'DiscussionID':'22848',
};
var urlfetch = Uri(
scheme: "https",
host: baseUrl,
path: discussionAPIPath,
queryParameters: parameters,
);
try {
http.Response response = await http.get(Uri.parse(urlfetch.toString()));
var data = jsonDecode(response.body.toString());
var snapshot = PaginatedModel.fromJson(data);
var itemsTotal = snapshot.courseLessonDetailsData!.first.discussionDetails!.first.discussionsMessages!.length;
if (itemsTotal < 10){
setState((){
hasMore = false;
});
}
print(data);
print('Discussion API HIT with offset $offSetCurrent');
// isLoading = false;
if (response.statusCode == 200) {
return PaginatedModel.fromJson(data);
}
else {
print(response.statusCode);
return null;
}
}
catch (e) {
print(e.toString());
rethrow;
// return null;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: isLoading
? CircularProgressIndicator()
: ListView.builder(
itemCount: listOfData.length,
itemBuilder: (context, index) {
//do your widget building here
// do whatever you did in your future builder here
return Text(listOfData[index].courseLessonDetailsData![0].requestStatus!);
},
),
// body: FutureBuilder(
// future: getDiscussion(offSetCurrent),
// builder: (dCtx, dSnapshot){
// if (dSnapshot.hasData){
// var itemCount = dSnapshot.data!.courseLessonDetailsData!.first.discussionDetails!.first.discussionsMessages!.length;
// return ListView.builder(
// controller: scrollController,
// shrinkWrap: true,
// itemCount: itemCount + 1,
// itemBuilder: (context, idx){
// if (idx < itemCount){
// return Column(
// children: List<Widget>.generate(dSnapshot.data!.courseLessonDetailsData!.first.discussionDetails!.length, (i) => Container(
// margin: const EdgeInsets.all(10),
// child: Column(
// children: [
// Html(data: dSnapshot.data!.courseLessonDetailsData!.first.discussionDetails!.first.discussionsMessages![idx].message.toString()),
// ],
// ),
// ))
// );
// }
// else {
// return Padding(
// padding: EdgeInsets.symmetric(vertical: 32),
// child: Center(
// child: hasMore? CircularProgressIndicator() : Text('No More Data'),
// ),
// );
// }
// }
// );
// }
// else {
// return const Center(
// child: CircularProgressIndicator(),
// );
// }
// }
// ),
);
}
}

Retrieve Data from Realtime Database in Flutter

I want to retrieve data from a realtime database for a flutter application. My data is built like this.
I need to loop through this data to display it on the application (ecommerce app, obviously). I have tried and failed in many ways. Currently when trying to get the data I see "Instance of '_Future'" as the message.
class Cart extends StatefulWidget {
Cart({Key? key}) : super(key: key);
#override
State<Cart> createState() => _CartState();
}
class _CartState extends State<Cart> {
DatabaseReference ref = FirebaseDatabase.instance.ref();
Object? products;
List productList = [];
String displayText = 'Results go here!';
snapshot() async {
final snapshot = await ref.child('Products').get();
productList = [];
if (snapshot.exists) {
productList.add(snapshot.value);
products = (snapshot.value);
print(snapshot);
print(snapshot.value);
} else {
print('No Data Available');
}
}
#override
void initState() {
super.initState();
snapshot();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const PreferredSize(
preferredSize: Size.fromHeight(60), child: MyAppBar()),
body: Column(
children: [
ElevatedButton(
onPressed: () async {
// await ref.set({"name": "Tyler"});
snapshot();
},
child: Text("Add Data"),
),
Text("${snapshot()}", style: TextStyle(color: Colors.white))
],
)
);
}
}
I also have this data class built from other posts I have seen. I have to admit, I am not entirely sure how to use it.
import 'dart:convert';
class ProductData {
final int productID;
final String productCategory;
final String productDesc;
final String productName;
final String productPrice;
final String productSize;
final bool productInStock;
final String productImage1;
final String productGender;
final String productImage2;
ProductData(
{required this.productID,
required this.productCategory,
required this.productDesc,
required this.productName,
required this.productPrice,
required this.productSize,
required this.productInStock,
required this.productImage1,
required this.productGender,
required this.productImage2});
ProductData copyWith(
{int? productID,
String? productCategory,
String? productDesc,
String? productName,
String? productPrice,
String? productSize,
bool? productInStock,
String? productImage1,
String? productGender,
String? productImage2}) {
return ProductData(
productID: productID ?? this.productID,
productCategory: productCategory ?? this.productCategory,
productDesc: productDesc ?? this.productDesc,
productName: productName ?? this.productName,
productPrice: productPrice ?? this.productPrice,
productSize: productSize ?? this.productSize,
productInStock: productInStock ?? this.productInStock,
productImage1: productImage1 ?? this.productImage1,
productGender: productGender ?? this.productGender,
productImage2: productImage2 ?? this.productImage2,
);
}
Map<String, dynamic> toMap() {
return <String, dynamic>{
'productID': productID,
'productCategory': productCategory,
'productDesc': productDesc,
'productName': productName,
'productPrice': productPrice,
'productSize': productSize,
'productInStock': productInStock,
'productImage1': productImage1,
'productGender': productGender,
'productImage2': productImage2,
};
}
factory ProductData.fromMap(Map<String, dynamic> map) {
return ProductData(
productID: map['productID'] as int,
productCategory: map['productCategory'] as String,
productDesc: map['productDesc'] as String,
productName: map['productName'] as String,
productPrice: map['productPrice'] as String,
productSize: map['productSize'] as String,
productInStock: map['productInStock'] as bool,
productImage1: map['productImage1'] as String,
productGender: map['productGender'] as String,
productImage2: map['productImage2'] as String,
);
}
String toJson() => json.encode(toMap());
factory ProductData.fromJson(String source) =>
ProductData.fromMap(json.decode(source) as Map<String, dynamic>);
#override
String toString() {
return 'ProductData(productID: $productID, productCategory: $productCategory, productDesc: $productDesc, productName: $productName, productPrice: $productPrice, productSize: $productSize, productInStock: $productInStock, productImage11: $productImage1, productGender: $productGender, productImage2: $productImage2)';
}
#override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ProductData &&
other.productID == productID &&
other.productCategory == productCategory &&
other.productDesc == productDesc &&
other.productName == productName &&
other.productPrice == productPrice &&
other.productSize == productSize &&
other.productInStock == productInStock &&
other.productImage1 == productImage1 &&
other.productGender == productGender &&
other.productImage2 == productImage2;
}
#override
int get hashCode {
return productID.hashCode ^
productCategory.hashCode ^
productDesc.hashCode ^
productName.hashCode ^
productPrice.hashCode ^
productSize.hashCode ^
productInStock.hashCode ^
productImage1.hashCode ^
productGender.hashCode ^
productImage2.hashCode;
}
}
Since the data is loaded from Firebase asynchronously, its get() method returns a Future. That's also why you had to declare your snapshot() function as async, which means that you also return a Future.
On its own the rendering code doesn't know anything about Futures, so it renders it by calling its toString() method, which leads to the output you see:
Instance of '_Future'
What you want instead is to wait for the future to resolve, which is just a fancy way of saying that you want to wait for the data to load. An easy way to do that is to use a FutureBuilder, which handles the asynchronous nature of a Future and all possible states it can be in.
That'd look something like:
snapshot() async {
final snapshot = await ref.child('Products').get();
productList = [];
if (snapshot.exists) {
productList.add(snapshot.value);
products = (snapshot.value);
} else {
print('No Data Available');
}
return productList;
}
body: Column(
children: [
ElevatedButton(
onPressed: () async {
snapshot();
},
child: Text("Add Data"),
),
FutureBuilder(
future: snapshot(),
builder: (BuildContext context, AsyncSnapshot asyncSnapshot) {
if (snapshot.hasData) {
var productList = asyncSnapshot.data! as List;
return Text(productList.length.toString());
} else if (snapshot.hasError) {
return Text('Error: ${asyncSnapshot.error}');
} else {
return CircularProgressIndicator(),
}
}
)
],
)

Understanding and implementing OOP (in flutter/dart) to abstract shared structure and functionality

(Edited to clarify & update with progress)
When building an app with various lists of items and some cross-referencing between them; e.g. I have a stock class and a StockSet that extends ChangeNotifier as well as holds some basic rest functionality etc. But I'm also going to have Clients, Meetings etc, all of which are basically the same except for minor differences in fields.
So I recon I should define a parent class, say Item and ItemSet that, and then the actual elements in my app will extend those. - Or is that over-engineering?
However, in say my current StockSet I have methods to fetch either a single, or a number of stock items, i.e. ...Future<Stock> _fetchAStock() async {...
the only significant difference between it and say client records will be the REST url, and type of object returned (and give or take a few fields).
Is it better to have individual methods in all the child level classes - seems simpler though more code = more potential errors etc. OR build the fetch functionality into the parent class with some kind of abstraction/specification/parameterization(/or external configuration look-up) of the case specific differences?
New-ish to OOP and Flutter/Dart and it seems clearer some days than others... this is an others :-)
What I have:
class StocksSetRoute extends StatelessWidget with asc_alertBar {
const StocksSetRoute({Key? key}) : super(key: key);
static const indexStr = 'stocks';
static const labelStr = 'Properties';
static GlobalKey myKey = GlobalKey();
static GlobalKey parentKey = GlobalKey();
#override
Widget build(BuildContext context) {
var stockSet = context.watch<StockSet>();
return Scaffold(
key: parentKey,
appBar: AppBar(
title: const TitleRow(indexStr: indexStr, labelStr: labelStr),
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// these will be filters, order toggle etc.
children: [
ElevatedButton(
onPressed: (stockSet.isRemoteEmpty)
? null
: () async {
try {
await stockSet.fetch(5);
} catch (e) {
alertBar(
'Could not fetch any more. ', stockSet as ItemSet,
backgroundColor: Colors.grey, context: context);
}
},
child: const Text('Fetch More'),
),
ElevatedButton(
onPressed:
(stockSet.stocks.isEmpty) ? null : () => stockSet.clear(),
child: const Text('Clear'),
),
],
),
Expanded(
child: StockListViewBuilder(
stockSet: stockSet, theKey: myKey, alert: alertBar))
],
),
);
}
}
class StockListViewBuilder extends StatefulWidget {
final StockSet stockSet;
final GlobalKey theKey;
final Function alert;
const StockListViewBuilder({
Key? key,
required this.stockSet,
required this.theKey,
required this.alert,
}) : super(key: key);
#override
State<StockListViewBuilder> createState() => _StockListViewBuilderState();
}
class _StockListViewBuilderState extends State<StockListViewBuilder>
with asc_alertBar {
final ScrollController _scrollController = ScrollController();
late double _scrollPosition;
late double _maxScrollExtent;
late bool isThisTheEnd = false;
_scrollListener() async {
setState(() {
_scrollPosition = _scrollController.position.pixels;
_maxScrollExtent = _scrollController.position.maxScrollExtent;
});
if (!isThisTheEnd && _scrollPosition / _maxScrollExtent > 0.90) {
isThisTheEnd = true;
if (widget.stockSet.isRemoteEmpty) {
alertBar('No more items available', null, context: context);
} else {
await widget.stockSet.fetch(5);
}
}
if (isThisTheEnd && _scrollPosition / _maxScrollExtent <= 0.90) {
isThisTheEnd = false;
}
}
#override
void initState() {
super.initState();
int listCount;
_scrollController.addListener(_scrollListener);
WidgetsBinding.instance.addPostFrameCallback((_) async {
listCount = widget.stockSet.stocks.length;
if (listCount < 10 && !widget.stockSet.isRemoteEmpty) {
try {
await widget.stockSet.fetch(10);
} catch (e) {
super.setState(() {
widget.alert("Can't load stock.", widget.stockSet,
backgroundColor: Colors.red);
});
}
}
});
}
#override
Widget build(BuildContext context) {
return ListView.builder(
scrollDirection: Axis.vertical,
controller: _scrollController,
shrinkWrap: true,
key: widget.theKey,
itemCount: widget.stockSet.stocks.length + 1,
itemBuilder: (context, index) {
if (index <= widget.stockSet.stocks.length - 1) {
return InkWell(
onTap: (() => Navigator.pushNamed(
context,
'/stocks/stock',
arguments: ScreenArguments(widget.stockSet.stocks[index]),
)),
child: StockListItem(
stock: widget.stockSet.stocks[index],
));
} else {
return LoadingItemNotifier(
isLoading: widget.stockSet.isBusyLoading,
);
}
},
);
}
}
class StockSet extends ItemSet {
final List<Stock> _stocks = [];
List<Stock> get stocks => _stocks;
List<int> getHaveStocksIds() {
final List<int> ids = _stocks.map((stock) => stock.id).toList();
return ids;
}
void add(Stock stock) {
_stocks.add(stock);
notifyListeners();
}
void remove(Stock stock) {
_stocks.remove(stock);
notifyListeners();
}
void clear() {
_stocks.clear();
isBusyLoading = false;
isRemoteEmpty = false;
notifyListeners();
}
Future<void> fetch([int num = 1]) async {
int i = 0;
for (i; i < num; i++) {
if (!isRemoteEmpty) {
try {
Stock tmpStock = await _fetchAStock();
if (getHaveStocksIds().contains(tmpStock.id)) {
throw Exception('We allready have ${tmpStock.id}');
}
add(tmpStock);
} catch (e) {
i = num;
isRemoteEmpty = true;
isBusyLoading = false;
notifyListeners();
throw Exception('No more to fetch $e');
}
}
}
return;
}
Future<Stock> _fetchAStock() async {
List<int> have = getHaveStocksIds();
final queryParameters = {
'exclude': json.encode(have),
};
isBusyLoading = true;
notifyListeners();
try {
final response = await http.post(
Uri.https('onethread.design', 'agency/wp-json/ypagp/v1/pr-get-a',
queryParameters),
);
isBusyLoading = false;
notifyListeners();
if (response.statusCode != 200) {
throw HttpException('${response.statusCode}');
}
final Map<String, dynamic> map = json.decode(response.body);
return Stock(
id: map['id'] as int,
title: map['title'] as String,
description: map['description'] as String,
thumbUrl: map['thumbUrl'] as String,
);
} on SocketException {
feedback = 'Please enable an internet connection.';
notifyListeners();
} on HttpException {
feedback = "Couldn't find the/a post.";
notifyListeners();
} on FormatException {
feedback = "Bad response format.";
} catch (e, s) {
feedback = 'fetchA catch $e $s ';
}
throw Exception('Could not _fetchAStock');
}
}
class ItemSet extends ChangeNotifier {
bool isBusyLoading = false;
bool isRemoteEmpty = false;
String? feedback;
}
what I'm aiming at (and have partially succeeded in implementing) is something like
class StockSet extends ItemSet and class ItemSet extends ChangeNotifier {...
class Item extends Equatable {
const Item({
required this.id,
required this.title,
required this.description,
});
final int id;
final String title;
final String description;
#override
List<Object> get props => [id, title, description];
}
and I think then
class Stock extends Item {
final String thumbUrl;
const Stock(
{required super.id, required super.title, required super.description, required this.thumbUrl});
#override
List<Object> get props => [id, title, description, thumbUrl];
}
though when it comes to the ItemSet's methods, e.g.
been trying things like e.g.
in ItemSet
Future<Type> fetchAItem(String typeSlug, Type type) async {
List<int> have = getHaveItemsIds();
final queryParameters = {
'exclude': json.encode(have),
};
developer.log('_fetchAItem $queryParameters');
isBusyLoading = true;
notifyListeners();
try {
final response = await http.post(
Uri.https('###', 'a###/v1/$typeSlug-get-a',
queryParameters),
);
developer.log('response.statusCode:${response.statusCode}');
isBusyLoading = false;
notifyListeners();
if (response.statusCode != 200) {
throw HttpException('${response.statusCode}');
}
final Map<String, dynamic> map = json.decode(response.body);
return Item(
id: map['id'] as int,
title: map['title'] as String,
description: map['description'] as String,
) as type;// no! - was worth a guess though
} on...
but I'm getting lost in the plumbing, should I use #override in the child classes? seems clunky - or maybe use more mixins?.
Thanks in advance
and the alertBar mixin...
mixin asc_alertBar {
void alertBar(
String message,
ItemSet? itemSet, {
String label = 'Okay',
Function? action,
Color? backgroundColor,
required BuildContext context,
}) {
if (itemSet != null) {
String? itemSetFeedback = itemSet.feedback;
if (itemSetFeedback != null) {
message += '\n$itemSetFeedback';
itemSet.feedback = null;
}
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: backgroundColor,
duration: const Duration(seconds: 15),
content: AnimatedText(
textContent: message,
durationFactor: 0.25,
),
action: SnackBarAction(
label: label,
onPressed: () => action,
),
),
);
}
}
I hope the question makes sense.

type 'List<dynamic>' is not a subtype of type 'String'

It contains lots of list and nested objects. I need to display all the DishName from the json file
Json URL adress : Json address!! Check this out
i wanted to display the DishName in to my app in which there are lots of dishname in the json list.
i parsed the json file using instantly parse json website.
But now i dont know how to implent it on my app
code i wrote :
body: Container(
child: Center(
child: FutureBuilder(
future:
DefaultAssetBundle.of(context).loadString('jsons/data.json'),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
var myData = jsonDecode(snapshot.data);
final welcome = welcomeFromJson(myData);
print(myData.length);
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
title: Text(welcome[index]
.tableMenuList[index]
.categoryDishes[index]
.dishName),
));
},
itemCount: myData == null ? 0 : myData.length,
);
},
),
),
),
i am getting error : type 'List' is not a subtype of type 'String'
this is the json class:-
// To parse this JSON data, do
//
// final welcome = welcomeFromJson(jsonString);
import 'dart:convert';
List<Welcome> welcomeFromJson(String str) =>
List<Welcome>.from(json.decode(str).map((x) => Welcome.fromJson(x)));
String welcomeToJson(List<Welcome> data) =>
json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class Welcome {
String restaurantId;
String restaurantName;
String restaurantImage;
String tableId;
String tableName;
String branchName;
String nexturl;
List<TableMenuList> tableMenuList;
Welcome({
this.restaurantId,
this.restaurantName,
this.restaurantImage,
this.tableId,
this.tableName,
this.branchName,
this.nexturl,
this.tableMenuList,
});
factory Welcome.fromJson(Map<String, dynamic> json) => Welcome(
restaurantId: json["restaurant_id"],
restaurantName: json["restaurant_name"],
restaurantImage: json["restaurant_image"],
tableId: json["table_id"],
tableName: json["table_name"],
branchName: json["branch_name"],
nexturl: json["nexturl"],
tableMenuList: List<TableMenuList>.from(
json["table_menu_list"].map((x) => TableMenuList.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"restaurant_id": restaurantId,
"restaurant_name": restaurantName,
"restaurant_image": restaurantImage,
"table_id": tableId,
"table_name": tableName,
"branch_name": branchName,
"nexturl": nexturl,
"table_menu_list":
List<dynamic>.from(tableMenuList.map((x) => x.toJson())),
};
}
class TableMenuList {
String menuCategory;
String menuCategoryId;
String menuCategoryImage;
String nexturl;
List<CategoryDish> categoryDishes;
TableMenuList({
this.menuCategory,
this.menuCategoryId,
this.menuCategoryImage,
this.nexturl,
this.categoryDishes,
});
factory TableMenuList.fromJson(Map<String, dynamic> json) => TableMenuList(
menuCategory: json["menu_category"],
menuCategoryId: json["menu_category_id"],
menuCategoryImage: json["menu_category_image"],
nexturl: json["nexturl"],
categoryDishes: List<CategoryDish>.from(
json["category_dishes"].map((x) => CategoryDish.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"menu_category": menuCategory,
"menu_category_id": menuCategoryId,
"menu_category_image": menuCategoryImage,
"nexturl": nexturl,
"category_dishes":
List<dynamic>.from(categoryDishes.map((x) => x.toJson())),
};
}
class AddonCat {
String addonCategory;
String addonCategoryId;
int addonSelection;
String nexturl;
List<CategoryDish> addons;
AddonCat({
this.addonCategory,
this.addonCategoryId,
this.addonSelection,
this.nexturl,
this.addons,
});
factory AddonCat.fromJson(Map<String, dynamic> json) => AddonCat(
addonCategory: json["addon_category"],
addonCategoryId: json["addon_category_id"],
addonSelection: json["addon_selection"],
nexturl: json["nexturl"],
addons: List<CategoryDish>.from(
json["addons"].map((x) => CategoryDish.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"addon_category": addonCategory,
"addon_category_id": addonCategoryId,
"addon_selection": addonSelection,
"nexturl": nexturl,
"addons": List<dynamic>.from(addons.map((x) => x.toJson())),
};
}
class CategoryDish {
String dishId;
String dishName;
double dishPrice;
String dishImage;
DishCurrency dishCurrency;
int dishCalories;
String dishDescription;
bool dishAvailability;
int dishType;
String nexturl;
List<AddonCat> addonCat;
CategoryDish({
this.dishId,
this.dishName,
this.dishPrice,
this.dishImage,
this.dishCurrency,
this.dishCalories,
this.dishDescription,
this.dishAvailability,
this.dishType,
this.nexturl,
this.addonCat,
});
factory CategoryDish.fromJson(Map<String, dynamic> json) => CategoryDish(
dishId: json["dish_id"],
dishName: json["dish_name"],
dishPrice: json["dish_price"].toDouble(),
dishImage: json["dish_image"],
dishCurrency: dishCurrencyValues.map[json["dish_currency"]],
dishCalories: json["dish_calories"],
dishDescription: json["dish_description"],
dishAvailability: json["dish_Availability"],
dishType: json["dish_Type"],
nexturl: json["nexturl"] == null ? null : json["nexturl"],
addonCat: json["addonCat"] == null
? null
: List<AddonCat>.from(
json["addonCat"].map((x) => AddonCat.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"dish_id": dishId,
"dish_name": dishName,
"dish_price": dishPrice,
"dish_image": dishImage,
"dish_currency": dishCurrencyValues.reverse[dishCurrency],
"dish_calories": dishCalories,
"dish_description": dishDescription,
"dish_Availability": dishAvailability,
"dish_Type": dishType,
"nexturl": nexturl == null ? null : nexturl,
"addonCat": addonCat == null
? null
: List<dynamic>.from(addonCat.map((x) => x.toJson())),
};
}
enum DishCurrency { SAR }
final dishCurrencyValues = EnumValues({"SAR": DishCurrency.SAR});
class EnumValues<T> {
Map<String, T> map;
Map<T, String> reverseMap;
EnumValues(this.map);
Map<T, String> get reverse {
if (reverseMap == null) {
reverseMap = map.map((k, v) => new MapEntry(v, k));
}
return reverseMap;
}
}
I personally use this one for generating dart classes from JSON https://javiercbk.github.io/json_to_dart/
From my experience most of these automatic class builders are flawed to some extent and it is good practice to go over the generated dart manually. Or try your luck and walk the stack trace to see where parsing failed (usually when handling int and double from what I've seen).
Anyhow back to your problem, from looking at your code I see you are misusing indexes and generally having problems with json data structure:
title: Text(welcome[index]
.tableMenuList[index]
.categoryDishes[index]
.dishName),
The code above can easily throw "RangeError (index): Invalid value: Not in range..." (example if welcome[ 2 ] has only 1 tableMenuList). Even if all those arrays above are somehow the same length you would still not get the expected result.. The list would end up showing you 1st meal from the 1st menu list and 1st category dish, then next one would be 2nd meal from the 2nd menu and the 2nd restaurant.
Also because of the same reason as above the following code isn't enough:
itemCount: myData == null ? 0 : myData.length,
You need to have an itemCount for each of those lists you are using.
I've modified your code to work with the given JSON and changed a few things, in the end it looks like this.
I'm posting my source bellow, hopefully it will make things clearer for you:
pubspec.yaml
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
http: any
welcome_model.dart
class Welcome {
String restaurantId;
String restaurantName;
String restaurantImage;
String tableId;
String tableName;
String branchName;
String nexturl;
List<TableMenuList> tableMenuList;
Welcome(
{this.restaurantId,
this.restaurantName,
this.restaurantImage,
this.tableId,
this.tableName,
this.branchName,
this.nexturl,
this.tableMenuList});
Welcome.fromJson(Map<String, dynamic> json) {
restaurantId = json['restaurant_id'];
restaurantName = json['restaurant_name'];
restaurantImage = json['restaurant_image'];
tableId = json['table_id'];
tableName = json['table_name'];
branchName = json['branch_name'];
nexturl = json['nexturl'];
if (json['table_menu_list'] != null) {
tableMenuList = new List<TableMenuList>();
json['table_menu_list'].forEach((v) {
tableMenuList.add(new TableMenuList.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['restaurant_id'] = this.restaurantId;
data['restaurant_name'] = this.restaurantName;
data['restaurant_image'] = this.restaurantImage;
data['table_id'] = this.tableId;
data['table_name'] = this.tableName;
data['branch_name'] = this.branchName;
data['nexturl'] = this.nexturl;
if (this.tableMenuList != null) {
data['table_menu_list'] =
this.tableMenuList.map((v) => v.toJson()).toList();
}
return data;
}
}
class TableMenuList {
String menuCategory;
String menuCategoryId;
String menuCategoryImage;
String nexturl;
List<CategoryDishes> categoryDishes;
TableMenuList(
{this.menuCategory,
this.menuCategoryId,
this.menuCategoryImage,
this.nexturl,
this.categoryDishes});
TableMenuList.fromJson(Map<String, dynamic> json) {
menuCategory = json['menu_category'];
menuCategoryId = json['menu_category_id'];
menuCategoryImage = json['menu_category_image'];
nexturl = json['nexturl'];
if (json['category_dishes'] != null) {
categoryDishes = new List<CategoryDishes>();
json['category_dishes'].forEach((v) {
categoryDishes.add(new CategoryDishes.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['menu_category'] = this.menuCategory;
data['menu_category_id'] = this.menuCategoryId;
data['menu_category_image'] = this.menuCategoryImage;
data['nexturl'] = this.nexturl;
if (this.categoryDishes != null) {
data['category_dishes'] =
this.categoryDishes.map((v) => v.toJson()).toList();
}
return data;
}
}
class CategoryDishes {
String dishId;
String dishName;
double dishPrice;
String dishImage;
String dishCurrency;
double dishCalories;
String dishDescription;
bool dishAvailability;
int dishType;
String nexturl;
List<AddonCat> addonCat;
CategoryDishes(
{this.dishId,
this.dishName,
this.dishPrice,
this.dishImage,
this.dishCurrency,
this.dishCalories,
this.dishDescription,
this.dishAvailability,
this.dishType,
this.nexturl,
this.addonCat});
CategoryDishes.fromJson(Map<String, dynamic> json) {
dishId = json['dish_id'];
dishName = json['dish_name'];
dishPrice = json['dish_price'];
dishImage = json['dish_image'];
dishCurrency = json['dish_currency'];
dishCalories = json['dish_calories'];
dishDescription = json['dish_description'];
dishAvailability = json['dish_Availability'];
dishType = json['dish_Type'];
nexturl = json['nexturl'];
if (json['addonCat'] != null) {
addonCat = new List<AddonCat>();
json['addonCat'].forEach((v) {
addonCat.add(new AddonCat.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['dish_id'] = this.dishId;
data['dish_name'] = this.dishName;
data['dish_price'] = this.dishPrice;
data['dish_image'] = this.dishImage;
data['dish_currency'] = this.dishCurrency;
data['dish_calories'] = this.dishCalories;
data['dish_description'] = this.dishDescription;
data['dish_Availability'] = this.dishAvailability;
data['dish_Type'] = this.dishType;
data['nexturl'] = this.nexturl;
if (this.addonCat != null) {
data['addonCat'] = this.addonCat.map((v) => v.toJson()).toList();
}
return data;
}
}
class AddonCat {
String addonCategory;
String addonCategoryId;
int addonSelection;
String nexturl;
List<Addons> addons;
AddonCat(
{this.addonCategory,
this.addonCategoryId,
this.addonSelection,
this.nexturl,
this.addons});
AddonCat.fromJson(Map<String, dynamic> json) {
addonCategory = json['addon_category'];
addonCategoryId = json['addon_category_id'];
addonSelection = json['addon_selection'];
nexturl = json['nexturl'];
if (json['addons'] != null) {
addons = new List<Addons>();
json['addons'].forEach((v) {
addons.add(new Addons.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['addon_category'] = this.addonCategory;
data['addon_category_id'] = this.addonCategoryId;
data['addon_selection'] = this.addonSelection;
data['nexturl'] = this.nexturl;
if (this.addons != null) {
data['addons'] = this.addons.map((v) => v.toJson()).toList();
}
return data;
}
}
class Addons {
String dishId;
String dishName;
double dishPrice;
String dishImage;
String dishCurrency;
double dishCalories;
String dishDescription;
bool dishAvailability;
int dishType;
Addons(
{this.dishId,
this.dishName,
this.dishPrice,
this.dishImage,
this.dishCurrency,
this.dishCalories,
this.dishDescription,
this.dishAvailability,
this.dishType});
Addons.fromJson(Map<String, dynamic> json) {
dishId = json['dish_id'];
dishName = json['dish_name'];
dishPrice = json['dish_price'];
dishImage = json['dish_image'];
dishCurrency = json['dish_currency'];
dishCalories = json['dish_calories'];
dishDescription = json['dish_description'];
dishAvailability = json['dish_Availability'];
dishType = json['dish_Type'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['dish_id'] = this.dishId;
data['dish_name'] = this.dishName;
data['dish_price'] = this.dishPrice;
data['dish_image'] = this.dishImage;
data['dish_currency'] = this.dishCurrency;
data['dish_calories'] = this.dishCalories;
data['dish_description'] = this.dishDescription;
data['dish_Availability'] = this.dishAvailability;
data['dish_Type'] = this.dishType;
return data;
}
}
welcome_page.dart
import 'package:flutter/material.dart';
import 'welcome_model.dart';
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:http/http.dart' as http;
class WelcomePage extends StatefulWidget {
#override
_WelcomePageState createState() => new _WelcomePageState();
}
class _WelcomePageState extends State<WelcomePage> {
List<Welcome> welcomeList;
bool isLoading = true;
int selectedRestaurantIndex = 0;
String titleString = "Restaurant";
#override
void initState() {
super.initState();
_fetchData();
}
#override
void setState(fn) {
if(mounted){
super.setState(fn);
}
}
#override
void dispose() {
super.dispose();
}
_fetchData() async{
setState(() {
isLoading = true;
});
// String url = Uri.encodeFull("http://www.mocky.io/v2/5dfccffc310000efc8d2c1ad"); // original json has only one restaurant
// the one bellow has 3 restaurants,
// 1st one was left untouched,
// 2nd one (Steak house) has only "From The Barnyard" and "Fast Food" menu types
// 3rd one (Bistro cafe) has only "Fresh From The Sea" and "Fast Food" menu types
String url = Uri.encodeFull("http://www.mocky.io/v2/5e4aa68c2f0000690097d3f6");
var parsedJson;
try {
http.Response response = await http.get(url).
timeout(Duration(seconds: 10));
if (response.statusCode == 200) {
if (response.bodyBytes != null) {
parsedJson = json.decode(utf8.decode(response.bodyBytes));
}
} else {
print('RESPONSE CODE != 200 !');
}
} on TimeoutException catch (e) {
print('Error: $e');
} on SocketException catch (e) {
print('Error: $e');
} on Error catch (e) {
print('Error: $e');
}
if(parsedJson!=null){
if(parsedJson.length>0){
welcomeList = new List<Welcome>();
for(int i=0; i<parsedJson.length; i++) {
try {
welcomeList.add(Welcome.fromJson(parsedJson[i]));
} catch (e, s){
print('Eroor parsing from JSON! For # $i');
print('Error: $e');
print('Stacktrace: $s');
}
}
if(welcomeList[0]!=null && welcomeList[0].restaurantName!=null) {
titleString = welcomeList[0].restaurantName;
}
}
}
setState(() {
isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
iconTheme: IconThemeData(
color: Colors.white,
),
title: Text(titleString),
centerTitle: true,
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text("Select a restaurant", style: TextStyle(color: Colors.white, fontSize: 28),textAlign: TextAlign.center,
),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
),
),
(welcomeList!=null && welcomeList.length>0) ?
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: welcomeList.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.all(8),
child: RaisedButton(
onPressed: () {
setState(() {
selectedRestaurantIndex = index;
titleString = welcomeList[index].restaurantName.toString();
});
Navigator.pop(context);
},
padding: EdgeInsets.all(12),
child: Text(welcomeList[index].restaurantName.toString()),
)
);
;}
)
: Text("No restaurants loaded...")
]
)
),
body: (isLoading)
? Center(child: CircularProgressIndicator(),)
: Container(
child: Center(
child: ListView.builder(
itemBuilder: (BuildContext context, int index1) {
return Card(
child: ListTile(
subtitle: Text(welcomeList[selectedRestaurantIndex].tableMenuList[index1].menuCategory.toString()),
title: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemBuilder: (BuildContext context, int index2) {
return Card(
child: ListTile(
title: Text(welcomeList[selectedRestaurantIndex].tableMenuList[index1].categoryDishes[index2].dishName.toString()),
));
},
itemCount: (welcomeList[selectedRestaurantIndex].tableMenuList[index1] == null || welcomeList[selectedRestaurantIndex].tableMenuList[index1].categoryDishes == null) ? 0 : (welcomeList[selectedRestaurantIndex].tableMenuList[index1].categoryDishes.length) ,)
)
);
},
itemCount: (welcomeList == null || welcomeList[selectedRestaurantIndex] == null || welcomeList[selectedRestaurantIndex].tableMenuList == null) ? 0 : (welcomeList[selectedRestaurantIndex].tableMenuList.length) ,
)
),
),
);
}
}
JSON with 3 restaurants here or here.

How to load data from Firestore using keys

I'm trying to build an e-commerce app using Flutter and Firestore, I'm having a challenge in building the cart. using the codes below, I have been able to get the products a user wishes to add to the cart using the product id. My challenge is how to use the id or the keys to fetch the details of the products from Firestore and store the cart products either in Firestore or SQLite so that I can query from there and display them on the cart page.
appstate.dart
class AppStateModel extends Model {
final Map<int, int> _productsInCart = <int, int>{};
Map<int, int> get productsInCart => Map<int, int>.from(_productsInCart);
void addProductToCart(int productId) {
if (!_productsInCart.containsKey(productId)) {
_productsInCart[productId] = 1;
} else {
_productsInCart[productId]++;
}
notifyListeners();
}
void removeItemFromCart(int productId) {
if (_productsInCart.containsKey(productId)) {
if (_productsInCart[productId] == 1) {
_productsInCart.remove(productId);
} else {
_productsInCart[productId]--;
}
}
notifyListeners();
}
void clearCart() {
_productsInCart.clear();
notifyListeners();
}
}
product_display.dart
page with onPressed function to get the id of the item clicked to add to cart
CupertinoActionSheetAction(
child: const Text('Add To Cart'),
onPressed: () {
model.addProductToCart(products[index].id);
},
)
product.dart
class Products{
final String category;
final String description;
final int id;
final int price;
final String title;
final String url;
const Products( {this.category,this.description,this.id, this.price, this.title, this.url,
});
}
CartProduct.dart
class CartProducts{
final String category;
final String description;
final int id;
final int price;
final String title;
final String url;
const CartProducts( {this.category,this.description,this.id, this.price, this.title, this.url,
});
}
Now let say I have products with ids 1, 4, 6, 9, 11 in product cart, when I print in the console using print(model.productsInCart.keys), this was the output (1, 4, 6, 9, 11), now my challenge is how to use these ids to query the products with ids 1,4,6,9,11 from Firestore collection products and store them either in Firebase or SQLite so that I can display them in the cart page for a user to view his/her items in cart.
I think this is what you are trying to do is
Firestore.instance.collection("collection").document("id").get().then((querySnapshot){
print(querySnapshot.data);
});
Obviously replace collection with your collection name and id with the id you are trying to fetch. Here I am using .then syntax, but you can pass everything before .then to the FutureBuilder as usual.
Edit:
You'll need to add a helper method for fetching all the data from the firestore.
Future<List<Products>> getCollection() async {
List<int> idList = [];
// productsInCart[key] = value; where key is id and value is amount in cart
productsInCart.forEach((key, value) {
idList.add(key);
});
List<Products> productList = [];
for (var id in idList) {
var documents = (await Firestore.instance
.collection('products')
.where('id', isEqualTo: id)
.getDocuments())
.documents;
if (documents.length > 0) {
var doc = documents[0]
.data; // if there are multiple documents with given id get first document
var prod = Products(
id: doc['id'],
title: doc['title'],
price: doc['price'],
category: doc['category'],
description: doc['description']);
productList.add(prod);
}
}
return productList;
}
then use FutureBuilder to build the list
FutureBuilder<List<Products>>(
future: getCollection(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var list = snapshot.data;
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) => ListTile(
title: Text("${list[index].title}"),
subtitle: Text(
"Amount in cart : ${productsInCart[list[index].id]}"),
));
} else {
return Text("");
}
},
);
I am using Future and FutureBuilder instead of Stream and StreamBuilder, because having to query using multiple ids is a tedious task in cloud firestore, since firestore doesn't have official support for Logical OR. So having to gather data from multiple stream sources is difficult. Using FutureBuilder has the same output as using StreamBuilder as long as product detail is not being changed while app is being used.
Edit 2:
To use multiple stream sources use StreamGroup from async package. Here is what final code looks like
Stream<List<Products>> _getCollection() async* {
List<int> idList = [];
// productsInCart[key] = value; where key is id and value is amount in cart
productsInCart.forEach((key, value) {
idList.add(key);
});
StreamGroup<QuerySnapshot> streamGroup = StreamGroup();
for (var id in idList) {
var stream = Firestore.instance
.collection('products')
.where('id', isEqualTo: id)
.snapshots();
streamGroup.add(stream);
}
//using map to store productDetails so that same products from multiple stream events don't get added multiple times.
Map<int, Products> productMap = {};
await for (var val in streamGroup.stream) {
var documents = val.documents;
var doc = documents[0].data;
var product = Products.fromMap(doc);
productMap[product.id] = product;
yield productMap.values.toList();
}
}
StreamBuilder<List<Products>>(
stream: _getCollection(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var values = snapshot.data;
return ListView.builder(
itemCount: values.length,
itemBuilder: (context, index) => ListTile(
title: Text(values[index].title),
subtitle: Text(
"Amount in cart : ${productsInCart[values[index].id]}"),
));
} else {
print("There is no data");
return Text("");
}
},
),
I've added named constructor for convenience
Products.fromMap(Map map) {
this.id = map['id'];
this.title = map['title'];
this.description = map['description'];
this.price = map['price'];
this.category = map['category'];
}