kotlin how to share common retrofit response handler code - kotlin

Having code to get from two different endpoints, but the response json have same scheme and the response handler code are same.
internal interface DataApi {
#GET("/api/api_1")
fun getData_1(#QueryMap params: Map<String, String>?): Call<DataMpdel>
#GET("/api/api_2")
fun getData_2(#QueryMap params: Map<String, String>?): Call<DataMpdel>
}
private lateinit var dataApi: DataApi
init {
dataApi = createDataApi()
}
private fun createDataApi() : DataApi {
val restAdapter = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(getGsonConverterFactory())
.build()
return restAdapter.create(DataApi::class.java)
}
It could use if/else to call the different endpoint, but that will duplicate the handler code.
Update: create the handler function and in both onResposne() just call that function.
Any suggestion to be better with shared handler code?
private fun fetchFirst(){
if (!UseDataApi_1) {
//
dataApi. getData_1(getQueryParams()).enqueue(object : Callback<DataModel> {
override fun onResponse(call: Call<DataModel>?, response: Response<DataModel>?) {
// same handler code
}
override fun onFailure(call: Call<DataModel>?, throwable: Throwable?) {
// same error handler code
}
})
} else {
//
dataApi. getData_1(getQueryParams()).enqueue(object : Callback<DataModel> {
override fun onResponse(call: Call<DataModel>?, response: Response<DataModel>?) {
// same handler code
}
override fun onFailure(call: Call<DataModel>?, throwable: Throwable?) {
// same error handler code
}
})
}
}

If you want to minimize the duplicate code, create the callback as a class object and use it for both calls (something like this):
private val callback = object : Callback<DataModel> {
override fun onResponse(call: Call<DataModel>?, response: Response<DataModel>?) {
// handler code
}
override fun onFailure(call: Call<DataModel>?, throwable: Throwable?) {
// error handler code
}
}
private fun fetchFirst() {
if (UseDataApi_1) {
dataApi.getData_1(getQueryParams()).enqueue(callback)
} else {
dataApi.getData_2(getQueryParams()).enqueue(callback)
}
}

Related

Why is a for loop causing an infinite loop in retrofit?

I'm using MutableLiveData<List> for the backing field. I've tried everything but it will continue to make API calls and it never breaks out of the for loop. I'm not sure whether changing the immutable list value of MutableLiveData triggers this.
fun getTweets(userIDs: List<Long>){
val ids = listOf(1168929753237094400, 3456625696)
viewModelScope.launch {
for(id in ids){
tweetService.getUsername(id).enqueue(object : Callback<TwitterUserResponse> {
override fun onResponse(call: Call<TwitterUserResponse>, response: Response<TwitterUserResponse>) {
val username = response.body()!!.user.username
tweetService.getTweets(id).enqueue(object: Callback<TwitterResponse>{
override fun onResponse(call: Call<TwitterResponse>, response: Response<TwitterResponse>) {
_tweets.value = response.body()!!.tweets.apply { forEach { it.username = username } }
}
override fun onFailure(call: Call<TwitterResponse>, t: Throwable) {}
})
}
override fun onFailure(call: Call<TwitterUserResponse>, t: Throwable) {}
})
}
}
}

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

How can I access header in a service?

I'm trying to handle JWT-authentication in gRPC on my backend. I can extract the JWT in an interceptor but how do I access it in my service? I think it should be done with a CoroutineContextServerInterceptor but this doesn't work:
val jwtKey: Context.Key<String> = Context.key("jwtKey")
fun main() {
ServerBuilder.forPort(8980).intercept(UserInjector).addService(MyService).build().start().awaitTermination()
}
object UserInjector : CoroutineContextServerInterceptor() {
override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext {
val jwtString = headers.get(Metadata.Key.of("jwt", Metadata.ASCII_STRING_MARSHALLER))
println("coroutineContext: $jwtString")
return GrpcContextElement(Context.current().withValue(jwtKey, jwtString))
}
}
object MyService : MyServiceGrpcKt.MyServiceCoroutineImplBase() {
override suspend fun testingJWT(request: Test.MyRequest): Test.MyResponse {
println("testingJWT: ${jwtKey.get()}")
return Test.MyResponse.getDefaultInstance()
}
}
Output:
coroutineContext: something
testingJWT: null
I think you'll need to propagate that in its own coroutine context element.
class JwtElement(val jwtString: String) : CoroutineContext.Element {
companion object Key : CoroutineContext.Key<JwtElement>
override val key: CoroutineContext.Key<JwtElement>
get() = Key
}
object UserInjector : CoroutineContextServerInterceptor() {
override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext {
val jwtString = headers.get(Metadata.Key.of("jwt", Metadata.ASCII_STRING_MARSHALLER))
println("coroutineContext: $jwtString")
return JwtElement(jwtString)
}
}
object MyService : MyServiceGrpcKt.MyServiceCoroutineImplBase() {
override suspend fun testingJWT(request: Test.MyRequest): Test.MyResponse {
println("testingJWT: ${coroutineContext[JwtElement]}")
return Test.MyResponse.getDefaultInstance()
}
}

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!")
}
})

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