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! :)
Related
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.
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 am building a client application which uses Firebase for two things:
User Authentication
Using a realtime database
I have managed to set up everything correctly on my client and on my backend server (using Firebase's Admin SDK) and am able to correctly authenticate users and allow them to read/write to the database.
I am also using Retrofit2 to send requests from the client to the backend.
As part of allowing users access to the database, it is needed to send the user's token to the backend so the user can be verified.
To do this, I have the following logic:
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
user.getIdToken(false).addOnCompleteListener {
if (it.isSuccessful) {
val token = it.result?.token
//retrofit logic to send request happens from here
}
}
As you can see, getting the Id token of the user is an asynchronous call and in the current code base that I have, I have this code block for each one of my calls to the backend (duplication).
I want to know how I can export this snippet to a function (maybe a suspend method?) so that it can be reused for every call to the backend
I have searched online and have seen many SO questions, but none that fit this scenario.
I have thought about passing in a callback, but I have several methods that communicate to the backend, and each of them will require a different callback method.
The solution I am looking for looks something like this:
fun fetchDataFromDB() {
getIdTokenForUser()
//wait till it finishes and then
//perform request to DB
}
fun updateDataInDB() {
getIdTokenForUser()
//wait till it finishes and then
//perform request to DB
}
//......
I have tried reading about and implementing coroutines, but I lack the knowledge to do so correctly.
EDIT
Thanks to #Doug Stevenson for his answer and direction, I have managed to construct the following:
private suspend fun getUserIdToken(user: FirebaseUser) = coroutineScope {
val job = async {
user.getIdToken(false).result?.token
}
job.await()
}
And I use it in this fashion:
fun updateDB(context: Context) = runBlocking {
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
val token = getUserIdToken(user)
}
}
Is this the correct approach? Since the answers given below present a different implementation.
getIdToken is asynchronous returns a Task object. If you want to use a Task object in a Kotlin coroutine, you can use the library kotlinx-coroutines-play-services to add an extension method await() to the Task that makes it usable in a coroutine. With that, you can write something like this:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.9"
import kotlinx.coroutines.tasks.await
suspend fun getIdTokenForUser(user: FirebaseUser): GetTokenResult {
return try {
user.getIdToken(false).await()
}
catch (e: Exception) {
// handle error
}
}
You might have to update the types here - I didn't try to compile or test this.
See also:
Android kotlin task to be executed using coroutines
Coroutines And Firebase: How to Implement Javascript-like Promise.all()
Using Firebase with Kotlin coroutines
In order to go from a callback based API like the following one:
val myCallback = object : ServiceCallback() {
override fun onResult(theobject: Something) {
// your callback code here
}
override fun onFailure(ex: Throwable) {
// error handling
}
}
theService.enqueue(callback)
You can use suspendCoroutine
What it does is that it suspends execution until the continuation is satified by the callback. So you can write a KTX like the following:
suspend fun Service.getSomething(): Something = suspendCoroutine{ cont ->
val callback = object : ServiceCallback(){
override fun onSuccess(data: Something): Unit = cont.resume(data)
override fun onFailure(ex: Throwable): Unit = cont.resume(ex)
}
this.enqueue(callback)
}
In my app I am trying to use MVVM with repositories databases and all that. I like to keep all my external dependencies and such separate and compartmentalized into their own files/modules so that they can easily be replaced or swapped out.
With Realm I could make this work really well by using unmanaged objects. I can have a RealmHelper class for example which just opens a realm instance, queries or performs some transaction and then closes the realm and returns an object.
So how can I accomplish something similar with managed objects? The problem is in this case that you have to know when to close the realm. The obvious solution here I think is to let the database know when you are done with it, but this seems like a tedious and unoptimized solution. Is there another better way?
So I have attempted to come up with a solution to this myself. I haven't tested it very well yet but my idea is basically to modify the LiveRealmResults file from the official example to let the caller (RealmHelper for example) know when it changes states between inactive and active. When it is active the caller will open the realm and pass in the results. When it changes to inactive the caller will close the realm. This is what my LiveRealmResults looks like:
#MainThread
class LiveRealmResults<T : RealmModel>(
private val getResults: () -> RealmResults<T>,
private val closeRealm: () -> Unit
) : LiveData<List<T>>() {
private var results: RealmResults<T>? = null
private val listener = OrderedRealmCollectionChangeListener<RealmResults<T>> {
results, _ ->
this#LiveRealmResults.value = results
}
override fun onActive() {
super.onActive()
results = getResults()
if (results?.isValid == true) {
results?.addChangeListener(listener)
}
if (results?.isLoaded == true) {
value = results
}
}
override fun onInactive() {
super.onInactive()
if (results?.isValid == true) {
results?.removeChangeListener(listener)
}
removeObserver()
}
}
It will be used like so:
class RealmHelper() {
fun getObjects(): LiveData<List<Objects>> {
var realm: Realm? = null
return LiveRealmResults<Objects>(getResults = {
realm = Realm.getDefaultInstance()
realm!!.where<Objects>().findAll()
}, removeObserver = {
realm?.close()
})
}
}
This method at least allows me to keep all realm logic in the RealmHelper, only exposing LiveData and not RealmResults. Whenever the LiveData is inactive the Realm is closed. In my example I'm returning RealmObject but I'm fine converting from RealmObject to normal object so I'm am not concerned with that part for this example.
I converted one of my apps to the new Firestore. I am doing things like saving a document on a button click, and then in the onSuccess listener, going to a different activity.
I also use the fact that Firestore save operations return tasks, to group tasks together using Tasks.whenAll:
val allTasks = Tasks.whenAll(
createSupporter(supporter),,
setStreetLookup(makeStreetKey(supporter.street_name)),
updateCircleChartForUser(statusChange, createMode = true),
updateStatusCountForUser(statusChange))
allTasks.addOnSuccessListener(this#SignUpActivity, successListener)
allTasks.addOnFailureListener(this#SignUpActivity, onFailureListener)
Finally, I get the document id from a successful save and store it in preferences or in a local database for later use (within the onSuccessListener)
This all works great. Until there is a loss of network connectivity. Then everything falls apart, because the tasks never complete and the onSuccess/onFailure/onComplete listeners never get called. So the app just hangs.
I am working around this by checking for network availability before each save, and then doing a work-around by creating tasks without any listeners. I am also generating a document id locally using a UUID generator.
This, BTW, was not the way the app worked with the old firebase. In that case, everything ran nicely when offline and I saw documents getting synced up whenever the app came online.
My workaround for Firestore seems a terrible hack. Has anyone come up with a better solution?
See related Firestore database on insert/delete document callbacks not being invoked when there is no connection
addOnCompleteListener not called offline with cloud firestore
Cloud Firestore provide us feature for handle offline data but you need to use “Snapshot” (QuerySnapshot, DocumentSnapshot) to handle this case, unfortunately it not documented well. This is some code example (I use Kotlin Android) to handle case using Snapshot:
UPDATE DATA:
db.collection("members").document(id)
.addSnapshotListener(object : EventListener<DocumentSnapshot> {
override fun onEvent(snapshot: DocumentSnapshot?,
e: FirebaseFirestoreException?) {
if (e != null) {
Log.w(ContentValues.TAG, "Listen error", e)
err_msg.text = e.message
err_msg.visibility = View.VISIBLE;
return
}
snapshot?.reference?.update(data)
}
})
ADD DATA:
db.collection("members").document()
.addSnapshotListener(object : EventListener<DocumentSnapshot> {
override fun onEvent(snapshot: DocumentSnapshot?,
e: FirebaseFirestoreException?) {
if (e != null) {
Log.w(ContentValues.TAG, "Listen error", e)
err_msg.text = e.message
err_msg.visibility = View.VISIBLE;
return
}
snapshot?.reference?.set(data)
}
})
DELETE DATA:
db.collection("members").document(list_member[position].id)
.addSnapshotListener(object : EventListener<DocumentSnapshot> {
override fun onEvent(snapshot: DocumentSnapshot?,
e: FirebaseFirestoreException?) {
if (e != null) {
Log.w(ContentValues.TAG, "Listen error", e)
return
}
snapshot?.reference?.delete()
}
})
You can see code example here: https://github.com/sabithuraira/KotlinFirestore and blog post http://blog.farifam.com/2017/11/28/android-kotlin-management-offline-firestore-data-automatically-sync-it/
When there is a loss of network connectivity (there is no network connection on user device), neither onSuccess() nor onFailure() are triggered. This behavior makes sense, since the task is considered completed only when the data has been committed (or rejected) on the Firebase server. So onSuccess() will fire only when the task completes successfully.
There is no need to check for network availability before each save. There is a workaround that easily can help you see if the Firestore client indeed can't connect to the Firebase server, which is by enabling debug logging:
FirebaseFirestore.setLoggingEnabled(true);
Operations that write data to the Firestore database are defined to signal completion once they've actually committed to the backend. As a result, this is working as intended: while offline they won't signal completion.
Note that the Firestore clients internally guarantee that you can read your own writes even if you don't wait for the completion of the task from delete. The Firestore client is designed to continue functioning fine without an internet connection. So writing/deleting to the database without an internet connection is (by design) possible, and will never yield an error.
I found out how to do it using info at http://blog.farifam.com.
Basically you must use SnapshotListeners instead of OnSuccess listeners for offline work.
Also, you cannot use Google's tasks because they won't compete offline.
Instead (since Tasks are basically Promises), I used the Kotlin Kovenant library which can attach listeners to promises. One wrinke is that you must configure Kovenant to allow multiple resolution for a promise, since the event listener can be called twice (once when the data is added to the local cache, and once when it is synced to the server).
Here is an example snippet of code, with success/failure listeners, that runs both online and offline.
val deferred = deferred<DocumentSnapshot, Exception>() // create a deferred, which holds a promise
// add listeners
deferred.promise.success { Log.v(TAG, "Success! docid=" + it.id) }
deferred.promise.fail { Log.v(TAG, "Sorry, no workie.") }
val executor: Executor = Executors.newSingleThreadExecutor()
val docRef = FF.getInstance().collection("mydata").document("12345")
val data = mapOf("mykey" to "some string")
docRef.addSnapshotListener(executor, EventListener<DocumentSnapshot> { snap: DocumentSnapshot?, e: FirebaseFirestoreException? ->
val result = if (e == null) Result.of(snap) else Result.error(e)
result.failure {
deferred.reject(it) // reject promise, will fire listener
}
result.success { snapshot ->
snapshot.reference.set(data)
deferred.resolve(snapshot) // resolve promise, will fire listener
}
})
For offline support you need to set Source.CACHE
docRef.get(Source.CACHE).addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
// Document found in the offline cache
DocumentSnapshot document = task.getResult();
} else {
//error
}
}
});
This code in Dart language since I use in Flutter but you easily can change it to your platform and language
Future<void> updateDoc(String docPath, Map<String, dynamic> doc) async {
doc["updatedAt"] = Utils().getCurrentTimestamp();
DocumentReference documentReference = _firestore.doc(docPath);
Completer completer = Completer();
StreamSubscription streamSubscription;
streamSubscription = documentReference
.snapshots(includeMetadataChanges: true)
.listen((DocumentSnapshot updatedDoc) {
// Since includeMetadataChanges is true this will stream new data as soon as
// it update in local cache so it data has same updateAt it means it new data
if (updatedDoc.data()["updatedAt"] == doc["updatedAt"]) {
completer.complete();
streamSubscription.cancel();
}
});
documentReference.update(doc);
return completer.future;
}
So since includeMetadataChanges is set it will send data to stream when local cache changes to so when you call update you will receive data as soon as local cache update. You can use Completer to complete your future. Now your method only wait to update local cache and you can use await for updateDoc