I am trying to create Observable in kotlin .But its giving error unresolved reference on OnSubscribe method
fun getDisposableObserver(): Observable<Background> {
return Observable.create(object :Observable.OnSubscribe<Background> ->{})
}
I tried this snippet .It also does not work
Observable.create(object : ObservableOn.OnSubscribe<Int> {
override fun call(subscriber: Subscriber<in Int>) {
for(i in 1 .. 5)
subscriber.onNext(i)
subscriber.onCompleted()
}
})
What i am doing wrong ?,How can i create Observable?
If you want to control the items emission by your self, you can create an Observable with .create method, like this
Observable.create({ e: ObservableEmitter<String> -> e.onNext("") })
Observable.create(object: ObservableOnSubscribe<String> {
override fun subscribe(e: ObservableEmitter<String>) {
e.onNext("")
}
})
But in this case you will have to call onNext, onComplete, onError by your own.
But if you want a much simpler solution, you can create it like
Observable.just(1)
Observable.fromCallable { 1 }
Simple example with ObservableEmitter
val obs = Observable.create<Int> {
for(i in 1 .. 5)
it.onNext(i)
it.onComplete()
}
I don't know if you are using Architecture Components, but if i want to observe Background i think that MutableLiveData and LiveData could help
private val _background = MutableLiveData<Background>()
val background: LiveData<Background> = _background
fun editBackground(newBackground : Background) {
_background.postValue(newBackground)
}
Put this code inside a ViewModel or a Presenter.
Then in your View (Activity/Fragment) you can observe background in this way
viewModel.background.observe(this, Observer { newValue ->
})
Related
Fragment
private fun makeApiRequest() {
vm.getRandomPicture()
var pictureElement = vm.setRandomPicture()
GlobalScope.launch(Dispatchers.Main) {
// what about internet
if (pictureElement != null && pictureElement!!.fileSizeBytes!! < 400000) {
Glide.with(requireContext()).load(pictureElement!!.url)
.into(layout.ivRandomPicture)
layout.ivRandomPicture.visibility = View.VISIBLE
} else {
getRandomPicture()
}
}
}
viewmodel
fun getRandomPicture() {
viewModelScope.launch {
getRandomPictureItemUseCase.build(Unit).collect {
pictureElement.value = it
Log.d("inspirationquotes", "VIEWMODEL $pictureElement")
Log.d("inspirationquotes", "VIEWMODEL VALUE ${pictureElement.value}")
}
}
}
fun setRandomPicture(): InspirationQuotesDetailsResponse? {
return pictureElement.value
}
Flow UseCase
class GetRandomPictureItemUseCase #Inject constructor(private val api: InspirationQuotesApi): BaseFlowUseCase<Unit, InspirationQuotesDetailsResponse>() {
override fun create(params: Unit): Flow<InspirationQuotesDetailsResponse> {
return flow{
emit(api.getRandomPicture())
}
}
}
My flow task from viewmodel doesn't goes on time. I do not know how to achieve smooth downloading data from Api and provide it further.
I was reading I could use runBlocking, but it is not recommended in production as well.
What do you use in your professional applications to achieve nice app?
Now the effect is that that image doesn't load or I have null error beacause of my Log.d before GlobalScope in Fragment (it is not in code right now).
One more thing is definding null object I do not like it, what do you think?
var pictureElement = MutableStateFlow<InspirationQuotesDetailsResponse?>(null)
EDIT:
Viewmodel
val randomPicture: Flow<InspirationQuotesDetailsResponse> = getRandomPictureItemUseCase.build(Unit)
fragment
private fun makeApiRequest() = lifecycleScope.launch {
vm.randomPicture
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect { response ->
if (response.fileSizeBytes < 600000) {
Log.d("fragment", "itGetsValue")
Glide.with(requireContext()).load(response.url)
.into(layout.ivRandomPicture)
layout.ivRandomPicture.visibility = View.VISIBLE
} else {
onFloatingActionClick()
}
}
}
Edit2 problem on production, another topic:
Link -> What is the substitute for runBlocking Coroutines in fragments and activities?
First of all, don't use GlobalScope to launch a coroutine, it is highly discouraged and prone to bugs. Use provided lifecycleScope in Fragment:
lifecycleScope.launch {...}
Use MutableSharedFlow instead of MutableStateFlow, MutableSharedFlow doesn't require initial value, and you can get rid of nullable generic type:
val pictureElement = MutableSharedFlow<InspirationQuotesDetailsResponse>()
But I guess we can get rid of it.
Method create() in GetRandomPictureItemUseCase returns a Flow that emits only one value, does it really need to be Flow, or it can be just a simple suspend function?
Assuming we stick to Flow in GetRandomPictureItemUseCase class, ViewModel can look something like the following:
val randomPicture: Flow<InspirationQuotesDetailsResponse> = getRandomPictureItemUseCase.build(Unit)
And in the Fragment:
private fun makeApiRequest() = lifecycleScope.launch {
vm.randomPicture
.flowWithLifecycle(lifecycle, State.STARTED)
.collect { response ->
// .. use response
}
}
Dependency to use lifecycleScope:
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
I have two tasks. Both load data to view model (eg. LoadDataList1UseCase and LoadDataList2UseCase).
When new fragment is started then data should be loaded in view model. But when any of load process finish then data fetched by it should be loaded to view (recycler view) but only when both finish then progress bar should be hidden.
I figure out some like below but doesn't work. What I miss? How look like correct approach?
class LoadDataList1UseCase {
operator fun invoke() = flow {
delay(3_000)
emit("a")
}
}
class LoadDataList2UseCase {
operator fun invoke() = flow { emit("b")}
}
//------------ method in view model:
suspend fun loadData() = withContext(Dispatchers.IO) {
loadDataList1
.onEatch { /*update screan*/}
loadDataList2
.onEatch { /*update screan*/}
}
and run it in runBlocking
I'm totally newbie in coroutine. In rx, I would be try mix combinedLatest and doOnComplite
You do it in a similar way to RxJava, it is even named combine() as well:
loadDataList1
.combine(loadDataList2, ::Pair)
.collect { (first, second) ->
/*update screen*/
}
You can use merge function to merge flows concurrently. In this case collect action lambda will be invoked when data is emitted in either of flows. In your ViewModel class:
class MyViewModel(...) : ViewModel() {
fun loadData() = viewModelScope.launch {
merge(loadDataList1(), loadDataList2())
.collect {
// update UI (RecyclerView)
}
// both flows are finished, hide progress bar here
}
}
There is also combine function (not extension function) that accepts Flows to combine and transform block, it defined like the following:
fun <T1, T2, R> combine(flow: Flow, flow2: Flow, transform: suspend (T1, T2) -> R): Flow
You can use it in your ViewModel class:
class MyViewModel(...) : ViewModel() {
init {
combine(
loadDataList1(),
loadDataList2()
) { result1, result2 ->
// use result1 and result2
// hide progress
}.launchIn(viewModelScope) // Terminal flow operator that launches the collection of the given flow in the scope. It is a shorthand for scope.launch { flow.collect() }.
}
}
The above approach combines Flows and invokes transform with result1 and result2 params only when both are available.
What I've tried so far
fun getCPByID(ids: List<Int>): List<CheckingPointVo> {
var list : List<CheckingPointVo> = emptyList()
coroutineScope.launch {
list = someMethod()
}
return list
}
here I tried to use async and await but that cannot be run from a non suspend function. Is there a way to do this ?
Not really with the current structure, you're basically trying to combine synchronous code with async.
You have 3 possible options though to make it async:
Use a callback:
fun getCPByID(ids: List<Int>, listCallback: (List<CheckingPointVo>) -> Unit) {
coroutineScope.launch {
listCallback(someMethod())
}
}
Note: If you're using it from Java, this should work with either Java lambdas or Function. But you may create an interface for this, like :
Interface ListCallback {
fun onListReceived(list: List<CheckingPointVo>)
}
fun getCPByID(ids: List<Int>, listCallback: ListCallback) {
.... // Same implementation
}
// Call it from Java
getCPByID(ids, new ListCallback() {
void onListReceived(List<CheckingPointVo> list) {
...
}
});
Use either an observable pattern, use a Flow or LiveData. A possible example:
fun getCPByID(ids: List<Int>) = coroutineScope.launch {
flow {
emit(someMethod())
}
}
}
Make your function a suspend function and use coroutineScope.launch from the caller
I am using MVVM, LiveData and trying and implement Repository pattern.
But, calling a method in my repository class - RegisterRepo which returns LiveData is not working. I have no idea why. Any suggestions would be greatly appreciated.
Boilerplate code is removed for breivity.
Activity' s onCreateMethod
mViewModel.status.observe(this, Observer {
when (it) {
true -> {
Log.d("----------", " true ") //These message is never being printed.
}
false -> {
Log.d("----------", "false ") //These message is never being printed.
}
}
})
button.setOnClickListener {
mViewModel.a()
}
ViewModel
class AuthViewModel (val repo: RegisterRepo): ParentViewModel() {
//...
var status = MutableLiveData<Boolean>()
fun a() {
status = repo.a()
}
}
RegisterRepo
class RegisterRepo () {
fun a(): MutableLiveData<Boolean> {
var result = MutableLiveData<Boolean>()
result.value = true
return result
}
}
However, if I change my code in ViewModel to this, everything is working fine.
ViewModel
class AuthViewModel (val repo: RegisterRepo): ParentViewModel() {
//...
var status = MutableLiveData<Boolean>()
fun a() {
status.value = true //Change here causing everything work as expected.
}
}
In the first ViewModel code, when method a is called, you assign another LiveData to status variable, this live data is different from the one observed by the Activity, so that the value won't be notify to your Activity
the 2nd way is correct to use and it will work fine the 1st is not working because you are creating new MutableLive data in your RegisterRepo, so basically at the time your create an observable to "status" is deferent where you assign a value into it is different. so the second one is the only way to do this
I'm trying to create a Flow that needs to emit values from a callback but I can't call the emit function since the SAM is a normal function
Here's the class with the SAM from a library that I can't really modify it the way I need it to be.
class ValueClass {
fun registerListener(listener: Listener) {
...
}
interface Listener {
fun onNewValue(): String
}
}
And here's my take on creating the Flow object
class MyClass(private val valueClass: ValueClass) {
fun listenToValue = flow<String> {
valueClass.registerListener { value ->
emit(value) // Suspension functions can only be called on coroutine body
}
}
}
I guess it would've been simple if I could change the ValueClass but in this case, I can't. I've been wrapping my head around this and trying to look for implementations.
At least from what I know so far, one solution would be to use GlobalScope like this
class MyClass(private val valueClass: ValueClass) {
fun listenToValue = flow<String> {
valueClass.registerListener { value ->
GlobalScope.launch {
emit(value)
}
}
}
}
Now, this works but I don't want to use GlobalScope since I'll be using viewModelScope to tie it to my app's lifecycle.
Is there any way to work around this?
Thanks in advance. Any help would be greatly appreciated!
You can use callbackFlow to create a Flow from the callback. It will look something like:
fun listenToValue(): Flow<String> = callbackFlow {
valueClass.registerListener { value ->
trySend(value)
channel.close() // close channel if no more values are expected
}
awaitClose { /*unregister listener*/ }
}
Or if only one value is expected from the callback, you can use suspendCoroutine or suspendCancellableCoroutine. It this case listenToValue() function must be suspend and later called from a coroutine(e.g. someScope.launch):
suspend fun listenToValue(): String = suspendCoroutine { continuation ->
valueClass.registerListener { value ->
continuation.resumeWith(value)
}
}