What is the equivalent code for this live data transformation in StateFlow / SharedFlow?
val myLiveData: LiveData<MyLiveData> = Transformations
.switchMap(_query) {
if (it == null) {
AbsentLiveData.create()
} else {
repository.load()
}
Basically, I want to listen to every query changes to react what to return. So, anything similar to that using StateFlow / SharedFlow is welcome.
First, create an helper extension function:
fun <R> Flow<R>.toStateFlow(coroutineScope: CoroutineScope, initialValue: R) = stateIn(coroutineScope, SharingStarted.Lazily, initialValue)
Use mapLatest{} for Transformations.map():
val studentNames = _students.mapLatest { students ->
students.map { "${it.name}" }
}.toStateFlow(uiScope, emptyList()) //uiScope is viewModelScope
Use flatMapLatest{} for Transformations.switchMap():
val asteroids = _asteroidFilter.flatMapLatest { filter ->
asteroidRepository.getAsteroidsFlow(filter)
}.toStateFlow(uiScope, emptyList())
Use combine() for MediatorLiveData:
val sumScore = combine(_team1Score, _team2Score) { score1, score2 ->
score1 + score2
}.toStateFlow(uiScope, 0)
switchMap is deprecated in flows and should use either of flatMap, transform or transformLatest to convert one type of flows to others. An example for that would be
val myFlow = flowOf<Int>().transform<Int, String> { flowOf("$it") }}
I guess you can use same logic for StateFlow or SharedFlows.
Related
I need to handle current and previous value in flow collect, so I need some operator that acts like that:
----A----------B-------C-----|--->
---(null+A)---(A+B)---(B+C)--|--->
One idea is something like:
fun <T: Any> Flow<T>.withPrevious(): Flow<Pair<T?, T>> = flow {
var prev: T? = null
this#withPrevious.collect {
emit(prev to it)
prev = it
}
}
But this way there is no control over a context in which first flow will be executed. Is there more flexible solution?
Flows are sequential, so you can use a variable to store the previous value:
coroutineScope.launch {
var prevValue = null
flow.collect { newValue ->
// use prevValue and newValue here
...
// update prevValue
prevValue = newValue
}
}
There's an operator which makes this very easy: runningFold
The docs have an example on how to use it to collect each emission of a flow; this can be easily adapted to fit our needs
data class History<T>(val previous: T?, val current: T)
// emits null, History(null,1), History(1,2)...
fun <T> Flow<T>.runningHistory(): Flow<History<T>?> =
runningFold(
initial = null as (History<T>?),
operation = { accumulator, new -> History(accumulator?.current, new) }
)
// doesn't emit until first value History(null,1), History(1,2)...
fun <T> Flow<T>.runningHistoryAlternative(): Flow<History<T>> =
runningHistory().filterNotNull()
You might need to tweak nullabilities to fit your usecase
I am trying to use SharedFlow as data provider for a Fragment in MVVM architecture.
In the Fragment class:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.data.collect { value ->
handleData(data)
}
}
}
viewModel.init()
}
In the ViewModel class:
private val _data: MutableSharedFlow<DataState> = MutableSharedFlow()
val data: SharedFlow<DataState> = _data
fun init() {
...
//(listen for other data providers that generate data for SharedFlow)
...
viewModelCoroutineScope.launch {
val dataCollection = interactor.getDataCollection()
dataCollection.forEach { data ->
if (data != null) {
_data.emit(DataState(data = data))
}
}
}
}
The problem is that in 50% cases viewmodel.init() starts before subscriber under scope is connected to Flow - which results in some data lost.
Why SharedFlow is used? That is because ViewModel have subscriptions to other data sources which could send a lot of data instances in the irregular way all needed to collect, so StateFlow/LiveData with their "store only last value" is not good for this.
When I've tried to pin viewmodel.init() to subscriber coroutine like this:
val job = viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.data.collect { value ->
handleData(data)
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
job.join()
viewModel.init()
}
the ViewModel emits data, but Fragment is never collects it.
What is right way to guarantee that subscribers is on before call of the ViewModel to start data sending through SharedFlow?
You should give your SharedFlow a replay value of 1 so late subscribers will still get the most recent value. You need this anyway. If the screen rotates, the recreated Fragment will need the latest value to show in the UI.
private val _data: MutableSharedFlow<DataState> = MutableSharedFlow(replay = 1)
But actually, it would be better to use shareIn instead of MutableSharedFlow, because then you can pause collection when there are no active subscribers, so you can avoid unnecessary monitoring of resources when the associated Fragment is off-screen. Like this:
val data: SharedFlow<DataState> = interactor.getDataCollection()
.mapNotNull { it?.let(::DataState) }
.shareIn(viewModelScope, SharingStarted.whileSubscribed(5000L), replay = 1)
If getDataCollection() is a suspend function, you could do it like this:
val data: SharedFlow<DataState> = flow {
interactor.getDataCollection().emitAll()
}
.mapNotNull { it?.let(::DataState) }
.shareIn(viewModelScope, SharingStarted.whileSubscribed(5000L), replay = 1)
If it's not a suspend function, why do you have a getter function at all? Kotlin uses properties instead.
I have a
val map = Map<String,String>
map.put("Nurseiyt","android")
I want to get a value by subString like:
map["Nurs"] should return "android"
is it possible?
Use kotlin.Collections, there are methods like filter.
Two things - it's better to use regular expression. So, you can even get better control what will be returned. And the second one, there can be more than one elements matched to that regex. So that's why I return list.
fun <T> substringKey(map: Map<String, T>, regex: Regex): List<T> {
return map.filter { it.key.contains(regex) }
.map { it.value }
}
If you want to use that notation you need to create your own map and override proper operator. What's worth to notice, you cannot return list of values then. So, in this case I just return first found value.
class SubstringMap<V> : HashMap<String, V>() {
override operator fun get(key: String): V? {
return this.entries.first { it.key.contains(key) }.value
}
}
fun main() {
val map = SubstringMap<String>()
map["Nurseiyt"] = "android"
println(map["Nurs"]) // "android"
}
And as the last thing - in kotlin you can create your own operator, like withKeyPart. This would be much better than overriding default operator (because I wouldn't expect that [] operator will work in different way than usual.
infix fun <V> Map<String, V>.withKeyPart(keyPart: String): List<V> {
return this.filter { it.key.contains(keyPart) }
.map { it.value }
}
and then call it like this:
fun main() {
val map = HashMap<String, String>()
map withKeyPart "KeyPart" // infix notation
map.withKeyPart("KeyPart") // standard call
}
Filtering the map, as per other answers, is simple and straightforward, but it doesn't scale well; it takes time proportional to the size of the map, so if the map could grow big, it could get very slow.
If you're always going to be searching for a leading substring, i.e. the start of a map key, then a better general solution is a data structure called a trie. This lets you search efficiently, with just one lookup per character.
Of course, writing one from scratch may not be justified for your project. But there are third-party implementations you could use, such as this one in Apache Commons. Or see the answers to this question.
write top level function like this
fun HashMap<String, String>.getContainskeyValue(search: String): String?
{
var returnList = ArrayList<String?>()
this.keys.filter { it.contains(search) }.map {
returnList.add(this[it])
}
return returnList.first()
//if you want all keys 'contains' values just return list
/* Ex
map.put("Nurseiyt", "android")
map.put("Nurseiyt1", "androidone")
map.put("Nurseirt2", "andrrroidtwo")
val isContainsdata = map.getContainskeyValue("N")
println(" result " + containsdata)
output :result [andrrroidtwo, android, androidone]
*/
}
then call like this
val map = HashMap<String, String>()
map.put("Nurseiyt", "android")
val containsdata = map.getContainskeyValue("Nurs")
println(" result " + containsdata)
output
android
I know how to do this in RxJava 2.
And I know how RxKotlin helps with similar issues.
But it seems that RxKotlin.Observables doesn't have this helper function for the list overload and I cannot figure it out. How would you do this?
Most static functions in RxJava are extension functions in RxKotlin. This particular function is an extension on Iterable<Observable<T>>. You can call it like this:
listOfObservables.combineLatest { ... }
for RxJava 2 this could be done in this way
val list = Arrays.asList(
remoteRepository.getHospitals(),
remoteRepository.getQuestionCategories(),
remoteRepository.getQuestions(),
)
return Observable.combineLatest(list) {
val hospitals = it[0] as List<Hospital>
val questionCategories = it[1] as List<QuestionCategory>
val questions = it[2] as List<Question>
localRepository.insertHospitals(hospitals)
localRepository.insertQuestionCategories(questionCategories)
localRepository.insertQuestions(questions)
if (hospitals.isNotEmpty())
Constants.STATUS_OK
else
Constants.STATUS_ERROR
}
val list = Arrays.asList(Observable.just(1), Observable.just("2"))
Observable.combineLatest(list, object : FuncN<String>() {
fun call(vararg args: Any): String {
var concat = ""
for (value in args) {
if (value is Int) {
concat += value
} else if (value is String) {
concat += value
}
}
return concat
}
})
Observable.just(1), Observable.just("2") can be replaced with list of observable and login inside call fun will also changed as per requirements.
I can think on some dirty ways to calculate a moving average on Kotlin, but I'm not sure which one is the best. I know that kotlin has a lot of interesting features to work with collections and list. What do you think is the most efficient (or simplest) way to calculate a moving average?
Kotlin 1.2 will introduce a sliding window which you can combine with average obviously.
val data = listOf(1,2,5,6,2,7,8,5,9)
// 3 "period" moving average
val movingAverage = data.windowed(3,1,List<Int>::average)
// OR
val movingAverage = data.windowed(3,1) { it.average() }
Until then you would have to introduce your own sliding sequence.
class SlidingSequence<out T>(val source: Iterable<T>,
val slideSize: Int,
val slideStep: Int) : Sequence<List<T>> {
override fun iterator(): Iterator<List<T>> = object : AbstractIterator<List<T>>() {
private val iterator = if (slideSize > 0) source.iterator() else emptyList<T>().iterator()
private var buffer = listOf<T>()
override fun computeNext() = when {
iterator.hasNext() -> {
buffer = buffer.drop(slideStep).let {
it + iterator.asSequence().take(slideSize - it.size)
}
setNext(buffer)
}
else -> done()
}
}
}
fun <T> Iterable<T>.windowed(size: Int,
step: Int = 1): Sequence<List<T>> {
return SlidingSequence(this, size, step)
}
// and then you can do
val data = listOf(1,2,5,6,2,7,8,5,9)
// 3 "period" moving average
val movingAverage = data.windowed(3).map(List<Int>::average)
PS. I haven't looked at the code of Kotlin 1.2 windowed implementation, but since the function takes an immediate transform, I'm guessing the result is not lazy, where in the self implemented case above it's a lazy result, so you need to actually enumerate the sequence with something like .toList() to get the actual values.
Another one-line given period > 0 is:
data?.takeLast(period)?.reduce { v, d -> v + d}?: 0 / period
This also works if the data is empty or null due to takeLast() `s behaviour.