Kotlin Save Firestore query result for a variable - firebase

I have a problem with Firebase.
I also use Firestore on a website and in a Kotlin app.
On the website, I can save the result of the query to a variation in the following way:
const addStudentManu = async($this) => {
const userId = await db.collection('users').where('neptun','==',ASD123).get();
const getUserId = userId.docs.map(doc=>doc.id);
}
How can i do this in kotlin?

This is how it goes:
db.collection("users")
.whereEqualTo("neptun", "ASD123")
.get()
.addOnSuccessListener { documents ->
val list = mutableListOf<String>()
for (document in documents) {
Log.d(TAG, "${document.id}")
list.add(document.id)
}
println(list)
}
.addOnFailureListener { exception ->
Log.w(TAG, "Error getting documents: ", exception)
}
You can checkout the sample code snippets in the documentation.

While #Dharmaraj answer will work perfectly fine, when it comes to Kotlin, the most convenient way for saving the result of a query would be to use Kotlin Coroutines, We can create a suspend function and map all documents to their corresponding IDs, similar with your example. So please try the following lines of code:
private suspend fun getIdsFromFirestore(): List<String> {
val ids = db.collection("users").whereEqualTo("neptun", "ASD123").get().await()
return ids.documents.mapNotNull { doc ->
doc.id
}
}
As you can see, we have now an extension function called await() that will interrupt the Coroutine until the data from the database is available and then return it. That's almost the same thing when using async on the web.
Now we can simply call this from another suspend method like in the following lines of code:
private suspend fun getIds() {
try {
val ids = getIdsFromFirestore()
// Do what you need to do with the list of IDs
} catch (e: Exception) {
Log.d(TAG, e.getMessage()) //Don't ignore potential errors!
}
}

Related

How can show a document field from a Firestore collection document in a jetpack compose text view [duplicate]

This question already has answers here:
How to return a DocumentSnapShot as a result of a method?
(2 answers)
Closed 10 months ago.
I am very sorry if I break some rules, or if this has already been asked before. I have used so much time to google examples, and questions on stack overflow and other recourses. But I can simply not understand how I can get a document field from a firestore collection, and show the string value in a jetpack compose text function.
I am a very beginner in programming and Android. So I properly has some fundamental misunderstanding how I should do it but here is my attempt which doesn't work, and I can not understand why.
In Firestore I have a collection, called users with a document called holidaySavings that has a field of type string called name.
I want to show the value of name in a composable text function.
I have a class called storedData that handles Firestore. It has methods for creating and update a collection /document /fields. That works.
But I cant seem to be able to read a field value from a document to a jetpack composable text.
I can read the value from a document field, to the Log in Android studio.
Here is my function in my class where I handle the Firestore database
fun readDataTestFinal(): String{
val docRef = db.collection("users").document("holidaySavings")
var returnTest = ""
docRef.get()
.addOnSuccessListener { document ->
if (document != null) {
Log.d("Rtest", "DocumentSnapshot data: ${document.data}")
// I want to return this so I can use it in a composable text view
returnTest = document.get("name").toString()
} else {
Log.d("Rtest", "No such document")
}
}
.addOnFailureListener { exception ->
Log.d("Rfail", "get failed with ", exception)
}
return returnTest
}
And here I try to read the value into a jetpack compose Text function.
var newStringFromStoredData by remember {
mutableStateOf(storedData().readDataTestFinal())
}
Text(
modifier = Modifier.background(color = Color.Blue),
text = newStringFromStoredData
)
When I run the app. everything compiles fine, and I get the value from the document field fine, and can see it in the Log in Android Studio.
But the Compose function where call Text with the value newStringFromStoredData it doesn't show on the screen?
Can anyone tell me what it is I don't understand, and how it could be done so I can use the firestore document field and show the value in a jetpack compose Text function?
The Firebase call is async, which means it will not return the data immediately. This is why the API uses a callback. Therefore, your function readDataTestFinal is always returning an empty string.
One solution you can use is transform your function in a suspend function and call it using a coroutine scope. For example:
suspend fun readDataTestFinal(): String {
val docRef = firestore.collection("users")
.document("holidaySavings")
return suspendCoroutine { continuation ->
docRef.get()
.addOnSuccessListener { document ->
if (document != null) {
continuation.resume(document.get("name").toString())
} else {
continuation.resume("No such document")
}
}
.addOnFailureListener { exception ->
continuation.resumeWithException(exception)
}
}
}
In the code above, we are converting a callback call to suspend function.
In your composable, you can do the following:
var newStringFromStoredData by remember {
mutableStateOf("")
}
Text(newStringFromStoredData)
LaunchedEffect(newStringFromStoredData) {
newStringFromStoredData =
try { readDataTestFinal() } catch(e: Exception) { "Error!" }
}
The LaunchedEffect will launch your suspend function and update the result as soon it loads.
A better option would be define this call in a View Model and call this function from it. But I think this answers your question and you can improve your architecture later. You can start from here.
The most convenient, quickest, and apparently the best-in-your-case patch would be to use what are called valueEventListeners.
Firebase provides these helpful methods for you, so that you can keep your app's data up-to-date with the firebase servers.
val docRef = db.collection("cities").document("SF")
docRef.addSnapshotListener { snapshot, e -> // e is for error
// If error occurs
if (e != null) {
Log.w(TAG, "Listen failed.", e)
return#addSnapshotListener
}
// If backend value not received, use this to get current local stored value
val source = if (snapshot != null && snapshot.metadata.hasPendingWrites())
"Local"
else
"Server"
// If request was successful,
if (snapshot != null && snapshot.exists()) {
Log.d(TAG, "$source data: ${snapshot.data}")
//Update your text variable here
newStringFromStoredData = snapshot.data // Might need type-conversion
} else {
Log.d(TAG, "$source data: null")
}
}
This will not only solve your problem as described in the question, but will also ensure that whenever the value on the server is changed/updated, your text will update alongside it. It is usually a good best practice to use these listeners, and these are often converted into LiveData objects for respecting the 'separation-of-concerns' principle, but you can use this simple implementation for the simple use-case described.
Another thing, this would usually go in a viewModel, and hence, you should declare you text variable inside the viewmodel too.
Try it in the init block.
calss MVVM: ViewModel() {
init {
/* Paste Code From Above Here */
}
var newStringFromStoredData by mutableStateOf("")
}
Then read it in the Composable
Text(viewModel.newStringFromStoredData)

Realm query sometimes return no data

I am implementing a process to store hundreds of thousands of records and retrieve them by realm query.
The simplified code is as follows
Realm Object
final class Friend: Object {
#objc dynamic var number: Int64 = 0
#objc dynamic var name: String = ""
convenience init(number: Int64, label: String) {
self.init()
self.number = number
self.label = label
}
override class func primaryKey() -> String? {
return "number"
}
}
extension Realm {
static var `default`: Realm {
get {
do {
let realm = try Realm(configuration: RealmConstants.configuration)
return realm
} catch {
return self.default
}
}
}
}
RealmClient class
struct RealmClient {
static func save(_ list: [Friend],
completion: #escaping (Result<Void, Error>) -> Void) {
DispatchQueue.global().async {
autoreleasepool {
do {
let realm = Realm.default
try realm.write {
realm.add(list)
completion(.success(()))
}
} catch {
completion(.failure(error))
}
}
}
}
}
DataStore class (shared file with Call directory extension target)
class DataStore {
let realm = realm
init(realm: Realm.default) {
self.realm = realm
}
var recordcounts: Int {
return realm.objects(Friend.self).count
}
}
However, sometimes the realm query returns 0 records.
Query results
RealmClient.save(friendList) -> friendList is hundreds of thousands of data fetched from server
let dataStore = DataStore()
dataStore.recordcounts -> sometimes return 0
So I have implemented refreshing the realm instance, but it doesn't help.
Like this.
realm.refresh()
Question
I want to be able to always get the data in a query.
How can I implement stable data using a query?
Realm writes are transactional - the transaction is a list of read and write operations that Realm treats as a single indivisible operation.
The reads and writes within the closure {} following the write are part of the transaction; the data is not committed until all of those complete. Transactions are also all or nothing - either all operations complete or none.
Most importantly for this case, transactions are synchronous so adding a callback within the transaction is going to present intermittent results.
Move the callback outside the write transaction
try realm.write {
realm.add(list)
}
completion(.success(()))
It can be illustrated by how we used to write realm transactions:
realm.beginWrite()
write some data
// Commit the write transaction to make this data available to other threads
try! realm.commitWrite() <- data is not valid until after it's been committed
//data has been committed

I cannot access the data when extracting data from firebase in Flutter

My code is written like this, but I cannot access the data in the output I get?
void getMessages() async {
final messages = await _firestore.collection('mesajlar').get();
for (var message in messages.docs) {
print(message.data);
}
}
The problem is that you are calling data as a property when it is actually a function, you can see that in the documentation for DocumentSnapshot, which QueryDocumentSnapshot implements. So all you have to change is the print:
print(message.data());

Flutter firebase database.set(object) issue

I have a class Product and it is in List plist
Now I need to call the firebase database.set(plist) this is working with Java but when I tried to do it with flutter dart it showing error anybody have the solution for this problem
From StackOverflow, I understand use database.set('{"a":"apple"}) but when I am dealing with List I can't use this solution
update error message
error called Invalid argument: Instance of 'Product'
My code
String table_name="order";
FirebaseAuth.instance.currentUser().then((u){
if(u!=null){
FirebaseDatabase database = FirebaseDatabase(app: app);
String push=database.reference().child(table_name).child(u.uid).push().key;
database.reference().child(table_name).child(u.uid).child(push).set( (productList)).then((r){
print("order set called");
}).catchError((onError){
print("order error called "+onError.toString());
});
}
});
}
We cannot directly set object in Firebase. Unfortunately in Flutter there is no easy solution like java json.
Data types that are allowed are String, boolean, int, double, Map, List. inside database.set().
We can have a look at the official documentation of Flutter https://pub.dev/documentation/firebase_database/latest/firebase_database/DatabaseReference/set.html
Try setting object like this
Future<bool> saveUserData(UserModel userModel) async {
await _database
.reference()
.child("Users")
.child(userModel.username)
.set(<String, Object>{
"mobileNumber": userModel.mobileNumber,
"userName": userModel.userName,
"fullName": userModel.fullName,
}).then((onValue) {
return true;
}).catchError((onError) {
return false;
});
}
I hope this code will be helpful.
Extending a little bit an answer given as a comment above
You basically have to create an auxiliary map beforehand:
Map aux = new Map<String,dynamic>();
And then iterate through the array that you have adding the corresponding map for each child that you want to add:
productList.forEach((product){
//Here you can set the key of the map to whatever you like
aux[product.id] = product.toMap();
});
Just in case, the function toMap inside the Product class should be something like:
Map toMap() {
Map toReturn = new Map();
toReturn['id'] = id;
toReturn['name'] = name;
toReturn['description'] = description;
return toReturn;
}
And then, when you are calling the set function to save to firebase you can do something like:
.set({'productList':aux,})
Hope this was helpful to someone.

Fetch multiple documents by id in Firestore

I'm currently doing a proof of concept for an Android app with the new Firestore as backend/db. I need to fetch a bunch of documents by their id (they are all in the same collection)
Right now, I'm looping thru the id list and fetching them one by one and storing them in a list which in turn updates a RecycleView in the app. This seems to be a lot of work and it does not perform very well.
What is the correct way to fetch a list of documents from Firestore without having to loop all the ids and getting them one by one?
Right now my code looks like this
for (id in ids) {
FirebaseFirestore.getInstance().collection("test_collection").whereEqualTo(FieldPath.documentId(), id)
.get()
.addOnCompleteListener {
if (it.isSuccessful) {
val res = it.result.map { it.toObject(Test::class.java) }.firstOrNull()
if (res != null) {
testList.add(res)
notifyDataSetChanged()
}
} else {
Log.w(TAG, "Error getting documents.", it.exception)
}
}
}
This is the way i'm using until they will add this option
I made that with AngularFire2 but also with the rxfire libary is the same if you are using react or vue.
You can map the null items before subscribing if there are some documents that deleted.
const col = this.fire.db.collection('TestCol');
const ids = ['a1', 'a2', 'a3', 'a4'];
const queries = ids.map(el => col.doc(el).valueChanges());
const combo = combineLatest(...queries)
.subscribe(console.log)
Firestore does not currently support query by IDs.
According to AngularFirebase, this is in the roadmap for development in the future, but its not official:
Keep in mind, Firestore is still in beta. Firebase engineers hinted at some really cool features on the roadmap (geo queries, query by array of ids) - I’ll be sure to keep you posted :)
Here is the way you can get specific documents,
here is a sample code:
List<String> documentsIds = {your document ids};
FirebaseFirestore.getInstance().collection("collection_name")
.whereIn(FieldPath.documentId(), documentsIds).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : Objects.requireNonNull(task.getResult())) {
YourClass object = document.toObject(YourClass.class);
// add to your custom list
}
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
e.printStackTrace();
}
});

Resources