Generic api calling using kotlin Reified in Ktor - android

I am new to KMM and trying to create a generic function for api calling using ktor with reified and it seems to work fine with android but throws an error in iOS
This is my common api call return in Shared file.
#Throws(Exception::class)
suspend inline fun<reified T> post(url: String, requestBody: HashMap<String, Any>?) : Either<CustomException, T> {
try {
val response = httpClient.post<T> {
url(BASE_URL.plus(url))
contentType(ContentType.Any)
if (requestBody != null) {
body = requestBody
}
headers.remove("Content-Type")
headers {
append("Content-Type", "application/json")
append("Accept", "application/json")
append("Time-Zone", "+05:30")
append("App-Version", "1.0.0(0)")
append("Device-Type", "0")
}
}
return Success(response)
} catch(e: Exception) {
return Failure(e as CustomException)
}
}
It works good in android if I call it like this :-
api.post<MyDataClassHere>(url = "url", getBody()).fold(
{
handleError(it)
},
{
Log.d("Success", it.toString())
}
)
But I am not able to get it run on iOS devices it shows me error like this :-
some : Error Domain=KotlinException Code=0 "unsupported call of reified inlined function `com.example.myapplication.shared.apicalls.SpaceXApi.post`" UserInfo={NSLocalizedDescription=unsupported call of reified inlined function `com.example.myapplication.shared.apicalls.SpaceXApi.post`, KotlinException=kotlin.IllegalStateException: unsupported call of reified inlined function `com.example.myapplication.shared.apicalls.SpaceXApi.post`, KotlinExceptionOrigin=}
Any help in this is appreciated. Thanks

Okay so from the Slack conversation here it's clear that it's not possible to create this type of generic function as swift doesn't support reified. The only solution is we need to create different functions for every different api call we need.
For eg :- we can create a interface inside which we have all the api implementation and use it in the native platforms. Like this :-
interface ApiClient {
suspend fun logIn(…): …
suspend fun createBlogPost(…): …
// etc
}
Now we can use this in our native platforms.

Related

Unable to use Retrofit with Coroutines with KotlinExtensions

So here's the deal . This is the ApiInterface:
interface ApiInterface {
#GET("api/fetch-some-info")
suspend fun fetchSomeInfo(
#Query("some_query_param") queryParam: String,
): Call<Data>
}
Here is how I generate it
new Retrofit.Builder()
.client(mOkHttpClient)
.addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().build()))
.baseUrl(url)
.build().create(AdMediationV2.class);
here is Data class
#JsonClass(generateAdapter = true)
data class Data(
#Json(name = "data")
val info: String)
Now I am trying to call the function and receive data using KotlinExtensions for Retrofit, I am using implementation 'com.squareup.retrofit2:retrofit:2.9.0'
This is how I am trying to call this function. I want to keep a reference of the job so I might be able to cancel it later.
val job = CoroutineScope(Dispatchers.IO).async{
api.fetchSomeInfo("Param").await().let { data->
//handle response}
But I get this error
java.lang.IllegalArgumentException: Unable to create converter for retrofit2.Call<Data>
According to documentation I dont even need to add Call to interface, but unless I do I cannot call .await(). It is marked as red and this is what it says
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public suspend fun <T : Any> Call<TypeVariable(T)>.await(): TypeVariable(T) defined in retrofit2
public suspend fun <T : Any> Call<TypeVariable(T)?>.await(): TypeVariable(T)? defined in retrofit2
What am I doing wrong?
Note: The Api functions properly, when used in java code, call.enqueue() it works fine.
It also works just fine in this code :
var call : Deferred<Data>? = null
MainScope().launch {
call = async(Dispatchers.IO) {
api.fetchSomeInfo("Param")
}
call?.await().let {//handle reponse}
The function in your interface should return Data instead of Call<Data> since it's a suspend function. And since it's a suspend function, there will be no need for calling await() on anything. You can just call fetchSomeInfo() directly in a coroutine, without any concern about what dispatcher it is called from either.
interface ApiInterface {
#GET("api/fetch-some-info")
suspend fun fetchSomeInfo(
#Query("some_query_param") queryParam: String,
): Data
}
//...
val job = someCoroutineScope.launch {
try {
val data = api.fetchSomeInfo("Param")
// handle data
} catch (exception: Exception) {
// handle errors
}
}
Alternatively, you could remove the suspend keyword from your function so it does have to return a Call<Data> that you will then have to call await() on, but that's just adding convolution, so don't do that.
A couple notes about your coroutine... it is a code smell to create a coroutine scope just to launch a single coroutine and then immediately lose the reference. You should be launching from a persistent CoroutineScope that you presumably will be cancelling when you want it to go out of scope.
And when you call a Retrofit API (either by using a suspend function or calling await() on a Call object), you must wrap it in try/catch to handle any of the many possible errors that might occur.

Kotlin+Retrofit+RxJava2+Kodein causing slow HTTP requests

I have a mobile application that makes HTTP requests and waits for responses standardized by JSON attributes. I need to throw an Exception globally when a certain attribute is false.
Example:
HTTP RESPONSE 200 OK
{
"data": [],
"message": "An error was happened!",
"success": false
}
In the example above, the HTTP response returned status code 200, but the 'success' attribute is signaling that an error has occurred.
I need to make Retrofit understand that this attribute threw an exception from attr 'message'.
Googling I managed to find some examples where I was successful, using the Custom Call Adapter for this purpose:
class RxErrorHandlingCallAdapterFactory: CallAdapter.Factory() {
companion object {
fun create() : CallAdapter.Factory = RxErrorHandlingCallAdapterFactory()
}
private val _original by lazy {
RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())
}
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
val wrapped = _original.get(returnType, annotations, retrofit) as CallAdapter<out Any, *>
return RxCallAdapterWrapper(wrapped)
}
private class RxCallAdapterWrapper<R>(
val _wrappedCallAdapter: CallAdapter<R, *>
) : CallAdapter<R, Observable<R>> {
override fun responseType(): Type = _wrappedCallAdapter.responseType()
#SuppressLint("CheckResult")
override fun adapt(call: Call<R>): Observable<R> {
val adapted = (_wrappedCallAdapter.adapt(call) as Observable<R>)
if ((responseType() == DefaultResponseModel::class.java
|| (responseType() as Class<R>).superclass == DefaultResponseModel::class.java)
) {
(adapted as Observable<DefaultResponseModel>).blockingFirst()?.let {
if (it.success == false) {
throw Throwable(it.message)
}
}
}
return adapted
}
}
}
The problem in fact is that with each HTTP request my application literally crashes for a few long seconds, causing undesirable black screens.
Analyzing the code I managed to find the cause of the slowness, but I don't know how to solve the problem.
(adapted as Observable<DefaultResponseModel>).blockingFirst()
The code snippet above gets the Model of the 'DefaultResponseModel' class from the Stream of the Observable. But for some reason this causes an exaggerated slowdown in the App with each request.
I would be grateful if someone could help me with this endeavor :D
--
Libraries:
Reotrofit 2.6
Kodein 6.4.1
Rxjava2 2.1.1

Using OkHTTPClient() inside a coroutine always throws warning "inappropriate blocking method called"

What is the correct way to call the OkHTTP client inside a Coroutine?
CoroutineScope(IO).launch {
val request = Request.Builder()
.url("${host}/dots")
.build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback{
override fun onFailure(call: Call, e: IOException) {
isConnected.postValue(false)
}
override fun onResponse(call: Call, response: Response) {
val loadingStr = response.body()?.string().toString()
loadingStrings = loadingStr
Log.i("My_Error",loadingStrings)
}
})
}
In the onResponse the loadingStr variable shows warning for string() saying inappropriate blocking method called. Please tell me the correct way to do the same.
OkHttp provides two modes of concurrency
Synchronous blocking via execute
Asynchronous non-blocking via enqueue
Outside of these most frameworks you use will have bridge methods that convert between different modes and difference frameworks.
You should use a library like https://github.com/gildor/kotlin-coroutines-okhttp to do it for you. This code needs to do the basic normal path but also specifically needs to handle errors and separately cancellation. Your code inside coroutines should never be calling enqueue directly.
suspend fun main() {
// Do call and await() for result from any suspend function
val result = client.newCall(request).await()
println("${result.code()}: ${result.message()}")
}
This is another example from the Coil image loading library which as a framework makes sense to implement this itself rather than using a library
https://github.com/coil-kt/coil/blob/0af5fe016971ba54518a24c709feea3a1fc075eb/coil-base/src/main/java/coil/util/Extensions.kt#L45-L51
internal suspend inline fun Call.await(): Response {
return suspendCancellableCoroutine { continuation ->
val callback = ContinuationCallback(this, continuation)
enqueue(callback)
continuation.invokeOnCancellation(callback)
}
}
https://github.com/coil-kt/coil/blob/a17284794764ed5d0680330bfd8bca722a36bb5e/coil-base/src/main/java/coil/util/ContinuationCallback.kt
OkHttp can't implement this directly for at least two reasons
It would add a dependency Kotlin coroutines library, and require more secondary releases.
This problem isn't specific to Kotlin coroutines, so OkHttp would have code to deal with RxJava 1/2/3, Spring Reactor, KTor etc.

Verify suspend function with Matchers.anyObject()

I'm attempting to add coroutines to our Android application but I'm hitting a snag with our mocking framework. My interface has a suspend function like so:
interface MyInterface {
suspend fun makeNetworkCall(id: String?) : Response?
}
Here is how I'm attempting to verify the code was executed in my unit test
runBlocking {
verify(myInterface).makeNetworkCall(Matchers.anyObject())
}
When I do this I'm getting the following error
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at com.myproject.MyTest$testFunction$1.invokeSuspend(MyTest.kt:66)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
Is there another way we should verify that the appropriate method is being called when using coroutines? Any help would be appreciated.
I tried to write similar test using the code you have provided. Initially, I got same error as yours. However, when I used mockito-core v2.23.4, the tests were passed.
Here are quick steps which you can try:
add testCompile "org.mockito:mockito-core:2.23.4" to the dependencies list in your build.gradle file.
Run the tests again, and you should not get similar error.
As Matchers.anyObject() is deprecated, I used ArgumentMatchers.any().
Below you can see the client code:
data class Response(val message: String)
interface MyInterface {
suspend fun makeNetworkCall(id: String?) : Response?
}
class Client(val myInterface: MyInterface) {
suspend fun doSomething(id: String?) {
myInterface.makeNetworkCall(id)
}
}
Here is the test code:
class ClientTest {
var myInterface: MyInterface = mock(MyInterface::class.java)
lateinit var SUT: Client
#Before
fun setUp() {
SUT = Client(myInterface)
}
#Test
fun doSomething() = runBlocking<Unit> {
// Act
SUT.doSomething("123")
// Verify
Mockito.verify(myInterface).makeNetworkCall(ArgumentMatchers.any())
}
}

java.lang.RuntimeException: Failed to invoke public android.arch.lifecycle.LiveData() with no args

I'm trying to call API using Retrofit and Android architecture components but I'm getting this error
java.lang.RuntimeException: Failed to invoke public android.arch.lifecycle.LiveData() with no args
this is the data class for the response
data class ForecastResult(val city: City, val list: List<Forecast>)
Services Interface
interface ServicesApis { // using dummy data
#GET("data/2.5/forecast/")
fun getForecast(#Query("APPID") APPID: String = "xxxxxxxx"
, #Query("q") q: String = "94043", #Query("mode") mode: String = "json", #Query("units") units: String = "metric"
, #Query("cnt") cnt: String = "7"): Call<LiveData<ForecastResult>>
}
and the api implementation
class WeatherRepoImpl : WeatherRepo {
override fun getDailyForecast(): LiveData<Resource<ForecastResult>> {
val forecast: MutableLiveData<Resource<ForecastResult>> = MutableLiveData()
RestAPI.getAPIsrevice().getForecast().enqueue(object : Callback<LiveData<ForecastResult>> {
override fun onResponse(call: Call<LiveData<ForecastResult>>?, response: Response<LiveData<ForecastResult>>?) {
when {
response!!.isSuccessful -> {
forecast.postValue(Resource.success(response.body()?.value))
}
else -> {
val exception = AppException(responseBody = response.errorBody())
forecast.postValue(Resource.error(exception))
}
}
}
override fun onFailure(call: Call<LiveData<ForecastResult>>?, t: Throwable?) {
val exception = AppException(t)
forecast.postValue(Resource.error(exception))
}
})
return forecast
}
}
appreciate your help!
Remove LiveData from API call and create ViewModel class have contained in MutableLiveData Object.
For Example:
API Call Definition something like this: (Remove LiveData)
#GET("data/2.5/forecast/")
fun getForecast(#Query("APPID") APPID: String = "xxxxxxxx"
, #Query("q") q: String = "94043", #Query("mode") mode: String = "json", #Query("units") units: String = "metric"
, #Query("cnt") cnt: String = "7"): Call<ForecastResult>
Create One ViewModel Class:
class YourViewModel: ViewModel() {
var allObjLiveData = MutableLiveData<YOUROBJECT>()
fun methodForAPICall(){
mApiService?.getForecast(.....)?.enqueue(object : Callback<YOUROBJECT>
{
override fun onFailure(call: Call<YOUROBJECT>, t: Throwable) {
allObjLiveData.value = null
}
override fun onResponse(call: Call<YOUROBJECT>, response: Response<YOUROBJECT>) {
allObjLiveData.value=response.body() //Set your Object to live Data
}
})
}
}
Now in your activity or fragment initialization ViewModel class.
And observe to live data.
yourViewModelClassObj?.allObjLiveData?.observe(this, Observer {
//whenever object change this method call every time.
}
})
So You can use livedata in ViewModel class.
Yes so probably your API's response is not being correctly serialized.
Anyway, it doesn't make sense to wrap your LiveData to an API response. Just have your exposed object like so:
someLivedataObject: LiveData<YourApiResponseModel>
// So whenever the APIs response comes in:
someLivedataObject.postValue(YourApiResponseModel)
If for some reason doesn't work use
someLivedataObject.setValue(YourApiResponseModel)
You can read more about the difference between these on the documentation, notice that I'm calling the LiveData#setValue() method, do not use Kotlin's deference setter method.
And since your view is observing changes to your exposed someLivedataObject the UI is updated.
This is similar to the RxJava counterpart where API responses with Observable wrapper don't really make sense, it's not a continuous stream of data, hence makes more sense to use Single.
Take these suggestions with a grain of salt, I'm not fully aware of your application's use cases and there are exceptions to these rules.
I use LiveDataAdapter to convert Call to LiveData, that way you don't need wrap your Retrofit response as Call<LiveData<T>>.
This adapter is similar to Rx adapter by Jake.
Get the LiveDataAdapterFactory and LiveDataAdapter from here.
After adding the Adapter, you'll need to set it:
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(LiveDataCallAdapterFactory())
.baseUrl(BASE_URL)
.client(httpClient)
.build()
Self Promotion: I have made a video on the same over at: https://caster.io/lessons/android-architecture-components-livedata-with-retrofit

Categories

Resources