I'm writing a Kotlin app that has a class. I need that class to extend JsonObjectRequest, since I need to override the function
override fun parseNetworkResponse(response: NetworkResponse?): Response<T>
That's because I need to interpret in Kotlin the HTTP response code the server is sending.
However, I admit to being new to Kotlin and haven't managed to figure out how to extend the JsonObjectRequest class. I keep running into silly compiler issues.
Can someone provide a quick example of that?
After a bit of iteration, i managed to finally figure it out. Posting it here since it may be useful to others -
class DataRequest(
method: Int,
uri: String,
jsonObject: JSONObject,
listener: Response.Listener<JSONObject>,
errorListener: Response.ErrorListener
) :
JsonObjectRequest(method, uri, jsonObject, listener, errorListener)
{
override fun parseNetworkResponse(response: NetworkResponse): Response<JSONObject>
{
try
{
val jsonString = String(
response.data,
Charset.forName(HttpHeaderParser.parseCharset(response.headers))
)
return Response.success(
JSONObject(jsonString), HttpHeaderParser.parseCacheHeaders(response)
)
} catch (e: UnsupportedEncodingException)
{
return Response.error(ParseError(e))
} catch (je: JSONException)
{
return Response.error(ParseError(je))
}
}
}
Related
This example is from the documentation of HttpUrlConnection:
URL url = new URL("http://www.android.com/");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
readStream(in);
}
finally {
urlConnection.disconnect();
}
The documentation says:
Once the response body has been read, the HttpURLConnection should be closed by calling disconnect().
I tried use the Java class to load an image in a functional style:
fun fetch (url: String): ImageBitmap =
URL(url)
.openConnection()
.also { it.setRequestProperty (authorization.header, authorization.basicauth()) }
.getInputStream()
.buffered()
.use { BitmapFactory.decodeStream(it) }
.asImageBitmap()
Now I am wondering how to add the disconnect call?
I want to achieve this:
fun fetch (url: String): ImageBitmap {
var connection: HttpURLConnection? = null
return try {
URL(url)
.openConnection()
.also { it.setRequestProperty(authorization.header, authorization.basicauth()) }
.also { connection = it as HttpURLConnection }
.getInputStream()
.buffered()
.use { BitmapFactory.decodeStream(it) }
.asImageBitmap()
} finally {
connection?.disconnect()
}
}
But in a less ugly manner.
There isn't a stdlib solution for your case, but kotlin has the use() extension method defined on (Auto)Closeable to make this pattern more functional for those interfaces.
You could add a use extension method to HttpUrlConnection yourself that calls its disconnect() method, using the same approach as the source of use.
Of course you would still need to write the try finally once, but it is now hidden when using HttpUrlConnection.
On first sight you'd end up with something like this, you might still need some null handling somewhere.
public fun <T : HttpURLConnection, R> T.use(block: (T) -> R): R {
try {
return block(this)
} finally {
disconnect()
}
}
(URL(url).openConnection() as HttpURLConnection).use {
// do things with connection `it`
}
// connection is now disconnected()
Your existing way of doing this is clear enough as it is. Kotlin is not a purely functional language, so trying to use higher-order functions all the time can sometimes make your code harder to read (See also Principle of least astonishment), not to mention that you are using a Java API, which isn't designed for something like this at all.
One way I've thought of though, is:
fun fetch (url: String) =
(URL(url).openConnection() as HttpURLConnection).apply {
runCatching {
setRequestProperty(authorization.header, authorization.basicauth())
inputStream
.buffered()
.use { BitmapFactory.decodeStream(it) }
.asImageBitmap()
}.also { disconnect() }.getOrThrow()
}
I do the entire operation that could through inside a runCatching to catch any exceptions, disconnect from the connection, then throw the exception back out again.
In terms of the order of execution, this should be the same as try...finally like this:
fun fetch (url: String): ImageBitmap {
val connection = URL(url).openConnection() as HttpURLConnection
return try {
connection
.also { it.setRequestProperty(authorization.header, authorization.basicauth()) }
.getInputStream()
.buffered()
.use { BitmapFactory.decodeStream(it) }
.asImageBitmap()
} finally {
connection.disconnect()
}
}
I try to write a kotlin multiplatform library (android and ios) that uses ktor. Thereby I experience some issues with kotlins coroutines:
When writing tests I always get kotlinx.coroutines.JobCancellationException: Parent job is Completed; job=JobImpl{Completed}#... exception.
I use ktors mock engine for my tests:
client = HttpClient(MockEngine)
{
engine
{
addHandler
{ request ->
// Create response object
}
}
}
A sample method (commonMain module) using ktor. All methods in my library are written in a similar way. The exception occures if client.get is called.
suspend fun getData(): Either<Exception, String> = coroutineScope
{
// Exception occurs in this line:
val response: HttpResponse = client.get { url("https://www.google.com") }
return if (response.status == HttpStatusCode.OK)
{
(response.readText() as T).right()
}
else
{
Exception("Error").left()
}
}
A sample unit test (commonTest module) for the above method. The assertTrue statement is never called since the exception is thrown before.
#Test
fun getDataTest() = runTest
{
val result = getData()
assertTrue(result.isRight())
}
Actual implementation of runTest in androidTest and iosTest modules.
actual fun<T> runTest(block: suspend () -> T) { runBlocking { block() } }
I thought when I use coroutineScope, it waits until all child coroutines are done. What am I doing wrong and how can I fix this exception?
you can't cache HttpClient of CIO in client variable and reuse, It would be best if change the following code in your implementation.
val client:HttpClient get() = HttpClient(MockEngine) {
engine {
addHandler { request ->
// Create response object
}
}
}
The library must be updated, this glitch is in the fix report here: https://newreleases.io/project/github/ktorio/ktor/release/1.6.1
The problem is that you cannot use the same instance of the HttpClient. My ej:
HttpClient(CIO) {
install(JsonFeature) {
serializer = GsonSerializer()
}
}.use { client ->
return#use client.request("URL") {
method = HttpMethod.Get
}
}
Currently, the ktor client logging implementation is as below, and it works as intended but not what I wanted to have.
public class Logging(
public val logger: Logger,
public var level: LogLevel,
public var filters: List<(HttpRequestBuilder) -> Boolean> = emptyList()
)
....
private suspend fun logRequest(request: HttpRequestBuilder): OutgoingContent? {
if (level.info) {
logger.log("REQUEST: ${Url(request.url)}")
logger.log("METHOD: ${request.method}")
}
val content = request.body as OutgoingContent
if (level.headers) {
logger.log("COMMON HEADERS")
logHeaders(request.headers.entries())
logger.log("CONTENT HEADERS")
logHeaders(content.headers.entries())
}
return if (level.body) {
logRequestBody(content)
} else null
}
Above creates a nightmare while looking at the logs because it's logging in each line. Since I'm a beginner in Kotlin and Ktor, I'd love to know the way to change the behaviour of this. Since in Kotlin, all classes are final unless opened specifically, I don't know how to approach on modifying the logRequest function behaviour. What I ideally wanted to achieve is something like below for an example.
....
private suspend fun logRequest(request: HttpRequestBuilder): OutgoingContent? {
...
if (level.body) {
val content = request.body as OutgoingContent
return logger.log(value("url", Url(request.url)),
value("method", request.method),
value("body", content))
}
Any help would be appreciative
No way to actually override a private method in a non-open class, but if you just want your logging to work differently, you're better off with a custom interceptor of the same stage in the pipeline:
val client = HttpClient(CIO) {
install("RequestLogging") {
sendPipeline.intercept(HttpSendPipeline.Monitoring) {
logger.info(
"Request: {} {} {} {}",
context.method,
Url(context.url),
context.headers.entries(),
context.body
)
}
}
}
runBlocking {
client.get<String>("https://google.com")
}
This will produce the logging you want. Of course, to properly log POST you will need to do some extra work.
Maybe this will be useful for someone:
HttpClient() {
install("RequestLogging") {
responsePipeline.intercept(HttpResponsePipeline.After) {
val request = context.request
val response = context.response
kermit.d(tag = "Network") {
"${request.method} ${request.url} ${response.status}"
}
GlobalScope.launch(Dispatchers.Unconfined) {
val responseBody =
response.content.tryReadText(response.contentType()?.charset() ?: Charsets.UTF_8)
?: "[response body omitted]"
kermit.d(tag = "Network") {
"${request.method} ${request.url} ${response.status}\nBODY START" +
"\n$responseBody" +
"\nBODY END"
}
}
}
}
}
You also need to add a method from the Ktor Logger.kt class to your calss with HttpClient:
internal suspend inline fun ByteReadChannel.tryReadText(charset: Charset): String? = try {
readRemaining().readText(charset = charset)
} catch (cause: Throwable) {
null
}
First, I would like to ask you, if you think that this question deserves -1, be honest enough and please explain why.
This is the code for Application class:
class WeatherApp: Application() {
override fun onCreate() {
super.onCreate()
Realm.init(this)
Realm.setDefaultConfiguration(
RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.initialData(DatabaseInitTransaction(applicationContext))
.build()
)
}
}
This is the transaction class:
class DatabaseInitTransaction(private val applicationContext: Context): Realm.Transaction {
override fun execute(realm: Realm) {
Log.d("DatabaseInitTransaction", "execute called.")
val cityDao = CityDao(realm)
realm.deleteAll()
var stream :InputStream? = null
try {
stream = applicationContext.assets.open("city.list.json")
cityDao.createAllFromJson(CityEntity::class.java, stream)
} catch (thr: Throwable) {
} finally {
stream?.close()
}
}
}
So, on cold start method execute() of DatabaseInitTransaction class is never get called. I could not figure out why, please, help!
You must check first your Custom Applicatiom Class name is written in Manifest file.
in
<Application>
tag with Class name
I come from Java and I'm following a tutorial online regarding using the Volley library to make web requests in Android.
The instructor created the request variable like this:
val registerRequest = object : StringRequest(Method.POST, URL_REGISTER, Response.Listener {
println(it) // will print the response
complete(true)
}, Response.ErrorListener {
Log.d("ERROR", "Could not register user: $it")
complete(false)
}) {
override fun getBodyContentType(): String {
return "application/json; charset=utf-8"
}
override fun getBody(): ByteArray {
return requestBody.toByteArray()
}
}
I understand that he's creating a registerRequest variable of type StringRequest. But what I don't understand is why he prefixed StringRequest with object : here.
Also I understand that StringRequest constructor takes in an Int, String, Lambda, Lambda. After that it becomes confusing to me because the developer was able to declare some override methods after the constructor closes. Why did they do this? From what I can tell, this is similar to subclassing StringRequest, then writing the override methods there? Am I right?
Coming from Java, this way of writing code is quite unusual to me.