How use the coroutine within extend function in the latest kotlin-couroutine - kotlin

In the example: kotlin-examples/coroutines/src/main/kotlin/movierating/App.kt
There is the flowing code:
fun Route.coroutineHandler(fn: suspend (RoutingContext) -> Unit) {
handler { ctx ->
launch(ctx.vertx().dispatcher()) {
try {
fn(ctx)
} catch (e: Exception) {
ctx.fail(e)
}
}
}
}
In the latest kotlin-coroutine,to invoke launch must depend on a CoroutineScope;
So the launch can't be invoked in the extend function Route.coroutineHandler() ;
If always use GlobalScope.launch() to start couroutine,how manage the life-cycle properly?
So I use the flowing method:
interface SuspendHandler<E>: Handler<E>,CoroutineScope {
override fun handle(event: E) {
launch {
suspendHandle(event)
}
}
suspend fun suspendHandle(event: E)
}
fun <E> vertxSuspendHandler(vertx: Vertx = getDefaultVertx(),
block:suspend CoroutineScope.(E)->Unit): SuspendHandler<E>{
return object: SuspendHandler<E> {
override val coroutineContext: CoroutineContext
get() = vertx.dispatcher()
override suspend fun suspendHandle(event: E) {
block(event)
}
}
}
I don't know how use extend function in the latest coroutine api;

You can achieve that by adding the following extension:
fun Route.suspendHandler(requestHandler: suspend (RoutingContext) -> Unit) {
handler { ctx ->
CoroutineScope(ctx.vertx().dispatcher()).launch {
requestHandler(ctx)
}.invokeOnCompletion {
it?.run { ctx.fail(it) }
}
}
}
You can place this extension anywhere in the code.

Related

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
}
}
}

vertx-lang-kotlin-coroutines test failed

I have been writing a test class:
class TestVerticle {
#BeforeEach
fun deploy_verticle(vertx: Vertx, testContext: VertxTestContext) {
vertx.deployVerticle(Verticle(), testContext.completing())
}
#Test
fun test(vertx: Vertx, testContext: VertxTestContext) {
testContext.verify {
GlobalScope.launch(vertx.dispatcher()) {
val reply = vertx.eventBus().requestAwait<Long>(AVIOEXTDMZAddr, "1")
assert(reply.body() == 1010L)
testContext.completeNow()
}
}
}
}
If the method start() of Verticle is written in the "common" way, the Test is passed positively:
override suspend fun start() {
vertx.eventBus().consumer<String>(AVIOEXTDMZAddr){
it.reply(1010L)
}
}
Differently, if I implement a different solution, with the use of vertx-lang-kotlin-coroutines API, the test throws a java.util.concurrent.TimeoutException
override suspend fun start() {
val consumerChannel = vertx.eventBus().consumer<String>(AVIOEXTDMZAddr).toChannel(vertx)
for (msg in consumerChannel) {
msg.reply(1010L)
}
}
what am I doing wrong?
Loop on channel blocks the coroutine. In this case, it blocks start of your verticle.
Wrap your for loop in launch block:
async {
for (msg in consumerChannel) {
msg.reply(1010L)
}
}
}

Handling hundreds of routes in Vert.x best practices

Please have a look at the piece of code below. Now suppose i'll have hundreds of entity like "person". How would you code such a thing to get it clean, concise, efficient, well structured ? Tx
class HttpEntryPoint : CoroutineVerticle() {
private suspend fun person(r: RoutingContext) {
val res = vertx.eventBus().requestAwait<String>("/person/:id", "1").body()
r.response().end(res)
}
override suspend fun start() {
val router = Router.router(vertx)
router.get("/person/:id").coroutineHandler { ctx -> person(ctx) }
vertx.createHttpServer()
.requestHandler(router)
.listenAwait(config.getInteger("http.port", 8080))
}
fun Route.coroutineHandler(fn: suspend (RoutingContext) -> Unit) {
handler { ctx ->
launch(ctx.vertx().dispatcher()) {
try {
fn(ctx)
} catch (e: Exception) {
e.printStackTrace()
ctx.fail(e)
}
}
}
}
}
You're looking for subrouter.
https://vertx.io/docs/vertx-web/java/#_sub_routers
From the top of my head:
override suspend fun start() {
router.mountSubrouter("/person", personRouter(vertx))
// x100 if you'd like
}
Then in your PersonRouter.kt:
fun personRouter(vertx: Vertx): Router {
val router = Router.router(vertx)
router.get("/:id").coroutineHandler { ctx -> person(ctx) }
// More endpoints
return router
}

kotlin getting a subscriber to observe an observable using RxJava2

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
}
}

RxJava 2 overriding IO scheduler in unit test

I'm trying to test the following RxKotlin/RxJava 2 code:
validate(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { ... }
I'm attempting to override the schedulers as follows:
// Runs before each test suite
RxJavaPlugins.setInitIoSchedulerHandler { Schedulers.trampoline() }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
However, I get the following error when running the test:
java.lang.ExceptionInInitializerError
...
Caused by: java.lang.NullPointerException: Scheduler Callable result can't be null
at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
at io.reactivex.plugins.RxJavaPlugins.applyRequireNonNull(RxJavaPlugins.java:1317)
at io.reactivex.plugins.RxJavaPlugins.initIoScheduler(RxJavaPlugins.java:306)
at io.reactivex.schedulers.Schedulers.<clinit>(Schedulers.java:84)
Has anyone experienced this problem?
The test worked fine when using RxKotlin/RxJava 1 and the following scheduler overrides:
RxAndroidPlugins.getInstance().registerSchedulersHook(object : RxAndroidSchedulersHook() {
override fun getMainThreadScheduler() = Schedulers.immediate()
})
RxJavaPlugins.getInstance().registerSchedulersHook(object : RxJavaSchedulersHook() {
override fun getIOScheduler() = Schedulers.immediate()
})
I suggest you take a different approach and add a layer of abstraction to your schedulers. This guy has a nice article about it.
It would look something like this in Kotlin
interface SchedulerProvider {
fun ui(): Scheduler
fun computation(): Scheduler
fun trampoline(): Scheduler
fun newThread(): Scheduler
fun io(): Scheduler
}
And then you override that with your own implementation of SchedulerProvider:
class AppSchedulerProvider : SchedulerProvider {
override fun ui(): Scheduler {
return AndroidSchedulers.mainThread()
}
override fun computation(): Scheduler {
return Schedulers.computation()
}
override fun trampoline(): Scheduler {
return Schedulers.trampoline()
}
override fun newThread(): Scheduler {
return Schedulers.newThread()
}
override fun io(): Scheduler {
return Schedulers.io()
}
}
And one for testing classes:
class TestSchedulerProvider : SchedulerProvider {
override fun ui(): Scheduler {
return Schedulers.trampoline()
}
override fun computation(): Scheduler {
return Schedulers.trampoline()
}
override fun trampoline(): Scheduler {
return Schedulers.trampoline()
}
override fun newThread(): Scheduler {
return Schedulers.trampoline()
}
override fun io(): Scheduler {
return Schedulers.trampoline()
}
}
Your code would look like this where you call RxJava:
mCompositeDisposable.add(mDataManager.getQuote()
.subscribeOn(mSchedulerProvider.io())
.observeOn(mSchedulerProvider.ui())
.subscribe(Consumer<Quote> {
...
And you'll just override your implementation of SchedulerProvider based on where you test it. Here's a sample project for reference, I am linking the test file that would use the testable-version of SchedulerProvider: https://github.com/Obaied/DingerQuotes/blob/master/app/src/test/java/com/obaied/dingerquotes/QuotePresenterTest.kt#L31
Figured it out! It had to do with the fact that in this code:
validate(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { ... }
validate(data) was returning an Observable, which was emitting the following: emitter.onNext(null). Since RxJava 2 no longer accepts null values, flatMap was not getting called. I changed validate to return a Completable and updated the scheduler override to the following:
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
Now the tests pass!
As an alternative to proposed solutions, this has been working fine for a while in my projects.
You can use it in your test classes like this:
#get:Rule
val immediateSchedulersRule = ImmediateSchedulersRule()
And the class looks like this:
class ImmediateSchedulersRule : ExternalResource() {
val immediateScheduler: Scheduler = object : Scheduler() {
override fun createWorker() = ExecutorScheduler.ExecutorWorker(Executor { it.run() })
// This prevents errors when scheduling a delay
override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable {
return super.scheduleDirect(run, 0, unit)
}
}
override fun before() {
RxJavaPlugins.setIoSchedulerHandler { immediateScheduler }
RxJavaPlugins.setComputationSchedulerHandler { immediateScheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { immediateScheduler }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediateScheduler }
RxAndroidPlugins.setMainThreadSchedulerHandler { immediateScheduler }
}
override fun after() {
RxJavaPlugins.reset()
}
}
You can find a way to migrate from TestRule to ExternalResource here and get more info on testing RxJava 2 here.
This is the exact syntax that worked for me:
RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline())