Firestore query cache - android

I have 1 method in my repository in which I receive a status. Either this status is 0 or 1. If it is 0 I need to generate a different query than if it is 1, let's say
fun getData(status:Int) {
val docRef = FirebaseFirestore.getInstance().collection("orders")
if(status == 0){
docRef.whereEqualTo("status",0)
}else{
docRef.whereGreaterThanOrEqualTo("status",1).whereLessThan("status",4)
}
val suscription = docRef.addSnapshotListener { ... }
Now, I use this method to either query with status 0 or status 1 different documents in my collection, now, when I come back where status is 0 in my UI, will Firestore cache this two queries and return me the cached docRef of status 0? or it will be requiring all the documents again because is in the same method and there are not two different docRefs?
I wonder this because I have a bottomnavigation where I switch tabs, I don't want to require the data if its already queried.
I want to know if this conditional If statement will cache the two queries into my client when I need either the first one or the second one below
Edit
This question is because if I need to create a separate method with all the same data but with a different reference to hold the data
thanks

First of all, your method should look like this:
fun getData(status:Int) {
val docRef = FirebaseFirestore.getInstance().collection("orders")
if(status == 0){
docRef = docRef.whereEqualTo("status",0)
} else {
docRef = docRef.whereGreaterThanOrEqualTo("status",1).whereLessThan("status",4)
}
}
val suscription = docRef.addSnapshotListener { /* ... */ }
And this because Cloud Firestore queries are immutable, which means that you cannot change the properties of an existing query. If you change the value by calling .whereEqualTo("status",0) method, it becomes a new query.
Firestore cache these two queries and return me the cached docRef of status 0?
Firestore will cache all the documents that are returned by your query. If the if part of the statement is triggered, then you'll have in the cache only those documents, otherwise you'll have the other ones.
I want to know if this conditional If statement will cache the two queries into my client when I need either the first one or the second one below
If you switch between both tabs and both queries are executed, you'll have all the documents from both queries cached.

Related

mutableStateOf() not assinging new object?

I am updating the old mutableStateOf() object data by modifying it but new data is not reflecting on it.
variable: as
val offTime = mutableStateOf<List<OffTime>>(emptyList())
update code
fun updateOffTime(newOffTime: OffTime, index: Int){
val updatedOffTime = offTime.value.mapIndexed { i, offTime ->
var result = offTime
if(index == i) result = newOffTime
result
}
offTime.value = updatedOffTime
Log.d(TAG, "updateOffTime: $updatedOffTime")
Log.d(TAG, "updateOffTime: ${offTime.value}")
}
Note: It works when the object OffTime is without id
i.e. OffTime(fromTime, toTime) :- works
OffTime(id,fromTime, toTime) :- doesn't works
I don't know exactly your use-case, neither your entire code implementation, but if I may ask why are you using an ordinary collection list instead of a SnapshotStateList or an extension of its new instance, mutableStateListOf when its part of your requirement to perform list operations?
Have you tried converting your offtime as a SnapshotStateList like this?
val offTime = mutableStateListOf<OffTime>(mutableStateListOf())
and performing updates liks this?
offTime.add( ... )
//or
offtime.remove(...)
//or
offTime[index] = offtime copy
//or
var offTimeIterator = offTime.lisIterator() // where you can safely modify indeces
SnapshotStateList is created exactly for such use-cases in compose, where you can perform normal list operations such as (add, remove, update, or batch updates) and guarantees re-composition.
Your call
offTime.value = updatedOffTime // if this is a new instance of a list
will trigger an entire re-composition as the entire list reference had been changed, but with SnapshotStateList, any changes to the structure is guaranteed to match a specific re-composition, say if you modify an item at index 7, and if this is observed by say a LazyColumn only LazyColumn's 7th index will re-compose
Also I don't know if your Offtime is a data class or a standard class, I would recommend it to be a data-class so you can easily copy() a certain instance of it, pass a new value to a certain property of it and re-assign it in a SnapshotStateList.

Android Room Database - Query validation for success or failure

We are using Room database in android to store the data.
Now, In Dao class we all perform various queries like Insert, Select, Delete, Update etc.
I want to know that How can We know that these queries executed successfully?
i.e If I am doing as below :
appDatabase.userDao().insert(userData)
How Can I notified that the user data is inserted in particular table and operation is successful ?
And Yes Is there any tools or plugin available through which we can see the data of Room database? (I have googled about it, but it was a bit confused regarding Device Monitor)
Please guide.
Everything is explained in the documentation here.
We can return a value for insert, update and delete operations to determine if it was successful or not.
#Insert
fun insert(item: ItemEntity): Long // returns row that entity was inserted at or -1 on failure
#Update
fun update(item: ItemEntity): Int // returns the number of rows updated successfully
#Delete
fun delete(item: ItemEntity): Int // returns the number of rows deleted successfully
And then when you do the operation:
val result = cache.insert(item.toItemEntity())
println("result is $result")
Queries don't seem to have an obvious way to implement this functionality though.
I assume that you re using room database to persist your data , the insert() method room returns a long value and you can use that value to check if data is persisted
// make insert method returns a long value
long insert()
Then you can do get check for your long value
long value = appDatabase.userDao().insert(userData)
if(value == -1) no data insert
if(value == 0) data inserted
PS : If i'm wrong , someone corrects me , thank you
First you need to assign Dao functions to return a Long variable as follows
#Dao
interface DoctorCallDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(dcrData: DCRData):Long
}
Then you can catch this when you insert data into the database
val state:Long= database.doctorCallDao.insert(dcrData)
if(state>0) {
//Successfully inserted
} else {
//Error occured
}

transformations.switchMap works only once or possibly not at all

I am looking for some help with transformations.switchMap. I somehow can't get it to work and start to wonder if I actually understand things correctly.
Based on my understanding the first parameter is like a trigger, whenever it's values changes the, second parameter/function will be mapped/called to return a new/modified liveData object.
In my case I have:
#MainActivity
viewModel.repository.items.observe(requireActivity(), Observer { weight -> weight?.let { adapter!!.setItems(it) } })
#ViewModel
lateinit var items: LiveData<List<Weight>> # Used in my RecyclerView
var filterChanged = MutableLiveData<Long>(0L) # The "trigger" value
override fun init() {
super.init()
items = Transformations.switchMap(filterChanged) {repository.getItems(0L, filterEnd)}
}
repository.getItems() returns a live data object filtered by filterEnd (SQL ...where timestamp <= filterEnd), with timestamp being a date in numerical representation.
This works! But only once, during initialisation of the Fragment. Once the fragment has initialised I can change the filterChanged trigger variable as often as I want,
the repository.getItems() function does not get called a second time (applying an updated value for filterEnd).
It requires start/stop of a secondary activity (settings or about) which will recreate my viewModel to update the filter value.
I saw several ways of crafting the Transformations.switchMap statement, using lambdas, one parameter or two, etc., but I either couldn't get them to compile or they didn't do what I expected.
Now, have I understood correctly how this is supposed to work? Any idea what I might be doing wrong?
Edit: Added the repository section to clarify things:
#Repository
items = getItems(filterStart, filterEnd) # This is where I enter from the ViewModel
// This function redirects to the actual dao functions,
fun getItems(filterStart: Long, filterEnd: Long): LiveData<List<Weight>> {
Log.d("Repository ", "getItems: $filterStart, $filterEnd")
if ((filterStart != 0L) && (filterEnd == 0L)) return dao.getItemsGreaterThan(filterStart)
else if ((filterStart == 0L) && (filterEnd != 0L)) return dao.getItemsLessThan(filterEnd)
else if ((filterStart != 0L) && (filterEnd != 0L)) return dao.getItemsRange(filterStart, filterEnd)
else return dao.getItems()
}
At one point I simplified things and called the dao functions directly, like so:
items = dao.getItemsLessThan(filterEnd)
Even bypassed the repository allt ogether and called the respective dao function directly from the ViewModel, but that didn't make any difference either.
And then there are the corresponding dao functions, like the below:
#Query("SELECT * from weight WHERE timestamp >=:start ORDER BY timestamp DESC")
abstract fun getItemsGreaterThan(start: Long): LiveData<List<Weight>>

How to avoid unnecessary re-rendering of RecyclerView when using Flowable (RxJava)?

Trying to implement MVVM with room flowable # rxjava + retrofit2.
When the activity starts, I request a list of items from the repository. The repository retrieves them from the database (room) and returns Flowable to the ViewModel. At the same moment, the repository requests the current list from the API, and as soon as the result is returned, I delete the existing entries in the database and insert what arrived from the API.
The observer in activity, of course, updates recyclerview three times. Because of this, ui "blinks" when updating the list:
the list is not empty -> the list is empty -> the list is not empty.
After receiving the flowable from Room for the first time, the observer then receives an empty list (when deleting entries in the database) and then a new list after inserting it into the database from API.
How to avoid this behavior of RecyclerView? Is there any well-established best practice, true way, etc.?
PaymentRepository method for retrieving payment list:
private Flowable<List<Payment>> getPayments(Long accountId) {
// requesting actual data from API
paymentService.getPayments().flatMapCompletable(payments -> paymentDao
.deleteAllByAccountId(accountId)
.andThen(paymentDao.insert(payments))
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
).subscribe();
// and return cached list from db
return paymentDao.findPaidByAccountId(accountId)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
ViewModel
private CompositeDisposable disposable = new CompositeDisposable();
public MutableLiveData<Resource<List<Payment>>> result = new MutableLiveData<>(Resource.loading());
public PaymentListViewModel() {
disposable.add(
paymentRepository.getPayments().subscribe(
payments -> result.setValue(Resource.success(payments)),
throwable -> result.setValue(paymentService.parseError(throwable).toResource())
)
);
}
Observer in Activity
viewModel.result.observe(this, resource -> {
switch (resource.status) {
case SUCCESS:
adapter.setItems(resource.data);
binding.preloader.setVisibility(View.GONE);
break;
case ERROR:
Toast.makeText(this, resource.message, Toast.LENGTH_LONG).show();
binding.preloader.setVisibility(View.GONE);
break;
case LOADING:
binding.preloader.setVisibility(View.VISIBLE);
break;
}
});
I'm not a Room expert, maybe there is so RX-way to update values without emitting it into listeners.
But here is the way that doesn't request it:
private Flowable<List<Payment>> getPayments(Long accountId) {
// requesting actual data from API
Flowable<List<Payment>> request = paymentService.getPayments()
.flatMapCompletable(payments -> paymentDao
.deleteAllByAccountId(accountId)
.andThen(paymentDao.insert(payments))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
);
return paymentDao.findPaidByAccountId(accountId)
.take(1)
.switchMap(dbList -> request.startWith(dbList))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.distinctUntilChanged();
}
in this scenario you take only first value from DB (take(1)) and then wait for the values from API.
switchMap (also could be flatMap here, no diff in this case) guaranty you that DB value will be first and only after that "listen" for API.
The only problem here if you suppose to update DB values somewhere else, while this Activity is running. In that case this updates will not be displayed.

How to add, update, delete, and read on cloud firestore?

I want to learn do add, insert, update, and delete on cloud firestore.
I already read cloud firestore documentation but i dont understand it since im really new to Could firestore and i just started learing android studio.
I already make the constructor and planning to use ListView to read the data and Delete and Update on the setOnLongClickListener with Intent to make editing easier. And i use another activity for the add function.
Most of the tutorial i meet are putting all in 1 place and make it harder to understand it.
And mixing code that i got from different resource making the code harder to understand and it look weird.
So what is the easy to understand code to do this with this database?
https://i.stack.imgur.com/ngnR7.png
You should require user authentication if you are going to be using the Firebase Android SDK to post data to your server and then your firebase.rules on your server should check that caller has right level of access.
Get an instance of FirebaseFirestore as in
private val firestore: FirebaseFirestore
get() = FirebaseFirestore.getInstance()
documents on Firebase always follow the pattern document/collection/document/collection/... example val docName = "animalDoc/mammalCollection/rodentDoc/miceCollection/JerryDoc
get all mice:
firestore.collection("animalDoc/mammalCollection/"
+"rodentDoc/miceCollection").get()
.addOnSuccessListener { result -> //result is just a Kotlin collection
val myFavoriteMouse = result.find { it["name"] == "Jerry" }
// do something with Jer
}
Set a mouse
val docName = "animalDoc/mammalCollection/rodentDoc/miceCollection/JerryDoc"
firestore.document(docName).set(mapOfData).addOnCompleteListener {
if (it.isSuccessful) {
// log your success or whatever
} else {
// log your failure or whatever
}
}
Update a mouse
val docName = "animalDoc/mammalCollection/rodentDoc/miceCollection/JerryDoc"
val docRef = firestore.document(docName)
firestore.runTransaction { transaction ->
transaction.update(docRef, "color", "brown")
}
Delete a mouse
val docName = "animalDoc/mammalCollection/rodentDoc/miceCollection/JerryDoc"
firestore.document(docName).delete()

Categories

Resources