document.exists() returns false from firestore - android

I'm trying to check whether a document in the firestore exists. I copied an exact location of the document (col3) from the firestore console, so it must correct.
The document.exists() returns false despite the document being saved in the database. I followed Google guide from this site.
I've set the break point and checked the DocumentSnapshot object, but it very hard to follow e.g zza, zzb, zzc...
private fun nameExists(userId: String, colName: String): Boolean{
val nameExists = booleanArrayOf(false)
val docRefA = fbDb!!.document("users/X9ogJzjJyOgGBV0kmzk7brcQXhz1/finalGrades/col3")
val docRefB = fbDb!!.collection("users")
.document(userId)
.collection("finalGrades")
.document(colName)
docRefA.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val document = task.result
if (document.exists()) {
nameExists[0] = true
}else{
Log.d(TAG, "no such document")
}
} else {
Log.d(TAG, "get failed with ", task.exception)
}
}
return nameExists[0]
}

Thanks to #Frank van Puffelen hints, I was able to debug the code. From what I researched, the following code is a classic approach to solve this type of problem.
Perhaps someone will find it useful.
Step 1.
Define a functional interface with a parameter of the same type as the primitive value or object you want to return from the asynchronous operation.
interface OnMetaReturnedListener{
fun onMetaReturned(colExists: Boolean)
}
Step 2. Create a method passing an interface reference as an argument. This method will be running the synchronous operation. Once the value is retrieved, call the functional interface method and pass the retrieved value/object as the method argument.
private fun nameExists(metaReturnedListener: OnMetaReturnedListener, colName: String){
val userId = fbAuth!!.uid!!
val docRefB: DocumentReference = fbDb!!.collection("users")
.document(userId)
.collection("finalGrades")
.document(colName)
docRefB.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val doc: DocumentSnapshot = task.result!!
val colExists = doc.exists()
metaReturnedListener.onMetaReturned(colExists)
} else {
Log.d(TAG, "get failed with ", task.exception)
}
}
}
Step 3.
Call the method defined in step 2. Pass an object that you defined in step 1. This object will be invoked from the method in step 2, so the overridden method will receive the value/object as a parameter, which you can then assign to a variable of the same type. You can also pass other parameters of different types, but you have to update the method signature in step 2
fun saveModulesToFirestore(colName: String, saveCode: String) {
var collectionExists = false
if (saveCode == FirebaseHelper.ADD_TO_COLLECTION){
nameExists(object: OnMetaReturnedListener{
override fun onMetaReturned(colExists: Boolean) {
collectionExists = colExists
if (collectionExists){
val dialog = CollectionExistsDialogFragment.newInstance()
dialog.show(fragmentManager, DIALOG_COLLECTION_EXISTS)
return
} else{
addNewCollectionToFirebase(colName)
}
}
}, colName)
}
}

Related

Returning a value which is inside of add on Success Listener in kotlin [duplicate]

This question already has answers here:
How to return a list from Firestore database as a result of a function in Kotlin?
(3 answers)
Why does my function that calls an API or launches a coroutine return an empty or null value?
(4 answers)
Closed 11 months ago.
I am facing a problem to get a return type in a function in kotlin
The function is
override suspend fun uploaded_docs_retrival() : ArrayList<String> {
Firebase.firestore.collection("profiles").whereEqualTo("uid", "$uid").get()
.addOnSuccessListener { result ->
for (document in result) {
val myObject = document.toObject(docretreving::class.java)
val x = myObject.Docs
}
}
return x
}
Here I am retrieving the information from Fire store Mapping it to a object
However in above Function I am unable to return x as it says x is unresolved
My data class is as Follows:
data class docretreving(
val Docs : ArrayList<String> = arrayListOf<String>()
)
How do I write the code in such a way that I can return value x which is a Array list from my function uploaded_docs_retrival()
Edit the below code worked :-
override suspend fun uploaded_docs_retrival() : ArrayList<String> {
return try {
val result = Firebase.firestore.collection("profiles").whereEqualTo("uid", "$uid").get()
.await()
result.first().toObject(docretreving::class.java).Docs
} catch(e: Exception) {
Log.e("TAG", "Failed to upload: ${e.message.orEmpty()}")
ArrayList()
}
}
Call await() instead of adding a success listener. Wrap it in try/catch because it throws an exception on failure.
I'm not sure exactly why you're returning a single item when Firestore can return multiple. I'm just taking the first item in the results below:
override suspend fun uploaded_docs_retrival() : ArrayList<String> {
return try {
val result = Firebase.firestore.collection("profiles").whereEqualTo("uid", "$uid").get()
.await()
require(result.isNotEmpty()) { "No values returned." }
result.first().toObject(docretreving::class.java).Docs
} catch(e: Exception) {
Log.e(TAG, "Failed to upload: ${e.message.orEmpty()}")
ArrayList() // or you can rethrow or return null if you want
}
}
If you don't have access to the await() extension function, add this dependency in build.gradle:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.0"
You are getting 'x is unresolved' because the scope of 'val x' is within the for loop.
You won't be able to access it outside the loop and listener.
The best way to get list of strings, is to pass a listener as parameter to the method. You can also pass the 'SuccessListener' to get the values.
override suspend fun uploaded_docs_retrival(listener : SuccessListener) = Firebase.firestore.collection("profiles").whereEqualTo("uid","$uid").get() .addOnSuccessListener(listener)

Is there any way to return value and end a function in callback?

I am struggling with Firestore.
suspend fun writeClientMemo(userId: String, memo: Memo): MemoWriteResponse {
val response = MemoWriteResponse()
val docRef = Firebase.firestore
.collection(COLLECTION_USER)
.document(userId)
val result = Tasks.await(docRef.get())
if (result.exists() && result.data != null) {
Log.d("memo", "data exists ")
val res = docRef.update(FIELD_MEMO_LIST, FieldValue.arrayUnion(memo))
res.addOnSuccessListener {
Log.d("memo", "success")
response.isSuccessful = true
// What I want to do is something like these:
// 1. return#addOnSuccessListener response
// 2. return response
}.addOnFailureListener { e ->
Log.d("memo", "failed")
response.isSuccessful = false
response.errorMessage = "${e.toString}"
}
}
return response // At the moment this always get false(default is false)
}
I tried to make this synchronous but didn't work.
when {
res.isSuccessful -> {
response.isSuccessful = true
Log.d("memo", "true: ${response.toString()}")
}
res.isCanceled -> {
response.run {
isSuccessful = false
errorMessage = "ADD: Error writing document}"
}
Log.d("memo", "false: ${response.toString()}")
}
res.isComplete -> {
Log.d("memo", "false2: ${response.toString()}")
}
else->{
Log.d("memo", "false3: ${response.toString()}")
}
}
I always get false3 here.
Is there any way to return value and end a function in a callback?
No, you cannot simply return a value of an asynchronous operation as a result of a method. Firebase API, as most modern cloud/ APIs are asynchronous. This means that it takes some time until the operation completes. Since you are using the Kotlin programming language, I recommend you consider using Kotlin Coroutines and call .await() like this:
docRef.get().await();
This means that you suspend the operation until the data is available. This means that this value can be returned as a result of a method.
For more information you can check my answers from the following post:
How to return a list from Firestore database as a result of a function in Kotlin?
Or read the following article:
How to read data from Cloud Firestore using get()?
Where I have explained four ways in which you can deal with the Firestore using get().
P.S.
I tried to make this synchronous but didn't work
Never try to do that. It's best to get used to dealing with Firestore in such a way. Hopefully, my explanation from the above answer and article can help you a little bit.
You can achieve this using suspendCoroutine() utility function. It is a common practice to use it to convert asynchronous callback-based API to synchronous suspend API. You use it like this:
suspend fun loadSomeData(): String {
return suspendCoroutine { cont ->
loadSomeDataAsync(
onSuccess = {
cont.resume("ok!")
},
onFailure = {
cont.resume("failed!")
// OR:
cont.resume(null)
// OR:
cont.resumeWithException(Exception("failed"))
}
)
}
}
suspendCoroutine() suspends execution in outer scope until one of resume*() function is called. Then it resumes with the provided result.

FireStore not return all results

I want to get data from firestore, pretend that I have 10 documents in a collection. after that I must get data inside every document, so I save data in ArrayList. But FireBase never return all documents in a collection. Sometimes it returns only 5 ,6 docs in collection that has 10 docs.
my fireBaseUtil :
fun getDocumentByQueryAList( idQuery: List<String>, callBack: (ArrayList<DocumentSnapshot>) -> Unit) {
val listDocumentSnapshot = ArrayList<DocumentSnapshot>()
collRef = fireStore.collection("myCollection")
val size = idQuery.size
for (i in 0 until size) {
val query = collRef.whereEqualTo("fieldQuery", idQuery[i])
query.get().addOnSuccessListener { documents ->
for (document in documents) {
listDocumentSnapshot.add(document)
if (i == size - 1) {
callBack.invoke(listDocumentSnapshot)
}
}
}
}
}
I log out when size = 10 , but i = 8 it called invoke....
UserRepository:
FireBaseUtil.getDocumentByQueryAList{
// myList.addAll(listGettedByCallback)
}
->> when I want to have data in my list I call FireBaseUtil.getDocumentByQueryAList. I know firebase return value async but I dont know how to get all my doc then receiver.invoke("callbackValue").
Please tell me is there any solution. Thank in advance.
The problem you are experiencing is that you are expecting the queries to be run in order like so:
get idQuery[0], then add to list, then
get idQuery[1], then add to list, then
get idQuery[2], then add to list, then
...
get idQuery[8], then add to list, then
get idQuery[9], then add to list, then
invoke callback
But in reality, all of the following things happen in parallel.
get idQuery[0] (add to list when finished)
get idQuery[1] (add to list when finished)
get idQuery[2] (add to list when finished)
...
get idQuery[8] (add to list when finished)
get idQuery[9] (add to list and invoke callback when finished)
If the get idQuery[9] finishes before some of the others, you will be invoking your callback before the list is completely filled.
A primitive way to fix this would be to count the number of finished get queries, and when all of them finish, then invoke the callback.
fun getDocumentByQueryAList( idQuery: List<String>, callBack: (ArrayList<DocumentSnapshot>) -> Unit) {
val listDocumentSnapshot = ArrayList<DocumentSnapshot>()
collRef = fireStore.collection("myCollection")
val size = idQuery.size
val finishedCount = 0
for (i in 0 until size) {
val query = collRef.whereEqualTo("fieldQuery", idQuery[i])
query.get().addOnSuccessListener { documents ->
for (document in documents) {
listDocumentSnapshot.add(document)
}
if (++finishedCount == size) { // ++finishedCount will add 1 to finishedCount, and return the new value
// all tasks done
callBack.invoke(listDocumentSnapshot)
}
}
}
}
However, this will run into issues where the callback is never invoked if any of the queries fail. You could use a addOnFailureListener or addOnCompleteListener to handle these failed tasks.
The more correct and proper way to do what you are expecting is to make use of Tasks.whenAll, which is used in a similar fashion to how you see JavaScript answers using Promise.all. I'm still new to Kotlin syntax myself, so expect the following block to potentially throw errors.
fun getDocumentByQueryAList( idQueryList: List<String>, callBack: (ArrayList<DocumentSnapshot>) -> Unit) {
val listDocumentSnapshot = ArrayList<DocumentSnapshot>()
collRef = fireStore.collection("myCollection")
val getTasks = new ArrayList<Task<Void>>();
for (idQuery in idQueryList) {
val query = collRef.whereEqualTo("fieldQuery", idQuery)
getTasks.add(
query.get()
.onSuccessTask { documents ->
// if query succeeds, add matching documents to list
for (document in documents) {
listDocumentSnapshot.add(document)
}
}
)
}
Tasks.whenAll(getTasks)
.addOnSuccessListener { results ->
callback.invoke(listDocumentSnapshot)
}
.addOnFailureListener { errors ->
// one or more get queries failed
// do something
}
}
Instead of using the callback, you could return a Task instead, where the last bit would be:
return Tasks.whenAll(getTasks)
.onSuccessTask { results ->
return listDocumentSnapshot
}
This would allow you to use the following along with other Task and Tasks methods.
getDocumentByQueryAList(idQueryList)
.addOnSuccessListener { /* ... */ }
.addOnFailureListener { /* ... */ }
Use smth like this using RxJava:
override fun getAllDocs(): Single<List<MyClass>> {
return Single.create { emitter ->
db.collection("myCollection").get()
.addOnSuccessListener { documents ->
val list = mutableListOf<MyClass>()
documents.forEach { document ->
list.add(mapDocumentToMyClass(document))}
emitter.onSuccess(list)
}
.addOnFailureListener { exception ->
emitter.onError(exception)
}
}
}
private fun mapDocumentToMyClass(documentSnapshot: QueryDocumentSnapshot) =
MyClass(
documentSnapshot.get(ID).toString(),
documentSnapshot.get(SMTH).toString(),
// some extra fields
null
)
Or smth like this using coroutine:
override suspend fun getAllDocs(): List<MyClass> {
return try {
val snapshot =
db.collection("myCollection")
.get()
.await()
snapshot.map {
mapDocumentToMyClass(it)
}
} catch (e: Exception) {
throw e
}
}
This functions helps you to get all data from one doc

How to assign values inside a lambda expression

In Android, I was learning authentication in firebase. I want to store a Boolean value to a variable which is whether the task was successful or not. Here is my code:-
fun signIn(userEmail: String, userPassword: String): Boolean {
var successful = false
mAuth.signInWithEmailAndPassword(userEmail, userPassword)
.addOnCompleteListener {
successful = it.isSuccessful
Log.d(TAG, "successful = $successful")
}
Log.d(TAG, "successful = $successful")
return successful
}
When this method is invoked, the successful variable inside the higher-order function changes to true but it has no effect when it comes outside. Here is my log file:-
D/UserAuthentication: successful = true
D/UserAuthentication: successful = false
How do I fix this?
Your inner Log is called after your outer Log because the method signInWithEmailAndPassword is Asynchronous
Do something like this:
fun signIn(userEmail: String, userPassword: String, callback: (Task<AuthResult>) -> Unit) {
mAuth.signInWithEmailAndPassword(userEmail, userPassword)
.addOnCompleteListener {
callback.invoke(it)
}
}
Then call this function like this:
signIn("your_username", "your_password") {
if(it.isSuccessfull){
//Your login was successfull
}
}

Kotlin - How to properly map result using Either?

I'm trying to read a list of objects from the database and mapping it to another type of list.
// Returns either a Failure or the expected result
suspend fun getCountries(): Either<Failure, List<CountryItem>> {
// Get the result from the database
val result = countryLocalDataSource.getCountries()
// Left means Failure
if (result.isLeft) {
// Retrieve the error from the database
lateinit var error: Failure
result.either({
error = it
}, {})
// Return the result
return Either.Left(error)
}
// The database returns a List of Country objects, we need to map it to another object (CountryItem)
val countryItems: MutableList<CountryItem> = mutableListOf()
// Iterate the Country List and construct a new List of CountryItems
result.map { countries -> {
countries.forEach {
// Assign some values from an Enum (localized string resources)
val countryEnumValue = Countries.fromId(it.id)
countryEnumValue?.let { countryIt ->
val countryStringNameRes = countryIt.nameStringRes;
// Create the new CountryItem object (#StringRes value: Int, isSelected: Bool)
countryItems.add(CountryItem(countryStringNameRes, false))
}
}
} }
// Because this is a success, return as Right with the newly created List of CountryItems
return Either.Right(countryItems)
}
For the sake of readability I didn't included the whole Repository or the DAO classes and I have left comments in the code snippet above.
In a nutshell: I'm using Kotlin's Coroutines for accessing the database in a separated thread and I'm handling the response on the UI Thread. Using the Either class in order to return two different results (failure or success).
The above code works, however It's too ugly. Is this the right approach to deliver the result?
What I'm trying to do is to refactor the code above.
The whole problem is caused by the two different object types. The Database Data Source API is returning an Either<Failure, List<Country>>, meanwhile the function is expected to return an Either<Failure, List<CountryItem>>.
I can't deliver a List<CountryItem> directly from the Database Data Source API, because Android Studio doesn't let me compile the project (entities implementing interfaces, compile error, etc.). What I'm trying to achieve is to map the Either result in a nicer way.
Try using Kotlin's Result
So in your case you can write something like:
return result.mapCatching { list: List<Country> -> /*return here List<CountryItem>>*/ }
And for checking result call:
result.fold(
onSuccess = {...},
onFailure = {...}
)
In order to invoke a constructor you should call Result.success(T) or Result.failure(Throwable)
Unfortunately, you'll also need to suppress use-as-return-type warnings How to
You can simplify by checking the type of Either and accessing the value directly. In your case:
access Left via result.a -> Failure
access Right via result.b -> List<Country>
ex:
when (result) {
is Either.Left -> {
val failure: Failure = result.b
...
}
is Either.Right -> {
val countries: List<Country> = result.b
...
}
}
An alternative is to use the either() function (normally this is called fold()):
result.either(
{ /** called when Left; it == Failure */ },
{ /** called when Right; it == List<Country> */ }
)
Assume your Country class is defined as follow:
data class Country(val name: String) {}
and your CountryItem class is defined as follow:
data class CountryItem(private val name: String, private val population: Int) {}
and your CountryLocalDataSource class with a method getCountries() like this:
class DataSource {
suspend fun getCountries(): Either<Exception, List<Country>> {
return Either.Right(listOf(Country("USA"), Country("France")))
//return Either.Left(Exception("Error!!!"))
}
}
then the answer to your question would be:
suspend fun getCountryItems(): Either<Exception, List<CountryItem>> {
when (val countriesOrFail = DataSource().getCountries()) {
is Either.Left -> {
return Either.Left(countriesOrFail.a)
}
is Either.Right -> {
val countryItems = countriesOrFail.b.map {
CountryItem(it.name, 1000)
}
return Either.Right(countryItems)
}
}
}
To call your getCountryItems(), here is an example:
suspend fun main() {
when (val countriesOrFail = getCountryItems()) {
is Either.Left -> {
println(countriesOrFail.a.message)
}
is Either.Right -> {
println(countriesOrFail.b)
}
}
}
Here's the sample code in the playground: https://pl.kotl.in/iiSrkv3QJ
A note about your map function:
I'm guessing you don't actually need the result to be a MutableList<CountryItem> but you had to define so because you want to add an element as you iterate through the input list List<Country>.
Perhaps the following is the case: If you have a List<Country> with 2 elements like in the example, and you want to map so that the result becomes a List<CountryItem> with also 2 corresponding elements, then you don't need to call forEach inside a fun that gets passed to the higher-order function map. But this may be an entirely new question.

Categories

Resources