Had a simple query with Firestore inside ViewModel, everything is okay except it does not go inside the for statement and never triggers and this usually happens on first launch or install of the project.
Note: This is the first page (Fragment) of ViewPager while the second page which just have an identical approach on getting documents works fine.
fun fetchAssignments(){
fireStore.collection(appContext.getString(R.string.assignment))
.whereEqualTo(appContext.getString(R.string.type), GlobalConfig.userData.shift)
.orderBy(appContext.getString(R.string.time), Query.Direction.DESCENDING)
.get()
.addOnSuccessListener {
val assList = mutableListOf<AssignmentData>()
for (item in it){
val assignment = item.toObject(AssignmentData::class.java)
assignment.documentId = item.id
assList.add(assignment)
}
assignmentList.value = assList
}
.addOnFailureListener {
assignmentList.value = listOf()
}
}
But on my second run the for statement is working, maybe cache helps as well.
This must be a bug because why such query works on the second fragment with almost same query and not with the first fragment, and why when I call the function again via retry button it works as intended?
For more info check its ticket
Related
I have this recycler view that shows paginated data with a PagingDataAdapter. It works well when making a single request to the service but I sometimes need to make two requests to get old and new events and show both in my recycler view. Is there a way I can combine the two flows of paginated data before showing them to the user? As it is, if I make the two requests, only the last one remains because of the invalidate() in the submitData method.
This is the current implementation that doesn't work the way I need it to:
private fun fetchEvents() {
fetchEventsJob?.cancel()
binding.cameraVmsEventsRecycler.adapter = null
fetchEventsJob = lifecycleScope.launch {
cameraViewModel.searchCameraEvents(
cameraId = videoDetector.id,
dateTo = if(eventsByDate) "" else dayPickerDateTo,
pageSize = REQUEST_LIMIT
cameraViewModel.filtersApplied
).collectLatest { eventsCollection ->
val events = eventsCollection.map { event ->
CameraEventModel.CameraEventItem(
VideoEventModel(
event.eventId,
event.faceId,
null,
event.state
)
)
}.insertSeparators {
before: CameraEventModel.CameraEventItem?, after: CameraEventModel.CameraEventItem? ->
renderSeparators(before, after)
}
binding.cameraEventsRecycler.adapter = eventsAdapter
eventsAdapter.submitData(events)
}
}
}
Upong calling fetchEvents() with different parameters, only the last flow of data remains due to the submitData().
Is there a way I can manage to do what I want? I can't use Room in this project.
You are explicitly calling collectLatest: it cancels collection when new items emit. The effect is, if you suspend in the collector lambda before your call to submitData() you only get the last item from the flow.
If you want to use all items from searchCameraEvents you might need to use toList():
val flow = cameraViewModel.searchCameraEvents(/*...*/)
val eventsCollection: List<PagingData<List<Event>>> = flow.toList()
val events = eventsCollection
// get list out of paging data
.flatMap { it.data }
// flatten list of lists
.flatten()
// map as needed
.map { event ->
CameraEventModel.CameraEventItem(/*...*/)
}
Mind, that you are handling paged data here. You want to make sure to keep a proper ordering. Also, if you retrieve more than one page from your flow, you're actually skipping the paging mechanism somehow. So you might also need to keep track of this in order not to produce duplicates in the UI.
I used the lifecycle callback onCreate to fetch data like below
mWeOutViewModel.getPlaceListLiveData()
.observe(this, weOutItemViewModels -> {
AppLogger.i(getCustomTag() + "adding items " + weOutItemViewModels.size());
if (weOutItemViewModels != null && weOutItemViewModels.size() > 0)
mWeOutListAdapter.addToExisting(weOutItemViewModels);
});
As you can see the AppLogger output the initial size which is 0 when the fragment is displayed, then I fetch the data and call postValue (setValue crashes the app and it expected because I fetch data from the internet using a background thread). So I call post value like below :
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> oldList = placeMutableLiveData.getValue();
oldList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+oldList.size());
placeMutableLiveData.postValue(oldList);
}
As you can see the other AppLogger before postValue, the size of the list is displayed(not empty), but nothing happens until the app crashes and nothing is shown in the logs. I have no ways of debugging since even on debug mode nothing happens. The post value doesn't trigger the observer.
I initialize the mutableLivedata like this :
private final MutableLiveData<List<WeOutGroupedViewModels>> placeMutableLiveData = new MutableLiveData<>();
and access like this :
public LiveData<List<WeOutGroupedViewModels>> getPlaceListLiveData() {
return placeMutableLiveData;
}
Event when I make the livedata public to access directly the livedata, there is no change (just in case someone thinks that's is where the issue comes from)
Instead of placeMutableLiveData.postValue(oldList);
I recommend using
placeMutableLiveData.postValue(Collections.unmodifiableList(new ArrayList<>(newList));
That way, the next time you access this list, you won't be able to mutate it in place, which is a good thing. You're not supposed to mutate the list inside a reactive state holder (MutableLiveData).
So theoretically it should look like this:
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> newList = new ArrayList<>(placeMutableLiveData.getValue());
newList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+newList.size());
placeMutableLiveData.postValue(Collections.unmodifiableList(newList));
}
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.
My database query operations can take a long time, so I want to display a ProgressBar while the query is in progress. This is especially a problem when the user changes the sorting options, because it displays the old list for a while until the new list comes in and the RecyclerView is updated. I just don't know where to capture the Loading and Success states for a query like this.
Here's my method for getting the PagedList from the database:
fun getGameList(): LiveData<PagedList<Game>> {
// Builds a SimpleSQLiteQuery to be used with #RawQuery
val query = buildGameListQuery()
val dataSourceFactory: DataSource.Factory<Int, Game> = database.gameDao.getGameList(query)
val data: LiveData<PagedList<Game>> = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE)
.build()
return data
}
And I update my list by observing this:
val games = Transformations.switchMap(gameRepository.sortOptions) {
gameRepository.getGameList()
}
Do I need a custom DataSource and DataSource.Factory? If so, I have no idea where to even begin with that. I believe it would be a PositionalDataSource, but I can't find any examples online for implementing a custom one.
I also tried adapter.registerAdapterDataObserver() on my RecyclerView adapter. This fires various callbacks when the new list data is being displayed, but I can't discern from the callbacks when loading has started and stopped.
I was ultimately able to fix this by observing the games LiveData. However, it wasn't exactly straightforward.
Here's my DatabaseState class:
sealed class DatabaseState {
object Success : DatabaseState()
object LoadingSortChange: DatabaseState()
object Loading: DatabaseState()
}
Capturing the Loading state was easy. Whenever the user updates the sort options, I call a method like this:
fun updateSortOptions(newSortOptions: SortOptions) {
_databaseState.value = DatabaseState.LoadingSortChange
_sortOptions.value = newSortOptions
}
The Success state was the tricky one. Since my sorting options are contained in a separate Fragment from the RecyclerView, the games LiveData observer fires twice upon saving new sort options (once when ListFragment resumes, and then again a bit later once the database query is completed). So I had to account for this like so:
/**
* The observer that triggers this method fires once under normal circumstances, but fires
* twice if the sort options change. When sort options change, the "success" state doesn't occur
* until the second firing. So in this case, DatabaseState transitions from LoadingSortChange to
* Loading, and finally to Success.
*/
fun updateDatabaseState() {
when (databaseState.value) {
Database.LoadingSortChange -> gameRepository.updateDatabaseState(DatabaseState.Loading)
DatabaseState.Loading -> gameRepository.updateDatabaseState(DatabaseState.Success)
}
}
Finally, I needed to make some changes to my BindingAdapter to smooth out some remaining issues:
#BindingAdapter("gameListData", "databaseState")
fun RecyclerView.bindListRecyclerView(gameList: PagedList<Game>?, databaseState: DatabaseState) {
val adapter = adapter as GameGridAdapter
/**
* We need to null out the old list or else the old games will briefly appear on screen
* after the ProgressBar disappears.
*/
adapter.submitList(null)
adapter.submitList(gameList) {
// This Runnable moves the list back to the top when changing sort options
if (databaseState == DatabaseState.Loading) {
scrollToPosition(0)
}
}
}
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()