realm linking object returns null - android

I have a model named Item. the model has linkingObjects to model ItemModifier. The Question is, why do I have to query for it to return a result ?
open class Item() : RealmObject() {
#PrimaryKey
var id: String = ""
#LinkingObjects("item")
val itemModifiers: RealmResults<ItemModifier>? = null
}
var item = Item()
item.id = UUID.randomUUID().toString()
realm.copyToRealmOrUpdate(itemModifier)
var itemModifier = ItemModifier()
itemModifier.id = UUID.randomUUID().toString()
itemModifier.item = item
realm.copyToRealmOrUpdate(itemModifier)
for (itemModifier in item.itemModifiers) { // this returns nullOrEmpty.
Log.e("test", itemModifier.id)
}
queriedItem = Realm.getDefaultInstance().where(Item::class.java).equalTo("id", item.id).findFirst()!!
for (itemModifier in queriedItem.itemModifiers) { // this return itemModifier.
Log.e("test", itemModifier.id)
}

Firstly, I assume the first realm.copyToRealmOrUpdate(itemModifier) is a typo and should be realm.copyToRealmOrUpdate(item).
Having done this the item you created (an unmanaged object) has now been copied into the realm, but the reference you hold is still to the unmanaged object. This is why a look at its linking objects field gives you an empty list. Whereas, as you have shown, retrieving the managed object via query gives you the result you expect.
Note that according to the docs (here), the copyToRealmOrUpdate method returns a reference to the managed object, so you could use that immediately and you should get the correct result. E.g.:
var item = Item()
item.id = UUID.randomUUID().toString()
val managedItem = realm.copyToRealmOrUpdate(item)
var itemModifier = ItemModifier()
itemModifier.id = UUID.randomUUID().toString()
itemModifier.item = managedItem
realm.copyToRealmOrUpdate(itemModifier)
for (itemModifier in managedItem.itemModifiers) { // this should now work.
Log.e("test", itemModifier.id)
}

Related

How to map aggregated data from Firestore document back to a Kotlin List

Would like to reduce the number of reads in my app with Firestore. So I created a document in my collection with the following structure to hold values for 10 "records", that I will get with a single read - since the size of this document is pretty decent, no worries about the 1MB limit size for a single document. I am updating the content of this document with cloud functions.
name of collection: helperz
name of document: top10
field name in top10: tresholdCounter - this I need to check if a single map should be added to top10 or not
field name in top10: top10 . . array of maps
helperz/top10/tresholdCounter
helperz/top10/top10/0/author
helperz/top10/top10/0/name
helperz/top10/top10/0/url
helperz/top10/top10/1/author
helperz/top10/top10/1/name
helperz/top10/top10/1/url
helperz/top10/top10/2/author
helperz/top10/top10/2/name
helperz/top10/top10/2/url
helperz/top10/top10/3/author
helperz/top10/top10/3/name
helperz/top10/top10/3/url
helperz/top10/top10/4/author
helperz/top10/top10/4/name
helperz/top10/top10/4/url
..
helperz/top10/top10/10/author
helperz/top10/top10/10/name
helperz/top10/top10/10/url
I have a data class . . like this:
data class MyClass(
var name: String? = null,
var url: String? = null,
var author: String? = null,
var counter: Int = 0,
var free: Boolean? = false,
var global: Boolean?=true,
var poses: ArrayList<MyPoze>? = null,
var docId: string? = null,
var category: ArrayList<String>? = null,
#get:PropertyName(CREATED)
#set:PropertyName(CREATED)
#ServerTimestamp var created: Timestamp? = null
)
There are some other fields as well, but for the purpose of this problem, that should be ok.
I have a code for retrieving data from Firestore (in my viewModel):
private fun getHelperzTop10() = viewModelScope.launch {
Log.d("MP23", "getHelperzTop10")
val docRef = db.collection("helperz").document("top10")
docRef.addSnapshotListener { snapshot, e ->
if (e != null) {
Log.w("MP23", "Listen failed.", e)
return#addSnapshotListener
}
if (snapshot != null && snapshot.exists()) {
val docSize = firestoreDocument.getSize(snapshot);
Log.d("MP23","docSize in Bytes: $docSize, in KB: ${docSize * 0.001}, in MB: ${docSize * 0.000001}"
)
val top10 = snapshot.data
Log.d("MP23", top10!!::class.simpleName.toString() )
if ("top10" in keys) {
val top10arr = top10["top10"] as ArrayList<MyClass>
Log.d("MP23", "we have top10 in top10")
Log.d("MP23", top10arr!!::class.simpleName.toString())
Log.d("MP23", top10arr.toString())
//// 👇 PROBLEM HERE ///
// here I need somehow to cast this array of maps as MyClass, but I am getting error:
// java.util.ArrayList cannot be cast to java.lang.MyClass
// or
// java.util.ArrayList cannot be cast to java.lang.Object[] - with some other versions
}
} else {
Log.d("MP23", "Current data: null")
}
}
}
If someone can suggest how can I cast result to MyClass, that would be great. In normal situation (where I have documents that I query against) nicely as single docs, I can easily do:
val singleRec = singleRec.toObject(MyClass::class.java)
If you want to get the top10 array as a List<MyClass>, the simplest solution that I can think of would be to create another class:
data class Document(
var top10: MutableList<MyClass>? = null
)
And read the content of the document like this:
docRef.get().addOnCompleteListener {
if (it.isSuccessful) {
val snapshot = it.result
snapshot?.let {
val document = snapshot.toObject(Document::class.java)
document?.let {
val top10 = document.top10
top10?.let {
for (myClass in top10) {
val name = myClass.name
Log.d("TAG", "$name")
}
}
}
}
} else {
it.exception?.message?.let { message ->
Log.d("TAG", message) //Never ignore potential errors!
}
}
}
I will also recommend you read the following resource for a better understanding:
How to map an array of objects from Cloud Firestore to a List of objects?

Kotlin - Modify nested Data (Add item to List in an Object in another List in an Object)

My User object has a List<Post>, each Post has a List<Tag>.
Now I would like to add a Tag item to the List<Tag>.
User:
data class User(
val id: String,
val name: String,
val posts: List<Post> = listOf()
)
Post:
data class Post(
val id: Int,
val name: String
val tags: List<Tags> = listOf()
)
Now I would like to update my MutableStateFlow containing the User object:
private val _userStateFlow: MutableStateFlow<User?> = MutableStateFlow(User())
val userStateFlow: StateFlow<User?> = _userStateFlow
To find the correct Post (containing the List<Tag> I want to update) I have the id of it (passedPostId)
val postsList = userStateFlow.value?.posts!!.toMutableList()
val tagsList = postsList.find { it.id == passedPostId }?.tags?.toMutableList()
tagsList.add(Tag("test"))
Now I got an updated tagsList but I need an updated postsList to pass it to my MutableStateFlow.
I am not sure how to do it.
This is how I update my MutableStateFlow:
_userStateFlow.update { it?.copy(posts = newPosts)
If I have the index of the post I want to update everything works fine, but In my Case I only can call .find because all i have is the id of the post.
val posts = userStateFlow.value?.posts!!.toMutableList()
posts[index] = posts[index].copy(tags = posts[index].tags.plus(Tag("test")
userStateFlow.value = userStateFlow.value?.copy(posts = posts)
You can create a function to add a tag to your immutable Post object - may as well stick it inside the class:
data class Post(
val id: Int,
val name: String
val tags: List<Tag> = listOf()
) {
// copy this object, replacing the tag list with a copy that has the new one appended
fun addTag(newTag: Tag) = copy(tags = tags + newTag)
}
Then you can do:
// alternative to putting it in the User class - whatever feels better
fun User.addTag(postId: Int, tag: Tag) =
// copy this User, including copying its list of Posts
// but any with a matching ID get the tag added
copy(posts = posts.map { post ->
if (post.id == postId) post.addTag(tag) else post
})
and update with
userStateFlow.value = userStateFlow.value!!.addTag(passedPostId, Tag("test"))
Try it here if you like
I fixed it by simply getting the index of the Post so I could use the code that worked before:
val postPosition = postsList.indexOfFirst {
it.id == passedPostId
}
postsList[postPosition] = postsList[postPosition].copy(tags = tagsList)
_userStateFlow.update { it?.copy(posts = postsList)
I feel pretty stupid to not have thought of that in the first place.

Can't update MutableStateFlow with model

I'm trying to update a parameter of my Model. I have a MutableStateFlow with a list with some of my model created.
data class MyModel(
val id: Int,
val category: String,
var completed: Boolean
)
val listOfModel = listOf(
MyModel(
id = 0,
category = "shopping",
completed = true
), MyModel(
id = 1,
category = "web",
completed = false
)
)
var _modelStateFlow = MutableStateFlow(listOfModel)
var modelStateFlow = _modelStateFlow.asStateFlow()
What I want to do in my other class, is to update the "completed" parameter in the model. That's what I tried but I get the following error:
Type mismatch. Required: List<"MyModel"> Found: MyModel
_modelStateFlow.update { it[current.value!!].copy(completed = !modelStateFlow.value[current.value!!].completed) }
You can do it like this:
_modelStateFlow.update { list ->
list.mapIndexed { index, myModel ->
if(index == indexToUpdate) myModel.copy(completed = !myModel.completed)
else myModel
}
}
The reason you are getting that error is because, you need to return a new list inside update function which represents the new value of StateFlow. You can create that new list using map function. Update the model at the desired index keeping others as they are.

Firestore startAt skips the snapshot given to it and behaves as startAfter instead

I have a PagingSource that pages through a firestore collection to return documents.
class ClipPageDataSource(mParams:Bundle, private val mAds:Boolean):PagingSource<QuerySnapshot, Clip>(), ClipDataSource {
var query : Query?= null
private val mFirestore = FirebaseFirestore.getInstance()
private var mBaseQuery = mFirestore.collection(SharedConstants.COLLECTION_CLIPS)
.orderBy("createdAt",Query.Direction.DESCENDING)
private var mLikedQuery = mFirestore.collection(SharedConstants.COLLECTION_USERS)
.document(Prefs.getString(SharedConstants.PREF_SERVER_USER_ID,Firebase().getCurrentUserId()))
.collection(SharedConstants.SUB_COLLECTION_USER_LIKES)
.orderBy("createdAt",Query.Direction.DESCENDING)
private var mSavedQuery = mFirestore.collection(SharedConstants.COLLECTION_USERS)
.document(Prefs.getString(SharedConstants.PREF_SERVER_USER_ID,Firebase().getCurrentUserId()))
.collection(SharedConstants.SUB_COLLECTION_SAVES)
.orderBy("createdAt",Query.Direction.DESCENDING)
val mine = mParams.getBoolean(ClipDataSource.PARAM_MINE)
val liked = mParams.getBoolean(ClipDataSource.PARAM_LIKED)
val saved = mParams.getBoolean(ClipDataSource.PARAM_SAVED)
val user = mParams.getString(ClipDataSource.PARAM_USER)
val first = mParams.getString(ClipDataSource.PARAM_FIRST)
val private = mParams.getBoolean(ClipDataSource.PARAM_PRIVATE)
override fun getRefreshKey(state: PagingState<QuerySnapshot, Clip>): QuerySnapshot? {
return null
}
override suspend fun load(params: LoadParams<QuerySnapshot>): LoadResult<QuerySnapshot, Clip> {
try {
query = when {
liked -> {
mLikedQuery
}
saved -> {
mSavedQuery
}
else -> {
mBaseQuery
}
}
if(mine){
query = query!!.whereEqualTo("createdBy.uid",Prefs.getString(SharedConstants.PREF_SERVER_USER_ID,Firebase().getCurrentUserId()))
}else if(user!=null){
query = query!!.whereEqualTo("createdBy.uid",user)
}
query = if (private){
query!!.whereEqualTo("private",true)
}else{
query!!.whereEqualTo("private",false)
}
first?.let {
val item = mFirestore.collection(SharedConstants.COLLECTION_CLIPS).document(it).get().await()
if (item!=null){
query = query!!.startAt(item)
}
Log.d(TAG,"the first item fetched is ${item.data!!["id"]}")
}
query = query!!.limit(15)
val currentPage = params.key ?: query!!.get().await()
if (currentPage.size() < 1)
return LoadResult.Page(emptyList(),null,null)
val lastDocumentSnapshot = currentPage.documents[currentPage.size() - 1]
val nextPage = query!!.startAfter(lastDocumentSnapshot).get().await()
val clips = currentPage.map {
it.toObject(Clip::class.java)
}
return LoadResult.Page(clips,null,nextPage)
}catch (e:Exception){
return LoadResult.Error(e)
}
}
companion object{
private const val TAG = "DataSource"
}
}
So, in the above code, I have three different collections to fetch data from, and the required one is selected based on the parameters passed.
Now, when I fetch data using the mBaseQuery, and passing an id in first parameter, it returns the data correctly.
But, when I fetch data using the mLikedQuery or the mSavedQuery, instead of returning data from the id passed in first parameter, it uses the next item as the first one. Basically, startAt works as startAfter.
I have checked the snapshot fetched using the id passed in first is correct. So, the block in first?.let, works correctly. But, when the final query is executed, it skips the first item passed in startAt and instead starts from the next item in list.
This only happens with mLikedQuery and mSavedQuery and not with mBaseQuery.
Anybody got any idea what's happening here?
The DocumentReference you are providing to startAt is always for a document from the collection SharedConstants.COLLECTION_CLIPS:
val item = mFirestore.collection(SharedConstants.COLLECTION_CLIPS).document(it).get().await()
This works fine for your mBaseQuery because that query is querying the documents in the SharedConstants.COLLECTION_CLIPS collection, however your mLikedQuery and mSavedQuery are querying documents from different collections so providing a DocumentReference from the SharedConstants.COLLECTION_CLIPS collection as the startAt value here doesn't make sense, the query can't start at a document that doesn't exist in the collection you're querying.
Perhaps you need to set the item you provide to startAt based on which query is being used, e.g.:
...
first?.let {
val item = when {
liked -> {
mFirestore.collection(SharedConstants.COLLECTION_USERS)
.document(Prefs.getString(SharedConstants.PREF_SERVER_USER_ID,Firebase().getCurrentUserId()))
.collection(SharedConstants.SUB_COLLECTION_USER_LIKES).document(it).get().await()
}
saved -> {
mFirestore.collection(SharedConstants.COLLECTION_USERS)
.document(Prefs.getString(SharedConstants.PREF_SERVER_USER_ID,Firebase().getCurrentUserId()))
.collection(SharedConstants.SUB_COLLECTION_SAVES).document(it).get().await()
}
else -> {
mFirestore.collection(SharedConstants.COLLECTION_CLIPS).document(it).get().await()
}
}
...

Kotlin code to remove duplicate object of one particular type from mutable list

I want to remove all duplicate objects of one type from a list.
For Eg.
val models: MutableList<Model> = MutableList<DrawableModel>
models.add(Student)
models.add(Student)
models.add(Teacher)
models.add(Teacher)
models.add(Teacher)
Expected Output:
Student
Student
Teacher
I want only one instance of a teacher on the list.
I tried with
models.distinctBy{ it is Teacher}
But it is applying distinct to the entire list not to a particular object and returns.
Student
Teacher
You can try something like this:
object Kotlin
{
#JvmStatic
fun main(args: Array<String>)
{
val teacher = Teacher(0, "T1");
val student = Student(1, "S1")
val models = mutableListOf(teacher, teacher, student, student, student)
// creating new list which is connection of two lists. First -> only Students. Second -> Distinct Teacher
val newModels = models.filterIsInstance<Teacher>().distinct() + models.filterIsInstance<Student>()
println(models) // [Teacher(ID=0, name=T1), Teacher(ID=0, name=T1), Student(ID=1, name=S1), Student(ID=1, name=S1)]
println(newModels) // [Teacher(ID=0, name=T1), Student(ID=1, name=S1), Student(ID=1, name=S1)]
}
// classes like in the question.
open class Model(val id: Int)
data class Teacher(val ID: Int, val name: String) : Model(ID)
data class Student(val ID: Int, val name: String) : Model(ID)
So basically it is the main part:
val newModels = models.filterIsInstance<Teacher>().distinct() + models.filterIsInstance<Student>()
You can set a Boolean so it skips the first one it finds.
var first = true
models.removeAll { if (it is Teacher) (!first).also { first = false } else false }
If you want a new list, use filterNot instead of removeAll.
distinctBy doesn't work because it's converting the objects to distinct keys. Since you're key type is Boolean, there can only be two items in the list.
Try this:
var list = arrayListOf<Any>()
list.add(Person("JAVA",20))
list.add(Person("JAVA",20))
list.add(Student("SQL",24))
list.add(Student("SQL",24))
list.add(Student("SQL",24))
var filterList = arrayListOf<Any>()
var studentCount = 0
// filtering student object only
list.forEach {
if (it is Student && studentCount == 0) {
studentCount++
filterList.add(it)
} else if (it is Person) filterList.add(it)
}
filterList.stream().forEach { println(it) }// output : [JAVA,20],[JAVA,20],[SQL,24]

Categories

Resources