kotlinx.serialization.SerializationException: Serializer for class 'MultiPartFormDataContent' is not found - android

I'm trying to upload multiple files.
val ktorVersion = "1.5.0"
val serializationVersion = "1.0.1"
That is how I'm doing that:
override suspend fun uploadFiles(
binaryFiles: Map<String,ByteArray>
): BaseResponse<List<String>> {
return client.submitForm {
url(fileUploadUrl)
method = HttpMethod.Post
body = MultiPartFormDataContent(
formData {
headers{
append("Content-Type", "application/json")
append("Authorization", "Bearer $token}")
}
binaryFiles.entries.forEach {
append(
key = "files",
value = it.value,
headers = Headers.build {
append(HttpHeaders.ContentDisposition, "filename=${it.key}")
}
)
}
}
)
}
}
But it throws exception
kotlinx.serialization.SerializationException: Serializer for class 'MultiPartFormDataContent' is not found.
Mark the class as #Serializable or provide the serializer explicitly.
at kotlinx.serialization.internal.Platform_commonKt.serializerNotRegistered(Platform.common.kt:91)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:130)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
at io.ktor.client.features.json.serializer.KotlinxSerializerKt.buildSerializer(KotlinxSerializer.kt:82)
at io.ktor.client.features.json.serializer.KotlinxSerializerKt.access$buildSerializer(KotlinxSerializer.kt:1)
at io.ktor.client.features.json.serializer.KotlinxSerializer.writeContent$ktor_client_serialization(KotlinxSe
at io.ktor.client.features.json.serializer.KotlinxSerializer.write(KotlinxSerializer.kt:26)
at io.ktor.client.features.json.JsonFeature$Feature$install$1.invokeSuspend(JsonFeature.kt:150)
at io.ktor.client.features.json.JsonFeature$Feature$install$1.invoke(JsonFeature.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.proceedWith(SuspendFunctionGun.kt:123)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invokeSuspend(HttpCallValidator.kt:106)
at io.ktor.client.features.HttpCallValidator$Companion$install$1.invoke(HttpCallValidator.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invokeSuspend(HttpRequestLifecycle.kt:37)
at io.ktor.client.features.HttpRequestLifecycle$Feature$install$1.invoke(HttpRequestLifecycle.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:79)
at io.ktor.client.HttpClient.execute(HttpClient.kt:187)
at io.ktor.client.statement.HttpStatement.executeUnsafe(HttpStatement.kt:104)
at com.example.package.core.data.network.services.upload.FileUploadApiImpl.uploadFiles(FileUploadApi.kt:99)
I have also tried in that way but got same problem:
suspend fun uploadFilesTest(
binaryFiles: Map<String,ByteArray>
): BaseResponse<List<String>> {
return client.post(fileUploadUrl) {
headers {
append("Content-Type", ContentType.Application.Json)
append("Authorization", "Bearer $token")
}
body = MultiPartFormDataContent(
formData {
binaryFiles.entries.forEach {
this.appendInput(
key = "files",
size = it.value.size.toLong(),
headers = Headers.build {
append(HttpHeaders.ContentDisposition, "filename=${it.key}")
},
){ buildPacket { writeFully(it.value) }}
}
}
)
}
}

Looking at your last snippet where you say you have the same problem, it seems like you are specifying content-type in headers like this
append("Content-Type", ContentType.Application.Json)
but you also want it to be multipart by setting body to be MultiPartFormDataContent which also needs to be defined as a header content-type:multipart/form-data; ... so it might be that this conflict is causing the issue.
I actually just tried it myself in my code and that seemed to be the issue for me.

I did this for Android but it may also work for ktor. At first, add this line to dependencies in the build project gradle:
classpath "org.jetbrains.kotlin:kotlin-serialization:1.5.21"
Add also these to the build app gradle:
plugins {
...
id 'kotlinx-serialization'
}
dependencies {
...
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2'
}

Related

I can't find the Dispathcers.IO in the coroutine

i'm studying android by a book. according to the book, i need to create a repository to get data from the network response. and i had create a livedata to store info.
here are the codes on my book.
object Repository {
fun searchPlaces(query: String) = liveData(Dispatchers.IO) {
val result = try {
val placeResponse = SunnyWeatherNetwork.searchPlaces(query)
if (placeResponse.status == "ok") {
val places = placeResponse.places
Result.success(places)
} else {
Result.failure(RuntimeException("response status is ${placeResponse.status}"))
}
} catch (e: Exception) {
Result.failure<Place>(e)
}
emit(result)
}
But when I copied the codes, i found that the second row's Dispatchers.IO was wrong. and i tried to import the kotlinx.coroutines.Dispatchers, the IDE can't find it.
finally i abandoned the codes, and the app run successfully. i can't understand the specific principles
this is my build.gradle.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
and how can i use the Dispatchers.IO?
Dispatchers.IO belongs to kotlinx-coroutines-core package.
Add kotlinx-coroutines-core implementation to your module's build.gradle
dependencies{
def coroutinesVersion = '1.5.2-native-mt'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
}

How to call method defined in Gradle Plugin in Consumer?

I'm still new in developing Gradle Plugin. But what i want to do is to define global method to call in consumer gradle file.
So my plugin class looking something like this:
class Main : Plugin<Project> {
override fun apply(target: Project) {
// Some configuration
}
fun modulePath(moduleName: String, target: Project): Any {
val propFile = File("${target.rootDir}/local.properties")
if (!propFile.exists()) propFile.createNewFile()
val props = Properties()
props.load(propFile.inputStream())
val useAAR = props.getProperty("useAAR", "false")?.toBoolean() ?: false
val devModule = props.getProperty("modules", ":app").split(" ")
return if (useAAR && !devModule.contains(moduleName)) {
"$groupId:$moduleName:$versionId"
} else {
target.project(moduleName)
}
}
companion object {
const val groupId = "local"
const val versionId = "0.1"
}
}
I can apply the plugin just fine in the consumer. But I still don't know how to call modulePath method in consumer's gradle file. Something like this:
apply plugin: 'main'
dependencies {
implementation modulePath(':someModule')
}
When i'm calling the modulePath, i'm getting error that the method is not defined.
Thanks in advance.

Simple HTTP request example in Android using Kotlin

I am new to Android development with Kotlin and I am struggling on finding any useful documentation on how to create a simple GET and POST requests with the best current practices as possible. I am coming from an Angular development and there we used a reactive development using RxJS.
Normally I would create a service file that would hold all my request functions, then I would use this service in whichever component and subscribe to the observable.
How would you do this in Android? Is there a good started example of things that have to be created. From the first look, everything looks so complicated and over-engineered
I suggest you to use the official recommendation of OkHttp, or the Fuel library for easier side and it also has bindings for deserialization of response into objects using popular Json / ProtoBuf libraries.
Fuel example:
// Coroutines way:
// both are equivalent
val (request, response, result) = Fuel.get("https://httpbin.org/ip").awaitStringResponseResult()
val (request, response, result) = "https://httpbin.org/ip".httpGet().awaitStringResponseResult()
// process the response further:
result.fold(
{ data -> println(data) /* "{"origin":"127.0.0.1"}" */ },
{ error -> println("An error of type ${error.exception} happened: ${error.message}") }
)
// Or coroutines way + no callback style:
try {
println(Fuel.get("https://httpbin.org/ip").awaitString()) // "{"origin":"127.0.0.1"}"
} catch(exception: Exception) {
println("A network request exception was thrown: ${exception.message}")
}
// Or non-coroutine way / callback style:
val httpAsync = "https://httpbin.org/get"
.httpGet()
.responseString { request, response, result ->
when (result) {
is Result.Failure -> {
val ex = result.getException()
println(ex)
}
is Result.Success -> {
val data = result.get()
println(data)
}
}
}
httpAsync.join()
OkHttp example:
val request = Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build()
// Coroutines not supported directly, use the basic Callback way:
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
println("$name: $value")
}
println(response.body!!.string())
}
}
})
you can use something like that:
internal inner class RequestTask : AsyncTask<String?, String?, String?>() {
override fun doInBackground(vararg params: String?): String? {
val httpclient: HttpClient = DefaultHttpClient()
val response: HttpResponse
var responseString: String? = null
try {
response = httpclient.execute(HttpGet(uri[0]))
val statusLine = response.statusLine
if (statusLine.statusCode == HttpStatus.SC_OK) {
val out = ByteArrayOutputStream()
response.entity.writeTo(out)
responseString = out.toString()
out.close()
} else {
//Closes the connection.
response.entity.content.close()
throw IOException(statusLine.reasonPhrase)
}
} catch (e: ClientProtocolException) {
//TODO Handle problems..
} catch (e: IOException) {
//TODO Handle problems..
}
return responseString
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
//Do anything with response..
}
}
and for call:
RequestTask().execute("https://v6.exchangerate-api.com/v6/")
HttpClient is not supported any more in sdk 23. You have to use URLConnection or downgrade to sdk 22 (compile 'com.android.support:appcompat-v7:22.2.0')
If you need sdk 23, add this to your gradle:
android {
useLibrary 'org.apache.http.legacy'
}
You also may try to download and include HttpClient.jar directly into your project or use OkHttp instead
The best practice you ever get to just go through the basics of networking call and create some demo applications using Android Studio.
If you want to click start then follow this tutorial
Simplet netwroking call in Kotlin
https://www.androidhire.com/retrofit-tutorial-in-kotlin/
Also, I would like to suggest Please create some demo application for GET and POST request and then merge these examples into your project.

No interface method isAsyncStarted() error running wiremock android tests on a Meizu device

I have an Android app which performs a network request. I'm using wiremock for instrumentation tests and retrofit in the app.
The request is mocked like this:
stubFor(
get(urlMatching("/hello/world"))
.willReturn(
aResponse()
.withStatus(HttpURLConnection.HTTP_OK)
.withHeader("Content-Type", "text/plain")
.withBody(expectedServerResponse)
)
)
The retrofit interface:
interface HelloWorldService {
#GET("/hello/world")
fun getHelloWorld(): Call<ResponseBody>
}
The application code which performs the request:
val helloWorldService =
Retrofit.Builder()
.baseUrl(intent.getStringExtra(EXTRA_BASE_URL))
.client((application as MyApplication).createRetrofitHttpClient())
.build()
.create(HelloWorldService::class.java)
helloWorldService.getHelloWorld().enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
textView.text = if (response.isSuccessful) {
response.body()?.string()
} else {
response.message()
}
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
textView.text = t.message
}
})
When the mocked response's content length is large (over 256 characters), and the test is run on a Meizu Note 5, wiremock finds the response and looks like it's about to send the 200 response, as I see this in the logcat log:
Matched response definition:
{
"status" : 200,
"body" : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"headers" : {
"Content-Type" : "text/plain"
}
}
Response:
HTTP/1.1 200
Content-Type: [text/plain]
Matched-Stub-Id: [a45b21c9-95f6-45fa-a7c3-acf473485fb6]
However, the app actually receives an 500 error code, with this message in the error response:
No interface method isAsyncStarted()Z in class Ljavax/servlet/http/HttpServletRequest; or its super classes (declaration of 'javax.servlet.http.HttpServletRequest' appears in /system/framework/meizu2_jcifs.jar)
The problem is that wiremock and meizu both embed javax.servlet classes, but different (incompatible) versions. One workaround for this is to relocate (change the package names) of the javax.servlet classes embedded by wiremock.
Instead of including wiremock like this:
androidTestImplementation "com.github.tomakehurst:wiremock-standalone:2.23.2"
Use the gradle shadow plugin and use the shadowed jar instead:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:5.1.0'
}
}
apply plugin: com.github.jengelman.gradle.plugins.shadow.ShadowPlugin
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
android {
dependencies {
shadow "com.github.tomakehurst:wiremock-standalone:2.23.2"
androidTestImplementation files("$buildDir/libs/shadow.jar")
}
}
tasks.configureEach { theTask ->
def taskName = theTask.name.toString()
if (taskName =~ /generate.*Sources/) {
theTask.dependsOn tasks.named("shadowJar")
}
}
tasks.register("shadowJar", ShadowJar) {
baseName = 'shadow'
configurations = [project.configurations.shadow]
relocate ('javax.servlet', 'com.example.wiremockexample.javax.servlet'){}
}
A full sample project explaining this problem in more detail is here: https://github.com/libon/WiremockMeizuError
And a PR on the project implements this answer: https://github.com/libon/WiremockMeizuError/pull/1

How to create a JSON CustomTypeAdapter in Apollo GraphQL on Android with Kotlin

I'm struggling to figure out how to add a CustomTypeAdapter to my ApolloClient.
For a mutation, our server is expecting json input. The corresponding iOS app is passing in a json string.
When I pass in a string I get a message asking if I've forgotten to add a customtype.
Here is my attempt:
build.gradle
apollo {
useSemanticNaming = true
customTypeMapping['ISOTime'] = "java.util.Date"
customTypeMapping['JSON'] = "java.lang.JSONObject"
}
Here's where it is instantiated.
val jsonCustomTypeAdapter = object : CustomTypeAdapter<JSONObject> {
override fun decode(value: CustomTypeValue<*>): JSONObject {
return JSONObject()
}
override fun encode(value: JSONObject): CustomTypeValue<*> {
return CustomTypeValue.GraphQLJsonString(value.toString())
}
}
mApolloClient = ApolloClient
.builder()
.serverUrl(baseUrl)
.addCustomTypeAdapter(jsonCustomTypeAdapter)
.normalizedCache(cacheFactory, CacheKeyResolver.DEFAULT)
.httpCache(ApolloHttpCache(cacheStore, null))
.okHttpClient(mHttpClient)
.build()
It seems Apollo has generated a CustomType enum implementing ScalarType but I'm not sure if or how to use it.
#Generated("Apollo GraphQL")
public enum CustomType implements ScalarType {
JSON {
#Override
public String typeName() {
return "Json";
}
#Override
public Class javaType() {
return Object.class;
}
},
ID {
#Override
public String typeName() {
return "ID";
}
#Override
public Class javaType() {
return String.class;
}
}
}
I've attempted the example given on the apolloandroid github but it hasn't worked for me and it is in Java and after I convert it to Kotlin, it doesn't compile.
Any hints or direction to persue would be appreciated. Thanks.
It turns out Apollo had auto generated the type and all I had to do was declare it correctly in the build.gradle. I didn't need to add any custom type adapter to the ApolloClient.
NOTE: The type Json was provided by our server.
apollo {
useSemanticNaming = true
customTypeMapping['ISOTime'] = "java.util.Date"
customTypeMapping['Json'] = "java.lang.String"
}

Categories

Resources