Squareup Retrofit/Moshi example process not shutting down - kotlin

I have a working "hello world" App for Squareup's Retrofit2 http client (https://square.github.io/retrofit/) using JSON unmarschalling with Moshi (https://github.com/square/moshi) ...
Everything works fine, but without the System.exit(0) the App will run forever.
Any idea why this is??
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
import retrofit2.http.Path
fun main() {
MainApp().doIt()
println("main READY!")
System.exit(0)
}
interface GitHubService {
#GET("users/{user}/repos")
fun listRepos(#Path("user") user: String): Call<List<Repo>>
}
#JsonClass(generateAdapter = true)
data class Repo(val full_name: String, val private: Boolean)
class MainApp {
fun doIt() {
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
val retrofitGithub = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
val githubService = retrofitGithub.create(GitHubService::class.java)
val reposCall: Call<List<Repo>> = githubService.listRepos("HoffiMuc")
val response = reposCall.execute()
if (response.isSuccessful) {
val repos = response.body() ?: ArrayList<Repo>()
if ( repos.isNotEmpty() ) {
for (repo in repos) {
println(repo)
}
} else {
println("Response Body is null or no repo in result list")
}
} else {
println(response.headers())
}
}
}

What happens if you add an explicit dependency on the latest release of OkHttp? The old version Retrofit depends on used non-daemon threads.

Related

Using async func in Compose Web

I should use Ktor client in Compose Web. But, It can't be called in Compose Web due to async/non-async problem.
Environment is template project made by IntelliJ IDEA.
First, I use this:
val client=HttpClient(Js){
install(ContentNegotiation){
json()
}
}
suspend fun shorterCall(url:String):String{
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
suspend fun main() {
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({ style {
height(100.vh)
margin(0.px)
width(100.vw)
} }) {
Input(type = InputType.Url) {
onInput {
val input=it.value.trim()
if(input.startsWith("http"))
i=shorterCall(input)
else
i="NaN"
}
}
Text(i)
}
}
}
Then, I got that error:
Suspend function can be called only within a coroutine body.
So, I tried another one:
import kotlinx.coroutines.*
fun shorterCall(url:String):String{
var ret:String
suspend fun t():String {
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
runBlocking{
ret=t()
}
return ret
}
//main func is same as upper one.
Then, I got that error:
Unresolved reference: runBlocking
+editing body 1: When I use GlobalScope.launch or js("JSCode"), It raise that error:
e: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
e: java.lang.IllegalStateException: FATAL ERROR: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
(a lot of internal errors bellow)
You can use the GlobalScope.launch() method to launch a job for a request in a browser environment:
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import io.ktor.client.*
import io.ktor.client.engine.js.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable
fun main() {
val client = HttpClient(Js) {}
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({
style {
height(100.vh)
margin(0.px)
width(100.vw)
}
}) {
Input(type = InputType.Url) {
onInput {
val input = it.value.trim()
if (input.startsWith("http")) {
GlobalScope.launch {
i = client.shorterCall(input)
}
} else {
i = "NaN"
}
}
}
Text(i)
}
}
}
suspend fun HttpClient.shorterCall(url: String): String {
val response = get(url) {
contentType(ContentType.Application.Json)
}
return response.bodyAsText()
}

Type adapter warning when registering java.util.List

When trying to register a type adapter, where list is java.util.List
GsonBuilder().registerTypeAdapter(object : TypeToken<List<MyObject>>() {}.type, MyObjectsTypeAdapter())
I get the following warning:
This class shouldn't be used in Kotlin. Use kotlin.collections.List or
kotlin.collections
My type adapter uses a kotlin.collections.list.
class MyObjectsTypeAdapter: TypeAdapter<List<MyObject>>() {
...
}
However if I don't use java.util.List in my type adapter, gson does not match the type correctly.
Am I doing something else wrong when registering my type adapter?
Below is small demo showing Retrofit usage with Gson. A few important points:
To use Gson as converter you have to add the com.squareup.retrofit2:converter-gson dependency and add the converter factory with addConverterFactory(GsonConverterFactory.create()); see Retrofit documentation
The custom TypeAdapterFactory in the example below is not needed; it just shows how you could customize the Gson instance
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import retrofit2.*
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
data class MyObject(
val message: String
)
interface DemoService {
#GET("demo")
fun demo(): Call<List<MyObject>>
}
fun main() {
val port = 8080
// Start a demo server returning the JSON response
embeddedServer(Netty, port) {
routing {
get("/demo") {
call.respondText("[{\"message\":\"Hello from server\"}]")
}
}
}.start(wait = false)
val gson = GsonBuilder()
.registerTypeAdapterFactory(object: TypeAdapterFactory {
override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType != MyObject::class.java) {
return null
}
#Suppress("UNCHECKED_CAST") // safe due to type check at beginning
val delegate = gson.getDelegateAdapter(this, type) as TypeAdapter<MyObject>
val adapter = object: TypeAdapter<MyObject>() {
override fun write(writer: JsonWriter, value: MyObject?) {
return delegate.write(writer, value)
}
override fun read(reader: JsonReader): MyObject? {
val value: MyObject? = delegate.read(reader)
return value?.copy(message = "custom-prefix: ${value.message}")
}
}
#Suppress("UNCHECKED_CAST") // safe due to type check at beginning
return adapter as TypeAdapter<T>
}
})
.create()
val retrofit = Retrofit.Builder()
.baseUrl("http://localhost:$port/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val service = retrofit.create(DemoService::class.java)
val response = service.demo().execute()
println(response.body())
}
The following dependencies were used:
// For demo server
implementation("org.slf4j:slf4j-simple:2.0.0")
implementation("io.ktor:ktor-server-core:2.1.0")
implementation("io.ktor:ktor-server-netty:2.1.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.google.code.gson:gson:2.9.1")

Where should i place the code to observe internet connection so that the user is notified if the device is online or offline?

I have the code to monitor if internet is available. It returns a LiveData and it is observed in the MainActivity . The code is given below.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding=DataBindingUtil.setContentView(this,R.layout.activity_main)
NetworkStatusHelper(this#MainActivity).observe(this, Observer {
when(it){
NetworkStatus.Available-> Snackbar.make(binding.root, "Back online", Snackbar.LENGTH_LONG).show()
NetworkStatus.Unavailable-> Snackbar.make(binding.root, "No Internet connection", Snackbar.LENGTH_LONG).show()
}
})
}
NetworkHelper
package com.todo.utils.networkhelper
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.util.Log
import androidx.lifecycle.LiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket
class NetworkStatusHelper(private val context: Context): LiveData<NetworkStatus>() {
var connectivityManager: ConnectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private lateinit var connectivityManagerCallback: ConnectivityManager.NetworkCallback
val validNetworkConnections: ArrayList<Network> = ArrayList()
fun getConnectivityCallbacks() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
val networkCapability =
connectivityManager.getNetworkCapabilities(network)
val hasNetworkConnection =
networkCapability?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
?: false
if (hasNetworkConnection) {
determineInternetAccess(network)
}
}
override fun onLost(network: Network) {
super.onLost(network)
validNetworkConnections.remove(network)
announceNetworkStatus()
}
// override fun onCapabilitiesChanged(
// network: Network,
// networkCapabilities: NetworkCapabilities
// ) {
// super.onCapabilitiesChanged(network, networkCapabilities)
//
// Log.d("validNetworkConnection","onCapabilitiesChanged size "+validNetworkConnections.size)
//
//
// if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
// determineInternetAccess(network)
// } else {
// validNetworkConnections.remove(network)
// }
// announceNetworkStatus()
// }
private fun determineInternetAccess(network: Network) {
CoroutineScope(Dispatchers.IO).launch {
if (InternetAvailability.check()) {
withContext(Dispatchers.Main) {
validNetworkConnections.add(network)
announceNetworkStatus()
}
}
}
}
fun announceNetworkStatus() {
if (validNetworkConnections.isNotEmpty()) {
postValue(NetworkStatus.Available)
} else {
postValue(NetworkStatus.Unavailable)
}
}
}
} else {
TODO("VERSION.SDK_INT < LOLLIPOP")
}
override fun onActive() {
super.onActive()
connectivityManagerCallback = getConnectivityCallbacks()
val networkRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
NetworkRequest
.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
} else {
TODO("VERSION.SDK_INT < LOLLIPOP")
}
connectivityManager.registerNetworkCallback(networkRequest, connectivityManagerCallback)
}
override fun onInactive() {
super.onInactive()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
connectivityManager.unregisterNetworkCallback(connectivityManagerCallback)
}
}
object InternetAvailability {
fun check() : Boolean {
return try {
val socket = Socket()
socket.connect(InetSocketAddress("8.8.8.8",53))
socket.close()
true
} catch ( e: Exception){
e.printStackTrace()
false
}
}
}
}
The problem is here is , the Snackbar is displayed even when the app is opened for the first time .I don't want the Snackbar to be displayed when the app is opened for the first time when network is available. If network is unavailable, then the Snackbar should be displayed even when the app is opened for the first time.
Can someone help to improve the code with correct logic to implement the same.
If your helper class is a Flow, then you can use Flow operators to easily customize its behavior. You should keep the instance of your helper class in a ViewModel so it can maintain its state when there are configuration changes.
Here's a Flow version of your class's functionality. I actually just made it into a function, because I think that's simpler.
I removed the List<Network> but you can add it back in if you think it's necessary. I don't think it makes sense to keep a List that can only ever hold at most one item. A device cannot have multiple simultaneous network connections. If you do need it, it won't work for pre-Lollipop, so you will have to juggle differing functionality and probably do need a class instead of just a function.
I think you can probably remove the checkAvailability() function as it is redundant, but I put it in because you have it.
I added a pre-Lollipop version based on a broadcast receiver, since you seem to want to add support for that.
#get:RequiresPermission("android.permission.ACCESS_NETWORK_STATE")
val Context.networkStatus: Flow<NetworkStatus> get() = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> getNetworkStatusLollipop(this)
else -> getNetworkStatusPreLollipop(this)
}
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
#RequiresPermission("android.permission.ACCESS_NETWORK_STATE")
private fun getNetworkStatusLollipop(context: Context): Flow<NetworkStatus> = callbackFlow {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val callback = object : ConnectivityManager.NetworkCallback() {
private var availabilityCheckJob: Job? = null
override fun onUnavailable() {
availabilityCheckJob?.cancel()
trySend(NetworkStatus.Unavailable)
}
override fun onAvailable(network: Network) {
availabilityCheckJob = launch {
send(if(checkAvailability()) NetworkStatus.Available else NetworkStatus.Unavailable)
}
}
override fun onLost(network: Network) {
availabilityCheckJob?.cancel()
trySend(NetworkStatus.Unavailable)
}
}
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
#RequiresPermission("android.permission.ACCESS_NETWORK_STATE")
private fun getNetworkStatusPreLollipop(context: Context): Flow<NetworkStatus> = callbackFlow {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val receiver = object: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
launch {
if (connectivityManager.activeNetworkInfo?.isConnectedOrConnecting == true) {
send(if(checkAvailability()) NetworkStatus.Available else NetworkStatus.Unavailable)
} else {
send(NetworkStatus.Unavailable)
}
}
}
}
context.registerReceiver(receiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
awaitClose { context.unregisterReceiver(receiver) }
}
private suspend fun checkAvailability() : Boolean = withContext(Dispatchers.IO) {
try {
Socket().use {
it.connect(InetSocketAddress("8.8.8.8", 53))
}
true
} catch (e: Exception){
e.printStackTrace()
false
}
}
Then in your ViewModel, you can use Flow operators to easily expose a Flow that skips initial NetworkStatus.Available values:
class MyViewModel(application: Application): AndroidViewModel(application) {
val changedNetworkStatus = application.context.networkStatus
.dropWhile { it == NetworkStatus.Available } // ignore initial available status
.shareIn(viewModelScope, SharingStarted.Eagerly, 1) // or .asLiveData() if preferred
}

Receiver class does not define or inherit an implementation of the resolved method 'abstract boolean getDevelopmentMode()'

I'm trying to run local server with KTOR and to cover it with tests. I wrote some code and after writing some tests, the tests successfully raised the local server and passed. However, if I try to start a local server, I get this error
Exception in thread "main" java.lang.AbstractMethodError: Receiver
class io.ktor.server.engine.ApplicationEngineEnvironmentReloading does
not define or inherit an implementation of the resolved method
'abstract boolean getDevelopmentMode()' of interface
io.ktor.application.ApplicationEnvironment.
I attach the code and the stack trace from below
server.kt
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.gson.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
gson()
}
routing {
val controller = Controller()
get("/converter{from}{to}") {
try {
val fromCurrency = call.request.queryParameters["from"]
val toCurrency = call.request.queryParameters["to"]
val rate = controller.converter(fromCurrency, toCurrency)
val response = Response(
"1 $fromCurrency = $rate $toCurrency",
null
)
call.respond(HttpStatusCode.OK, response)
} catch (e: ControllerException) {
val response = Response(
null,
e.message
)
call.respond(HttpStatusCode.BadRequest, response)
}
}
get {
call.respond(HttpStatusCode.NotFound, Response())
}
}
}
#Serializable
data class Response(
#SerialName("converterResponse")
val converterResponse: String? = null,
#SerialName("errorMessage")
val errorMessage: String? = null
)
converterTest.kt
import com.google.gson.Gson
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.server.testing.*
import kotlin.test.Test
import kotlin.test.assertEquals
class ConverterTest {
#Test
fun `Identical Correct Currencies`() {
withTestApplication(Application::module) {
handleRequest(HttpMethod.Get, "/converter?from=USD&to=USD").apply {
assertEquals(HttpStatusCode.OK, response.status())
val expectedResponse = Response("1 USD = 1 USD")
assertEquals(
Gson().toJson(expectedResponse),
response.content
)
}
}
}
}
stacktrace
Exception in thread "main" java.lang.AbstractMethodError: Receiver class io.ktor.server.engine.ApplicationEngineEnvironmentReloading does not define or inherit an implementation of the resolved method 'abstract boolean getDevelopmentMode()' of interface io.ktor.application.ApplicationEnvironment.
at io.ktor.application.Application.<init>(Application.kt:20)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:269)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:125)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:245)
at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:126)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:26)
at ServerKt.main(server.kt:11)
build.gradle
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.5.30" // or kotlin("multiplatform") or any other kotlin plugin
application
}
group = "me.admin"
version = "1.0-SNAPSHOT"
repositories {
jcenter()
mavenCentral()
maven { url = uri("https://dl.bintray.com/kotlin/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
}
dependencies {
val kotlin_version = "1.5.30"
val ktor_version = "1.6.3"
implementation("io.ktor:ktor-server-netty:1.4.0")
implementation("io.ktor:ktor-client-cio:1.4.0")
implementation("io.ktor:ktor-html-builder:1.4.0")
implementation("io.ktor:ktor-client-serialization:1.3.2-1.4-M2")
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.2")
implementation("org.slf4j:slf4j-api:2.0.0-alpha5")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC")
implementation(kotlin("stdlib-jdk8"))
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
testImplementation("io.ktor:ktor-server-test-host:$ktor_version")
implementation("io.ktor:ktor-gson:$ktor_version")
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
application {
mainClassName = "ServerKt"
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
}
The dependency io.ktor:ktor-server-netty:1.4.0 causes the AbstractMethodError. To fix it just use the same version for all Ktor dependencies:
implementation("io.ktor:ktor-server-netty:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
implementation("io.ktor:ktor-html-builder:$ktor_version")
implementation("io.ktor:ktor-client-serialization:$ktor_version")

kotlin flow is not emitting values from different function

I am trying to implement kotlin stateflow, but not able to know the reason why it is not working.
Current output:
verificatio 34567
Expected Output:
verificatio 34567
verificatio failed
package stateflowDuplicate
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val firebasePhoneVerificationListener = FirebaseOTPVerificationOperation1()
val oTPVerificationViewModal = OTPVerificationViewModal1(firebasePhoneVerificationListener)
oTPVerificationViewModal.fail()
}
class OTPVerificationViewModal1(private val firebasePhoneVerificationListener: FirebaseOTPVerificationOperation1) {
init {
startPhoneNumberVerification()
setUpListener()
}
suspend fun fail(){
firebasePhoneVerificationListener.fail()
}
private fun startPhoneNumberVerification() {
firebasePhoneVerificationListener.initiatePhoneVerification("34567")
}
private fun setUpListener() {
runBlocking {
firebasePhoneVerificationListener.phoneVerificationFailed.collect {
println("verificatio $it")
}
}
}
}
Second class
package stateflowDuplicate
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.runBlocking
class FirebaseOTPVerificationOperation1 {
private val _phoneVerificationFailed = MutableStateFlow<String?>(null)
val phoneVerificationFailed: StateFlow<String?>
get() = _phoneVerificationFailed
fun initiatePhoneVerification(phoneNumber: String) {
_phoneVerificationFailed.value = phoneNumber
}
suspend fun fail() {
delay(200L)
_phoneVerificationFailed.value = "failed"
}
}
Tried to understand the concept from these links,
Link1
Link2
You have to start a new coroutine to call collect because the coroutine will keep collecting values until its Job gets cancelled. Don't use runBlocking builder for that, use launch builder instead:
private fun setUpListener() = launch {
firebasePhoneVerificationListener.phoneVerificationFailed.collect {
println("verificatio $it")
}
}
Now to make it work you need to implement CoroutineScope interface in your class. You can do it like this:
class OTPVerificationViewModal1(
private val firebasePhoneVerificationListener: FirebaseOTPVerificationOperation1
): CoroutineScope by CoroutineScope(Dispatchers.Default) {
...
}
If you run it now you will get this output:
verificatio 34567
verificatio failed