I'm creating a second version of an Android/Windows remote control app using kotlin for client and server. I'm familiar with the basics of coroutines on android but to my surprise/unfortunateness, coroutines have behaved differently on the two platforms. I tested the EXACT same code and on android it just works but on the computer nothing happens
Main.kt
fun main(): Unit = runBlocking {
CoroutineScope(Job()).launch {
println("connecting server")
Servidor.ligar()
}
CoroutineScope(Job()).launch {
println("connecting client")
Cliente.ligar()
}
}
Server.kt
object Servidor {
suspend fun ligar() = withContext(Dispatchers.IO) {
val porta = 1777
var mensagem: String
val server = ServerSocket(porta)
println("Trying to connect through $porta...")
val client = server.accept()
val mPrintWriter = PrintWriter(client.getOutputStream(), true)
val br = BufferedReader(InputStreamReader(client.getInputStream()))
println("Connected by port: $porta")
while (br.readLine().also { mensagem = it } != null) {
println("The message: $mensagem")
if (mensagem == "bye") {
mPrintWriter.println("bye")
break
} else {
mensagem = "Server returns $mensagem"
mPrintWriter.println(mensagem)
}
}
mPrintWriter.close()
br.close()
client.close()
}
}
Client.kt
object Cliente {
suspend fun ligar() = withContext(Dispatchers.IO) {
val portNumber = 1777
var str = "initialize"
val mSocket = Socket(InetAddress.getLocalHost(), portNumber)
val br = BufferedReader(InputStreamReader(mSocket.getInputStream()))
val pw = PrintWriter(mSocket.getOutputStream(), true)
pw.println(str)
println(str)
while (br.readLine().also { str = it } != null) {
println(str)
pw.println("bye")
if (str == "bye") break
}
br.close()
pw.close()
mSocket.close()
}
}
The code for client/server connection works perfectly, when executed from an activity in android.
This is the output from Logcat when the code is run on android (Android Studio):
connecting server
connecting client
Trying to connect through 1777...
initialize
Connected by port: 1777
The message: initialize
Server returns initialize
The message: bye
bye
This is the Logcat output when the code is run on windows (Intellij IDEA)
connecting server
calling customer
Process finished with exit code 0
I'm clearly not knowing how to deal with coroutines outside the android environment, how can i make this work as intended?
Ps: Here is the list of libraries i'm using on desktop version on app:
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4
You are not waiting for your launched coroutines. When you are in a runBlocking, it only blocks to wait for it’s launched children. But you are creating new CoroutineScopes for launching coroutines, so they are not children of the runBlocking coroutine. They are fired off asynchronously. Then runBlocking returns and your app terminates immediately.
Related
I am using Health Connect to read records, like steps and exercises. I use Health Connect in a few different places in Kotlin, and the code generally looks something like:
suspend fun fetchStepData(
healthConnectClient: HealthConnectClient,
viewModel: StepViewViewModel,
): StepViewViewModel {
kotlin.runCatching {
val todayStart = Instant.now().atZone(ZoneId.systemDefault()).toLocalDate().atStartOfDay();
val response: ReadRecordsResponse<StepsRecord>
try {
response = healthConnectClient.readRecords(
ReadRecordsRequest(
StepsRecord::class,
timeRangeFilter = TimeRangeFilter.after(todayStart)
)
)
var steps: Long = 0;
if (response.records.isNotEmpty()) {
for (stepRecord in response.records) {
steps += stepRecord.count
}
}
return viewModel
} catch (e: Exception) {
Log.e("StepUtil", "Unhandled exception, ", e)
}
}
return viewModel
}
I have an update function that is run when focus changes to ensure that the app is in the foreground.
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
binding.root.invalidate()
val fragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_main)?.childFragmentManager?.primaryNavigationFragment
if (fragment is MyFragment) {
displayLoadingIndicator(true)
runBlocking {
if (fragment.fetchStepData(this#MainActivity.healthConnectClient, fragment.getViewModel()) != null) {
displayLoadingIndicator(false)
}
}
}
I have a loading indicator present when I am fetching the data.
I use a drawer, and if I wait about 15 seconds and press the drawer button corresponding with MyFragment, the application hangs on the loading indicator, never successfully dismissing it.
I've tried stepping through the application in debug mode, and as I do, I always hang on
response = healthConnectClient.readRecords(
ReadRecordsRequest(
StepsRecord::class,
timeRangeFilter = TimeRangeFilter.after(todayStart)
)
)
in fetchStepData. I did at one point have my application making multiple requests for HealthConnectClient.getOrCreate(context), but I have since consolidated them to one instantiation call. I'm thinking I may be reading the data wrong and maybe I need to use getChanges, or maybe I'm being rate limited. Does anyone have any insight? Thanks in advance!
I want to receive callerId callbacks in my Android application. I've followed an example provided by it's creators which is available here.
My listening function looks like this (exact copy but in Kotlin):
private fun startListening() {
val socket = DatagramSocket(null)
val address = InetSocketAddress("255.255.255.255", 3520)
socket.reuseAddress = true
socket.broadcast = true
socket.bind(address)
var looping = true
var buffer = ByteArray(65507)
while (looping) {
val dp = DatagramPacket(buffer, buffer.size)
try {
log.debug("Waiting for call")
socket.receive(dp)
val recString = String(dp.data, 0, dp.length)
log.debug("Received new message: $recString")
_callReceivedLive.postValue(recString)
} catch (e: Exception) {
log.error("Exception in CallerIdListener", e)
looping = false
}
}
}
After installing it on device with Android 5.1.1 Version everything seems fine. Every single call I mock using Ethernet Emulator is received by the application.
Now after firing this app on Samsung Galaxy Tab with Android Version 9 it only receives like 30% of calls.
Any idea what might be wrong?
I'm using ktor for a kotlin/native app. Currently I'm testing things out on the android side and because of that I have some code directly in the viewmodel. The app needs to listen to incoming udp datagrams but it doesn't seem to receive anything. What am I doing wrong in my implementation?
How I listen to the incoming data while testing:
viewModelScope.launch(Dispatchers.IO) {
aSocket(ActorSelectorManager(Dispatchers.IO)).udp()
.bind(InetSocketAddress("localhost", 40779))
.use { udpSocket ->
Log.d("hereUDP", udpSocket.localAddress.toString())
try {
while (!udpSocket.isClosed) {
val datagram = udpSocket.receive()
Log.d("hereUDP", "received")
val line = datagram.packet.readText()
Log.d("hereUDP", line)
if (line.toLowerCase() == "q")
udpSocket.close()
}
Log.d("hereUDP", "closed")
} catch (e:Throwable){
Log.d("hereUDP", "something went wrong", e)
udpSocket.close()
}
}
}
The code I actually wanted to get working:
coroutineScope {
launch(Dispatchers.IO + socket!!.socketContext) {
socket?.incoming
?.consumeEach {
val data = it.packet.readBytes()
val address = it.address as InetSocketAddress
listener?.onReceive(data, HostEndpoint(address.hostName))
}
}
}
What I did in my terminal:
➜ platform-tools nc -u -c 192.168.1.130 40779
bla
bla
^C
I have a TCP Server on Windows, and I want to send and receive text strings between the server and my Android device.
I spent alot of time searching for an example using Kotlin but I didn't find any useful code, so I'm now only able to create the socket and connect.
fun connect() {
try{
val soc = Socket("192.168.1.5", 1419)
val dout = DataOutputStream(soc.getOutputStream())
dout.writeUTF("1")
dout.flush()
dout.close()
soc.close()
}
catch (e:Exception){
e.printStackTrace()
}
}
You can check this simple example. Hope it'll help you!
Server:
fun main() {
val server = ServerSocket(9999)
println("Server running on port ${server.localPort}")
val client = server.accept()
println("Client connected : ${client.inetAddress.hostAddress}")
val scanner = Scanner(client.inputStream)
while (scanner.hasNextLine()) {
println(scanner.nextLine())
break
}
server.close()
}
Client:
fun main() {
val client = Socket("127.0.0.1", 9999)
client.outputStream.write("Hello from the client!".toByteArray())
client.close()
}
You can also do it with ktor, it's a kotlin based asynchronous framework. It uses coroutines natively which allow concurrency.
Use Kotlin 1.4 and ktor 1.6.0, add it to your build.gradle.kts:
plugins {
kotlin("jvm") version "1.4.32"
}
dependencies {
implementation("io.ktor:ktor-server-netty:1.6.0")
implementation("io.ktor:ktor-network:1.6.0")
}
Then you can use the sockets, it's still a bit experimental but it's getting there, with newer version ktor-network is now necessary.
Here is the code:
Server:
suspend fun server() {
val server = aSocket(ActorSelectorManager(Executors.newCachedThreadPool().asCoroutineDispatcher())).tcp()
.bind(InetSocketAddress("127.0.0.1", 2323))
println("Server running: ${server.localAddress}")
val socket = server.accept()
println("Socket accepted: ${socket.remoteAddress}")
val input = socket.openReadChannel()
val output = socket.openWriteChannel(autoFlush = true)
val line = input.readUTF8Line()
println("received '$line' from ${socket.remoteAddress}")
output.writeFully("$line back\r\n".toByteArray())
}
Client:
suspend fun client() {
val socket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp()
.connect(InetSocketAddress("127.0.0.1", 2323))
val input = socket.openReadChannel()
val output = socket.openWriteChannel(autoFlush = true)
output.writeFully("hello\r\n".toByteArray())
println("Server said: '${input.readUTF8Line()}'")
}
Run them both:
fun main() {
CoroutineScope(Dispatchers.Default).launch { server() }
runBlocking { client() }
}
When you run them, the client will send a message, the server will respond and you should see something like this:
Server running: /127.0.0.1:2323
Socket accepted: /127.0.0.1:56215
received 'hello' from /127.0.0.1:56215
Server said: 'hello back'
Find more example on their documentation simple echo server
There are 2 important things based on my experiment:
get permission in AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
create the socket from a background thread, the following works for me:
Executors.newSingleThreadExecutor().execute {
val socket = Socket("192.168.0.15", 50000)
val scanner = Scanner(socket.getInputStream())
val printWriter = PrintWriter(socket.getOutputStream())
while (scanner.hasNextLine()) {
Log.d(TAG, "${ scanner.nextLine() }")
}
}
This is the source code in GitHub.
There is a video of my experiment.
I am using Fuel to send and receive requests from an API.
However, I am not able to show Toast Messages or AlertDialogs, if the request was not successful.
Sample Code:
private fun validatePassword(): Boolean {
var minPasswordLength = 0
val password = findViewById<EditText>(R.id.input_password_register).text.toString()
val password_repeat: String = findViewById<EditText>(R.id.input_password_repeat_register).text.toString()
"/auth/password.json".httpGet().responseString { request, response, result ->
//do something with response
request.header(mapOf("Content-Type" to "application/json"))
Log.println(Log.ASSERT, "password_Curl", request.cUrlString())
when (result) {
is Result.Failure -> {
val data = response.data.toString(Charsets.UTF_8)
Log.println(Log.ASSERT, "Response_Password_Fail", data)
val jelement = JsonParser().parse(data)
val jobject = jelement.asJsonObject
Toast.makeText(this, jobject.get("Error").asString, Toast.LENGTH_SHORT).show()
}
is Result.Success -> {
val data = response.data.toString(Charsets.UTF_8)
Log.println(Log.ASSERT, "Response_Passwd_Succes", data)
val jelement = JsonParser().parse(data)
val jobject = jelement.asJsonObject
minPasswordLength = jobject.get("minimal_length").asInt
}
}
}
return password.length >= minPasswordLength && password.equals(password_repeat)
}
I have tried to run the makeText command on the UIThread using:
runOnUiThread{Toast.makeText(this, jobject.get("Error").asString, Toast.LENGTH_SHORT).show()}
This did not work either.
I do however get to the code while debugging:
Thanks for the help! Cheers.
UPDATE:
While debugging I noticed that minPasswordLength stays 0 even though "minimal_length" from the API has the value 8. So maybe it is a Threading problem?
Use the implementation 'com.github.kittinunf.fuel:fuel-android:1.15.0 instead of implementation 'com.github.kittinunf.fuel:fuel:1.15.0' in the dependencies (build.gradle file).
The JVM implementation of the library doesn't automatically invoke the handlers in the UI thread.
This works for me even in the JVM implementation:
runOnUiThread { Toast.makeText(this, "Some text", Toast.LENGTH_SHORT).show() }
I think that it didn't work for you because the JSON doesn't contain the "Error" but "details" field.