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

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()

Related

Firestore QuerySnapShot is empty on first install or run

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

Jetpack Compose MutableLiveData not updating UI Components

I am trying to display several download progress bars at once via a list of data objects containing the download ID and the progress value. The values of this list of objects is being updated fine (shown via logging) but the UI components WILL NOT update after their initial value change from null to the first progress value. Please help!
I see there are similar questions to this, but their solutions are not working for me, including attaching an observer.
class DownLoadViewModel() : ViewModel() {
...
private var _progressList = MutableLiveData<MutableList<DownloadObject>>()
val progressList = _progressList // Exposed to the UI.
...
//Update download progress values during download, this is called
// every time the progress updates.
val temp = _progressList.value
temp?.forEach { item ->
if (item.id.equals(download.id)) item.progress = download.progress
}
_progressList.postValue(temp)
...
}
UI Component
#Composable
fun ExampleComposable(downloadViewModel: DownloadViewModel) {
val progressList by courseViewModel.progressList.observeAsState()
val currentProgress = progressList.find { item -> item.id == local.id }
...
LinearProgressIndicator(
progress = currentProgress.progress
)
...
}
I searched a lot of text to solve the problem that List in ViewModel does not update Composable. I tried three ways to no avail, such as: LiveData, MutableLiveData, mutableStateListOf, MutableStateFlow
According to the test, I found that the value has changed, but the interface is not updated. The document says that the page will only be updated when the value of State changes. The fundamental problem is the data problem. If it is not updated, it means that State has not monitored the data update.
The above methods are effective for adding and deleting, but the alone update does not work, because I update the element in T, but the object has not changed.
The solution is to deep copy.
fun agreeGreet(greet: Greet) {
val g = greet.copy(agree = true) // This way is invalid
favourites[0] = g
}
fun agreeGreet(greet: Greet) {
val g = greet.copy() // This way works
g.agree = true
favourites[0] = g
}
Very weird, wasted a lot of time, I hope it will be helpful to those who need to update.
As far as possible, consider using mutableStateOf(...) in JC instead of LiveData and Flow. So, inside your viewmodel,
class DownLoadViewModel() : ViewModel() {
...
private var progressList by mutableStateOf(listOf<DownloadObject>()) //Using an immutable list is recommended
...
//Update download progress values during download, this is called
// every time the progress updates.
val temp = progress.value
temp?.forEach { item ->
if (item.id.equals(download.id)) item.progress = download.progress
}
progress.postValue(temp)
...
}
Now, if you wish to add an element to the progressList, you could do something like:-
progressList = progressList + listOf(/*item*/)
In your activity,
#Composable
fun ExampleComposable(downloadViewModel: DownloadViewModel) {
val progressList by courseViewModel.progressList
val currentProgress = progressList.find { item -> item.id == local.id }
...
LinearProgressIndicator(
progress = currentProgress.progress
)
...
}
EDIT,
For the specific use case, you can also use mutableStateListOf(...)instead of mutableStateOf(...). This allows for easy modification and addition of items to the list. It means you can just use it like a regular List and it will work just fine, triggering recompositions upon modification, for the Composables reading it.
It is completely fine to work with LiveData/Flow together with Jetpack Compose. In fact, they are explicitly named in the docs.
Those same docs also describe your error a few lines below in the red box:
Caution: Using mutable objects such as ArrayList or mutableListOf() as state in Compose will cause your users to see incorrect or stale data in your app.
Mutable objects that are not observable, such as ArrayList or a mutable data class, cannot be observed by Compose to trigger recomposition when they change.
Instead of using non-observable mutable objects, we recommend you use an observable data holder such as State<List> and the immutable listOf().
So the solution is very simple:
make your progressList immutable
while updating create a new list, which is a copy of the old, but with your new progress values

How to offer a transformed liveData from DB on Room, if initialization/update of the DB might be needed?

Background
I'm creating some SDK library, and I want to offer some liveData as a returned object for a function, that will allow to monitor data on the DB.
The problem
I don't want to reveal the real objects from the DB and their fields (like the ID), and so I wanted to use a transformation of them.
So, suppose I have this liveData from the DB:
val dbLiveData = Database.getInstance(context).getSomeDao().getAllAsLiveData()
What I did to get the liveData to provide outside, is:
val resultLiveData: LiveData<List<SomeClass>> = Transformations.map(
dbLiveData) { data ->
data.map { SomeClass(it) }
}
This works very well.
However, the problem is that the first line (to get dbLiveData) should work on a background thread, as the DB might need to initialize/update, and yet the Transformations.map part is supposed to be on the UI thread (including the mapping itself, sadly).
What I've tried
This lead me to this kind of ugly solution, of having a listener to a live data, to be run on the UI thread:
#UiThread
fun getAsLiveData(someContext: Context,listener: OnLiveDataReadyListener) {
val context = someContext.applicationContext ?: someContext
val handler = Handler(Looper.getMainLooper())
Executors.storageExecutor.execute {
val dbLiveData = Database.getInstance(context).getSomeDao().getAllAsLiveData()
handler.post {
val resultLiveData: LiveData<List<SomeClass>> = Transformations.map(
dbLiveData) { data ->
data.map { SomeClass(it) }
}
listener.onLiveDataReadyListener(resultLiveData)
}
}
}
Note: I use simple threading solution because it's an SDK, so I wanted to avoid importing libraries when possible. Plus it's quite a simple case anyway.
The question
Is there some way to offer the transformed live data on the UI thread even when it's all not prepared yet, without any listener ?
Meaning some kind of "lazy" initialization of the transformed live data. One that only when some observer is active, it will initialize/update the DB and start the real fetching&conversion (both in the background thread, of course).
The Problem
You are an SDK that has no UX/UI, or no context to derive Lifecycle.
You need to offer some data, but in an asynchronous way because it's data you need to fetch from the source.
You also need time to initialize your own internal dependencies.
You don't want to expose your Database objects/internal models to the outside world.
Your Solution
You have your data as LiveData directly from your Source (in this particular, albeit irrelevant case, from Room Database).
What you COULD do
Use Coroutines, it's the preferred documented way these days (and smaller than a beast like RxJava).
Don't offer a List<TransformedData>. Instead have a state:
sealed class SomeClassState {
object NotReady : SomeClassState()
data class DataFetchedSuccessfully(val data: List<TransformedData>): SomeClassState()
// add other states if/as you see fit, e.g.: "Loading" "Error" Etc.
}
Then Expose your LiveData differently:
private val _state: MutableLiveData<SomeClassState> = MutableLiveData(SomeClassState.NotReady) // init with a default value
val observeState(): LiveData<SomeClassState) = _state
Now, whoever is consuming the data, can observe it with their own lifecycle.
Then, you can proceed to have your fetch public method:
Somewhere in your SomeClassRepository (where you have your DB), accept a Dispatcher (or a CoroutineScope):
suspend fun fetchSomeClassThingy(val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default) {
return withContext(defaultDispatcher) {
// Notify you're fetching...
_state.postValue(SomeClassState.Loading)
// get your DB or initialize it (should probably be injected in an already working state, but doesn't matter)
val db = ...
//fetch the data and transform at will
val result = db.dao().doesntmatter().what().you().do()
// Finally, post it.
_state.postValue(SomeClassState.DataFetchedSuccessfully(result))
}
}
What else I would do.
The fact that the data is coming from a Database is or should be absolutely irrelevant.
I would not return LiveData from Room directly (I find that a very bad decision on Google that goes against their own architecture that if anything, gives you the ability to shoot your own feet).
I would look at exposing a flow which allows you to emit values N times.
Last but not least, I do recommend you spend 15 minutes reading the recently (2021) published by Google Coroutines Best Practices, as it will give you an insight you may not have (I certainly didn't do some of those).
Notice I have not involved a single ViewModel, this is all for a lower layer of the architecture onion. By injecting (via param or DI) the Dispatcher, you facilitate testing this (by later in the test using a Testdispatcher), also doesn't make any assumption on the Threading, nor imposes any restriction; it's also a suspend function, so you have that covered there.
Hope this gives you a new perspective. Good luck!
OK I got it as such:
#UiThread
fun getSavedReportsLiveData(someContext: Context): LiveData<List<SomeClass>> {
val context = someContext.applicationContext ?: someContext
val dbLiveData =
LibraryDatabase.getInstance(context).getSomeDao().getAllAsLiveData()
val result = MediatorLiveData<List<SomeClass>>()
result.addSource(dbLiveData) { list ->
Executors.storageExecutor.execute {
result.postValue(list.map { SomeClass(it) })
}
}
return result
}
internal object Executors {
/**used only for things that are related to storage on the device, including DB */
val storageExecutor: ExecutorService = ForkJoinPool(1)
}
The way I've found this solution is actually via a very similar question (here), which I think it's based on the code of Transformations.map() :
#MainThread
public static <X, Y> LiveData<Y> map(
#NonNull LiveData<X> source,
#NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
#Override
public void onChanged(#Nullable X x) {
result.setValue(mapFunction.apply(x));
}
});
return result;
}
Do note though, that if you have migration code (from other DBs) on Room, it might be a problem as this should be on a background thread.
For this I have no idea how to solve, other than trying to do the migrations as soon as possible, or use the callback of "onCreate" (docs here) of the DB somehow, but sadly you won't have a reference to your class though. Instead you will get a reference to SupportSQLiteDatabase, so you might need to do a lot of manual migrations...

Firestore query cache

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.

How to read a series of items under a child node in Firebase using FirebaseAdapter

As the title states, I'm trying to read a bunch of nodes under a nested node in Firebase, and display the information using FirebaseAdapter. I'm using the parseSnapshot method to try and grab the information I need but I think I'm misunderstanding exactly how to go about getting the information. The user section of the database is structured like so:
I want only the information under UserInfo, and so I currently have the following code setup to intialize a recyclerview adapter (which gets the information)
private fun setupRequiredRecyclerView() {
val requiredItems = private_items_recycler
val context = this
val userDataRef = mDatabaseReference.child("Users/${prefs.UID}/UserInfo")
val mAdapter = RequiredItemsAdapter(User::class.java, R.layout.privaterecyclerview_item_row, RequiredProfileItemsViewHolder::class.java, userDataRef, context)
//load data into adapter
requiredItems.adapter = mAdapter
//add divider between items
requiredItems.addItemDecoration(Utilities.createDivider(this))
}
But the data snapshot I get back in the parseSnapshot method only seems to contain "dateJoined" and no other nodes, I'm guessing there's something wrong with my reference, but I don't know how to structure it - going up to "Users/UID" gets me everything but it also gets me UserInfoComplete, which I don't want (and as far as I know, there's no way to ignore that data in parseSnapshot, as FirebaseAdapter grabs every child node)
Does anyone out there know how exactly I need to structure my database reference to only get the UserInfo data?
(If necessary, this is my current parseSnapshot method):
override fun parseSnapshot(snapshot: DataSnapshot?): User {
lateinit var user : User
Log.i("Snapshot Data", snapshot!!.value.toString())
var dateJoined = snapshot!!.value
var dateOfBirth = snapshot.child("dateOfBirth").value
var gender = snapshot.child("Gender").value
var location = snapshot.child("Location").value
var phoneNumber = snapshot.child("phoneNumber").value
Log.i("Snapshot Data", snapshot!!.value.toString())
user.dateJoined = dateJoined as Long
return user
}

Categories

Resources