Okay, so I have multiple specific document ids that are all the same, but each one is used for different collections and purposes. Using a collection group as these documents are nested in subcollections, I was able to locate these documents and print them. How would I go about updating a specific field in all of these documents at the same time with some other data? In my case these documents despite being in different collections have a field called, plastics and I want to update them with int data.
Here is the code I used to retrieve these documents and filter to only print the one's I specificly need:
final String uid = FirebaseAuth.instance.currentUser.uid;
QuerySnapshot querySnapshot = await FirebaseFirestore
.instance
.collectionGroup("Members")
.get();
for (int i = 0; i < querySnapshot.docs.length; i++) {
var a = querySnapshot.docs[i];
if (a.id == uid) {
print(a.id);
}
}
Also here is some code I've used before for updating a field in a single document, but not all of them like I need to in this case.
Future<bool> addPlastic(String amount) async {
try {
String uid = auth.currentUser.uid;
var value = double.parse(amount);
DocumentReference documentReference =
FirebaseFirestore.instance.collection('some collection').doc(some document);
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot snapshot = await transaction.get(documentReference);
if (!snapshot.exists) {
documentReference.set({'plastics': value});
return true;
}
double newAmount = snapshot.data()['plastics'] + value;
transaction.update(documentReference, {'plastics': newAmount});
return true;
});
} catch (e) {
return false;
}
}
You have two options, either loop through all the collections and sub collections (tedious), or store a list document references in one of the documents and change the data by looping through all of these document references ( better option). If you need some code or guidelines on how to do that, let me know.
EDIT
To use the document reference part, first, when creating the document, you have to do something like this
document(id).set({
"value1":"oko",
"plastic":"240",
//And the rest of your values
"otherSnapshots":[/*Your list of documentSnapshots with the same ID. Update it in this whenever you have new one by following the next snippet*/]
})
When you create a new document, navigate to this and add in the document Reference by
snapshot.reference.update({
'otherSnapshots':FieldValue.arrayUnion([/*Document Reference*/])
});
And next time, when you want to update all of them, use this field, loop through it and then you will get all the document references. I cannot create a code that you can directly copy paste into your code without seeing how u store data
I want to use sqflite in my app. To do this, I'm trying to follow this tutorial: https://flutter.dev/docs/cookbook/persistence/sqlite. However, I don't know where to place the code inside my application. In the tutorial, the code seems to be placed at the main() function - but, if I do that, how can I call the insert, update and delete methods at other files?
Update:
As suggested by #Madhavam Shahi, I created a file databaseServices.dart. Now, at the other file, I'm importing databaseServices.dart and trying to use it as below:
import 'databaseServices.dart';
DataBaseServices db=DataBaseServices();
db.delete() //example
However, it is not working. I think the databaseServices.dart is not structured the right way, but I can't spot the error. I know I must be making a very newbie mistake. Here is the code for the databaseServices.dart:
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'counter.dart';
class DatabaseServices {
void whatever() async {
// Open the database and store the reference.
final Future<Database> database = openDatabase(
// Set the path to the database.
join(await getDatabasesPath(), 'counter_database.db'),
// When the database is first created, create a table to store counters;
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
"CREATE TABLE counters(id INTEGER PRIMARY KEY, name TEXT, value INTEGER)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
// Define a function that inserts counters into the database.
Future<void> insertCounter(Counter counter) async {
// Get a reference to the database.
final Database db = await database;
// Insert the Counter into the correct table. Here, if a counter is inserted twice,
// it replace any previous data.
await db.insert(
'counters',
counter.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// A method that retrieves all the counters from the counters table.
Future<List<Counter>> counters() async {
// Get a reference to the database.
final Database db = await database;
// Query the table for all the Counters.
final List<Map<String, dynamic>> maps = await db.query('counters');
// Counvert the List<Map<String, dynamic>> into a List<Counter>
return List.generate(maps.length, (i) {
return Counter(
id: maps[i]['id'],
name: maps[i]['name'],
value: maps[i]['value'],
);
});
}
// Method to update a Counter in the database
Future<void> updateCounter(Counter counter) async {
final db = await database;
await db.update(
'counters',
counter.toMap(),
where: "id = ?",
whereArgs: [counter.id],
);
}
//Delete a Counter from the database
Future<void> deleteCounter(int id) async {
final db = await database;
await db.delete(
'counters',
where: "id = ?",
whereArgs: [id],
);
}
}
}
No, it doesn't matter wherever you create the database.
You can create a file databaseServices.dart which manages the database service. It'll be easy for you to manage your code then.
In the cookbook, they are just showing an example, how you can use sqlflite.
But, however you should place this line WidgetsFlutterBinding.ensureInitialized(); in your main() method before anything, IF you are doing an asynchronous task in the main() method.
Update:
To perform CRUD in other files,
import your databaseServices.dart file that file, in which you want to perform the CRUD.
import 'databaseServices.dart';
DataBaseServices db=DataBaseServices();// create an object (DataBaseServices is the name of the class)
//Now, you can access all the methods,
db.delete()//example
Alternatively, if you don't want to create a class in the databaseServices.dart file, and want to keep every function a top level function, then you can do the following.
import 'databaseServices.dart' as db;
//Now, you can access all the top level functions or variables.
db.delete()//example.
Update 2:-
To make database accessible to every function,
move Future database outside the whatever () method, and place it just below the class name. (Making it global so that every function can access it), notice i removed the "final" keyword, because we are going to initialise it later, in the whatever method. Now, in the whatever method do what you were, but instead of final Future database = //yoir code, do this, database =//your code.. by doing this, you will be initialising the database variable, and as database variable is a global variable(declared outside any function, inside a class), any function can access it. But you have to keep in mind that, database has to be initialised before you call any other method which requires database, because if you will not call whatever () method before any other function, then the database won't be initialised, and hence your other functions won't work.
Example,
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'counter.dart';
class DatabaseServices {
Future<Database> database;//making database global so that every function inside the class can access it.
void whatever() async {
// Open the database and store the reference.
database = openDatabase(
// Set the path to the database.
join(await getDatabasesPath(), 'counter_database.db'),
// When the database is first created, create a table to store counters;
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
"CREATE TABLE counters(id INTEGER PRIMARY KEY, name TEXT, value INTEGER)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
}//Function whatever () ends here
// Define a function that inserts counters into the database.
Future<void> insertCounter(Counter counter) async {
// Get a reference to the database.
final Database db = await database;
// Insert the Counter into the correct table. Here, if a counter is inserted twice,
// it replace any previous data.
await db.insert(
'counters',
counter.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// A method that retrieves all the counters from the counters table.
Future<List<Counter>> counters() async {
// Get a reference to the database.
final Database db = await database;
// Query the table for all the Counters.
final List<Map<String, dynamic>> maps = await db.query('counters');
// Counvert the List<Map<String, dynamic>> into a List<Counter>
return List.generate(maps.length, (i) {
return Counter(
id: maps[i]['id'],
name: maps[i]['name'],
value: maps[i]['value'],
);
});
}
// Method to update a Counter in the database
Future<void> updateCounter(Counter counter) async {
final db = await database;
await db.update(
'counters',
counter.toMap(),
where: "id = ?",
whereArgs: [counter.id],
);
}
//Delete a Counter from the database
Future<void> deleteCounter(int id) async {
final db = await database;
await db.delete(
'counters',
where: "id = ?",
whereArgs: [id],
);
}
}
Now, since there are no nested functions, you can easily create an object of the class, and call the functions as you need easily :)
I am asking this a lot here, I know, but nobody gives me a right answer.
I just need to return a list with values from a sub collection of Firebase.
This is my code:
List mapToList({DocumentSnapshot doc, List<DocumentSnapshot> docList}) {
if (docList != null) {
List<Store> storeList = [];
docList.forEach((document) async {
String productName;
String name = document.data[StringConstant.nameField];
QuerySnapshot productRef = await document.reference.collection('products').getDocuments();
productRef.documents.forEach((value){
productName = value["name"];
});
Store otherStore = Store(name, productName);
storeList.add(otherStore);
});
print(storeList.length);
return storeList;
} else {
return null;
}
}
Or I want something like this:
List mapToList({DocumentSnapshot doc, List<DocumentSnapshot> docList}) {
if (docList != null) {
List<Store> storeList = [];
docList.forEach((document) async {
//I KNOW THIS IS WRONG, BUT I NEED SOMETHING LIKE THE LINE BELOW
String productName = document.data.reference.collection("products").data["productName];
String name = document.data["name];
Store otherStore = Store(name, productName);
storeList.add(otherStore);
});
return storeList;
} else {
return null;
}
}
How can I get this list?
Subcollections do not have "fields" that you can access directly in the manner you wish to. As is explained in The Cloud Firestore Data Model, collections and subcollections both are essentially just names for a set of documents. You cannot access fields directly in a subcollection, you must first reference a document in the subcollection, and then the document's fields. Documents are the only things that contain fields.
For your situation, I'd suggest simply storing a map in the products field of each of the documents you're considering here. Or, if you absolutely must use a subcollection (perhaps as a means of keeping flexibility for future schema changes), use document.collection('products').document('productName')["value"] or something similar.
Subcollections work like this because they are inteded to provide a method to store data relevant only to a particular document, such that the document's security settings are thus also inherently applied to the subcollection. As in the example in the link - rooms is a collection of chatrooms, each of which is a document with its name and a reference to a messages subcollection, each of which contains the message and its author. The messages subcollection loses context without the room document.
I was trying to simulate a situation where two users (on seperate devices) both run a Transaction at the same time. To imitate this, I made a List<String> of strings which would be added to the database without a delay between them.
However, only the first item in the List was added to the database, the second never arrived. What am I doing wrong? I am trying to have both items added to the database.
The call to the Transaction happens in the code below, along with the creation of the list:
List<String> items = new List<String>();
items.add("A test String 1");
items.add("A test String 2");
for (String q in questions)
{
database.updateDoc( q );
}
The code I use for updating the data in my database:
void updateDoc( String item ) async
{
var data = new Map<String, dynamic>();
data['item'] = item;
Firestore.instance.runTransaction((Transaction transaction) async {
/// Generate a unique ID
String uniqueID = await _generateUniqueQuestionCode();
/// Update existing list
DocumentReference docRef = Firestore.instance
.collection("questions")
.document("questionList");
List<String> questions;
await transaction.get(docRef)
.then (
(document) {
/// Convert List<dynamic> to List<String>
List<dynamic> existing = document.data['questions'];
questions = existing.cast<String>().toList();
}
);
if ( ! questions.contains(uniqueID) )
{
questions.add( uniqueID );
var newData = new Map<String, dynamic>();
newData['questions'] = questions;
transaction.set(docRef, newData );
}
/// Save question
docRef = Firestore.instance
.collection("questions")
.document( uniqueID );
transaction.set(docRef, data);
});
}
In reality, I have a few fields in the document I'm saving but they would only complicate the code.
I keep track of a list of documents because I need to be able to retreive a random document from the database.
When executing the first code snippet, only the first item in the list will be added to the database and to the list that keeps track of the documents.
No error is thrown in the debug screen.
What am I missing here?
As explained by Doug Stevenson in the comments under my question:
That's not a typical use case for a single app instance. If you're
trying to find out if transactions work, be assured that they do.
They're meant to defend against cases where multiple apps or processes
are making changes to a document, not a single app instance.
And also:
The way the Firestore SDK works is that it keeps a single connection
open and pipelines each request through that connection. When there
are multiple clients, you have multiple connection, and each request
can hit the service at a different time. I'd suspect that what you're
trying to simulate isn't really close to the real thing.
I have a firestore table of users, and another firestore table of blogs which has uid on each document. I would like to do join query in flutter by joining the two tables by their uid. I can't find a way of doing this in the fire quiz state management. I wanted to define a new class class.
BlogData<T> {
final Firestore _db = Firestore.instance;
final String path;
CollectionReference ref;
BlogData({ this.path }) {
ref = _db.collection(path);
}
Future<List<T>> getData() async {
// get blog data and do a join here to a user document by uid (document/blog.uid) return a future of blogs with user data such as name, country added to each blog in the future list.
}
Stream<List<T>> streamData() {
// get blog data and do a join here to the user document by uid (document/blog.uid) return a stream of blogs with user data such as name, country added to each blog in the stream.
}
}
How can I achieve this?