Rabikant

Posted on December 3rd

How to make a WebSocket tester in Android

"Lets learn How to make a WebSocket tester in Android"

Without WebSockets, real-time communication between clients and servers is impossible, which are critically important for online chating, real time data streaming, and multiplayer gaming. In this blog post, I will explain how to build a WebSocket tester using Jetpack Compose and PieSocket and for a better understanding, I will divide the code into several parts.

Project Overview

We’ll implement an Android application that:

  1. Uses API key to connect with PieSocket for the Publication of announcements to channel or channels.
  2. Sends messages to the WebSocket server as well.
  3. Messages: shown received messages and records sent messages.
  4. Based on Jetpack Compose which provides a modern declarative approach to building user interface.

When developing a WebSocket tester app using Jetpack Compose, and PieSocket, there are things that you will need to add the correct dependencies and make sure that the application can communicate over the network. In this guide we’ll go over how to add dependencies like Material Design and PieSocket and how to configure an AndroidManifest to include Internet permission.

Adding Dependencies

That is why dependencies regarding the specific libraries which should be used in the given project are reported in the build.gradle. As promised here is the step by step guide to understanding how to add necessary dependencies for Material Design and PieSocket.

Open build.gradle (Module: app)

  1. When working in the Android Studio, switch first to the ‘Project’ view and there open the section ‘Gradle Scripts’.
  2. Open the file labeled build.gradle (Module: app).

Add Material Design Dependency

Material Design offers basic elements for the creation of Android’s User Interface. Add the following line in the dependencies block:

implementation ("com.google.android.material:material:1.12.0")

Add PieSocket SDK Dependency

PieSocket simplifies WebSocket integration in Android. Include this dependency in the same dependencies block:

implementation("com.piesocket:channels-sdk:1.0.5")

Final dependencies Block

Your dependencies block should look similar to this:


dependencies {
    implementation("androidx.compose.material3:material3:1.2.0")
    implementation("androidx.compose.ui:ui:1.5.0")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
    implementation("com.google.android.material:material:1.12.0")
    implementation("com.piesocket:channels-sdk:1.0.5")
}

Sync the Project

After adding the dependencies, click the Sync Now button that appears at the top of the editor. This ensures the libraries are downloaded and included in your project.

Configuring Internet Access

For your app to communicate with the PieSocket WebSocket server, it needs permission to access the internet. This is done by modifying the AndroidManifest.xml file.

Open AndroidManifest.xml

  1. Navigate to the manifests folder under app/src/main.
  2. Open the AndroidManifest.xml file.

Add Internet Permission

Include the following permission inside the tag:

<uses-permission android:name="android.permission.INTERNET" />

Final AndroidManifest.xml

Your file should look like this:

<manifest xmlns:android="<http://schemas.android.com/apk/res/android>"
    package="com.example.piesockettester">

    <!-- Internet permission -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.PieSocketTester">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Verifying the Configuration

After completing the setup:

  1. Rebuild the Project: Go to Build > Rebuild Project to ensure all configurations are applied correctly.
  2. Check for Sync Errors: Ensure there are no errors in the Gradle console.

Why These Dependencies and Permissions?

Material Design Dependency

The Material Design library enables the use of modern UI components like:

  • TextField
  • Button
  • Card These elements help create a user-friendly interface with consistent styling.

PieSocket SDK Dependency

The PieSocket SDK simplifies WebSocket integration by:

  • Providing ready-to-use methods for connecting and subscribing to channels.
  • Handling event listeners for real-time updates.
  • Abstracting the complexity of WebSocket protocols.

Internet Permission

This permission is essential because:

  • WebSocket communication requires internet access to connect to the PieSocket servers.
  • Without this permission, the app cannot perform network operations, resulting in connection failures.

Bringing It All Together

With these configurations:

  • The Material Design library ensures a polished, modern UI.
  • The PieSocket SDK streamlines WebSocket functionality.
  • Internet access allows real-time communication with PieSocket servers.

Here’s a complete example showing how these dependencies and permissions integrate into the application:

build.gradle

dependencies {
    implementation("androidx.compose.material3:material3:1.2.0")
    implementation("androidx.compose.ui:ui:1.5.0")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
    implementation("com.google.android.material:material:1.12.0")
    implementation("com.piesocket:channels-sdk:1.0.5")
}

AndroidManifest.xml

<manifest xmlns:android="<http://schemas.android.com/apk/res/android>"
    package="com.example.piesockettester">

    <!-- Internet permission -->
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.PieSocketTester">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Dependencies and Setup

Before diving into the code, ensure you have the following:

  1. PieSocket Dependency: Add the PieSocket library to your build.gradle file:Replace VERSION with the latest PieSocket version.

    implementation 'com.piesocket:channels:VERSION'
    
  2. Compose Material3: Ensure Material3 is included for the Compose UI.

Setting Up the Main Activity

The MainActivity serves as the app's entry point, handling the WebSocket logic and passing it to the UI.

Code Explanation

class MainActivity : ComponentActivity() {
    private var piesocket: PieSocket? = null
    private var channel: Channel? = null
    private var isConnected by mutableStateOf(false)
    private var receivedMessage by mutableStateOf("")
    private var sentMessages = mutableStateListOf<String>()
    private var errorMessage by mutableStateOf("")
  • PieSocket: Manages the WebSocket connection.
  • State Variables:
    • isConnected: Tracks the WebSocket connection status.
    • receivedMessage: Holds the latest message received.
    • sentMessages: Logs messages sent by the user.
    • errorMessage: Displays any connection or operational errors.

Connecting to PieSocket

private fun connectToPieSocket(apiKey: String) {
        try {
            val options = PieSocketOptions().apply {
                this.apiKey = apiKey
                this.clusterId = "free.blr2"
            }

            piesocket = PieSocket(options)
            channel = piesocket?.join("default") // Default channel name

            channel?.listen("system:connected", object : PieSocketEventListener() {
                override fun handleEvent(event: PieSocketEvent) {
                    isConnected = true
                    receivedMessage = "Connected to PieSocket channel"
                    errorMessage = ""
                }
            })

            channel?.listen("message", object : PieSocketEventListener() {
                override fun handleEvent(event: PieSocketEvent) {
                    receivedMessage = "Received: ${event.data?.toString()}"
                }
            })

            channel?.listen("system:error", object : PieSocketEventListener() {
                override fun handleEvent(event: PieSocketEvent) {
                    errorMessage = "Error: ${event.data?.toString()}"
                }
            })
        } catch (e: Exception) {
            isConnected = false
            errorMessage = "Failed to connect: ${e.message}"
            e.printStackTrace()
        }
    }
  • Validation: Ensures API key and channel name are not empty.
  • PieSocket Initialization: Configures and connects to a specific PieSocket channel.
  • Event Listeners:
    • system:connected: Updates UI when a connection is established.
    • message: Processes incoming messages.
    • system:error: Handles connection errors.

Sending Messages

    private fun sendMessage(message: String) {
        if (isConnected) {
            val event = PieSocketEvent("message").apply {
                data = message
            }
            channel?.publish(event)
            sentMessages.add("Sent: $message")
        } else {
            errorMessage = "Not connected to the WebSocket"
        }
    }
  • Connection Check: Ensures the WebSocket is active before sending messages.
  • Message Publishing: Sends the message to the connected channel.
  • Logging: Adds the message to the sentMessages list for display.

Designing the UI with Compose

Jetpack Compose is used to create a reactive, declarative UI. The WebSocketTesterApp composable provides a seamless interface for the user.

Setting Up the Layout

@Composable
fun WebSocketTesterApp(
    connectToPieSocket: (String) -> Unit,
    sendMessage: (String) -> Unit,
    isConnected: () -> Boolean,
    receivedMessage: () -> String,
    sentMessages: () -> List<String>,
    errorMessage: () -> String
) {
    var message by remember { mutableStateOf("") }
    var apiKey by remember { mutableStateOf("") }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Top
    ) 
  • Composable Parameters: Functions and states passed down from the MainActivity.
  • State Management: Local state variables (message, apiKey, channelName, etc.) are defined using remember.

Displaying Connection Status

if (isConnected()) {
            Text(
                text = "Connected",
                color = Color.Green,
                fontWeight = FontWeight.Bold,
                modifier = Modifier.padding(bottom = 16.dp)
            )
        } else {
            Text(
                text = "Not Connected",
                color = Color.Red,
                fontWeight = FontWeight.Bold,
                modifier = Modifier.padding(bottom = 16.dp)
            )
        }
  • Dynamic Status: Updates UI to reflect the WebSocket connection state.

Error Messages

        if (errorMessage().isNotEmpty()) {
            Text(
                text = errorMessage(),
                color = Color.Red,
                fontWeight = FontWeight.Bold,
                modifier = Modifier.padding(bottom = 16.dp)
            )
        }
  • Displays any errors encountered during connection or message handling.

Input Fields and Buttons

    TextField(
            value = apiKey,
            onValueChange = { apiKey = it },
            label = { Text("API Key") },
            modifier = Modifier
                .fillMaxWidth()
                .padding(bottom = 16.dp)
        )

        Button(
            onClick = { connectToPieSocket(apiKey) },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Connect to PieSocket")
        }
  • API Key: Inputs for PieSocket credentials.
  • Connect Button: Initiates the connection process using the provided credentials.

Sending Messages

Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(vertical = 16.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            TextField(
                value = message,
                onValueChange = { message = it },
                label = { Text("Message") },
                modifier = Modifier
                    .weight(1f)
                    .padding(end = 8.dp)
            )
            Button(
                onClick = { sendMessage(message) },
                modifier = Modifier.padding(start = 8.dp)
            ) {
                Text("Send")
            }
        }
  • Message Input: Field for typing the message to be sent.
  • Send Button: Publishes the message to the WebSocket server.

Displaying Messages

        if (receivedMessage().isNotEmpty()) {
            Text(
                text = "Received Message:",
                fontSize = 20.sp,
                modifier = Modifier.padding(vertical = 8.dp)
            )
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .background(Color.White)
                    .padding(vertical = 4.dp),
                shape = RoundedCornerShape(8.dp),
            ) {
                Text(
                    text = receivedMessage(),
                    modifier = Modifier.padding(8.dp),
                    fontSize = 16.sp
                )
            }
        }
  • Received Message: Displays the latest message from the WebSocket server.

Sent Messages Log

 if (sentMessages().isNotEmpty()) {
            Text(
                text = "Sent Messages:",
                fontSize = 20.sp,
                modifier = Modifier.padding(vertical = 8.dp)
            )
            Column(modifier = Modifier.fillMaxWidth()) {
                sentMessages().forEach { msg ->
                    Card(
                        modifier = Modifier
                            .fillMaxWidth()
                            .background(Color.White)
                            .padding(vertical = 4.dp),
                        shape = RoundedCornerShape(8.dp),
                    ) {
                        Text(
                            text = msg,
                            modifier = Modifier.padding(8.dp),
                            fontSize = 16.sp
                        )
                    }
                }
            }
        }
  • Message History: Displays all messages sent during the session.

Previewing the UI

The DefaultPreview function previews the UI in Android Studio.



@Composable
fun DefaultPreview() {
    WebSocketTesterApp(
        connectToPieSocket = { _ -> },
        sendMessage = {},
        isConnected = { false },
        receivedMessage = { "" },
        sentMessages = { emptyList() },
        errorMessage = { "" }
    )
}

Conclusion

This WebSocket tester app demonstrates the power of Jetpack Compose and PieSocket for real-time communication. By modularizing the logic and UI, the app is both extensible and user-friendly. You can enhance it further by adding features like authentication, custom event handling, or persistent message storage.

Complete Code

The project is available on our GitHub : https://github.com/piehostHQ/android-websocket-tester

Comments

Leave a comment.

Share your thoughts or ask a question to be added in the loop.