Why tor get this error Parent job is Completed - android

I'm using ktor for the android client but I have an error.
When I run the app for the first time everything is fine and there is no issue, but when I click on the device back button and close the app, and open it again, the app is crashed and I get this error about the ktor:
Parent job is Completed
this is my ktor configure the module:
#InstallIn(SingletonComponent::class)
#Module
object NetworkModule {
private const val TIME_OUT = 60_000
#Singleton
#Provides
fun provideKtor(): HttpClient = HttpClient(Android) {
install(HttpCache)
defaultRequest {
contentType(ContentType.Application.Json)
accept(ContentType.Application.Json)
}
install(ContentNegotiation) {
json(json = Json {
prettyPrint = true
ignoreUnknownKeys = true
isLenient = true
encodeDefaults = false
})
}
install(HttpTimeout) {
connectTimeoutMillis = TIME_OUT.toLong()
socketTimeoutMillis = TIME_OUT.toLong()
requestTimeoutMillis = TIME_OUT.toLong()
}
install(ResponseObserver) {
onResponse { response ->
Log.d("HttpClientLogger - HTTP status", "${response.status.value}")
Log.d("HttpClientLogger - Response:", response.toString())
}
}
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
Log.v("Logger Ktor =>", message)
}
}
level = LogLevel.NONE
}
}
}
Note: I use ktor version "2.0.2".
const val ktor_client_core = "io.ktor:ktor-client-core:$ktor_version"
const val ktor_client_cio = "io.ktor:ktor-client-cio:$ktor_version"
const val ktor_serialization_json = "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
const val ktor_serialization = "io.ktor:ktor-client-serialization:$ktor_version"
const val ktor_android = "io.ktor:ktor-client-android:$ktor_version"
const val ktor_negotiation = "io.ktor:ktor-client-content-negotiation:$ktor_version"
const val ktor_okhttp = "io.ktor:ktor-client-okhttp:$ktor_version"
const val ktor_logging = "io.ktor:ktor-client-logging:$ktor_version"
How can i fix it?

I found the reason: This is related to Hilt Di (NetworkModule). I have to use an object instead of hilt module for now

The problem is that you cannot use the same instance of the HttpClient.
companion object {
val client get() = HttpClient(Android) { }
}

Related

Android compose ktor failed with error Lio/ktor/utils/io/NativeUtilsJvmKt

i am trying to setup ktor replacing retrofit on a compose simple android app but i am getting this error:
java.lang.NoClassDefFoundError: Failed resolution of: Lio/ktor/utils/io/NativeUtilsJvmKt;
at io.ktor.client.features.HttpSend.<init>(HttpSend.kt:49)
at io.ktor.client.features.HttpSend.<init>(HttpSend.kt:41)
at io.ktor.client.features.HttpSend$Feature.prepare(HttpSend.kt:75)
at io.ktor.client.features.HttpSend$Feature.prepare(HttpSend.kt:72)
at io.ktor.client.HttpClientConfig$install$3.invoke(HttpClientConfig.kt:77)
at io.ktor.client.HttpClientConfig$install$3.invoke(HttpClientConfig.kt:74)
at io.ktor.client.HttpClientConfig.install(HttpClientConfig.kt:97)
at io.ktor.client.HttpClient.<init>(HttpClient.kt:172)
at io.ktor.client.HttpClient.<init>(HttpClient.kt:81)
at io.ktor.client.HttpClientKt.HttpClient(HttpClient.kt:43)
My service is like this:
interface ApiService {
suspend fun getProducts(): List<ResponseModel>
companion object {
private val json = kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
isLenient = true
encodeDefaults = false
}
fun create(): ApiService {
return ApiServiceImpl(
client = HttpClient(Android) {
// Logging
install(Logging) {
level = LogLevel.BODY
}
// JSON
install(JsonFeature) {
serializer = KotlinxSerializer(json)
//or serializer = KotlinxSerializer()
}
// Timeout
install(HttpTimeout) {
requestTimeoutMillis = 15000L
connectTimeoutMillis = 15000L
socketTimeoutMillis = 15000L
}
}
)
}
}
}
and ApiServiceImpl
class ApiServiceImpl(private val client: HttpClient) : ApiService {
override suspend fun getProducts(): List<ResponseModel> {
return client.get {
url(HttpRoutes.PRODUCTS)
}
}
}
On my main activity I just want to make the api call and just display an attribute of the first item of the list(just for verifying that the call was successful).
class MainActivity : ComponentActivity() {
private val service = ColorBarApiService.create()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
val products = produceState(
initialValue = emptyList<ResponseModel>(),
producer = {
value = service.getProducts()
}
)
// just to see if api call is success
Text(text = "First product has description ${products.value[0].description}!")
}
}
}
}
}
Finally on app build.gradle I am using these:
// ktor
implementation "io.ktor:ktor-client-core:$ktor_version"
// HTTP engine: The HTTP client used to perform network requests.
implementation "io.ktor:ktor-client-android:$ktor_version"
// The serialization engine used to convert objects to and from JSON.
implementation "io.ktor:ktor-client-serialization:$ktor_version"
// Logging
implementation "io.ktor:ktor-client-logging:$ktor_version"
implementation "io.ktor:ktor-utils-jvm:$ktor_version"
implementation "io.ktor:ktor-client-okhttp:$ktor_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version"
with values
ext {
compose_ui_version = '1.2.1'
koin_version= "3.2.2"
koin_android_version= "3.2.3"
koin_android_compose_version= "3.2.2"
koin_ktor= "3.2.2"
ktor_version = '1.6.4'
serialization_version = '1.4.1'
}

I have a problem when i am testing ktor. Basically the ApplicationTest class is not injecting repository class it show an error of java..ClassCastExce

This is my application test class
class ApplicationTest {
private val heroRepository: HeroRepository by inject(HeroRepository::class.java)
#OptIn(InternalAPI::class)
#Test
fun `access all heroes endpoints, assert correct information`() = testApplication {
val response = client.get("/naruto/heroes")
assertEquals(
expected =
"""
{
success = true,
message = "ok",
prevPage = null,
nextPage = 2,
heroes = ${heroRepository.heroes[1]!!}
}
""".trimIndent() ,
actual = response.bodyAsText()
)
}
}
It show the error of java.lang.ClassCastException when heroRepository is getting inject and i am using koin for dependency injection
java.lang.ClassCastException: class com.example.repository.HeroRepositoryImpl cannot be cast to class com.example.repository.HeroRepository (com.example.repository.HeroRepositoryImpl is in unnamed module of loader io.ktor.server.engine.OverridingClassLoader$ChildURLClassLoader #7f6ad6c8; com.example.repository.HeroRepository is in unnamed module of loader 'app')
And this is my AllHeroesRoute and here it's perfectly injecting heroRepository
fun Route.getAllHeroes() {
val heroRepository: HeroRepository by inject()
get("/naruto/heroes") {
try {
val page = call.request.queryParameters["page"]?.toInt() ?: 1
require(page in 1..5)
val apiResponse = heroRepository.getAllHeroes(page = page)
call.respond(
message = apiResponse,
status = HttpStatusCode.OK
)
} catch (e: NumberFormatException) {
call.respond(
message = ApiResponse(success = false, message = "Only numbers allowed"),
status = HttpStatusCode.BadRequest
)
} catch (e: IllegalArgumentException) {
call.respond(
message = ApiResponse(success = false, message = "Heroes Not Found"),
status = HttpStatusCode.BadRequest
)
}
}
}
I had the same issue, disabling the developmentMode fixed it:
fun myTestFunc() = testApplication {
environment {
developmentMode = false
}
....
}

Android Mockito test cases with Retrofit

I am new to test cases and I am trying to write test cases for the below code but I did not get success after trying several method. My main target is to cover code coverage of MaintenanceStatusResponseHandler.kt class. I am using mockito to write the test cases. I am already implemented jococo for code coverage but I am facing some issue to write a test cases. Please help me to write test cases of MaintenanceStatusResponseHandler class
Thanks in advance
internal class MaintenanceStatusResponseHandler {
public fun getMaintenanceResponse(voiceAiConfig : VoiceAiConfig):MaintenanceStatus{
val maintenanceStatus = MaintenanceStatus()
val retrofitRepository = RetrofitRepository()
val maintenanceUrl : String
val jwtToken : String
when (voiceAiConfig.server) {
BuildConfig.ENV_PRODUCTION_SERVER -> {
jwtToken = BuildConfig.JWT_TOKEN_PRODUCTION
maintenanceUrl = BuildConfig.MAINTENANCE_PROD_URL
}
BuildConfig.ENV_STAGING_SERVER -> {
jwtToken = BuildConfig.JWT_TOKEN_STAGING
maintenanceUrl = BuildConfig.MAINTENANCE_SANDBOX_URL
}
else -> {
jwtToken = BuildConfig.JWT_TOKEN_SANDBOX
maintenanceUrl = BuildConfig.MAINTENANCE_SANDBOX_URL
}
}
val header = "${VoiceAISDKConstant.JWT_TOKEN_PREFIX} $jwtToken"
retrofitRepository.getRetrofit(maintenanceUrl)
.getMaintenanceStatus(header)
.subscribe { response: MaintenanceStatus.Content, error: Throwable? ->
error.let {
if (error != null) {
maintenanceStatus.error = error
}
}
response.let {
maintenanceStatus.content = response
}
}
return maintenanceStatus
}
}
repository class
class RetrofitRepository() {
val TAG = RetrofitRepository::class.java.canonicalName
fun getRetrofit(baseUrl: String?): VoiceAiServices {
val voiceAiServices: VoiceAiServices = Retrofit.Builder()
.baseUrl(baseUrl!!)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(VoiceAiServices::class.java)
return voiceAiServices
}
}
interface
interface VoiceAiServices {
#GET("/v1/api/status")
fun getMaintenanceStatus(#Header("Authorization")header: String): Single<MaintenanceStatus.Content>
}
Pojo class
data class MaintenanceStatus(
var error: Throwable? = null,
var content: Content? = null
) {
data class Content(
val enabled: Boolean,
val maintenanceMsg: String
)
}

How to use refershToken in ktor

Hey I am working in kotlin multiplatform moblie. I want to ask that if api returns 401 status, I want to call refresh api. I am reading the doc to configure ktor but unable to understand this. Can someone guide me on this. I tried some code in my side, Can some one guide me any proper example how to achieve in my solution.
commonMain
expect fun httpClient(config: HttpClientConfig<*>.() -> Unit = {}): HttpClient
iOSMain
actual class Platform actual constructor() {
actual val versionCode =
platform.Foundation.NSBundle.mainBundle.infoDictionary?.get("CFBundleVersion").toString()
actual val accessToken = ""
}
androidMain
actual fun httpClient(config: HttpClientConfig<*>.() -> Unit) = HttpClient(OkHttp) {
config(this)
install(ContentNegotiation) {
json(Json {
prettyPrint = true
ignoreUnknownKeys = true
explicitNulls = false
})
}
engine {
config {
retryOnConnectionFailure(true)
connectTimeout(30, TimeUnit.SECONDS)
readTimeout(40, TimeUnit.SECONDS)
}
}
defaultRequest {
header("Client-Version", Platform().versionCode)
}
HttpResponseValidator {
validateResponse { response ->
when (response.status.value) {
401 -> {
}
}
}
}
install(Auth) {
bearer {
loadTokens {
BearerTokens(tokenProvider.accessToken, "")
}
}
}
}
Platform.kt
package com.example.kotlinmultiplatformsharedmodule
lateinit var provider: VersionAndroidProvider
lateinit var tokenProvider: AndroidToken
actual class Platform actual constructor() {
actual val versionCode get() = provider.version
actual val accessToken: String
get() = tokenProvider.accessToken
}
interface VersionAndroidProvider {
val version: String
}
interface AndroidToken {
val accessToken: String
}
I need to call api, if api returns 401 status, I need to call refershToken api. After getting new accessToken from refreshToken api, I need to send this to api call.
If refreshToken is giving 401 then I need to infrom my application to logout.
If you use the Bearer provider in Ktor’s Authentication plugin then the refreshTokens lambda will be called when a server returns 401. For more information read the documentation. Here is an incomplete example for your use case:
val client = HttpClient(Apache) {
install(Auth) {
bearer {
loadTokens {
BearerTokens("initial_access_token", "initial_refresh_token")
}
refreshTokens {
val response = client.get("https://example.com/get_token")
if (response.status == HttpStatusCode.Unauthorized) {
// logout
null
} else {
// get token from a response
BearerTokens("new_access_token", "new_refresh_token")
}
}
}
}
}

Kotlin Exception when mocking suspend function java.io.EOFException: Premature end of stream: expected 1 bytes

I use KTor and Kotlin Serialization library in my android project, along with mockk and junit.jupiter for unit testing. I've encountered a problem when mocking ktor's suspend function readText(). The following unit test tests that initErrorMessage() function returns correct error message.
Test class:
class ErrorTest {
private val errorMessage = "objectId must be provided."
private val errorCode = 2689
private val correctResponseJson = "{\"code\":$errorCode,\"message\":\"$errorMessage\"}"
// ResponseException class is from ktor library
private val exceptionMock: ResponseException = mockk(relaxed = true)
#Test
fun `initErrorMessage should return correct error message`() = runTest {
coEvery { exceptionMock.response.readText() } returns correctResponseJson // <-- here is the Error occurs
val expectedError = errorMessage
val actualError = initErrorMessage(exceptionMock)
assertEquals(expectedError, actualError)
}
}
Method to test:
suspend fun initErrorMessage(cause: ResponseException): String {
return try {
val body = cause.response.readText()
val jsonSerializer = JsonObject.serializer()
val jsonObj = Json.decodeFromString(jsonSerializer, body)
jsonObj["message"].toString()
} catch (e: Exception) {
""
}
}
During execution of the first line in the test method I get an Error:
Premature end of stream: expected 1 bytes
java.io.EOFException: Premature end of stream: expected 1 bytes
at io.ktor.utils.io.core.StringsKt.prematureEndOfStream(Strings.kt:492)
at io.ktor.utils.io.core.internal.UnsafeKt.prepareReadHeadFallback(Unsafe.kt:78)
at io.ktor.utils.io.core.internal.UnsafeKt.prepareReadFirstHead(Unsafe.kt:61)
at io.ktor.utils.io.charsets.CharsetJVMKt.decode(CharsetJVM.kt:556)
at io.ktor.utils.io.charsets.EncodingKt.decode(Encoding.kt:103)
at io.ktor.utils.io.charsets.EncodingKt.decode$default(Encoding.kt:101)
at io.ktor.client.statement.HttpStatementKt.readText(HttpStatement.kt:173)
at io.ktor.client.statement.HttpStatementKt.readText$default(HttpStatement.kt:168)
at com.example.android.http.error.ErrorTest$initErrorMessage should return correct error message$1$1.invokeSuspend(ErrorTest.kt:37)
at com.example.android.http.error.ErrorTest$initErrorMessage should return correct error message$1$1.invoke(ErrorTest.kt)
at com.example.android.http.error.ErrorTest$initErrorMessage should return correct error message$1$1.invoke(ErrorTest.kt)
at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$2$1.invokeSuspend(RecordedBlockEvaluator.kt:28)
at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$2$1.invoke(RecordedBlockEvaluator.kt)
at io.mockk.InternalPlatformDsl$runCoroutine$1.invokeSuspend(InternalPlatformDsl.kt:20)
How to mock this suspend method readText() without an Error?
It turned out that the function readText() hasn't been mocked properly.
It is an extension function on HttpResponse and it has to be mocked using mockkStatic function, for example like this:
#BeforeEach
fun setup() {
mockkStatic(HttpResponse::readText)
}
setup() will be executed before each #Test, because it is marked with #BeforeEach annotation.
You can mock the HttpClientCall instead of the ResponseException to create an instance of the ResponseException without mocking (to avoid EOFException).
class ErrorTest {
private val errorMessage = "objectId must be provided."
private val errorCode = 2689
private val correctResponseJson = "{\"code\":$errorCode,\"message\":\"$errorMessage\"}"
#OptIn(InternalAPI::class)
#Test
fun `initErrorMessage should return correct error message`(): Unit = runBlocking {
val responseData = HttpResponseData(
statusCode = HttpStatusCode.OK,
requestTime = GMTDate.START,
headers = Headers.Empty,
version = HttpProtocolVersion.HTTP_1_1,
"",
coroutineContext
)
val call = mockk<HttpClientCall>(relaxed = true) {
// This is how a body received under the hood
coEvery { receive<Input>() } returns BytePacketBuilder().apply { writeText(correctResponseJson) }.build()
// There are cyclic dependencies between HttpClientCall and HttpResponse so it's not possible to mock it in place
every { response } returns DefaultHttpResponse(this, responseData)
}
val exception = ResponseException(call.response, "")
val expectedError = errorMessage
val actualError = initErrorMessage(exception)
assertEquals(expectedError, actualError)
}
}

Categories

Resources