How shorten Kotlin StateFlow code with Generics? - kotlin

I have this:
val navigateToMainFragmentEvent: StateFlow<State<Event<Boolean>>>
if (navigateToMainFragmentEvent.collectAsState().value is State.TriggerState) {
(viewModel.navigateToMainFragmentEvent.collectAsState().value
as State.TriggerState).data.getContentIfNotHandled()
?.let {
if (it) {
Timber.tag("Nurs").d("collect as state ")
navController.popBackStack()
navController.navigate(MAIN_SCRENN)
}
}
}
is it possible to shorten with generics the if statement?

val state = navigateToMainFragmentEvent.value
if (state is State.TriggerState) {
state.data.getContentIfNotHandled()?.let {
// do sth
}
}
As an advice: You can define ifNotHandled method with a lambda argument in your Event class to more shortening:
fun ifNotHandled(callback: () -> T) {
if (!hasBeenHandled) {
hasBeenHandled = true
callback.invoke(content)
}
}
val state = navigateToMainFragmentEvent.value
if (state is State.TriggerState) {
state.data.ifNotHandled {
// do sth
}
}

Related

MVVM Respository

i have a error on code that says
overload resolution ambiguity. all these functions match
class MovieRespository (val apiService: ApiService, val movieDao: MovieDao) {
fun getListMovie() = movieDao.streamAll()
.onErrorResumeNext{
apiService.getMyMovie()
.doOnSuccess {
if (it.results.isEmpty()){
}else{
movieDao.deleteAll()
it.results.let {
Timber.d("input data")
val semuadata = it.map { data -> Movie.from(data) }
movieDao.insert(semuadata)
}
}
}
}
}
also there something like this in my error
enter image description here
You simply have to specify the parameter you take in onErrorResumeNext:
i have a error on code that says
overload resolution ambiguity. all these functions match
class MovieRespository (val apiService: ApiService, val movieDao: MovieDao) {
fun getListMovie() = movieDao.streamAll()
.onErrorResumeNext{ next: Publisher<List<Movie>> ->
apiService.getMyMovie()
.doOnSuccess {
if (it.results.isEmpty()){
}else{
movieDao.deleteAll()
it.results.let {
Timber.d("input data")
val semuadata = it.map { data -> Movie.from(data) }
movieDao.insert(semuadata)
}
}
}
}
}

How to subscribe to StateFlow in kotlin-react useEffect

I'm trying to create a small counter example for kotlin-react with functionalComponent with kotlin 1.4-M2.
The example should use kotlinx.coroutines.flow. I'm struggling at collecting the values from the store in reacts useEffect hook.
Store:
object CounterModel { // Modified sample from kotlin StateFlow doc
private val _counter = MutableStateFlow(0) // private mutable state flow
val counter: StateFlow<Int> get() = _counter // publicly exposed as read-only state flow
fun inc() { _counter.value++ }
}
Component:
val counter = functionalComponent<RProps> {
val (counterState, setCounter) = useState(CounterModel.counter.value)
useEffect(listOf()) {
// This does not work
GlobalScope.launch { CounterModel.counter.collect { setCounter(it) } }
}
div {
h1 {
+"Counter: $counterState"
}
button {
attrs.onClickFunction = { CounterModel.inc() }
}
}
}
When I directly call CounterModel.counter.collect { setCounter(it) } it complains about Suspend function 'collect' should be called only from a coroutine or another suspend function.
How would you implement this useEffect hook?
And once the subscription works, how would you unsubscribe from it (use useEffectWithCleanup instead of useEffect)?
Finally found a solution. We can use onEach to do an action for every new value and then 'subscribe' with launchIn. This returns a job that can be canceled for cleanup:
object CounterStore {
private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> get() = _counter
fun inc() { _counter.value++ }
}
val welcome = functionalComponent<RProps> {
val (counter, setCounter) = useState(CounterStore.counter.value)
useEffectWithCleanup(listOf()) {
val job = CounterStore.counter.onEach { setCounter(it) }.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
div {
+"Counter: $counter"
}
button {
attrs.onClickFunction = { CounterStore.inc() }
+"Increment"
}
}
We can extract this StateFlow logic to a custom react hook:
fun <T> useStateFlow(flow: StateFlow<T>): T {
val (state, setState) = useState(flow.value)
useEffectWithCleanup(listOf()) {
val job = flow.onEach { setState(it) }.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
return state
}
And use it like this in our component:
val counter = useStateFlow(CounterStore.counter)
The complete project can be found here.
The Flow-Api is very experimental so this might not be the final solution :)
if's very important to check that the value hasn't changed,
before calling setState, otherwise the rendering happens twice
external interface ViewModelProps : RProps {
var viewModel : MyViewModel
}
val App = functionalComponent<ViewModelProps> { props ->
val model = props.viewModel
val (state, setState) = useState(model.stateFlow.value)
useEffectWithCleanup {
val job = model.stateFlow.onEach {
if (it != state) {
setState(it)
}
}.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
}

Idiomatic Arrow

I have the following method:
internal typealias MaybeError<T> = Either<GenericError, T>
override fun createCompany(companyDomain: CompanyDomain): MaybeError<CompanyDomain> =
checkCompany(companyDomain).map { it.toEntity() }.fold({ Either.left(it) }) { company ->
with (companyRepository) {
isCompanyExists(company).fold({ Either.left(it) }) { isExists ->
if (isExists) return#with Either.left(CompanyNameExists(companyDomain))
createCompany(company).fold({ Either.right(companyDomain) }) { Either.left(it) }
}
}
}
Is there a better/more idiomatic way to write this using Arrow?
It is hard to refactor because I can only assume what used methods should return. But I guess the methods returns MaybeError. In this case we can omit fold({ Either.left(it) }) and we can use map or flatMap.
internal typealias MaybeError<T> = Either<GenericError, T>
override fun createCompany(companyDomain: CompanyDomain): MaybeError<CompanyDomain> =
checkCompany(companyDomain)
.map { it.toEntity() }
.flatMap { company ->
companyRepository.isCompanyExists(company)
.flatMap { isExists ->
if (isExists) {
MaybeError.left(CompanyNameExists(companyDomain))
} else {
companyRepository.createCompany(company)
}
}
}

Single with flowable?

Try in rxJava2 Kotlin combine Single with Flowable but nothing not happening:
Does not undrstand what wrong
Flowable.create<Int>({ emmit ->
loadNewListener = object :Listener {
override fun onEmit(id: Int) {
emmit.onNext(id)
}
}
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.flatMapSingle {
loadNew(id = it.id)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ (data:Data) ->
}, {
Timber.e("Failed load data ${it.message}")
})
my method is returning Single:
private fun loadNew(id: Int): Single<Data> {
return when (pdfType) {
CASE_0 -> {
Single.create<Data> { emmit ->
service.get("data")
.enqueue(
object : Callback<Void> {
override fun onFailure(call: Call<Void>?, t: Throwable?) {
// failure
}
override fun onResponse(call: Call<Void>?, response: Response<Void>?) {
emmit.onSuccess(it.data)
}
}
}//single
}//case_0
CASE_1 -> 1Repository.loadsome1Rx(id = id).map { it.getData() }
CASE_2 -> 2Repository.loadsom2LocalRx(id = id).map { it.getData() }
else -> {
throw java.lang.RuntimeException("$this is not available type!")
}
}
What is wrong im my code?
Need Maby call Single in Flowable subscribe() seppurate
like this?
Flowable.create<Int>({ emmit ->
loadNewListener = object :Listener {
override fun onEmit(id: Int) {
emmit.onNext(id)
}
}
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe({
loadNew(id = it.id)
}, {
Timber.e("")
})
This code is workin but looks not simple as via combine try.
This simple example based on your code is working
var i = 0
fun foo() {
Flowable.create<Int>({ emmit ->
emmit.onNext(i)
i++
}, BackpressureStrategy.LATEST)
.debounce(500, TimeUnit.MILLISECONDS)
.flatMapSingle {
Single.create<String> { emmit ->
emmit.onSuccess("onSuccess: $it")
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.i("RX", "Subscribe: $it")
}, {
it.printStackTrace()
})
}
Check SingleEmitter.onSuccess() and SingleEmitter.onError() is called in all cases in when (pdfType)...
As #Stas Bondar said in answer below This simple example based on your code is working!!
Problem was in loadNewListener .
It does not init in time and has null value when need. Call create Flowable on init ViewModel but loadNewListener did not have time to create when i call him from fragment.
loadNewListener = object :Listener{...}
Becuse need some time mutch for init rxJava expression!
And combine flowable with single via flatMapSingle spent more time than just call single on flowable dubscrinbe!
So use temp field:
private var temp: Temp? = null
fun load(id: Int) {
loadNewListener.apply {
when {
this != null -> load(id = id)
else -> userEmitPdfTemp = Temp(id = id)
}
}
}
Flowable.create<Data>({ emmit ->
userEmitPdfTemp?.let {id->
emmit.onNext(Data(id))
userEmitPdfTemp =null
}
loadNewListener = object :Listener {
override fun load(id: Int) {
emmit.onNext(Data(id))
}
}
}

Convert Observable of one type to another

I have the following code
class CurrencyRepository #Inject constructor(val apiInterface: ApiInterface,
val ratesDao: RatesDao) {
fun getRates(): Observable<List<Rates>> {
val observableFromApi = getCurrencyFromApi()
val observableFromDb = getRatesFromDb()
return Observable.concatArrayEager(observableFromApi , observableFromDb)
}
private fun getCurrencyFromApi(): Observable<Currency> {
return apiInterface.getRates()
.doOnNext {
Timber.i(it.toString())
val map = it.rates
val keys = map.keys
for (key in keys) {
ratesDao.insertRate(Rates(key , map.get(key)))
}
}
}
private fun getRatesFromDb(): Observable<List<Rates>> {
return ratesDao.getAllRates()
.toObservable()
.doOnNext {
for (rate in it) {
Timber.i("Repository DB ${it.size}")
}
}
}
}
In getCurrencyFromApi(), getRates() returns me an Observable<Currency>. I would like this particular function to return Observable<List<Rates>> so that I can use it in Observable.concatArrayEager inside getRates() of CurrencyRepository
Currency contains a Map object which can be transformed into a List object. I am not clear on how to do that inside getCurrencyFromApi()
One of possible solutions is
fun getRatesFromApi(): Observable<List<Rates>> {
return apiInterface.getRates()
.flatMapIterable { it.rates.entries }
.map { Rates(it.key ,it.value) }
.doOnNext { ratesDao.insertRate(it) }
.toList()
.toObservable()
}
I advise you insert items in database in one batch, because it will be more efficient.