In this article i will show you the simple way to create webscocket android app (client side) and create websocket server side with Kotlin Ktor.
First, Let's Create Server Side (websocket server)
Open Intellij Ide or any ide you prefer then create a new project and select Ktor from left side then click next.
now you will see project plugins page, Search for 'Routing' then add it & search for 'WebSockets' and add it then click Create
after project builds you must have these files in your project:
Now open build.gradle.kts and add these dependencies
dependencies {
implementation("io.ktor:ktor-server-netty:$ktor_version")
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
implementation("io.ktor:ktor-server-websockets-jvm:$ktor_version")
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
implementation("ch.qos.logback:logback-classic:$logback_version")
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
}
Now we want to create application.conf
inside resources folder and add inside it these lines.
Ktor uses this file to determine the port on which it should run, and it also defines the entry point of our application.
ktor {
deployment {
port = 8080
port = ${?PORT}
}
application {
modules = [ com.example.ApplicationKt.module ]
}
}
now we have set the application entry point at com.example.ApplicationKt.module so let's go to Application.kt file, and this is our entry point.
you don't have to create
application.conf
file you can use fun main() to put your port & host (ip) instead of it.
then, go to Sockets.kt file and put this code in it
routing {
webSocket("/chat") {
send("You are connected!")
for(frame in incoming) {
frame as? Frame.Text ?: continue
val receivedText = frame.readText()
send("You said: $receivedText")
}
}
}
Now Our server is ready, Let's run it by opening application.kt file and press on the green play button and you must see this at the terminal
to check if our server is running properly or not, just open Postman & open new file & choose websocket request then put your server ws://url:port/chat in that scheme: ws://127.0.0.1:8080/chat and click connect
now if you send any message you will see it at realtime so our server is running correctly that's awesome.
But this is so simple and not a chat, Let's create more advanced websocket chat
first create new file in your main project package called Connections.kt then add this code in it
package com.example
import io.ktor.websocket.*
import java.util.concurrent.atomic.*
class Connection(val session: DefaultWebSocketSession) {
companion object {
val lastId = AtomicInteger(0)
}
val name = "user${lastId.getAndIncrement()}"
}
Go back to Sockets.kt and modify routing by adding these new codes
routing {
val connections = Collections.synchronizedSet<Connection?>(LinkedHashSet())
webSocket("/chat") {
println("Adding user!")
val thisConnection = Connection(this)
connections += thisConnection
try {
send("There are ${connections.count()} users here.")
for (frame in incoming) {
frame as? Frame.Text ?: continue
val receivedText = frame.readText()
val textWithUsername = "[${thisConnection.name}]: $receivedText"
connections.forEach {
it.session.send(textWithUsername)
println(textWithUsername)
}
}
} catch (e: Exception) {
println(e.localizedMessage)
} finally {
println("Removing $thisConnection!")
connections -= thisConnection
}
}
}
re-run the server and try sending messages with postman again to see the results.
now you see that the chat is little advanced and every user joins the chat takes unique username
Now Our Server Is Ready To Be Linked With Our Android App :)
Okay, Let's Create Client Side (Android Application)
Open Android studio or any Ide you have, then create a new project & select Kotlin as a primary language.
Then, open build.gradle app level file and put this implementation at dependencies.
dependencies {
implementation 'org.java-websocket:Java-WebSocket:1.4.0'
}
we will use View Binding in this project so don't forget to activate it at build.gradle file.
After gradle sync complete, open AndroidManifest.xml and put the internet permission.
<uses-permission android:name="android.permission.INTERNET" />
then we will store server url at strings.xml go to 'res > values > strings.xml' and add this line & modify your server url as you want
<string name="server_url" translatable="false">ws://127.0.0.1:8080</string>
Now we need to create chat ui so open activity_main.xml file and add this codes to add ListView, EditText and Button
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/linear2"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:padding="4dp">
<ListView
android:id="@+id/listview1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:choiceMode="none"
android:padding="8dp"/>
</LinearLayout>
<LinearLayout
android:id="@+id/linear1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="2dp">
<EditText
android:id="@+id/edit_msg"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Enter Message"
android:importantForAutofill="no"
android:padding="8dp"
android:textColor="#000000"
android:textColorHint="#607D8B"
android:textSize="12sp"
tools:ignore="TextFields" />
<Button
android:id="@+id/btnSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="Send"
android:textColor="#FFFFFF"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
after that Create new kotlin class called ChatWebSocketClient.kt and add these line of code
this class handle every action that happen like when connection open, close, error and when receive a message from server.
package com.encept.websocket_client
import org.java_websocket.client.WebSocketClient
import org.java_websocket.handshake.ServerHandshake
import java.net.URI
// initialize websocket client
class ChatWebSocketClient(serverUri: URI, private val messageListener: (String) -> Unit) : WebSocketClient(serverUri) {
override fun onOpen(handshakedata: ServerHandshake?) {
// When WebSocket connection opened
}
override fun onClose(code: Int, reason: String?, remote: Boolean) {
// When WebSocket connection closed
}
override fun onMessage(message: String?) {
// When Receive a message we handle it at MainActivity
messageListener.invoke(message ?: "")
}
override fun onError(ex: Exception?) {
// When An error occurred
}
fun sendMessage(message: String) {
send(message)
}
}
Now open MainActivity.kt and put these variables after class declaration
private lateinit var binding: ActivityMainBinding
private lateinit var webSocketClient: ChatWebSocketClient
private var AL: ArrayList<HashMap<String, Any>> = ArrayList()
Then, Add this onCreate function with these codes that initialize
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
//declare the ui widgets using binding
val btnSend = binding.btnSend
val editMsg = binding.editMsg
val listview1 = binding.listview1
// load server url from strings.xml
val serverUri = URI(getString(R.string.server_url))
webSocketClient = ChatWebSocketClient(serverUri) { message ->
// display incoming message in ListView
runOnUiThread {
run {
val _item = HashMap<String, Any>()
_item["message"] = message
AL.add(_item)
}
listview1.adapter = ListviewAdapter(AL)
}
}
// connect to websocket server
webSocketClient.connect()
btnSend.setOnClickListener {
try {
// send message to websocket server
webSocketClient.sendMessage(editMsg.text.toString())
editMsg.setText("")
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT).show()
}
}
}
- in case of you're using IDE that does not support auto import just add these imports to your MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.encept.websocketchat.databinding.ActivityMainBinding
import java.net.URI
import android.widget.*
import java.util.*
Now we need to close Websocket connection when activity destroy.
override fun onDestroy() {
super.onDestroy()
// close websocket connection
webSocketClient.close()
}
now we need to create listview custom view, so create new xml file called custom.xml and add this code we will just add TextView to display any message.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:orientation="horizontal"
tools:ignore="UselessParent">
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#000000"/>
</LinearLayout>
</LinearLayout>
After onCreate fun Add The ListView Adapter
class ListviewAdapter(private val _data: ArrayList<HashMap<String, Any>>) : BaseAdapter() {
override fun getCount(): Int {
return _data.size
}
override fun getItem(_index: Int): HashMap<String, Any> {
return _data[_index]
}
override fun getItemId(_index: Int): Long {
return _index.toLong()
}
override fun getView(_position: Int, _v: View?, _container: ViewGroup?): View? {
val _inflater = LayoutInflater.from(_container?.context)
var _view = _v
if (_view == null) {
_view = _inflater.inflate(R.layout.cul, _container, false)
}
val text2 = _view?.findViewById<TextView>(R.id.text2)
if (text2 != null) {
text2.text = _data[_position]["message"].toString()
}
return _view
}
}
That's all for today i hope this article helped you :)
Sources and References:
Ktor docs
websocket client
websocket server
Top comments (1)
if you have any question please contact me here.