I have the following code, that gets user roles from a io.jsonwebtoken.Claims object
val claims = jwtUtil.getAllClaimsFromToken(authToken)
val rolesMap = claims.get("role", ArrayList::class.java)
val roles = ArrayList<Role>()
for (rolemap in rolesMap) {
roles.add(Role.valueOf((rolemap as LinkedHashMap<String, String>)["authority"] as String))
}
Is there a better/cleaner way to get the role(s) as String in Kotlin?
You could do it like this. The main difference being the use of the higher order function map, which iterates a collection applying a function to create a new collection of the same size but of a new type.
Also, unless you need the intermidiate parts of the function to be assigned to variables for debugging/logging purposes then you can simply ommit assigning values and just chain function calls, but it can make it less easy to read (debatable):
return jwtUtil.getAllClaimsFromToken(authToken)
.get("role", ArrayList::class.java)
.map {
Role.valueOf((it as Map<String, String>)["authority"])
}
Related
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...
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.
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.
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)
first, I create empty Array(Kotlin) instance in companion object.
companion object {
var strarray: Array<String> = arrayOf()
var objectarray: LinkedHashMap<Int, List<Any>> = LinkedHashMap<Int, List<Any>>()
}
and I expected that I use empty array instance when read textString from CSV File.
fun csvFileToString():String {
val inputStream = File(Paths.get("").toAbsolutePath().toString()
.plus("/src/main/SampleCSVFile_2kb.csv")).inputStream()
val reader = inputStream.bufferedReader()
var iterator = reader.lineSequence().iterator()
var index:Int = 1;
while (iterator.hasNext()){
var lineText:String = iterator.next()
strarray.set(index, lineText)
index++
}
return ""
}
but when I run that source code
a.csvFileToString()
println(CsvParser.strarray)
occured exception
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
strarray.set(index, lineText) <<<<<<<<< because of this line
can I use Array(from kotlin collection) like ArrayList(from java collection)?
You can add a new item to an array using +=, for example: item += item
private var songs: Array<String> = arrayOf()
fun add(input: String) {
songs += input
}
Size of Array is defined at its creation and cannot be modified - in your example it equals 0.
If you want to create Array with dynamic size you should use ArrayList.
arrayOf gives you an array. Arrays have fixed length even in Java.
listOf gives you an immutable list. You cannot add or remove items in this list.
What you're looking for is mutableListOf<String>.
In your current approach, reusing a member property, don't forget to clear the list before every use.
Your code can be further simplified (and improved) like so:
out.clear()
inputStream.bufferedReader().use { reader -> // Use takes care of closing reader.
val lines = reader.lineSequence()
out.addAll(lines) // MutableList can add all from sequence.
}
Now imagine you wanted to consume the output list but needed to parse another file at the same time.
Consider working towards a pure function (no side effects, for now no accessing member properties) and simplifying it even further:
fun csvFileToString(): String { // Now method returns something useful.
val inputStream = File(Paths.get("").toAbsolutePath().toString()
.plus("/src/main/SampleCSVFile_2kb.csv")).inputStream()
inputStream.bufferedReader().use {
return it.lineSequence().joinToString("\n")
}
}
In this case we can totally skip the lists and arrays and just read the text:
inputStream.bufferedReader().use {
return it.readText()
}
I'm assuming that's what you wanted in the first place.
Kotlin has a lot of useful extension functions built-in. Look for them first.