(Problem solved) Set the value of a livedata variable of type <data class> to a list of strings? - kotlin

How to populate the value of this variable:
private val _urlList = MutableLiveData<List<Url>>()
of type Url:
data class Url(
val imgSrcUrl: String
)
with the incoming list of url strings from a firebase call?
Here is where the magic happens:
private fun getData(){
viewModelScope.launch {
try {
getImagesUrl {
"Here where I need to set the value of the variable to a listOf(it) with it being strings
of urls retrieved from firebase storage"
}
}catch (e: Exception){
"Handling the error"
}
}
}
Edit
The map function #dominicoder provided solved my problem, answer accepted.
Thank you all for your help

Your question is unclear because you're showing a live data of a single Url object but asking to stuff it with a list of strings. So first, your live data object needs to change to a list of Urls:
private val _urlList = MutableLiveData<List<Url>>()
Then, assuming getImagesUrl yields a list of strings, if I understood you correctly, then you would map that to a list of Urls:
getImagesUrl { listOfImageUrlStrings ->
_urlList.value = listOfImageUrlStrings.map { imageUrlString -> Url(imageUrlString) }
}
If that does not answer your question, you really need to review it and clarify.

You can set values on the MutableLiveDataObject in two ways (depends on what you're doing).
Setting the value as normal from the UI thread can be done with:
myLiveData.value = myobject
If you're setting it from a background thread like you might in a coroutine with a suspended function or async task etc then use:
myLiveData.postValue(myObject)
It's not clear from your question whether the LiveData is meant to hold a list as you mention both lists and single values. But your LiveData holds a set the values as a collection like a list, set or map. It's can be treated as a whole object so adding a value later needs to have the whole collection set again like:
myLiveData.value = mutableListOf<Url>()
//Response received and object created
myLiveData.value = myLiveData.value.apply {
add(myObject)
}
Or if the value is mutable updating the existing value (preferred as it's cleaner):
myLiveData.value.add(myObject)
The problem with that approach is you're exposing the map as a mutable/writeable object. Allowing accessors to change the values which you might not want.

Related

How to manually update a kotlin flow

I'm trying to update the source of a Flow in Kotlin and I'm not sure if this is the right approach and if it's possible with Flow at all.
I have a database containing posts for a user and this returns me a Flow<List<Post>>.
Now when I select another user I want the flow of the database to return me the posts of the newly selected user:
lateinit var userPosts: Flow<List<Post>>
private set
fun getPostsForUser(user: User) {
userPosts = database.getAllPostsForUser(user)
}
But the flow never gets updated with the data of the new selected user. Is Flow still the right choice in this case and if yes, how can I update the flow with the new posts?
I know how to do it manually with fetching data from the database and emitting it using LiveData, but I would like to avoid handling the update of posts everytime the user posts something new or a post is deleted.
I think maybe you're collecting one flow, and then setting a user, which changes the flow in the property, but not any existing previous flow that's already being collected. I'm not sure how else to explain what's happening.
It may be error-prone to have a public Flow property that is reliant on some other function that takes a parameter. You could return a Flow directly from the function so there is no ambiguity about the behavior. The fragment requests a Flow for a specific User and immediately gets it.
distinctUntilChanged() will prevent it from emitting an unchanged list that results from other changes in the repo.
fun getPostsForUser(user: User) Flow<List<Post>> =
database.getAllPostsForUser(user).distinctUntilChanged()
If you do want to use your pattern, I think it you could do it like this. This allows there to only ever be one Flow so it's safe to start collecting it early. Changing the user will change which values it's publishing. Although this is more complicated than above, it has the advantage of not requiring a data refresh on screen rotations and other config changes.
private val mutableUserPosts = MutableStateFlow<List<Post>>(emptyList())
val userPosts: Flow<List<Post>> = mutableUserPosts
private var userPostsJob: Job? = null
var user: User? = null
set(value) {
field = value
userPostsJob?.cancel()
value ?: return
userPostsJob = database.getAllPostsForUser(value)
.onEach { mutableUserPosts.emit(it) }
.launchIn(viewModelScope)
}
Or as Joffrey suggests, it's simpler with a User Flow if you don't mind using the unstable API function flatMapLatest. The user property here could be dropped if you don't mind exposing a public mutable flow where the value should be set externally. Or if you use this pattern repeatedly, you could make operator extension functions for StateFlow/MutableStateFlow to use it as a property delegate.
private val userFlow = MutableStateFlow<User?>(null)
var user: User?
get() = userFlow.value
set(value) {
userFlow.value = value
}
val userPosts: Flow<List<Post>> = userFlow.flatMapLatest { user ->
if (user == null) emptyFlow() else database.getAllPostsForUser(user)
}
This might be helpful for someone...
When you are collecting a flow downstream and in some situations, you need an updated flow or a completely different flow than before, Use flatmapLatest() function.
Example:
There is a flow of search results in an app. When a user enters a search text flow automatically should update with the latest data according to searched chars.
Place the query text in a StateFlow as,
private var _userSearchText = MutableStateFlow("")
val userSearchText = _userSearchText.asStateFlow()
as the user enters text just update the Stateflow as,
fun setUserNameSearchText(data: String) {
_userSearchText.value = data
}
and your flow collecting like,
val response = userSearchText.flatMapLatest {
searchRepository.getResults(it)
.cachedIn(viewModelScope)
}
Stateflow will trigger the API calls automatically. It will notify all the observers on them.
PS: Open to improving...

Kotlin- Is there any way to update a single field in collection item in Kotlin without foreach?

I am working on Android application using kotlin. I am pretty much new to kotlin and I have the following scenario.
I have the list of users in a List collection object with the fields such as firstName , lastName, mobile and hasDeleted
var myList: List<Users>
myList = <I have list of users here>
I would like to update only one flag hasDeleted with the value true for each Users.
I understand that we can use foreach to update the value. But, I would like to know if any other approach I can follow.
The only reason for not using forEach is if your Users object is immutable (which you should at least consider) and it is a data class defined as follows:
data class Users(val firstName: String,
val lastName: String,
val mobile: String,
val hasDeleted: Boolean)
If this is what you have, then map is your best option, since you can no longer change a Users object with hasDeleted = true because they are not mutable. In this case, you should use the following which will return a list with the updated Users objects.
myList.map { it.copy(hasDeleted = true) }
Other than this specific case, I see no good reason to avoid using forEach.
You can simply use map for it:
myList.map { it.hasDeleted = true }
it will update all hasDeleted as true in the list.
Yes you can do it with the following approach
var myList: List<User>
myList.map { it.hasDeleted = true}
The map will replace the value of hasDeleted for all the list items to true/false, whatever you will provide.
Here is a tested sample with expected results.

Extracting Nested POJO Object with Rest-Assured

I'm writing some tests using rest-assured and its Kotlin extensions to test some simple Spring MVC endpoints. I'm trying to understand how to extract values.
One endpoint returns a BookDetailsView POJO, the other returns a Page<BookDetailsView> (where Page is an interface provided by Spring for doing paging).
BookDetailsView is a really simple Kotlin data class with a single field:
data class BookDetailsView(val id: UUID)
For the single object endpoint, I have:
#Test
fun `single object`() {
val details = BookDetailsView(UUID.randomUUID())
whenever(bookDetailsService.getBookDetails(details.id)).thenReturn(details)
val result: BookDetailsView = Given {
mockMvc(mockMvc)
} When {
get("/book_details/${details.id}")
} Then {
statusCode(HttpStatus.SC_OK)
} Extract {
`as`(BookDetailsView::class.java)
}
assertEquals(details.id, result.id)
}
This works as expected, but trying to apply the same technique for the Page<BookDetailsView> runs afoul of all sorts of parsing challenges since Page is an interface, and even trying to use PageImpl isn't entirely straightforward. In the end, I don't even really care about the Page object, I just care about the nested list of POJOs inside it.
I've tried various permutations like the code below to just grab the bit I care about:
#Test
fun `extract nested`() {
val page = PageImpl(listOf(
BookDetailsView(UUID.randomUUID())
))
whenever(bookDetailsService.getBookDetailsPaged(any())).thenReturn(page)
val response = Given {
mockMvc(mockMvc)
} When {
get("/book_details")
} Then {
statusCode(HttpStatus.SC_OK)
body("content.size()", `is`(1))
body("content[0].id", equalTo(page.first().id.toString()))
} Extract {
path<List<BookDetailsView>>("content")
}
println(response[0].javaClass)
}
The final println spits out class java.util.LinkedHashMap. If instead I try to actually use the object, I get class java.util.LinkedHashMap cannot be cast to class BookDetailsView. There are lots of questions and answers related to this, and I understand it's ultimately an issue of the underlying JSON parser not knowing what to do, but I'm not clear on:
Why does the "simple" case parse without issue?
Shouldn't the type param passed to the path() function tell it what type to use?
What needs configuring to make the second case work, OR
Is there some other approach for grabbing a nested object that would make more sense?
Digging a bit into the code, it appears that the two cases may actually be using different json parsers/configurations (the former seems to stick to rest-assured JSON parsing, while the latter ends up in JsonPath's?)
I don't know kotlin but here is the thing:
path() doesn't know the Element in your List, so it'll be LinkedHashMap by default instead of BookDetailsView.class
to overcome it, you can provide TypeReference for this.
java example
List<BookDetailsView> response = ....then()
.extract().jsonPath()
.getObject("content", new TypeRef<List<BookDetailsView>>() {});
kotlin example
#Test
fun `extract nested`() {
var response = RestAssured.given().get("http://localhost:8000/req1")
.then()
.extract()
.jsonPath()
.getObject("content", object : TypeRef<List<BookDetailsView?>?>() {});
println(response)
//[{id=1}, {id=2}]
}

adding to a list that is contained in livedata and adding elements to that list

adding to a list that is contained in livedata and adding elements to that list
val resourcesLiveData by lazy { MutableLiveData<List<File>>() }
I thought this should work as my LiveData is a list of files and I just want to add elements to it. But the value of live data is always an empty list. The res is the different file resources I am trying to add
resourceLiveData.value?.toMutableList()?.add(res)
So I tried it more expicity using this version but the list is still empty
val listOfRes = resourceLiveData.value ?: emptyList()
listOfRes.toMutableList().add(res)
resourceLiveData.value = listOfRes.toList()
Can anyone see if I am doing something wrong.
Just want to add to the list that is contained in the value
Agree to #KeyserSoze answer, if you have to use only List then you can do below
resourceLiveData.value = resourceLiveData.value?.toMutableList()?.apply { add(res) }?: emptyList()
You are creating a new object by calling toMutableList() instead of updating the original.
Change your LiveData type to MutableList:
val resourcesLiveData by lazy { MutableLiveData<MutableList<File>>() }
Then, update the value accordingly:
resourceLiveData.value?.add(res)

belongsTo only being set on first and last member of hasMany

My adapter uses findHasMany to load child records for a hasMany relationship.
My findHasMany adapter method is directly based on the test case for findHasMany. It retrieves the contents of the hasMany on demand, and eventually does the following two operations:
store.loadMany(type, hashes);
// ...
store.loadHasMany(record, relationship.key, ids);
(The full code for the findHasMany is below, in case the issue is there, but I don't think so.)
The really strange behavior is: it seems that somewhere within loadHasMany (or in some subsequent async process) only the first and last child records get their inverse belongsTo property set, even though all the child records are added to the hasMany side. I.e., if posts/1 has 10 comments, this is what I get, after everything has loaded:
var post = App.Posts.find('1');
post.get('comments').objectAt(0).get('post'); // <App.Post:ember123:1>
post.get('comments').objectAt(1).get('post'); // null
post.get('comments').objectAt(2).get('post'); // null
// ...
post.get('comments').objectAt(8).get('post'); // null
post.get('comments').objectAt(9).get('post'); // <App.Post:ember123:1>
My adapter is a subclass of DS.RESTAdapter, and I don't think I'm overloading anything in my adapter or serializer that would cause this behavior.
Has anybody seen something like this before? It's weird enough I though someone might know why it's happening.
Extra
Using findHasMany lets me load the contents of the hasMany only when the property is accessed (valuable in my case because calculating the array of IDs would be expensive). So say I have the classic posts/comments example models, the server returns for posts/1:
{
post: {
id: 1,
text: "Linkbait!"
comments: "/posts/1/comments"
}
}
Then my adapter can retrieve /posts/1/comments on demand, which looks like this:
{
comments: [
{
id: 201,
text: "Nuh uh"
},
{
id: 202,
text: "Yeah huh"
},
{
id: 203,
text: "Nazi Germany"
}
]
}
Here is the code for the findHasMany method in my adapter:
findHasMany: function(store, record, relationship, details) {
var type = relationship.type;
var root = this.rootForType(type);
var url = (typeof(details) == 'string' || details instanceof String) ? details : this.buildURL(root);
var query = relationship.options.query ? relationship.options.query(record) : {};
this.ajax(url, "GET", {
data: query,
success: function(json) {
var serializer = this.get('serializer');
var pluralRoot = serializer.pluralize(root);
var hashes = json[pluralRoot]; //FIXME: Should call some serializer method to get this?
store.loadMany(type, hashes);
// add ids to record...
var ids = [];
var len = hashes.length;
for(var i = 0; i < len; i++){
ids.push(serializer.extractId(type, hashes[i]));
}
store.loadHasMany(record, relationship.key, ids);
}
});
}
Solution
Override the DS.RelationshipChange.getByReference method by inserting the following code into your app:
DS.RelationshipChange.prototype.getByReference = function(reference) {
var store = this.store;
// return null or undefined if the original reference was null or undefined
if (!reference) { return reference; }
if (reference.record) {
return reference.record;
}
return store.materializeRecord(reference);
};
Yes, this is overriding a private, internal method in Ember Data. Yes, it may break at any time with any update. I'm pretty sure this is a bug in Ember Data, but I'm not 100% certain this is the right solution. But it does solve this problem, and possibly other relationship-related problems.
This fix is designed to be applied to Ember Data master as of 29 Apr 2013.
Reason
DS.Store.loadHasMany calls DS.Model.hasManyDidChange, which retrieves references for all the child records and then sets the hasMany's content to the array of references. This kicks off a chain of observers., eventually calling DS.ManyArray.arrayContentDidChange, in which the first line is this._super.apply(this, arguments);, calling the superclass method Ember.Array.arrayContentDidChange. That Ember.Array method includes an optimization that caches the first and last object in the array and calls objectAt on only those two array members. So there's the part that singles out the first and last record.
Next, since DS.RecordArray implements an objectAtContent method (from Ember.ArrayProxy), the objectAtContent implementation calls DS.Store.recordForReference, which in turn calls DS.Store.materializeRecord. This last function adds a record property to the reference that is passed in as a side effect.
Now we get to what I think is a bug. In DS.ManyArray.arrayContentDidChange, after calling the superclass method, it loops through all the new references and creates a DS.RelationshipChangeAdd instance that encapsulates the owner and child record references. But the first line inside the loop is:
var reference = get(this, 'content').objectAt(i);
Unlike what happens above to the first and last record, this calls objectAt directly on the Ember.NativeArray and bypasses the ArrayProxy methods including the objectAtContent hook, which means that DS.Store.materializeRecord--which adds the record property on the reference object--may have never been called on some references.
Next, the relationship changes created in the loop are immediately afterward (in the same run loop) applied with this call tree: DS.RelationshipChangeAdd.sync -> DS.RelationshipChange.getFirstRecord -> DS.RelationshipChange.getByReference. This last method expects the reference object to have a record property. However, the record property is only set on the first and last reference objects, for reasons explained above. Therefore, for all but the first and last records, the relationship fails to be established because it doesn't have access to the child record object!
The above fix calls DS.Store.materializeRecord whenever the record property doesn't exist on the reference. The last line in the function is the only thing added. On the one hand, it looks like this was the original intention: that var store = this.store; line in the original declares a variable that isn't otherwise used in the function, so what's it there for? Also, without the added line, the function doesn't always return a value, which is a little unusual for a function which is expected to do so. On the other hand, this could lead to mass materialization in some cases where that would be undesirable (but, the relationships just won't work without it in some cases, it seems).
Possibly related
The "chain of observers" I mentioned takes a bit of an odd path. The initiating event was setting the content property on a DS.ManyArray, which extends Ember.ArrayProxy--therefore the content property has a dependent property arrangedContent. Importantly, the observers on arrangedContent are executed before observers on content are executed (see Ember.propertyDidChange). However, the default implementation of Ember.ArrayProxy.arrangedContentArrayDidChange simply calls Ember.Array.arrayContentDidChange, which DS.ManyArray implements! The point being, this looks like a recipe for some code to execute in an unintended order. That is, I think Ember.ManyArray.arrayContentDidChange may getting executed earlier than expected. If this is the case, the above mentioned code that expects the record property to already exist on all references may have been expecting this reasonably, as one of the observers directly on the content property may call DS.Store.materializeRecord on each reference. But I haven't dug deep enough to find out if this is true.