Simply fetching data from Cloud Firestore but Firestore + ViewModel + LiveData - android

I'm building an app with use of Cloud Firestore. I've already implemented RecyclerView with FirestoreRecyclerAdapter and set to it data from collection. Before I process further I need to understand something:
1. Inversely to local database (sqlite3, without LiveData) Firestore does send a request to a device when any change in the database occurs, am I right?
2. Assuming I was right in the "question" above. When I fetch data to the adapter like this:
In my activity onCreate:
val questions = FirebaseFirestore.getInstance().collection("DatabaseStructure")
val query = questions.orderBy("timestamp", Query.Direction.DESCENDING)
val options = FirestoreRecyclerOptions.Builder<DatabaseStructure>()
.setQuery(query, DatabaseStructure::class.java)
.build()
firestoreAdapter = FirestoreAdapter(options)
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = firestoreAdapter
My data class:
data class DatabaseStructure(
val question: String = "",
val description: String? = "",
val image: String? = "",
val leftAnswers: Int = 0,
val rightAnswers: Int = 0,
val timestamp: com.google.firebase.Timestamp = com.google.firebase.Timestamp(Date()),
val userName: String? = "Anonymous")
Every time activity is recreated, e.g users rotates device, reopens activity, data is fetched from the Cloud Firestore one more time, even if no changes appeared, am I right? To avoid this should I use ViewModel then?
Assuming question above was also true and I've implemented ViewModel to my application, that stores data from Cloud Firestore. Now if any change in the database appears, Firestore sends a request to a device to fetch data. Is there still any sense to observe data using LiveData?
To sum up, what is the best model to implement Firestore without unnecessary fetching data from database, to make it in the most efficient way and the least power consuming?
Thanks in advance!

Related

Local changes not updating Atlas in Flexible Sync'd realm

I have a collection containing Group objects with the following schema:
class Group : BaseEntity(), RealmObject {
class UserInfo : EmbeddedRealmObject {
var userId: Id? = null
var username: String? = null
var userBalance: Double = 0.0
}
#PrimaryKey
override var _id: Id = createId()
var groupName: String? = null
var users: RealmList<Id> = realmListOf()
var userInfo: RealmList<UserInfo> = realmListOf()
}
Group.users and Group.usersInfo is mimicking a dictionary with userId as key and UserInfo containing nickname, balance, etc. as value. Apparently the Kotlin SDK doesn't support RealmDictionary nor querying on embedded objects according to Kotlin RQL docs, that's why the schema is a little messy. My Sync subscription for the Groups collection is:
val config =
SyncConfiguration.Builder(realmUser, setOf(Group::class, Group.UserInfo::class))
.initialSubscriptions(rerunOnOpen = true) { realm ->
add(realm.query<Group>("$0 IN users", userId))
}
.name("groupRealm")
.build()
val realm: Realm by lazy { Realm.open(config) }
where the subscription query
add(realm.query<Group>("$0 IN users", userId))
is attempting to sync all Group objects that a userId belongs to, basically, through the Group.users and Group.userInfo fields I described earlier.
All the read and write operations into the local realm are fine, but when I go into the collections in Atlas, I don't see the updates in the Group collection. Also, when I add .waitForInitialRemoteData() to the config, my code times out. I also get an OtherSessionError: operation canceled (ProtocolErrorCode=201) in the App Services logs, and an empty query when it should show the subscription query mentioned above. Any help on this issue is greatly appreciated!

Kotlin - StateFlow not emitting updates to its collectors

I got a StateFlow of type UserStateModel (data class) in my app.
private val _userStateFlow: MutableStateFlow<UserStateModel?> = MutableStateFlow(UserStateModel())
val userStateFlow: StateFlow<UserStateModel?> = _userStateFlow
here is the UserStateModel
data class UserStateModel(
val uid: String? = null,
val username: String? = null,
val profileImageUrl: String? = null,
var isLoggedIn: Boolean = false,
val isPremiumUser: Boolean = false,
val posts: List<Post>? = listOf()
)
When I update the StateFlow with a new Username it emits the change to the collectors and the UI updates.
But when I change a property inside the posts: List? list it doesnt emit the changes.
When I change the size of the list it does, when I change the name property of the Post at index 0 it doesnt.
How can I detect changes to the child properties of the Data class?
Right now I use an ugly workaround, I add
val updateErrorWorkaround: Int = 0
to the UserStateModel data class and increase it by one so the collectors get notified
P.s I'm using MVVM + Clean Architecture and Jeptack Compose
EDIT
Thats my Post Model:
data class Post(
val id: Int,
val name: String,
val tags: MutableList<Tag>? = null
)
Here is how I update the MutableList:
val posts = userStateFlow.value?.posts
posts.get(index).tags?.add(myNewTag)
_userStateFlow.value = userStateFlow.value?.copy(posts = posts)
Those changes are not emitted to the collectors
StateFlow emits only if it detects changes to the value, it ignores replacing the value with the same data. To do this, it compares the previous value with the new one. For this reason, we shouldn't modify the data that we already provided to the StateFlow, because it won't be able to detect changes.
For example, we set value to a User(name=John). Then we mutate the same user object by modifying its name to James and we set the value to this "new" user object. StateFlow compares "new" User(name=James) with its stored value, which is now also User(name=James), so it doesn't see any changes.
In your example you created a copy of UserStateModel, but inside you re-use the same objects and you mutate them. In this case you added a new item to tags and this change affected old UserStateModel as well, so StateFlow doesn't detect the change.
To fix the problem, you need to copy all the data that was changed and do not mutate anything in-place. It is safer to make all the data immutable, so val and List - this way you are forced to make copies. I changed tags to val tags: List<Tag> = listOf(), then your code could look like the following:
val posts = userStateFlow.value?.posts!!.toMutableList()
posts[index] = posts[index].copy(tags = posts[index].tags + myNewTag)
userStateFlow.value = userStateFlow.value?.copy(posts = posts)
Here we create a copy of not only UserStateModel. We also copy posts list, the Post that we modify and we also copy the list of tags.
Alternatively, if this behavior of StateFlow is more annoying to you than helpful, you can use SharedFlow which doesn't compare values, but just emits.

Does Kotlin Flow Emits new data every time if something changed in room database?

Let's Say Here is Sample Code
LiveData Query
Query("SELECT IFNULL(COUNT(id),0) FROM Item WHERE status = :status")
fun getLiveData(status: Int): LiveData<Int>
Kotlin Flow Query
#Query("SELECT IFNULL(COUNT(id),0) FROM Item WHERE status = :status")
fun getFlowData(status: Int): Flow<Int>
So my Question is Flow gets new data if anything changes in the room database?
Yes Flow gets new data if anything changes in the room database if you collect that flow of course, like the example below:
val flow = getFlowData(2) // type Flow<Int>
flow.collect { data ->
// every time anything changes, the code inside collect is going to get called again
}
and also there is .first() that will give you only the latest data without live changes:
val data = getFlowData(2).first() // type Int
So it depends how you use Flow, and it depends on your needs.

I cannot seem to connect to Firestore database to save user's data

I'm currently working on an Android project and I've been stuck with this problem for a few hours now.
I'm trying to connect to my Firestore database. The idea is to store documents with additional info from the users. It's created on-register and then sent to the database.
Here's the code:
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success
Log.d("RegistroFirebase", "createUserWithEmail:success")
val user = auth.currentUser
// Create user's database document
writeNewUser(
user!!.uid, user!!.email, binding.signUpName.text.toString(),
binding.signUpSurname.text.toString(), "623623623")
Log.d("Crear documento usuario", "Success?")
reload("main")`
And the function:
private fun writeNewUser(userId: String, email: String?, name: String, surname: String, phone:String) {
val user = User(email, name, surname, phone)
db.child("users").child(userId).setValue(user)
}
Also I have a class for users:
data class User(val email:String? = null, val name:String? = null,
val surname:String? = null, val phone:String? = null) {}
As for the error, I get none. It just works but it doesn't add anything new to my Firestore 'user' collection.
Any help would be appreciated. Thank you in advance
You say:
I cannot seem to connect to Firestore database to save user's data
And it makes sense, since when using the following lines of code:
val user = User(email, name, surname, phone)
db.child("users").child(userId).setValue(user)
You are trying to write data to the Realtime Database and not to Cloud Firestore. While both, databases are a part of Firebase services, they are different databases with different mechanisms. To be able to write data to Firestore, please use the following line of code:
FirebaseFirestore db = FirebaseFirestore.getInstance();
CollectionReference usersRef = db.collection("users");
usersRef.document(userId).set(user)
You can also attach a listener to the complete operation, to see if something goes wrong.
Similar to user creation, the setValue function can also be listened to with addOnCompleteListener, addOnSuccessListener, addOnFailureListener.
Ref: Add a Completion Callback.
You need to diagnose the result through these.

Signing in again deletes the database in Firebase realtime database

After I login using google in firebase and store data it saves into the respective emails but when I logout and login again the data added before gets deleted.
I tried using updatechildren but still the data gets deleted and signing again.
Databasemodel.kt
val u_id = bdatTitleref.push().key
data class Databasemodel(val uid:String, val name:String, val dob:String,var rem:Int) {
constructor():this("",",","",0)
fun toMap():Map<String,Any>{
return mapOf(
"uid" to uid,
"name" to name,
"dob" to dob,
"rem" to rem
)
}
}
Mainactivity.kt
val ref = FirebaseDatabase.getInstance()
val rootref = ref.getReference("User")
val userref = rootref.child(uid.toString())
val bdayTitleref = userref.child("Birthday Persons")
val model = Databasemodel(u_id!!, et_name.text.toString().trim(), tv_show_date.text.toString(), 0)
val postModel=model.toMap()
bdayTitleref.child(u_id).updateChildren(postModel)
Here I wanted to add different child of Birthday Persons so help me with this
The problem in your code is the use of a reference that is incorrect. When you try to update postModel object in your database using the following line of code:
bdayTitleref.child(u_id).updateChildren(postModel)
You are passing to the .child(u_id) the uid of the user (nUmH ... TJk2) which is not correct since in your database that child is a pushed id and not the uid of the user that comes from the authentication process.
Please note that when you create a new instance of your Databasemodel class and you are converting it to a Map, when trying to write this Map into a database, you tell Firebase that everything under that location should be overwritten with the new data. So in the case of postModel, it replaces the old value with the new one.
If you want to update the child that exists within your Birthday Persons node, you should consider replacing the u_id with -LsjBQxNuNMZ_VBj4Hyp:
bdayTitleref.child("-LsjBQxNuNMZ_VBj4Hyp").updateChildren(postModel)
My feeling is that you only call val u_id = bdatTitleref.push().key once.
It'll be simpler if you just call push() when you're adding the new data to the database. So something like:
val ref = FirebaseDatabase.getInstance()
val rootref = ref.getReference("User")
val userref = rootref.child(uid.toString())
val bdayTitleref = userref.child("Birthday Persons")
val model = Databasemodel(u_id!!, et_name.text.toString().trim(), tv_show_date.text.toString(), 0)
val postModel=model.toMap()
bdayTitleref.push().setValue(postModel)
I did an silly mistake when I signed in immediately added the user's uid to database so when I signed in again after logging out then it again created new node with the same uid so the previous data was deleted.

Categories

Resources