kotlin getting a subscriber to observe an observable using RxJava2 - kotlin

Android Studio 3.0 Beta2
I have created 2 methods one that creates the observable and another that creates the subscriber.
However, I am having a issue try to get the subscriber to subscribe to the observable. In Java this would work, and I am trying to get it to work in Kotlin.
In my onCreate(..) method I am trying to set this. Is this the correct way to do this?
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/* CANNOT SET SUBSCRIBER TO SUBCRIBE TO THE OBSERVABLE */
createStringObservable().subscribe(createStringSubscriber())
}
fun createStringObservable(): Observable<String> {
val myObservable: Observable<String> = Observable.create {
subscriber ->
subscriber.onNext("Hello, World!")
subscriber.onComplete()
}
return myObservable
}
fun createStringSubscriber(): Subscriber<String> {
val mySubscriber = object: Subscriber<String> {
override fun onNext(s: String) {
println(s)
}
override fun onComplete() {
println("onComplete")
}
override fun onError(e: Throwable) {
println("onError")
}
override fun onSubscribe(s: Subscription?) {
println("onSubscribe")
}
}
return mySubscriber
}
}
Many thanks for any suggestions,

pay close attention to the types.
Observable.subscribe() has three basic variants:
one that accepts no arguments
several that accept an io.reactivex.functions.Consumer
one that accepts an io.reactivex.Observer
the type you're attempting to subscribe with in your example is org.reactivestreams.Subscriber (defined as part of the Reactive Streams Specification). you can refer to the docs to get a fuller accounting of this type, but suffice to say it's not compatible with any of the overloaded Observable.subscribe() methods.
here's a modified example of your createStringSubscriber() method that will allow your code to compile:
fun createStringSubscriber(): Observer<String> {
val mySubscriber = object: Observer<String> {
override fun onNext(s: String) {
println(s)
}
override fun onComplete() {
println("onComplete")
}
override fun onError(e: Throwable) {
println("onError")
}
override fun onSubscribe(s: Disposable) {
println("onSubscribe")
}
}
return mySubscriber
}
the things changed are:
this returns an Observer type (instead of Subscriber)
onSubscribe() is passed a Disposable (instead of Subscription)
.. and as mentioned by 'Vincent Mimoun-Prat', lambda syntax can really shorten your code.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Here's an example using pure RxJava 2 (ie not using RxKotlin)
Observable.create<String> { emitter ->
emitter.onNext("Hello, World!")
emitter.onComplete()
}
.subscribe(
{ s -> println(s) },
{ e -> println(e) },
{ println("onComplete") }
)
// ...and here's an example using RxKotlin. The named arguments help
// to give your code a little more clarity
Observable.create<String> { emitter ->
emitter.onNext("Hello, World!")
emitter.onComplete()
}
.subscribeBy(
onNext = { s -> println(s) },
onError = { e -> println(e) },
onComplete = { println("onComplete") }
)
}
i hope that helps!

Have a look at RxKotlin, that will simplify a lot of things and make code more concise.
val list = listOf("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
list.toObservable() // extension function for Iterables
.filter { it.length >= 5 }
.subscribeBy( // named arguments for lambda Subscribers
onNext = { println(it) },
onError = { it.printStackTrace() },
onComplete = { println("Done!") }
)

val observer = object: Observer<Int> {
override fun onNext(t: Int) {
// Perform the value of `t`
}
override fun onComplete() {
// Perform something on complete
}
override fun onSubscribe(d: Disposable) {
// Disposable provided
}
override fun onError(e: Throwable) {
// Handling error
}
}

Related

Is it possible to stop the flow 's collection from collect's code block?

I am a newbie in coroutine/flow and would like to know the appropriate way to close the flow from the collect's code block when it gets the value it wanted.
The code like this:
suspend fun findService(scope:CoroutineScope, context:Context, name:String) {
val flow = getWifiDebuggingConnectDiscoveryFlow( context )
try {
flow.collect {
if(name == it.serviceName) {
/* need to exit the collection and execute the code that follows */
}
}
println("service found!")
} catch(e: Throwable) {
println("Exception from the flow: $e")
}
/* need to do something after service found */
}
private fun getWifiDebuggingConnectDiscoveryFlow(context:Context) = callbackFlow {
val nsdManager:NsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
val listener = object : NsdManager.DiscoveryListener {
override fun onStartDiscoveryFailed(serviceType: String?, errorCode: Int) {cancel("onStartDiscoveryFailed")}
override fun onStopDiscoveryFailed(serviceType: String?, errorCode: Int) {cancel("onStopDiscoveryFailed")}
override fun onDiscoveryStarted(serviceType: String?) {}
override fun onDiscoveryStopped(serviceType: String?) {}
override fun onServiceLost(serviceInfo: NsdServiceInfo?) {}
override fun onServiceFound(serviceInfo: NsdServiceInfo?) {
if(serviceInfo==null) return
trySend(serviceInfo)
}
}
nsdManager.discoverServices(ServiceDiscovery.ADB_CONNECT_TYPE, NsdManager.PROTOCOL_DNS_SD, listener)
awaitClose { nsdManager.stopServiceDiscovery(listener) }
}
This problem has been bothering me for a long time, and I would appreciate any help I get.
You can use the first or firstOrNull operators. It will stop collecting as soon as the first element that complies the condition is received:
val service = flow.firstOrNull { name == it.serviceName }
...
You can find first official documentation here

How to handle Kotlin Jetpack Paging 3 exceptions?

I am new to kotlin and jetpack, I am requested to handle errors (exceptions) coming from the PagingData, I am not allowed to use Flow, I am only allowed to use LiveData.
This is the Repository:
class GitRepoRepository(private val service: GitRepoApi) {
fun getListData(): LiveData<PagingData<GitRepo>> {
return Pager(
// Configuring how data is loaded by adding additional properties to PagingConfig
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false
),
pagingSourceFactory = {
// Here we are calling the load function of the paging source which is returning a LoadResult
GitRepoPagingSource(service)
}
).liveData
}
}
This is the ViewModel:
class GitRepoViewModel(private val repository: GitRepoRepository) : ViewModel() {
private val _gitReposList = MutableLiveData<PagingData<GitRepo>>()
suspend fun getAllGitRepos(): LiveData<PagingData<GitRepo>> {
val response = repository.getListData().cachedIn(viewModelScope)
_gitReposList.value = response.value
return response
}
}
In the Activity I am doing:
lifecycleScope.launch {
gitRepoViewModel.getAllGitRepos().observe(this#PagingActivity, {
recyclerViewAdapter.submitData(lifecycle, it)
})
}
And this is the Resource class which I created to handle exceptions (please provide me a better one if there is)
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
companion object {
fun <T> success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(msg: String, data: T?): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> loading(data: T?): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
As you can see I am using Coroutines and LiveData. I want to be able to return the exception when it occurs from the Repository or the ViewModel to the Activity in order to display the exception or a message based on the exception in a TextView.
Your GitRepoPagingSource should catch retryable errors and pass them forward to Paging as a LoadResult.Error(exception).
class GitRepoPagingSource(..): PagingSource<..>() {
...
override suspend fun load(..): ... {
try {
... // Logic to load data
} catch (retryableError: IOException) {
return LoadResult.Error(retryableError)
}
}
}
This gets exposed to the presenter-side of Paging as LoadState, which can be reacted to via LoadStateAdapter, .addLoadStateListener, etc as well as .retry. All of the presenter APIs from Paging expose these methods, such as PagingDataAdapter: https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter
You gotta pass your error handler to the PagingSource
class MyPagingSource(
private val api: MyApi,
private val onError: (Throwable) -> Unit,
): PagingSource<Int, MyModel>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, YourModel> {
try {
...
} catch(e: Exception) {
onError(e) // <-- pass your error listener here
}
}
}

Kotlin Coroutine Unit Test Flow collection with viewModelScope

I want to test a method of my ViewModel that collects a Flow. Inside the collector a LiveData object is mutated, which I want to check in the end. This is roughly how the setup looks:
//Outside viewmodel
val f = flow { emit("Test") }.flowOn(Dispatchers.IO)
//Inside viewmodel
val liveData = MutableLiveData<String>()
fun action() {
viewModelScope.launch { privateAction() }
}
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
When I now call the action() method in my unit test, the test finishes before the flow is collected. This is how the test might look:
#Test
fun example() = runBlockingTest {
viewModel.action()
assertEquals(viewModel.liveData.value, "Test")
}
I am using the TestCoroutineDispatcher via this Junit5 extension and also the instant executor extension for LiveData:
class TestCoroutineDispatcherExtension : BeforeEachCallback, AfterEachCallback, ParameterResolver {
#SuppressLint("NewApi") // Only used in unit tests
override fun supportsParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Boolean {
return parameterContext?.parameter?.type === testDispatcher.javaClass
}
override fun resolveParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Any {
return testDispatcher
}
private val testDispatcher = TestCoroutineDispatcher()
override fun beforeEach(context: ExtensionContext?) {
Dispatchers.setMain(testDispatcher)
}
override fun afterEach(context: ExtensionContext?) {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance()
.setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
override fun postToMainThread(runnable: Runnable) = runnable.run()
override fun isMainThread(): Boolean = true
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
You can try either,
fun action() = viewModelScope.launch { privateAction() }
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
#Test
fun example() = runBlockingTest {
viewModel.action().join()
assertEquals(viewModel.liveData.value, "Test")
}
or
fun action() {
viewModelScope.launch { privateAction()
}
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
#Test
fun example() = runBlockingTest {
viewModel.action()
viewModel.viewModelScope.coroutineContext[Job]!!.join()
assertEquals(viewModel.liveData.value, "Test")
}
You could also try this,
suspend fun <T> LiveData<T>.awaitValue(): T? {
return suspendCoroutine { cont ->
val observer = object : Observer<T> {
override fun onChanged(t: T?) {
removeObserver(this)
cont.resume(t)
}
}
observeForever(observer)
}
}
#Test
fun example() = runBlockingTest {
viewModel.action()
assertEquals(viewModel.liveData.awaitValue(), "Test")
}
So what I ended up doing is just passing the Dispatcher to the viewmodel constructor:
class MyViewModel(..., private val dispatcher = Dispatchers.Main)
and then using it like this:
viewModelScope.launch(dispatcher) {}
So now I can override this when I instantiate the ViewModel in my test with a TestCoroutineDispatcher and then advance the time, use testCoroutineDispatcher.runBlockingTest {}, etc.

subscribing to an observable in the chain and setting the observer to the observable

kotlin 1.2.60
rxJava 2
I have the following code snippet below. I am wondering what is the difference in using the subscribe that is chained to the end of flatmap and printing the results or creating an observer and subscribing to the Observable.
For some reason I was expecting to get the same results. But when I print out the result in the onNext it displays the complete emitted item.
However, for the chained on subscribe it displays what I would expect.
fun main(args: Array<String>) {
val source2 = Observable.just("521934/2342/FOXTROT", "21962/12112/78886/TANGO", "283242/4542/WHISKEY/2348562")
source2.flatMap {
Observable.fromArray(*it.split("/").toTypedArray())
}
.subscribe { println(it) }
val observer = object : Observer<String> {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: String) {
println(t)
}
override fun onError(e: Throwable) {
}
}
source2.subscribe(observer)
}
The output is below:
from the chained subscribe:
521934
2342
FOXTROT
21962
12112
78886
TANGO
283242
4542
WHISKEY
2348562
from onNext:
521934/2342/FOXTROT
21962/12112/78886/TANGO
283242/4542/WHISKEY/2348562
source2 is an immutable collection. You are observing it twice. You should capture the result of the flatmap in a new variable and then observe that.
fun main(args: Array<String>) {
val source2 = Observable.just("521934/2342/FOXTROT", "21962/12112/78886/TANGO", "283242/4542/WHISKEY/2348562")
val source3 = source2.flatMap {
Observable.fromArray(*it.split("/").toTypedArray())
}
source3.subscribe { println(it) }
val observer = object : Observer<String> {
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: String) {
println(t)
}
override fun onError(e: Throwable) {
}
}
source3.subscribe(observer)
}

kotlin, got “Type mismatch. Required: Disposable? Found: Unit” when using observer object instance in the subscribe()

Edit:
based on Dmitry Ikryanov's suggestion,
using DisposableObserver will compile, but it causes crash
io.reactivex.exceptions.ProtocolViolationException: It is not allowed to
subscribe with a(n) com.DataManager$theObserver$1 multiple times. Please
create a fresh instance of com.DataManager$theObserver$1 and subscribe that
to the target source instead.
the only code of subecribWith(), which has been called only once
fun initSession() {
if (mDisposable != null && mDisposable!!.isDisposed) {
mDisposable!!.dispose()
}
mDisposable = RxBus.listen(DataEvent::class.java).subscribeWith(theObserver) <=== crash at here
}
the DisposableObserver is a member variable of the class:
var theObserver: DisposableObserver<DataEvent> = object : DisposableObserver<DataEvent>() {
override fun onComplete() {
Log.e(TAG, "onComplete: All Done!") }
override fun onNext(t: DataEvent) {
Log.e(TAG, "Next: " + t)
onDataReady(t) }
override fun onError(e: Throwable) {
Log.e(TAG, "onError: ")
}
}
===
Original question:
trying to use RxJava subscribe() in kotlin, get an error “Type mismatch. Required: Disposable? Found: Unit”, not sure what it means, anyone knows?
class DataEvent {}
using RxBus
object RxBus {
private val publisher = PublishSubject.create<Any>()
fun publish(event: Any) {
publisher.onNext(event)
}
// Listen should return an Observable and not the publisher
// Using ofType we filter only events that match that class type
fun <T> listen(eventType: Class<T>): Observable<T> = publisher.ofType(eventType)
}
when call like this, it is ok:
mDisposable = RxBus.listen(DataEvent::class.java).subscribe({
onDataReady(it)
})
but when call the RxBus.listen(DataEvent::class.java).subscribe(observer) with defined observer instance
it shows red underline: “Type mismatch. Required: Disposable? Found: Unit”
mDisposable = RxBus.listen(DataEvent::class.java).subscribe(observer)
the observer is:
var observer: Observer<DataEvent> = object : Observer<DataEvent> {
override fun onSubscribe(d: Disposable) {
Log.e(TAG, "onSubscribe: ")
}
override fun onNext(#NonNull t: DataEvent) {
Log.e(TAG, "onNext: " + t)
onDataReady(t)
}
override fun onError(e: Throwable) {
Log.e(TAG, "onError: ")
}
override fun onComplete() {
Log.e(TAG, "onComplete: All Done!")
}
}
It's because in RxJava 2.0 method subscribe(observer) was changed and return nothing.
Unlike the Observable of version 1.x, subscribe(Observer) does not allow external cancellation of a subscription and the Observer instance is expected to expose such capability.
You can use subscribeWith(observer).
Example:
val disposable = Observable.just("Hello world!")
.delay(1, TimeUnit.SECONDS)
.subscribeWith(object : DisposableObserver<String>() {
public override fun onStart() {
println("Start!")
}
fun onNext(t: Int?) {
println(t)
}
override fun onError(t: Throwable) {
t.printStackTrace()
}
override fun onComplete() {
println("Done!")
}
})