Data in SQLflite database is not deleting, in my Flutter project - flutter

So I have an issue of data not being deleted from my database. It is supposed to happen when a dismissible widget is swiped away.
This is the model class:
class DateModel {
//define schema
int? id;
final String dateText;
DateModel({this.id, required this.dateText});
//convert json data to DateModel instance
factory DateModel.fromMap(Map<String, dynamic> json) {
return DateModel(id: json["id"], dateText: json["dateText"]);
}
//convert DateModel instance to json
Map<String, dynamic> toMap() => {"id": id, "dateText": dateText};
}
Below is where some CRUD operations are defined. I concluded that the error is because I'm not properly storing the id upon insert (in the "add" function). Although I'm not certain I'm doing this right.
class DatabaseHelper {
static Database? _db;
//create singleton
DatabaseHelper._constructor();
static final DatabaseHelper instance = DatabaseHelper._constructor();
Future<Database> get db async {
return _db ??= await init_db("Date.db");
}
//create database when it doesn't exist
Future<Database> init_db(String name) async {
//locate database
var databasePath = await getDatabasesPath();
String path = join(databasePath, name);
//open database and return
return await openDatabase(path, version: 1, onCreate: _onCreate);
}
//Create date table
Future _onCreate(Database db, int version) async {
await db
.execute("CREATE TABLE Dates (id INTEGER PRIMARY KEY, dateText TEXT)");
}
//add user input to table
Future<int> add(DateModel dateModel) async {
Database db = await instance.db;
//store id of inserted date (generated by db.insert method)
final id = await db.insert("Dates", dateModel.toMap());
return dateModel.id = id;
}
//remove date from table
Future<int> remove(int id) async {
Database db = await instance.db;
return await db.delete("Dates", where: "id = ?", whereArgs: [id]);
}
Future<List> getDates() async {
Database db = await instance.db;
final data = await db.rawQuery("SELECT dateText FROM Dates");
if (data.isNotEmpty) {
return data;
} else {
return [];
}
}
}
And lastly, this is where the delete action is supposed to take place (in dismissible's onDismissed property):
Widget build(BuildContext context) {
return Consumer<Date>(builder: (context, date, child) {
return Scaffold(
body: FutureBuilder(
future: DatabaseHelper.instance.getDates(),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
if (!snapshot.hasData || snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator();
}
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return SlideTransition(
position:
Tween<Offset>(begin: Offset(1, 0), end: Offset(0, 0))
.animate(CurvedAnimation(
parent: _controller, curve: Curves.bounceIn)),
child: Dismissible(
key: UniqueKey(),
direction: DismissDirection.horizontal,
onDismissed: (direction) async {
final dateModel =
DateModel(dateText: date.dateList[index]);
//remove from list
date.dateList.removeAt(index);
//remove from database
await DatabaseHelper.instance.remove(dateModel.id!);
},
child: ListTile(title: Text(snapshot.data![index].toString()))),
);
});
},
),
);
});
}
I get the error that I'm doing a null check on a null value, which I believe is the dateModel.id.
I tried storing the id upon insert a couple of different ways. Nothing changed.

This is because you are trying to do exactly that: Putting a null check operator on a null value.
You are creating the dateModel like this:
final dateModel = DateModel(dateText: date.dateList[index]);
You do not pass any value for the id, which then is null.
Try something like this:
Map<String, dynamic> dateJson = snapshot.data![index] as Map<String, dynamic>;
DateModel dateModel = DateModel.fromMap(dateJson);
date.dateList.removeAt(index);
await DatabaseHelper.instance.remove(dateModel.id);
PS: You should not make the id optional in your DateModel class since this should always be given.

Related

Flutter Firebase Iterate through all documents that matches field value and inserting field from each one into a List of String

I'm trying to get afield value into a list of string from documents in the firebase that match the WHERE of a field value from a value of my own.
(I wanna show images above each other to create a full picture depending on the data, using two fields to be exact and then creating a list of images to show as positioned inside a Stack)
my code:
Implant Class:
class Implant {
final String id;
final String pid;
final String aptid;
late String type;
late double? sizew;
late int? sizel;
late String? positionQ;
late int? positionN;
Implant(this.id, this.pid, this.aptid, this.type, this.sizew, this.sizel,
this.positionQ, this.positionN);
Map<String, dynamic> toJson() => {
'ID': id,
'PID': pid,
'APTID': aptid,
'TYPE': type,
'SIZEW': sizew,
'SIZEL': sizel,
'POSITIONQ': positionQ,
'POSITIONN': positionN,
};
factory Implant.fromJson(Map<String, dynamic> json) {
return Implant(
json['ID'],
json['PID'],
json['APTID'],
json['TYPE'],
json['SIZEW'],
json['SIZEL'],
json['POSITIONQ'],
json['POSITIONN'],
);
}
static Map<String, dynamic> toMap(Implant implant) => {
'ID': implant.id,
'PID': implant.pid,
'APTID': implant.aptid,
'TYPE': implant.type,
'SIZEW': implant.sizew,
'SIZEL': implant.sizel,
'POSITIONQ': implant.positionQ,
'POSITIONN': implant.positionN,
};
static String encode(List<Implant> implant) => json.encode(
implant
.map<Map<String, dynamic>>((implant) => Implant.toMap(implant))
.toList(),
);
static List<Implant> decode(String implants) =>
(json.decode(implants) as List<dynamic>)
.map<Implant>((implant) => Implant.fromJson(implant))
.toList();
}
Future of list of string function:
static Future<List<String>> getImplants(Patient patient) async {
List<String> result = ['assets/images/teeth/empty.png'];
var collection = FirebaseFirestore.instance.collection('implants');
var docSnapshot = await collection.doc(patient.id).get();
if (docSnapshot.exists) {
Map<String, dynamic> data = docSnapshot.data()!;
result.add(data['POSITIONQ'] + data['POSITIONN']);
}
return result;
}
How I translate them into Stack and Positioned:
static Future<void> showPatientImplants(BuildContext context, Patient patient,
{bool spaces = true}) async {
List<String> myImplants;
myImplants = await getImplants(patient);
List<Widget> x = [Image.asset('assets/images/teeth/empty.png')];
myImplants.forEach((element) {
x.add(Image.asset(element));
});
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
content: SingleChildScrollView(
child: GestureDetector(
onTap: () {
log(myImplants.toString());
},
child: Stack(
children: x,
),
),
),
);
});
}
The only problem I'm facing -I think- is that the Future<List> function doesn't get me the values I need.
I tried using other functions I found on SO and google, nothing worked.
I guess I can try using the stream and Listview.builder just to get the files names but it seems like exactly how it shouldn't be done.
any help is appreciated.
Never mind Solved it using the following code:
static Future<List<String>> getImplants(Patient patient) async {
List<String> result = ['assets/images/teeth/empty.png'];
log('ID:${patient.id}');
var collection = FirebaseFirestore.instance
.collection('implants')
.where('PID', isEqualTo: patient.id);
var docSnapshot = await collection.get();
for (var element in docSnapshot.docs) {
if (element.exists) {
Map<String, dynamic> data = element.data();
result.add(data['POSITIONQ'].toString() + data['POSITIONN'].toString());
}
}
return result;
}
but if anyone has a better idea I appreciate it along with everyone who has a similar problem.

The getter 'length' was called on null. Receiver: null Tried calling: length FutureBuilder with List

I keep getting the error mentioned above during runtime of my flutter app. Basically what I am trying to achieve is to fetch data from an api and display it in a form of a SliverList by using a FutureBuilder.
This was working perfectly fine until I changed my code for the list from FutureBuilder<List> to FutureBuilder<List> to make use of the class EntertainerEvent which has all the fields I need for display from the json file.
How can I resolve this because it seems like the contructor or the application itslef is not picking up the data when I make use of a custom class.
This is the code for the EntertainerEvent class:
class EntertainerEvent {
final int eventId;
final int entertainerId;
final int eventTypeId;
final int categoryId;
final String eventName;
final String description;
final String imagePoster;
final String location;
final DateTime startDate;
final DateTime endDate;
final double entreeFee;
const EntertainerEvent({required this.eventId, required this.entertainerId, required this.eventTypeId,
required this.categoryId, required this.eventName, required this.description, required this.imagePoster,
required this.location, required this.startDate, required this.endDate, required this.entreeFee});
factory EntertainerEvent.fromJson(Map<String, dynamic> event) {
return EntertainerEvent(
eventId: event['EventID'],
entertainerId: event['EntertainerID'],
eventTypeId: event['EventTypeID'],
categoryId: event['CategoryID'],
eventName: event['EventName'],
description: event['Description'],
imagePoster: event['ImagePoster'],
location: event['Location'],
startDate: event['StartDate'],
endDate: event['EndDate'],
entreeFee: event['EntryFee'],
);
}
}
Below is the code for fetching data from the api:
Future<List<EntertainerEvent>> fetchEvents() async {
var result = await http.get(Uri.parse(apiUrl));
if (result.statusCode == 200) {
var content = result.body;
var arr = json.decode(content) as List;
return arr.map((eve) => new EntertainerEvent.fromJson(eve)).toList();
} else {
print('Not loaded');
throw Exception('Unable to fetch data from the Rest API');
}
}
late Future<List<EntertainerEvent>> _fetchEvents;
#override
void initState() {
_fetchEvents = fetchEvents();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<EntertainerEvent>>(
future: _fetchEvents,
builder: (BuildContext context, AsyncSnapshot snapshot) {
var childCount = 0;
if (snapshot.connectionState != ConnectionState.done) {
childCount = 1;
} else {
childCount = snapshot.data.length;
}
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
if (snapshot.hasData) {
List<EntertainerEvent> someData = snapshot.data;
print('data here');
//Do some stuff
}
}, childCount: childCount),
);
});
}
I do not know what exactly is it that I am missing because this code works if I use the type dynamic instead of the custom class EntertainerEvent.
Thank you all in advance!
Wrap it with hasData:
if(snapshot.hasData){
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
List<EntertainerEvent> someData = snapshot.data;
print('data here');
//Do some stuff
}, childCount: childCount),
);}
return CircularProgressIndicator();

How to load data on app start from file with Flutter and sqflite?

I have a whole list of data I want to load into my app when it starts up. I was able to extract into a JSON file so I can load it into my table, but it's not quite loading correctly and also seems to get added multiple times. It looks like I am loading in the file correctly via assets (printing the values in _loadIngredients gives me the right results). But when it is saved into the DB, it just gets saved as an integer and that is what shows up in the view.
I could not find a good example of this anywhere.
Where am I going wrong?
In my DatabaseHandler:
class DatabaseHandler{
DatabaseHandler._();
static final DatabaseHandler db = DatabaseHandler._();
static DatabaseHandler get() {
return db;
}
static Database _database;
final databaseName = "recipe.db";
DatabaseHandler._createInstance();
Future<Database> get database async {
if (_database != null)
return _database;
_database = await initDB();
return _database;
}
initDB() async {
var path = await getDatabasesPath();
print("Creating tables at path: $path");
var dbPath = join(path, 'recipe.db');
Database dbConnection = await openDatabase(dbPath, version: 1,
onCreate: (Database db, int version) async {
return db.execute(
"CREATE TABLE ingredients(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)",
);
}
);
await _loadIngredients(dbConnection);
return dbConnection;
}
_loadIngredients(Database db) async {
Batch batch = db.batch();
List<Map<String, dynamic>> records = await db.query('ingredients');
print(records);
String ingredientsJson = await rootBundle.loadString('assets/json/ingredients.json');
List ingredientsList = json.decode(ingredientsJson);
ingredientsList.forEach((val) {
print(val);
Ingredient ingredient = Ingredient.fromMap(val);
batch.insert("ingredients", ingredient.toMap(false));
});
var results = await batch.commit();
}
}
My ingredient class:
class Ingredient {
int id;
String name;
String categoryName;
DateTime dateCreated;
Ingredient(this.id, this.name, this.categoryName, this.dateCreated);
Map<String, dynamic> toMap(bool forUpdate) {
if(dateCreated == null) {
dateCreated = new DateTime.now();
}
var data = {
// 'id': id, since id is auto incremented in the database we don't need to send it to the insert query.
'name': utf8.encode(name),
'category_name': utf8.encode(categoryName),
'date_created': epochFromDate( dateCreated )
};
if(forUpdate){
data["id"] = this.id;
}
return data;
}
Ingredient.fromMap(Map map){
id = map["id"];
name = map["name"];
categoryName = map["category_name"];
dateCreated = map["date_created"];
}
// Converting the date time object into int representing seconds passed after midnight 1st Jan, 1970 UTC
int epochFromDate(DateTime dt) {
return dt.millisecondsSinceEpoch ~/ 1000 ;
}
// overriding toString() of the note class to print a better debug description of this custom class
#override toString() {
return {
'id': id,
'name': name,
'category_name': categoryName,
'date_created': epochFromDate( dateCreated )
}.toString();
}
}
My homepage class where I am initializing my DB:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Map<String, dynamic>> _allIngredientsInQueryResult = [];
var notesViewType ;
#override void initState() {
super.initState();
notesViewType = viewType.Staggered;
DatabaseHandler.db.initDB();
retrieveAllIngredientsFromDatabase();
}
#override
Widget build(BuildContext context) {
return
Container(
child: _ingredientList()
);
}
Widget _ingredientList() {
return Container(
child: ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: _allIngredientsInQueryResult.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber[100],
child: Center(child: Text('Entry ${_allIngredientsInQueryResult[index]["name"]}')),
);
},
separatorBuilder: (BuildContext context, int index) => const Divider(),
)
);
}
retrieveAllIngredientsFromDatabase() async {
var _testData = await DatabaseHandler.db.selectAllIngredients();
setState(() {
_allIngredientsInQueryResult = _testData;
});
}
}
Image of what I am seeing in the app:
Ingredients json
if you use utf8.encode(name), you convert your String in bytes something like flour = [102, 108, 111, 117, 114]
and when you display this values you must also set a utf8.decode(map["name"])
in your example something like
Text('Entry ' + utf8.decode(${_allIngredientsInQueryResult[index]["name"]})))
every time your initDB() is calling, the Data comes again in the DB. You can do it only in the onCreate part of the Sqlite DB
initDB() async {
var path = await getDatabasesPath();
print("Creating tables at path: $path");
var dbPath = join(path, 'recipe.db');
Database dbConnection = await openDatabase(dbPath, version: 1,
onCreate: (Database db, int version) async {
await db.execute(
"CREATE TABLE ingredients(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, category_name TEXT, date_created INTEGER)",
);
await _loadIngredients(db);
}
);
return dbConnection;
}
i would also use your model class and not a dynimac map
Define _allIngredientsInQueryResult
List<Ingredient> _allIngredientsInQueryResult = new List();
get selectAllIngredients, with the fromMap()
Future<List<Ingredient>> selectAllIngredients() async {
var dbClient = await database;
List<Map> result = await dbClient.query('ingredients');
List<Ingredient> r_ingredient = result.map((i) => Ingredient.fromMap(i)).toList();
return r_ingredient;
}
set Decode in the fromMap()
Ingredient.fromMap(Map map){
id = map["id"];
name = utf8.decode(map["name"]);
categoryName = utf8.decode(map["category_name"]);
dateCreated = DateTime.fromMillisecondsSinceEpoch(map["date_created"]);
}
get Ingredients
retrieveAllIngredientsFromDatabase() async {
DatabaseHandler.db.selectAllIngredients().then((List<Ingredient> r_ingredient) {
setState(() {
_allIngredientsInQueryResult = r_ingredient;
});
});
}
display in the Listview
Text('Entry ' + _allIngredientsInQueryResult[index].name)

Flutter Firestore array with Maps. Do they work?

I can't stream (read) Documents that contain array properties that are Maps in Firestore.
Using Firestore with a document containing an array with a simple String type works as expected. Easy to write (append with FieldValue.arrayUnion(['data1','data2','data3']) and stream back out with code like:
var test2 = List<String>();
for (var item in data['items']) {
print('The item $item');
test2.add(item);
}
test2 can now be used as my items property.
When I try and use a List where item type becomes a Map in Firestore and is just a simple Class containing a few Strings and a date property. I can write these to FireStore but I can't read them back out.
The following code fails: (no errors in the Debug Console but it doesn't run)
var test2 = List<Item>();
data['items'].forEach((item) {
var description = item['description'];
print('The description $description'); // <-- I don't get past this
final x = Item.fromMap(item); // <-- so I can't even attempt this
return test2.add(x);
});
I never get to actually call my Item.fromMap constructor: here is another try:
// fails
final theItems = data['items'].map((x) {
return Item.fromMap(x); // <-- never get here
});
Although the DEBUG CONSOLE doesn't say there is any problem. If I inspect theItems variable (the debugger 'jumps' a couple of lines down to my return) after the failed iteration it looks like this:
MappedListIterable
_f:Closure
_source:List (1 item)
first:Unhandled exception:\ntype '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>'\n#0 new Listing.fromMap.<anonymous closure> isEmpty:false
isNotEmpty:true
iterator:ListIterator
last:Unhandled exception:\ntype '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of
So bad things have happened but I have no idea why!
Has anyone actually written and retrieved Firestore array properties that contain Maps?
Any help in how to proceed would be greatly appreciated!
More: screen shot of the document
Here is the code that reads (streams) the collection
Stream<List<T>> collectionStream<T>({
#required String path,
#required T builder(Map<String, dynamic> data, String documentID),
#required String userId,
Query queryBuilder(Query query),
int sort(T lhs, T rhs),
}) {
Query query = Firestore.instance.collection(path);
if (queryBuilder != null) {
query = queryBuilder(query);
}
final Stream<QuerySnapshot> snapshots = query.snapshots();
return snapshots.map((snapshot) {
//print('document: path $path ${snapshot.documents[0]?.documentID}');
final result = snapshot.documents
.map((snapshot) => builder(snapshot.data, snapshot.documentID))
.where((value) => value != null)
.toList();
if (sort != null) {
result.sort(sort);
}
print('returning from CollectionStream');
return result;
});
}
the .map is where the problem comes in. The builder function resolves to this:
builder: (data, documentId) {
return Listing.fromMap(data, documentId);
},
Which ends up here
factory Listing.fromMap(
Map<String, dynamic> data,
String documentId,
) {
if (data == null) {
return null;
}
final theTime = (data['createdAt'] as Timestamp).toDate();
// see above code where I fail at getting at the items property/field
Here is the Item Class:
class Item {
Item(this.description, this.imageURL, this.thumbnailURL,
{this.status = ItemStatus.active,
this.type = ListingType.free,
this.price = 0,
this.isUpdate = false,
this.createdAt});
final String description;
final String imageURL;
final String thumbnailURL;
final ItemStatus status;
final ListingType type;
final int price;
final bool isUpdate;
DateTime createdAt;
factory Item.fromMap(
Map<String, dynamic> data,
) {
if (data == null) {
return null;
}
final theTime = (data['createdAt'] as Timestamp).toDate();
return Item(
data['description'],
data['imageURL'],
data['thumbnailURL'],
status: _itemStatusFromString(data['status']),
type: data['type'] == 'free' ? ListingType.free : ListingType.flatRate,
createdAt: theTime,
);
}
Map<String, dynamic> toMap() {
// enums are for shit in Dart
final statusString = status.toString().split('.')[1];
final typeString = type.toString().split('.')[1];
return {
'description': description,
'imageURL': imageURL,
'thumbnailURL': thumbnailURL,
'itemStatus': statusString,
'price': price,
'listingType': typeString,
if (!isUpdate) 'createdAt': DateTime.now(),
if (isUpdate) 'updatedAt': DateTime.now(),
};
}
}
The above is never called to read (we crash) ... it is called to write the data.
This is a known issue with Firestore. Here is the starting thread Flutter issues I found
From reading through the issues it seems lots of folks use a serializer package which is where the issue surfaced. Its still active...
Here is my solution NOT using a serializer and just doing it 'by hand'.
I was able simplify the problem and generate an error that was Google-able. Below is a single page which just writes and reads a single Document. The Class A and B are as small as can be.
So the code writes a single document to Firestore the contains two properties. A name and items. The items being the List of Class B that Class A contains. Checkout the Firebase console. The reading just does that.
No StreamController just the Console. The problem is in the fromMap method where we have to convert the array of objects in Firesote to a List of class instances in Dart. This should not be this difficult and at a minimum it should be documented ....
the line
var theRealItems = data['items'].map((i) => B.fromMap(i));
will generate the error. And needs to be replaced with
var theItems = data['items'].map((i) {
var z = Map<String, dynamic>.from(i);
print(z['description']);
return B.fromMap(z);
}).toList();
var theRealItems = List<B>.from(theItems);
Why this is so difficult is still a mystery to me! Anyone improving this code: I'm all ears.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class A {
A(this.name, this.items);
final name;
final List<B> items;
factory A.fromMap(
Map<String, dynamic> data,
String documentId,
) {
if (data == null) {
return null;
}
// we crash here !!!
var theRealItems = data['items'].map((i) => B.fromMap(i));
// uncomment the 6 lines below
// var theItems = data['items'].map((i) {
// var z = Map<String, dynamic>.from(i);
// print(z['description']);
// return B.fromMap(z);
// }).toList();
// var theRealItems = List<B>.from(theItems);
return A(data['name'], theRealItems);
}
Map<String, dynamic> toMap() {
var theItems = items.map((i) => i.toMap()).toList();
return {'name': name, 'items': theItems};
}
}
class B {
B(this.description);
final description;
factory B.fromMap(
Map<String, dynamic> data,
) {
if (data == null) {
return null;
}
return B(data['description']);
}
Map<String, dynamic> toMap() {
return {
'description': description,
};
}
}
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => _write(),
child: Text('Write the Doc'),
),
RaisedButton(
onPressed: () => _read(),
child: Text('Read the Doc ... check Debug Console!'),
),
],
),
),
);
}
_write() async {
try {
var b = B('Inside B!');
List<B> theList = List<B>();
theList.add(b);
var a = A('myName', theList);
await Firestore.instance
.collection('test')
.document('testDoc')
.setData(a.toMap());
print('returning from write!');
} catch (e) {
print('Error ${e.toString()}');
}
}
_read() async {
try {
var aFromFs = await Firestore.instance
.collection('test')
.document('testDoc')
.get();
var a = A.fromMap(aFromFs.data, aFromFs.documentID);
print('the A from FireBase $a with name ${a.name} first item ${a.items.first.description}');
print('returning from read!');
} catch (e) {
print('Oh no Error! ${e.toString()}');
}
}
}
In the below code Name refers to the name of your respective document and collection.
Let's say you want to get "imageURL" and "thumbnailUrl" for now and update the values without deleting or changing other fields inside the array.
String imageUrl ;
String thumbnailUrl;
DocumentReference _docReference = FirebaseFirestore.instance.collection(NAME).doc(NAME);
//refering to the specific document
Map<String, dynamic> neededData= allDocsFromTraining.data();
//getting all the keys and values inside the document
List<Map<String, dynamic>> _yourDocument=
(neededData["items"] as List<dynamic>)
.map((m) => Map<String, dynamic>.from(m))
.toList();
//only getting the list of items
for (int index = 0; index < _yourDocument.length; index++) {
Map<String, dynamic> _getMap = _yourDocument[index];
if (_getMap["price"] == 0)
//giving a condition to get the desired value and make changes
{
//setting the variables with the value from the database
setState(() {
imageUrl = _getMap["imageURL"];
thumbnailUrl = _getMap["thumbnailURL"]
});
///Use this if you want to update value of imageURL and thumbnailURL at that specific index of an array
_getMap['imageURL'] = "Your Updated URL";
_getMap['thumbnailURL'] = "Your Updated Url;
break;
} else {}
}
await _docReference.update({"items": _yourDocument});
//this adds the updated value to your database.

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'];
}