I'm developing an Android Q&A application. I'd like users to delete all of their posts when they delete their accounts. I found this article so I tried the same code, but it didn't work.
This is my code.
#Composable
fun DeleteAccounts(navController: NavController, uid: String) {
val db = Firebase.firestore
Button(
onClick = {
val batch = db.batch()
db.collection("posts")
.whereEqualTo("uid", "$uid")
.get().result.forEach {
batch.delete(it.reference)
}
batch.commit()
.addOnSuccessListener {
navController.navigate("Login")
}
}
And this is the error message.
java.lang.IllegalStateException: Task is not yet complete
at com.google.android.gms.common.internal.Preconditions.checkState(com.google.android.gms:play-services-basement##18.1.0:2)
at com.google.android.gms.tasks.zzw.zzf(com.google.android.gms:play-services-tasks##18.0.1:1)
at com.google.android.gms.tasks.zzw.getResult(com.google.android.gms:play-services-tasks##18.0.1:1)
at com.example.**app.DeleteAccountsViewKt$DeleteAccounts$1$5$1$1.invoke(DeleteAccountsView.kt:124)
at com.example.**app.DeleteAccountsViewKt$DeleteAccounts$1$5$1$1.invoke(DeleteAccountsView.kt:117)
What am I doing wrong? Thank you.
I tried another code and this works. But I don't know if this is correct.
val batch = db.batch()
db.collection("posts")
.whereEqualTo("uid", uid)
.get()
.addOnSuccessListener { result ->
for (document in result) {
batch.delete(document.reference)
}
batch.commit()
It's possible there are too many documents to delete in your Android client. If you read the documentation here: https://firebase.google.com/docs/firestore/manage-data/delete-data#collections It says that batch deleting documents in a client is not recommended.
You could try writing a Cloud Function to batch delete a lot of documents instead.
Related
I have a DAO class where I have fetchHubList method which fetches a collection of documents from cloud Firestore asynchronously using await(). This implementation used the "get()" method which I got to know later on does not fetch real-time updates. On trying to implement the code similarly using onSnapshotListener gives an error (which was quite expected to be honest, because get() and this methods return quite different things). Does anyone have any idea how to implement this?
How the code is currently:
suspend fun fetchHubList(): ArrayList<HubModel>? = try {
val hubList = ArrayList<HubModel>()
hubsListCollection.get().await().map { document ->
if (document != null) {
Log.d(TAG, "Data fetch successful!")
Log.d(TAG, "the document id is ${document.id}")
val temp = HubModel(document.get("hubName").toString(),
document.id.toString(),
document.get("isAdmin") as Boolean)
hubList.add(temp)
// hubList.add(document.toObject(HubModel::class.java))
} else {
Log.d(TAG, "No such document")
}
}
And what I want to implement here (and which is totally erroneous):
suspend fun fetchHubList(): ArrayList<HubModel>? = try {
val hubList = ArrayList<HubModel>()
hubsListCollection.addSnapshotListener().await().map { document ->
if (document != null) {
Log.d(TAG, "Data fetch successful!")
Log.d(TAG, "the document id is ${document.id}")
val temp = HubModel(document.get("hubName").toString(),
document.id.toString(),
document.get("isAdmin") as Boolean)
hubList.add(temp)
// hubList.add(document.toObject(HubModel::class.java))
} else {
Log.d(TAG, "No such document")
}
}
I use this function in my ViewModel class to create a LiveData wrapped ArrayList:
val hubList = MutableLiveData<ArrayList<HubModel>>()
private val hubListDao = HubListDao()
init {
viewModelScope.launch {
hubList.value = hubListDao.fetchHubList()
}
}
Thanks in advance!
You don't need addSnapshotListener, just use get:
hubsListCollection.get().await()
In order to observe changes in your collection you can extend LiveData:
class CafeLiveData(
private val documentReference: DocumentReference
) : LiveData<Cafe>(), EventListener<DocumentSnapshot> {
private var snapshotListener: ListenerRegistration? = null
override fun onActive() {
super.onActive()
snapshotListener = documentReference.addSnapshotListener(this)
}
override fun onInactive() {
super.onInactive()
snapshotListener?.remove()
}
override fun onEvent(result: DocumentSnapshot?, error: FirebaseFirestoreException?) {
val item = result?.let { document ->
document.toObject(Cafe::class.java)
}
value = item!!
}
}
And expose it from your view model:
fun getCafe(id: String): LiveData<Cafe> {
val query = Firebase.firestore.document("cafe/$id")
return CafeLiveData(query)
}
As #FrankvanPuffelen already mentioned in his comment, there is no way you can use ".await()" along with "addSnapshotListener()", as both are two totally different concepts. One is used to get data only once, while the second one is used to listen to real-time updates. This means that you can receive a continuous flow of data from the reference you are listening to.
Please notice that ".await()" is used in Kotlin with suspend functions. This means that when you call ".await()", you start a separate coroutine, which is a different thread that can work in parallel with other coroutines if needed. This is called async programming because ".await()" starts the coroutine execution and waits for its finish. In other words, you can use ".await()" on a deferred value to get its eventual result, if no Exception is thrown. Unfortunately, this mechanism doesn't work with real-time updates.
When it comes to Firestore, you can call ".await()" on a DocumentReference object, on a Query object, or on a CollectionReference object, which is actually a Query without filters. This means that you are waiting for the result/results to be available. So you can get a document or multiple documents from such calls. However, the following call:
hubsListCollection.addSnapshotListener().await()
Won't work, as "addSnapshotListener()" method returns a ListenerRegistration object.
I want to use a snapshot listener to listen to changes that might occur in my database to update my RecyclerView
In this case, you should consider using a library called Firebase-UI for Android. In this case, all the heavy work will be done behind the scenes. So there is no need for any coroutine or ".await()" calls, everything is synched in real-time.
If you don't want to use either Kotlin Coroutines, nor Firebase-UI Library, you can use LiveData. A concrete example can be seen in my following repo:
https://github.com/alexmamo/FirestoreRealtimePagination/blob/master/app/src/main/java/ro/alexmamo/firestorerealtimepagination/ProductListLiveData.java
Where you can subclass LiveData class and implement EventListener the interface.
I have a simple login/signup/dashboard app which i am working on. What i do is, i signup a user using firebase signup, and in that call, i add that user information in my collection and that gets added successfully and shows in collection as well. When i log in, the new user gets verified and in order to show the name of user on dashborad, i want to retrieve the name from collection using the ID of currently authenticated user. Look at the code below:
suspend fun getUserData() : User?{
firestoreDB.collection("users").document(auth.currentUser!!.uid)
.get()
.addOnSuccessListener { documentSnapshot ->
userInfo = documentSnapshot.toObject(User::class.java)!!
}
return userInfo
}
When I debugged this piece of code, i found that the part inside the .addOnSuccessListener is not getting execute at all. So, i get null in return.Can anyone one point out what am I doing wrong here?
The function is called from my viewmodel as:
fun getUserData() = viewModelScope.launch{
val user= repository.getUserData()
val username = user?.name
}
My Database looks like this:
If you want to create a suspend fun using the result of an asynchronous operation that returns a Play services Task object, don't use addOnSuccessListener. get() is asynchronous and returns immediately with the Task object. You will need a way to convert that Task into something that can work with coroutines to suspect the execution of the function until the Task is complete.
There is a library for this: kotlinx-couroutines-play-services. It will add an extension function await() to the task so you can use it in a suspend fun, an also use try/catch to trap errors. In fact, that page shows you the code pattern you will use:
val snapshot = try {
FirebaseFirestore.getInstance().document("users/$id").get().await()
} catch (e: FirebaseFirestoreException) {
return#async
}
In your case, more like this:
return firestoreDB
.collection("users")
.document(auth.currentUser!!.uid)
.get()
.await()
.toObject(User::class.java)!!
Be sure to have the caller try/catch for errors.
Can someone guide/help me how to write unit test for the getProfileDetails() function:
I have gone through various answers on Stackoverflow but unable to solve this
Here is the relevant code
class EditProfileViewModel(private val mFireStore: FirebaseFirestore,
private val mStorageReference: StorageReference) {
fun getProfileDetails() {
mFireStore.collection(Constants.USER).document("document_id")
.get()
.addOnSuccessListener {documentSnapshot ->
if (documentSnapshot != null){
val userProfile = documentSnapshot.toObject(UserProfile::class.java)
Log.d("TAG","UserProfile ${userProfile.toString()}")
}
}.addOnFailureListener { ex ->
Log.d("TAG",ex.message.toString())
}
}
}
I am able to mock firestore instance using mockito, but not able to figure a way to test code on onSuccess and onFailure.
Let me know if any other detail is required.
I am developing an android app with post and comments using the firebase firestore database. In the comment section, I am loading the lastest 20 comments using the following code
val db= FirebaseFirestore.getInstance()
db.collection("comments")
.whereEqualTo("post_id", postId)
.limit(20)
.orderBy("time", Query.Direction.DESCENDING)
.get(Source.SERVER)
.addOnSuccessListener { documents ->
}
.addOnFailureListener { exception ->
}
I show these comments in recyclerview. When the user clicks previous comments I fetch timestamp of older comment and run following code
val db= FirebaseFirestore.getInstance()
db.collection("comments")
.limit(20)
.whereLessThan("time",timestamp)
.whereEqualTo("post_id", postId)
.orderBy("time", Query.Direction.DESCENDING)
.get(Source.SERVER)
.addOnSuccessListener { documents ->
}
.addOnFailureListener { exception ->
}
it fetches 20 documents from firestore database. But it doesn't fetch older comment. Instead, it fetches the latest comments.
when I remove .orderBy("time", Query.Direction.DESCENDING) it loads very first comments from document for that post id
For example.If the post contains 60 comments. first, I want to load 60-40 comments. then I want to load 40-20. then 20-0 .
I have an App with Firestore. I have a lot of Repositories. They work from Firestore. When I call 2 method in same time then I got an error.
class CommentRepository : CommentRepositoryInterface {
val firebaseFirestore = FirebaseFirestore.getInstance()
companion object {
const val COLLECTION_NAME = "post_comments"
const val COMMENT_POST_ID_KEY = "postid"
}
override fun getPostCommentsById(postId: String): Observable<CommentModel> {
return Observable.create { subscriber ->
firebaseFirestore.collection(COLLECTION_NAME)
.whereEqualTo(COMMENT_POST_ID_KEY, postId)
.get()
.addOnCompleteListener { task ->
if (task.isSuccessful) {
for (document in task.result) {
if (document.exists()) {
val documentModel = document.toObject(CommentModel::class.java)
subscriber.onNext(documentModel)
}
}
subscriber.onComplete()
} else {
subscriber.onError(task.exception!!) // TODO
}
}
}
}
}
The another one is almost same like that, but that one is using another collection.
So when I called these functions, then I got the next error:
Internal error in Firestore (0.6.6-dev).
Caused by: java.lang.RuntimeException: Failed to gain exclusive lock to the Firestore client's offline persistence. This generally means you are using Firestore from multiple processes in your app. Keep in mind that multi-process Android apps execute the code in your Application class in all processes, so you may need to avoid initializing Firestore in your Application class. If you are intentionally using Firestore from multiple processes, you can only enable offline persistence (i.e. call setPersistenceEnabled(true)) in one of them.
In the MyApplication class I tried to set the Singleton's of firestore settings.
val settings = FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(true)
.build()
FirebaseFirestore.getInstance().firestoreSettings = settings
I found it in Firestore's Doc:
For Android and iOS, offline persistence is enabled by default.
Anyone have idea to solve this problem?
I've cleared the App's Caching and the problem solved.
Do it or just remove from the phone! :)