Get Firebase Entity Key upon its creation in Flutter - firebase

When creating a new entity in Firebase with the following test code:
class Person{
String name;
int age;
Person({this.name, this.age});
}
Person person = new Person(name: 'John', age: '42');
.
.
.
aMethod async() {
Firestore _firestore = Firestore.instance;
var firestoreResult = await _firestore.collection('Example').add({
'name': person.name,
'age': person.age
});
}
I want to retrieve its key from the firebase.
I saw in debugging mode that the firestoreResult returns the wanted key within its parameters like this:
firestoreResult._delegate._pathComponents[1]
The problem is that its parameters are private and thus I am getting the following error:
The getter _delegate is not defined for the class DocumentReference

To get the document id try the following:
var firestoreResult = await _firestore.collection('Example').add({
'name': person.name,
'age': person.age
});
var documentId = firestoreResult.documentID;
The add() method returns a Future of type DocumentReference and inside the class DocumentReference you can use the property documentID to get the id:
https://github.com/FirebaseExtended/flutterfire/blob/master/packages/cloud_firestore/cloud_firestore/lib/src/document_reference.dart#L39

var documentID = firestoreResult.documentID;

Related

Map an Array of objects from Firebase to List Flutter

I'm trying to retrieve an array of objects from Firebase and store it in Flutter Object as a List.
This is the collection, Firebase,
And this is the model class
class Merchant {
String shopName;
String address;
String description;
String thumbNail;
LatLng locationCoords;
Merchant(
{this.shopName,
this.address,
this.description,
this.thumbNail,
this.locationCoords});
}
final List<Merchant> merchant = []; // Map it to This List
I'd like to map it into this list above
final List<Merchant> merchant = [];
First add this method to your Merchant class:
Merchant.fromMap(Map<String, dynamic> map) {
shopName = map['shopName'];
address = map['address'];
description = map['description'];
thumbnail = map['thumbnail'];
locationCoords = map['locationCoords'];
}
This method will then be used to write the data into the Merchant Class/Struct.
Retrieving the data the could look something like the following:
import 'package:cloud_firestore/cloud_firestore.dart';
FirebaseFirestore _firestore = FirebaseFirestore.instance;
CollectionReference merchRef = _firestore.collection('merchants'));
Future<List<Merchant>> getAllMerchants() async {
List<Merchant> merchantList = [];
await merchRef.get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
Merchant merchant = Merchant.fromMap({
'shopName': doc['shopname'],
'address': doc['address'],
'description': doc['description'],
'thumbnail': doc['thumbnail'],
'locationCoords': doc['location'],
});
merchantList.add(merchant);
});
});
return merchantList;
}
P.S.:
HavenĀ“t tried this out yet and you might need some parsing for locationCoords, since it is of type LatLng.

how to access a collection inside a firestore document and assign it as a list to my dart list inside my model?

how can i access a firestore collection inside a document and assign it to a list in my model, i tried accessing it like this snap.reference.collection('submittedUsers').get(); but i can't use async/await in constructors so i didn't knew what to do, this my model code:
final String fileUrl;
final String title;
final String description;
final List<String> submittedUsers;
LectureModel({
#required this.fileUrl,
#required this.title,
#required this.description,
this.submittedUsers,
}) : super(
fileUrl: fileUrl,
title: title,
description: description,
submittedUsers: submittedUsers,
);
Map<String, dynamic> toDocument() {
return {
'fileUrl': fileUrl,
'title': title,
'description': description,
};
}
factory LectureModel.fromSnapshot(DocumentSnapshot snap) {
// my submittedUsers collection is inside this `snap` document
// i want to get that collection and i assign it's memebers to my model submittedUsers list
final data = snap.data();
return LectureModel(
fileUrl: data['fileUrl'] as String,
title: data['title'] as String,
description: data['description'] as String,
);
}
}
Your data class looks just fine., but in your current structure, also add a list to your factory in the model, like this:
factory LectureModel.fromSnapshot(DocumentSnapshot snap, List<String> submittedUsersList) {
final data = snap.data();
return LectureModel(
fileUrl: data['fileUrl'] as String,
title: data['title'] as String,
description: data['description'] as String,
submittedUsers: submittedUsersList,
);
}
}
But you need to call your method LectureModel.fromSnapshot inside a function, or a future builder or stream builder for example. And also fetch the subcollection after getting the parent document
For example, you need a function like this, and put it in your widget where you need it.
Future<List<LectureModel>> getLectures() async {
QueryDocumentSnapshot snap = await FirebaseFirestore.instance.collection('NAME_OF_PARENT_COLLECTION').get();
List<LectureModel> lectureList=[];
//this will check that there actually is documents in firebase
if(snap.docs.isNotEmpty){
for(var singleSnapDocument in snap.docs){
//then you have to get the subcollection seperately for every
//document.
List<String> listOfsubmittedUsers =[];
listOfsubmittedUsers = await
FirebaseFirestore.instance.collection('NAME_OF_PARENT_COLLECTION')
.doc(singleSnapDocument.id).collection('submittedUsers')
.get().then((result)=> result.docs.map((e) => e.data().toString()).toList());
//this will add a LectureModel object into our list lectureList
lectureList.add(LectureModel.fromSnapshot(singleSnap, listOfsubmittedUsers));
}
}
print('Length of lectureList is: ' + lectureList.length.toString());
return lectureList;
}
Now, anywhere in your code, you can use onPressed or in initState and just call your function getLectures. i.e
onPressed: () async {List<LectureModel> listOfLecture = await getLectures();}
Your problem should be solved.

Fllutter/Firestore - realtime listening to collections and subcollections

I have a firebase collection named 'reviews' with a sub-collection 'clients'.
I am looking to fetch all reviews in realtime with their owners from Firebase Firestore but I got a bit lost when it came to correctly mapping the data and returning the listener's result.
This is 'reviews' model:
class Review {
final String reviewTitle;
final String reviewContent;
final String reviewCategory;
final String reviewTimestamp;
final int reviewVotesCount;
final Client client;
Review(
{this.reviewTitle,
this.reviewContent,
this.reviewCategory,
this.reviewTimestamp,
this.reviewVotesCount,
this.client});
}
This is the Service class:
class ReviewService {
var currentUser = FirebaseAuth.instance.currentUser;
var firestoreInstance = FirebaseFirestore.instance;
List<Review> fetchAllThreads() {
Review review;
Client client;
List<Thread> mReviewsList = new List<Review>();
firestoreInstance.collection('reviews').snapshots().listen((result) {
result.docs.forEach((result) {
firestoreInstance
.collection('reviews')
.doc(result.id)
.collection('clients')
.get()
.then((result) {/*here I get the result.data()*/});
});
});
}
Question after I get result.data() how can I map it to my model so I can add the result object to mReviewsList and then return mReviewsList ?
You can add a factory constructor in your Review class to create it from a Map and same applies for Client.
factory Review.fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return Review(
reviewTitle: map['reviewTitle'],
reviewContent: map['reviewContent'],
reviewCategory: map['reviewCategory'],
reviewTimestamp: map['reviewTimestamp'],
reviewVotesCount: map['reviewVotesCount'],
client: Client.fromMap(map['client']),
);
}
If you're using VS Code, 'Dart Data Class Generator' extension can be handy there, and also there are multiple code generation packages in pub.dev for serialization and deserialization
Now in the place of your comment, you can do this:
mReviewsList.add(Review.fromMap(result.data()));
Update:
Based on Doug's comment, if you like to map your the data to your model and return a stream, you can create a helper function as follow:
Stream<List<T>> collectionStream<T>({
#required String path,
#required T builder(Map<String, dynamic> data),
}) {
final reference = FirebaseFirestore.instance.collection(path);
final snapshots = reference.snapshots();
return snapshots
.map((snapshot) => snapshot.docs.map((snapshot) => builder(snapshot.data())).toList());
}
To use it, simply call it as follow:
final stream = collectionStream<Review>(path: "reviews", builder: (data) => Review.fromMap(data));
if you like to fetch the data only once, you can create a helper function for that too:
Future<List<T>> getDocuments<T>({
String path,
#required T builder(Map<String, dynamic> data),
}) async {
final reference = FirebaseFirestore.instance.collection(path);
final snapshots = await reference.get();
final docs = snapshots.docs.map((doc) => builder(doc.data())).toList();
return docs;
}
and call it the same way:
final reviews = getDocuments<Review>(path: "reviews", builder: (data) => Review.fromMap(data));

Updating field in SQLite (Flutter)

Let's use this code snippet as an example. The data model is very simple:
class Dog {
final int id;
final String name;
final int age;
Dog({this.id, this.name, this.age});
}
To update the information, I'm using this function:
Future<void> updateDog(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
'dogs',
dog.toMap(),
// Ensure that the Dog has a matching id.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [dog.id],
);
}
await updateDog(Dog(id: 0, name: 'Fido', age: 42));
It works really fine and there aren't any problems. Now the question is, how to update only the field age without using name? So basically I want to do something like this
await updateDog(Dog(id: 0, age: 35));
and expect as a result "name: Figo, age: 35". But instead it removes Fido in null. So I get this as a result: "name: null, age: 35".
The example in the documentation looks like this:
// Update Fido's age and save it to the database.
fido = Dog(
id: fido.id,
name: fido.name,
age: fido.age + 7,
);
await updateDog(fido);
You either approach it with the raw SQL query like in chunhunghan's answer, or
query the Dog with the id, then override the fields, then update.
Why?
Lets look at your update code:
await updateDog(Dog(id: 0, age: 35));
When the update line is called, Dog.toMap() will be called and it will look like you are updating the name to null.
For you to do what you want here is the code:
Future<Dog> getDog(int id) async {
List<Map> result = await database.query(..... whereArgs: [id]);
if (result.length > 0) {
return new Dog.fromMap(result.first);
}
return null;
}
// Now in code
fido = await getDog(id);
// Update Fido's age and save it to the database.
fido = Dog(
id: fido.id,
name: fido.name,
age: 35, //<--
);
await updateDog(fido);
You can copy paste run full code below
Example code has two records to demo update effect
Solution 1 : You can use rawUpdate
code snippet
int count = await db.rawUpdate('UPDATE dogs SET age = ? WHERE id = ?', [35, 0]);
Solution 2 : You can revise toMap to only return id and age
Future<void> updateDog1(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
'dogs',
dog.toMap1(),
...
Map<String, dynamic> toMap1() {
return {
'id': id,
'age': age,
};
}
fido = Dog(
id: fido.id,
name: "not care",
age: 35,
);
await updateDog1(fido);
output
I/flutter ( 6570): [Dog{id: 0, name: Fido, age: 42}, Dog{id: 1, name: abc, age: 10}]
I/flutter ( 6570): updated: 1
I/flutter ( 6570): [Dog{id: 0, name: Fido, age: 35}, Dog{id: 1, name: abc, age: 10}]
full code solution 1
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
void main() async {
// Avoid errors caused by flutter upgrade.
// Importing 'package:flutter/widgets.dart' is required.
WidgetsFlutterBinding.ensureInitialized();
final database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'doggie_database.db'),
// When the database is first created, create a table to store dogs.
onCreate: (db, version) {
return db.execute(
"CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
Future<void> insertDog(Dog dog) async {
// Get a reference to the database.
final Database db = await database;
// Insert the Dog into the correct table. Also specify the
// `conflictAlgorithm`. In this case, if the same dog is inserted
// multiple times, it replaces the previous data.
await db.insert(
'dogs',
dog.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<List<Dog>> dogs() async {
// Get a reference to the database.
final Database db = await database;
// Query the table for all The Dogs.
final List<Map<String, dynamic>> maps = await db.query('dogs');
// Convert the List<Map<String, dynamic> into a List<Dog>.
return List.generate(maps.length, (i) {
return Dog(
id: maps[i]['id'],
name: maps[i]['name'],
age: maps[i]['age'],
);
});
}
Future<void> updateDog(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
'dogs',
dog.toMap(),
// Ensure that the Dog has a matching id.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [dog.id],
);
}
Future<void> deleteDog(int id) async {
// Get a reference to the database.
final db = await database;
// Remove the Dog from the database.
await db.delete(
'dogs',
// Use a `where` clause to delete a specific dog.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [id],
);
}
var fido = Dog(
id: 0,
name: 'Fido',
age: 42,
);
var fido1 = Dog(
id: 1,
name: 'abc',
age: 10,
);
// Insert a dog into the database.
await insertDog(fido);
await insertDog(fido1);
// Print the list of dogs (only Fido for now).
print(await dogs());
/*
// Update Fido's age and save it to the database.
fido = Dog(
id: fido.id,
name: fido.name,
age: fido.age + 7,
);
await updateDog(fido);
// Print Fido's updated information.
print(await dogs());*/
final Database db = await database;
int count =
await db.rawUpdate('UPDATE dogs SET age = ? WHERE id = ?', [35, 0]);
print('updated: $count');
print(await dogs());
/*// Delete Fido from the database.
await deleteDog(fido.id);
// Print the list of dogs (empty).
print(await dogs());*/
}
class Dog {
final int id;
final String name;
final int age;
Dog({this.id, this.name, this.age});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'age': age,
};
}
// Implement toString to make it easier to see information about
// each dog when using the print statement.
#override
String toString() {
return 'Dog{id: $id, name: $name, age: $age}';
}
}
full code solution 2
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
void main() async {
// Avoid errors caused by flutter upgrade.
// Importing 'package:flutter/widgets.dart' is required.
WidgetsFlutterBinding.ensureInitialized();
final database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'doggie_database.db'),
// When the database is first created, create a table to store dogs.
onCreate: (db, version) {
return db.execute(
"CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
Future<void> insertDog(Dog dog) async {
// Get a reference to the database.
final Database db = await database;
// Insert the Dog into the correct table. Also specify the
// `conflictAlgorithm`. In this case, if the same dog is inserted
// multiple times, it replaces the previous data.
await db.insert(
'dogs',
dog.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<List<Dog>> dogs() async {
// Get a reference to the database.
final Database db = await database;
// Query the table for all The Dogs.
final List<Map<String, dynamic>> maps = await db.query('dogs');
// Convert the List<Map<String, dynamic> into a List<Dog>.
return List.generate(maps.length, (i) {
return Dog(
id: maps[i]['id'],
name: maps[i]['name'],
age: maps[i]['age'],
);
});
}
Future<void> updateDog(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
'dogs',
dog.toMap(),
// Ensure that the Dog has a matching id.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [dog.id],
);
}
Future<void> updateDog1(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
'dogs',
dog.toMap1(),
// Ensure that the Dog has a matching id.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [dog.id],
);
}
Future<void> deleteDog(int id) async {
// Get a reference to the database.
final db = await database;
// Remove the Dog from the database.
await db.delete(
'dogs',
// Use a `where` clause to delete a specific dog.
where: "id = ?",
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [id],
);
}
var fido = Dog(
id: 0,
name: 'Fido',
age: 42,
);
var fido1 = Dog(
id: 1,
name: 'abc',
age: 10,
);
// Insert a dog into the database.
await insertDog(fido);
await insertDog(fido1);
// Print the list of dogs (only Fido for now).
print(await dogs());
// Update Fido's age and save it to the database.
fido = Dog(
id: fido.id,
name: "not care",
age: 35,
);
await updateDog1(fido);
// Print Fido's updated information.
print(await dogs());
/*final Database db = await database;
int count =
await db.rawUpdate('UPDATE dogs SET age = ? WHERE id = ?', [35, 0]);
print('updated: $count');
print(await dogs());*/
/*// Delete Fido from the database.
await deleteDog(fido.id);
// Print the list of dogs (empty).
print(await dogs());*/
}
class Dog {
final int id;
final String name;
final int age;
Dog({this.id, this.name, this.age});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'age': age,
};
}
Map<String, dynamic> toMap1() {
return {
'id': id,
'age': age,
};
}
// Implement toString to make it easier to see information about
// each dog when using the print statement.
#override
String toString() {
return 'Dog{id: $id, name: $name, age: $age}';
}
}

A simple Query in flutter/firebase database

I try to experience Firebase Live database with flutter.
I just would like to get a value in the datasnapshot of the firebase response.
My Firebase
My Code
static Future<User> getUser(String userKey) async {
Completer<User> completer = new Completer<User>();
String accountKey = await Preferences.getAccountKey();
FirebaseDatabase.instance
.reference()
.child("accounts")
.child(accountKey)
.child("users")
.childOrderBy("Group_id")
.equals("54")
.once()
.then((DataSnapshot snapshot) {
var user = new User.fromSnapShot(snapshot.key, snapshot.value);
completer.complete(user);
});
return completer.future;
}
}
class User {
final String key;
String firstName;
Todo.fromJson(this.key, Map data) {
firstname= data['Firstname'];
if (firstname== null) {
firstname= '';
}
}
}
I got Null value for firstname.
I guess I should navigate to the child of snapshot.value. But impossible to manage with foreach, or Map(), ...
Kind regards, Jerome
You are querying with a query and the documentation for Queries (here in JavaScript, but it is valid for all languages), says that "even when there is only a single match for the query, the snapshot is still a list; it just contains a single item. To access the item, you need to loop over the result."
I don't know exactly how you should loop, in Flutter/Dart, over the children of the snapshot but you should do something like the following (in JavaScript):
snapshot.forEach(function(childSnapshot) {
var childKey = childSnapshot.key;
var childData = childSnapshot.val();
// ...
});
and assuming that your query returns only one record ("one single match"), use the child snapshot when you do
var user = new User.fromSnapShot(childSnapshot.key, childSnapshot.value);
This will give you Users in reusable dialog. There might be slight disservice to yourself if you don't use stream and stream-builders, the solution below is a one time fetch of the users' collection on FirebaseDB.
class User {
String firstName, groupID, lastName, pictureURL, userID;
User({this.firstName, this.groupID, this.lastName, this.pictureURL, this.userID});
factory User.fromJSON(Map<dynamic, dynamic> user) => User(firstName: user["Firstname"], groupID: user["Group_id"], lastName: user["Lastname"], pictureURL: user["Picturelink"], userID: user["User_id"]);
}
Future<List<User>> users = Firestore.instance.collection("users").snapshots().asyncMap((users) {
return users.documents.map((user) => User.fromJSON(user.data)).toList();
}).single;

Resources