How to Build Tinode WebRTC App with Go and JavaScript?

Learn how to build real-time messaging applications using Tinode and WebRTC. This comprehensive guide covers server setup, creating a join screen, adding controls, and displaying participants across various platforms including JavaScript, Swift, Android, and Python.

Introduction to Tinode WebRTC Technology

In today's fast-paced digital world, real-time communication is crucial for creating responsive and engaging user experiences. This is where Tinode, combined with WebRTC technology, comes into play. Tinode is an open-source instant messaging server that facilitates seamless communication across various platforms. By leveraging WebRTC (Web Real-Time Communication), Tinode enables developers to build robust, real-time messaging applications that can handle video, voice, and data exchange directly between users' devices.

What is Tinode WebRTC?

Tinode is designed to provide a scalable, efficient, and easy-to-use instant messaging solution. It supports multiple programming languages, including Go for the server-side and JavaScript, Swift, Android, and Python for client applications. This versatility allows developers to create cross-platform messaging apps with ease. At the heart of Tinode's real-time communication capabilities lies WebRTC, a powerful technology that enables peer-to-peer connections without the need for intermediary servers.
WebRTC is a free, open-source project that provides web applications and websites with real-time communication capabilities via simple application programming interfaces (APIs). It allows audio, video, and data sharing between browser clients (peer-to-peer) and supports various protocols to ensure security and reliability. When integrated with Tinode, WebRTC enhances the functionality of messaging apps by enabling features such as live video calls, voice chats, and instant file transfers.

The Importance of WebRTC in Real-Time Communication

WebRTC has revolutionized the way real-time communication is handled on the web. Its ability to establish direct connections between users' devices means that data can be transferred quickly and securely, with minimal latency. This is particularly important for applications that require real-time interaction, such as video conferencing, online gaming, and collaborative tools.
Tinode takes advantage of WebRTC's capabilities to provide a comprehensive instant messaging solution. By using WebRTC, Tinode ensures that messages, whether text, audio, or video, are delivered in real-time, creating a smooth and engaging user experience. Additionally, WebRTC's built-in security features, including encryption and secure media streams, help protect user data and maintain privacy.
In summary, Tinode WebRTC combines the robust messaging capabilities of Tinode with the real-time communication power of WebRTC. This synergy enables developers to build feature-rich, cross-platform messaging applications that deliver fast, reliable, and secure communication experiences. Whether you're developing a simple chat app or a complex collaboration tool, Tinode WebRTC provides the foundation you need to create a high-performance, real-time communication solution.

Getting Started with the Code!

Creating a new Tinode app leveraging WebRTC technology involves several key steps, from setting up your development environment to understanding the project structure and architecture. This section will guide you through the initial setup and help you get started with building your own Tinode-based messaging application.

Create a New Tinode App

To begin, you'll need to create a new Tinode app. This involves setting up the server using Go and preparing the client-side code for JavaScript, Swift, Android, or Python, depending on your target platform.

Install

Go (Server)

Ensure you have Go installed on your system. You can download it from the

official Go website

.
[a] Clone the Tinode repository from GitHub:

sh

1     git clone https://github.com/tinode/chat.git
2     cd chat/server
[b] Build the server:

sh

1     go build
[c] Run the server:

sh

1     ./server

JavaScript (Client)

Ensure you have Node.js and npm installed on your system.
[a] Navigate to the chat/webapp directory:

sh

1     cd chat/webapp
[b] Install the dependencies:

sh

1     npm install
[c] Start the web application:

sh

1     npm start

Swift (iOS)

  • Ensure you have Xcode installed.
  • Open the chat/ios directory in Xcode.
  • Install the dependencies using CocoaPods:

sh

1     pod install
  • Build and run the project on a simulator or device.

Android

  • Ensure you have Android Studio installed.
  • Open the chat/android directory in Android Studio.
  • Build and run the project on an emulator or device.

Python

Ensure you have Python installed on your system.
[a] Navigate to the chat/python directory:

sh

1     cd chat/python
[b] Install the required packages:

sh

1     pip install -r requirements.txt
[c] Run the client:

sh

1     python main.py

Structure of the Project

Understanding the structure of the Tinode project is crucial for effective development. Here is a brief overview:

Server Directory (Go)

  • Contains the Go code for the server.
  • Key files include main.go (entry point) and configuration files (config.json).

Client Directories (JS, Swift, Android, Python)

  • Each directory contains the respective client-side code.
  • Key files include index.js for JS, AppDelegate.swift for iOS, MainActivity.java for Android, and main.py for Python.

Common Directories

  • docs: Contains documentation.
  • scripts: Contains utility scripts for various tasks.

App Architecture

tinode-webrtc
Tinode follows a modular architecture, making it scalable and easy to maintain. Here's a high-level overview of the architecture:

Server

  • The server is built using Go and handles all backend operations, including user authentication, message storage, and real-time communication.
  • It uses WebSockets for real-time data transfer, ensuring low latency and high performance.

Client

  • The client can be built using various technologies like JavaScript, Swift, Android, or Python.
  • It interacts with the server via WebSockets and REST APIs.
  • Each client platform has its own set of views and controllers to handle user interactions and display messages.

Database

  • Tinode supports various databases like PostgreSQL and MySQL.
  • The database stores user data, messages, and other relevant information.
By understanding this architecture, you can effectively navigate the project and make modifications as needed to build your custom messaging application.
With the installation steps and project structure clarified, you are now ready to dive into the actual development. In the next sections, we will guide you through the detailed steps to implement various components of your Tinode-based messaging application.

Step 1: Get Started with the Server

Setting up the server is the first critical step in creating your Tinode-based messaging application. The server handles all backend operations, including user authentication, message storage, and real-time communication. Tinode uses Go for the server-side code, which provides a robust and scalable foundation for your messaging app.

Setting Up the Server

[a] Install Go

Make sure you have Go installed on your system. If not, download and install it from the

official Go website

.

[b] Clone the Tinode Repository

Start by cloning the Tinode repository from GitHub. Open your terminal and run the following command:

sh

1     git clone https://github.com/tinode/chat.git
2     cd chat/server

[c] Build the Server

Navigate to the server directory and build the Go server:

sh

1     go build
This command compiles the server code and generates an executable file named server.

[d] Configuration

Tinode requires some configuration before you can run the server. Create a configuration file named config.json in the server directory. Here’s a basic example of what your configuration file might look like:

json

1     {
2       "host": "localhost",
3       "port": 6060,
4       "tls": {
5         "enabled": false,
6         "cert_file": "",
7         "key_file": ""
8       },
9       "db": {
10         "type": "mysql",
11         "dsn": "username:password@tcp(127.0.0.1:3306)/tinode?charset=utf8mb4&parseTime=True&loc=Local"
12       }
13     }
Replace the dsn with your actual database connection string. Tinode supports multiple databases like MySQL and PostgreSQL.

[e] Run the Server

Once you have configured the server, you can start it by running:

sh

1     ./server
If everything is set up correctly, you should see output indicating that the server is running and listening for connections on the specified port.

Example Code Snippets for Initialization

Here are some example snippets to help you get started with the server initialization:
Main Go File (main.go)

Go

1  package main
2
3  import (
4    "fmt"
5    "log"
6    "net/http"
7    "tinode/server"
8  )
9
10  func main() {
11    // Load configuration
12    config, err := server.LoadConfig("config.json")
13    if err != nil {
14      log.Fatal(err)
15    }
16
17    // Initialize the server
18    srv := server.NewServer(config)
19
20    // Start the server
21    fmt.Printf("Starting Tinode server on %s:%d\n", config.Host, config.Port)
22    if err := http.ListenAndServe(fmt.Sprintf("%s:%d", config.Host, config.Port), srv.Router); err != nil {
23      log.Fatal(err)
24    }
25  }
Configuration Loader (config.go)

Go

1  package server
2
3  import (
4    "encoding/json"
5    "io/ioutil"
6    "log"
7  )
8
9  type Config struct {
10    Host string `json:"host"`
11    Port int    `json:"port"`
12    TLS  struct {
13      Enabled  bool   `json:"enabled"`
14      CertFile string `json:"cert_file"`
15      KeyFile  string `json:"key_file"`
16    } `json:"tls"`
17    DB struct {
18      Type string `json:"type"`
19      DSN  string `json:"dsn"`
20    } `json:"db"`
21  }
22
23  func LoadConfig(file string) (*Config, error) {
24    data, err := ioutil.ReadFile(file)
25    if err != nil {
26      return nil, err
27    }
28    var config Config
29    if err := json.Unmarshal(data, &config); err != nil {
30      return nil, err
31    }
32    return &config, nil
33  }

Configuration Details

Database Configuration

Tinode supports various databases such as MySQL and PostgreSQL. Ensure you have the appropriate database running and update the dsn in the configuration file with your database credentials.

TLS Configuration

If you want to enable TLS for secure communication, set enabled to true and provide paths to your cert_file and key_file.
With your server set up and running, you now have the backend foundation ready for your Tinode-based messaging app. In the next part, we will focus on designing the user interface and wireframing the components required for the client side of your application.

Step 2: Wireframe All the Components

Designing a user-friendly interface is crucial for any messaging application. In this step, we'll cover the process of wireframing the components needed for your Tinode-based messaging app. Wireframing helps in visualizing the layout and functionality of your app before you start coding, ensuring a smooth development process.

Designing the UI

To create an effective and engaging UI for your messaging app, consider the following key components:

Login/Join Screen

  • This screen allows users to log in or join the chat. It should include fields for entering a username and password or other authentication methods.
  • Additional features might include options for registering a new account and resetting the password.

Chat Screen

The main interface where users can view and send messages. This screen typically includes:
1 - A message input box for typing new messages.
2 - A send button to submit messages.
3 - A display area for the chat history, showing sent and received messages.
4 - Options for attaching files, sending emojis, and other multimedia messages.

Contacts/Participants List

  • A list of contacts or chat participants. This can be integrated as a sidebar or a separate screen.
  • Each contact entry should display the user’s name and status (online/offline).

Profile Screen

Allows users to view and edit their profile information, such as username, profile picture, and status message.

Settings Screen

Provides options for configuring app settings, such as notifications, privacy settings, and account management.

Wireframing Tools and Best Practices

Wireframing can be done using various tools, such as:
  • Figma: A collaborative interface design tool.
  • Sketch: A vector graphics editor for macOS.
  • Adobe XD: A vector-based user experience design tool.
  • Balsamiq: A rapid wireframing tool that replicates the experience of sketching on a whiteboard.
Here are some best practices for wireframing:
  • Start with Sketches: Begin with rough sketches on paper or a whiteboard to quickly iterate on ideas.
  • Focus on Usability: Ensure that the layout is intuitive and easy to navigate. Place commonly used features where users can easily access them.
  • Keep It Simple: Avoid cluttering the interface with too many elements. Prioritize essential features.
  • Use Consistent Design Patterns: Maintain consistency in design patterns, such as button styles, font sizes, and color schemes, across different screens.

Example Wireframe Sketches

Login/Join Screen

  • Components: Username field, password field, login button, register link, forgot password link.
  • Layout: Centered form with inputs stacked vertically, logo at the top, buttons at the bottom.

Chat Screen

  • Components: Message display area, message input box, send button, attachments button, emoji picker.
  • Layout: Chat history occupies the main area, input box at the bottom, options for attachments and emojis to the side of the input box.

Contacts/Participants List

  • Components: List of contacts with names and status indicators.
  • Layout: Sidebar on the left or a separate screen with a scrollable list of contacts.

Profile Screen

  • Components: Profile picture, username, status message, edit button.
  • Layout: Profile picture at the top, followed by username and status message, edit button at the bottom.

Settings Screen

  • Components: List of settings categories (notifications, privacy, account management).
  • Layout: Scrollable list with categories and toggles/buttons for each setting.
By creating detailed wireframes, you can ensure that all necessary components are included and that the user experience is seamless. This preparation will make the actual implementation process smoother and more efficient.
In the next part, we will dive into implementing the join screen, providing code snippets and detailed instructions for each platform.

Step 3: Implement Join Screen

The join screen is a crucial part of your messaging application, as it serves as the entry point for users. This screen typically includes fields for entering a username and password, buttons for logging in or registering a new account, and possibly options for password recovery. In this section, we will guide you through implementing the join screen for your Tinode-based messaging application, covering JavaScript, Swift, Android, and Python.

JavaScript (React)

Let's break down the implementation of the join screen for each platform:

[a] Create the Join Component

First, create a new file named Join.js in your components directory.
Add the following code to create a basic join screen with React:

JavaScript

1     import React, { useState } from 'react';
2     import Tinode from 'tinode-sdk';
3
4     const Join = ({ onLogin }) => {
5       const [username, setUsername] = useState('');
6       const [password, setPassword] = useState('');
7
8       const handleLogin = async () => {
9         const tinode = new Tinode();
10         try {
11           await tinode.connect('wss://api.tinode.co');
12           await tinode.loginBasic(username, password);
13           onLogin(tinode);
14         } catch (err) {
15           console.error('Login failed', err);
16         }
17       };
18
19       return (
20         <div className="join-container">
21           <h2>Join Chat</h2>
22           <input
23             type="text"
24             placeholder="Username"
25             value={username}
26             onChange={(e) => setUsername(e.target.value)}
27           />
28           <input
29             type="password"
30             placeholder="Password"
31             value={password}
32             onChange={(e) => setPassword(e.target.value)}
33           />
34           <button onClick={handleLogin}>Login</button>
35         </div>
36       );
37     };
38
39     export default Join;

[b] Styling the Component

Create a CSS file named Join.css and add some basic styles:

CSS

1     .join-container {
2       display: flex;
3       flex-direction: column;
4       align-items: center;
5       justify-content: center;
6       height: 100vh;
7     }
8
9     .join-container input {
10       margin: 10px;
11       padding: 10px;
12       font-size: 16px;
13     }
14
15     .join-container button {
16       padding: 10px 20px;
17       font-size: 16px;
18       cursor: pointer;
19     }

[c] Integrate the Component

In your main application file (e.g., App.js), import and use the Join component:

JavaScript

1     import React, { useState } from 'react';
2     import Join from './components/Join';
3     import Chat from './components/Chat';
4
5     const App = () => {
6       const [tinode, setTinode] = useState(null);
7
8       return (
9         <div className="App">
10           {!tinode ? <Join onLogin={setTinode} /> : <Chat tinode={tinode} />}
11         </div>
12       );
13     };
14
15     export default App;

Swift (iOS)

[a] Create the Join View Controller

In your Xcode project, create a new Swift file named JoinViewController.swift.
Add the following code to create the join screen:

Swift

1     import UIKit
2     import TinodeSDK
3
4     class JoinViewController: UIViewController {
5       @IBOutlet weak var usernameTextField: UITextField!
6       @IBOutlet weak var passwordTextField: UITextField!
7
8       override func viewDidLoad() {
9         super.viewDidLoad()
10       }
11
12       @IBAction func loginButtonTapped(_ sender: UIButton) {
13         let tinode = Tinode()
14         tinode.connect().then {
15           return tinode.loginBasic(uname: self.usernameTextField.text!, password: self.passwordTextField.text!)
16         }.then { msg in
17           // Proceed to the chat screen
18           self.performSegue(withIdentifier: "showChat", sender: nil)
19         }.catch { err in
20           print("Login failed: \(err)")
21         }
22       }
23     }

[b] Design the Join Screen in Interface Builder

  • Open Main.storyboard.
  • Add a ViewController and set its class to JoinViewController.
  • Add UITextField components for username and password, and a UIButton for login.
  • Connect the outlets and action to the JoinViewController.

Android

[a] Create the Join Activity

In your Android Studio project, create a new Activity named JoinActivity.java.
Add the following code to create the join screen:

Java

1     package com.example.chatapp;
2
3     import android.content.Intent;
4     import android.os.Bundle;
5     import android.view.View;
6     import android.widget.EditText;
7     import android.widget.Toast;
8     import androidx.appcompat.app.AppCompatActivity;
9     import co.tinode.tinodesdk.Tinode;
10     import co.tinode.tinodesdk.model.ServerMessage;
11
12     public class JoinActivity extends AppCompatActivity {
13         private Tinode tinode;
14         private EditText usernameEditText;
15         private EditText passwordEditText;
16
17         @Override
18         protected void onCreate(Bundle savedInstanceState) {
19             super.onCreate(savedInstanceState);
20             setContentView(R.layout.activity_join);
21
22             tinode = new Tinode();
23             usernameEditText = findViewById(R.id.username);
24             passwordEditText = findViewById(R.id.password);
25         }
26
27         public void onLogin(View view) {
28             String username = usernameEditText.getText().toString();
29             String password = passwordEditText.getText().toString();
30
31             tinode.connect("wss://api.tinode.co").thenApply(ignored -> {
32                 return tinode.loginBasic(username, password);
33             }).thenAccept(msg -> {
34                 Intent intent = new Intent(JoinActivity.this, ChatActivity.class);
35                 startActivity(intent);
36                 finish();
37             }).exceptionally(e -> {
38                 runOnUiThread(() -> {
39                     Toast.makeText(JoinActivity.this, "Login failed: " + e.getMessage(), Toast.LENGTH_SHORT).show();
40                 });
41                 return null;
42             });
43         }
44     }

[b] Design the Join Screen Layout

In res/layout, create a new XML layout file named activity_join.xml:

xml

1     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2         android:layout_width="match_parent"
3         android:layout_height="match_parent"
4         android:orientation="vertical"
5         android:gravity="center"
6         android:padding="16dp">
7
8         <EditText
9             android:id="@+id/username"
10             android:layout_width="match_parent"
11             android:layout_height="wrap_content"
12             android:hint="Username" />
13
14         <EditText
15             android:id="@+id/password"
16             android:layout_width="match_parent"
17             android:layout_height="wrap_content"
18             android:hint="Password"
19             android:inputType="textPassword" />
20
21         <Button
22             android:layout_width="wrap_content"
23             android:layout_height="wrap_content"
24             android:text="Login"
25             android:onClick="onLogin" />
26     </LinearLayout>

Python (Kivy)

[a] Create the Join Screen

In your Kivy project, create a new file named join.kv for the Kivy language:

Kivy

1     BoxLayout:
2         orientation: 'vertical'
3         spacing: 10
4         padding: 50
5
6         TextInput:
7             id: username
8             hint_text: 'Username'
9
10         TextInput:
11             id: password
12             hint_text: 'Password'
13             password: True
14
15         Button:
16             text: 'Login'
17             on_press: app.login(username.text, password.text)

[b] Implement the Login Logic in Python

In your main application file (e.g., main.py), add the following code:

Python

1     from kivy.app import App
2     from kivy.uix.boxlayout import BoxLayout
3     from tinode import Tinode
4
5     class JoinScreen(BoxLayout):
6         pass
7
8     class ChatApp(App):
9         def build(self):
10             return JoinScreen()
11
12         def login(self, username, password):
13             tinode = Tinode()
14             tinode.connect('wss://api.tinode.co').then(
15                 lambda _: tinode.login_basic(username, password)
16             ).then(
17                 lambda _: self.root.current = 'chat'
18             ).catch(
19                 lambda e: print(f"Login failed: {e}")
20             )
21
22     if __name__ == '__main__':
23         ChatApp().run()
By following these steps, you can create a join screen for your Tinode-based messaging application on various platforms. The join screen allows users to authenticate and enter the chat, forming the first step towards a fully functional messaging app. In the next part, we will implement the controls necessary for interacting within the chat.

Step 4: Implement Controls

Adding controls to your messaging application is essential for enabling users to interact effectively within the chat. Controls typically include features like sending messages, attaching files, and using emojis. This section will guide you through implementing these controls for your Tinode-based messaging application across JavaScript, Swift, Android, and Python.

Adding Controls to Your App

Let's dive into the implementation of the main controls for each platform.

JavaScript (React)

[a] Create the Chat Component

First, create a new file named Chat.js in your components directory.
Add the following code to create the chat screen with message controls:

JavaScript

1     import React, { useState, useEffect } from 'react';
2     import Tinode from 'tinode-sdk';
3
4     const Chat = ({ tinode }) => {
5       const [messages, setMessages] = useState([]);
6       const [message, setMessage] = useState('');
7
8       useEffect(() => {
9         const topic = tinode.getTopic('general');
10         topic.subscribe().then(() => {
11           topic.onData = (msg) => {
12             setMessages((prevMessages) => [...prevMessages, msg]);
13           };
14         });
15       }, [tinode]);
16
17       const sendMessage = () => {
18         const topic = tinode.getTopic('general');
19         topic.publishMessage(message);
20         setMessage('');
21       };
22
23       return (
24         <div className="chat-container">
25           <div className="messages">
26             {messages.map((msg, index) => (
27               <div key={index} className="message">
28                 {msg.content}
29               </div>
30             ))}
31           </div>
32           <div className="controls">
33             <input
34               type="text"
35               value={message}
36               onChange={(e) => setMessage(e.target.value)}
37               placeholder="Type a message..."
38             />
39             <button onClick={sendMessage}>Send</button>
40           </div>
41         </div>
42       );
43     };
44
45     export default Chat;

[b] Styling the Component

Create a CSS file named Chat.css and add some basic styles:

CSS

1     .chat-container {
2       display: flex;
3       flex-direction: column;
4       height: 100vh;
5     }
6
7     .messages {
8       flex: 1;
9       overflow-y: auto;
10       padding: 10px;
11     }
12
13     .message {
14       margin: 10px 0;
15       padding: 10px;
16       border-radius: 5px;
17       background-color: #f1f1f1;
18     }
19
20     .controls {
21       display: flex;
22       padding: 10px;
23       border-top: 1px solid #ddd;
24     }
25
26     .controls input {
27       flex: 1;
28       padding: 10px;
29       margin-right: 10px;
30       font-size: 16px;
31     }
32
33     .controls button {
34       padding: 10px 20px;
35       font-size: 16px;
36       cursor: pointer;
37     }

[c] Integrate the Component

In your main application file (e.g., App.js), import and use the Chat component when the user is logged in:

JavaScript

1     import React, { useState } from 'react';
2     import Join from './components/Join';
3     import Chat from './components/Chat';
4
5     const App = () => {
6       const [tinode, setTinode] = useState(null);
7
8       return (
9         <div className="App">
10           {!tinode ? <Join onLogin={setTinode} /> : <Chat tinode={tinode} />}
11         </div>
12       );
13     };
14
15     export default App;

Swift (iOS)

[a] Create the Chat View Controller

In your Xcode project, create a new Swift file named ChatViewController.swift.
Add the following code to create the chat screen:

Swift

1     import UIKit
2     import TinodeSDK
3
4     class ChatViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
5       @IBOutlet weak var tableView: UITableView!
6       @IBOutlet weak var messageTextField: UITextField!
7
8       var tinode: Tinode!
9       var topic: DefaultComTopic!
10       var messages: [MsgServerData] = []
11
12       override func viewDidLoad() {
13         super.viewDidLoad()
14
15         tableView.dataSource = self
16         tableView.delegate = self
17
18         topic = tinode.getTopic(for: "general") as? DefaultComTopic
19         topic.subscribe().then {
20           self.topic.onData = { msg in
21             self.messages.append(msg)
22             self.tableView.reloadData()
23           }
24         }
25       }
26
27       @IBAction func sendMessage(_ sender: UIButton) {
28         let message = messageTextField.text!
29         topic.publishMessage(content: message)
30         messageTextField.text = ""
31       }
32
33       func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
34         return messages.count
35       }
36
37       func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
38         let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath)
39         cell.textLabel?.text = messages[indexPath.row].content
40         return cell
41       }
42     }

[b] Design the Chat Screen in Interface Builder

  • Open Main.storyboard.
  • Add a ViewController and set its class to ChatViewController.
  • Add a UITableView for displaying messages, a UITextField for message input, and a UIButton for sending messages.
  • Connect the outlets and action to the ChatViewController.

Android

[a] Create the Chat Activity

In your Android Studio project, create a new Activity named ChatActivity.java.
Add the following code to create the chat screen:

Java

1     package com.example.chatapp;
2
3     import android.os.Bundle;
4     import android.view.View;
5     import android.widget.EditText;
6     import android.widget.Toast;
7     import androidx.appcompat.app.AppCompatActivity;
8     import androidx.recyclerview.widget.LinearLayoutManager;
9     import androidx.recyclerview.widget.RecyclerView;
10     import java.util.ArrayList;
11     import java.util.List;
12     import co.tinode.tinodesdk.Tinode;
13     import co.tinode.tinodesdk.model.MsgServerData;
14
15     public class ChatActivity extends AppCompatActivity {
16         private Tinode tinode;
17         private EditText messageEditText;
18         private RecyclerView messagesRecyclerView;
19         private MessagesAdapter adapter;
20         private List<MsgServerData> messages;
21
22         @Override
23         protected void onCreate(Bundle savedInstanceState) {
24             super.onCreate(savedInstanceState);
25             setContentView(R.layout.activity_chat);
26
27             tinode = (Tinode) getIntent().getSerializableExtra("tinode");
28             messageEditText = findViewById(R.id.messageEditText);
29             messagesRecyclerView = findViewById(R.id.messagesRecyclerView);
30
31             messages = new ArrayList<>();
32             adapter = new MessagesAdapter(messages);
33             messagesRecyclerView.setLayoutManager(new LinearLayoutManager(this));
34             messagesRecyclerView.setAdapter(adapter);
35
36             Tinode.Topic topic = tinode.getTopic("general");
37             topic.subscribe().thenApply(ignored -> {
38                 topic.onData = msg -> {
39                     messages.add(msg);
40                     runOnUiThread(() -> adapter.notifyDataSetChanged());
41                 };
42                 return null;
43             }).exceptionally(e -> {
44                 runOnUiThread(() -> Toast.makeText(ChatActivity.this, "Subscription failed: " + e.getMessage(), Toast.LENGTH_SHORT).show());
45                 return null;
46             });
47         }
48
49         public void sendMessage(View view) {
50             String message = messageEditText.getText().toString();
51             Tinode.Topic topic = tinode.getTopic("general");
52             topic.publishMessage(message);
53             messageEditText.setText("");
54         }
55     }

[b] Design the Chat Screen Layout

In res/layout, create a new XML layout file named activity_chat.xml:

XML

1     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2         android:layout_width="match_parent"
3         android:layout_height="match_parent"
4         android:orientation="vertical">
5
6         <androidx.recyclerview.widget.RecyclerView
7             android:id="@+id/messagesRecyclerView"
8             android:layout_width="match_parent"
9             android:layout_height="0dp"
10             android:layout_weight="1"/>
11
12         <LinearLayout
13             android:layout_width="match_parent"
14             android:layout_height="wrap_content"
15             android:orientation="horizontal"
16             android:padding="10dp">
17
18             <EditText
19                 android:id="@+id/messageEditText"
20                 android:layout_width="0dp"
21                 android:layout_height="wrap_content"
22                 android:layout_weight="1"
23                 android:hint="Type a message..." />
24
25             <Button
26                 android:layout_width="wrap_content"
27                 android:layout_height="wrap_content"
28                 android:text="Send"
29                 android:onClick="sendMessage" />
30         </LinearLayout>
31     </LinearLayout>

Python (Kivy)

[a] Create the Chat Screen

In your Kivy project, create a new file named chat.kv for the Kivy language:

Kivy

1     BoxLayout:
2         orientation: 'vertical'
3         spacing: 10
4         padding: 10
5
6         ScrollView:
7             id: messages
8             BoxLayout:
9                 id: message_container
10                 orientation: 'vertical'
11                 size_hint_y: None
12
13
14                 height: self.minimum_height
15
16         BoxLayout:
17             size_hint_y: None
18             height: '40dp'
19             TextInput:
20                 id: message_input
21                 hint_text: 'Type a message...'
22                 size_hint_x: 0.8
23             Button:
24                 text: 'Send'
25                 size_hint_x: 0.2
26                 on_press: app.send_message()

[b] Implement the Chat Logic in Python

In your main application file (e.g., main.py), add the following code:

Python

1     from kivy.app import App
2     from kivy.uix.boxlayout import BoxLayout
3     from kivy.uix.label import Label
4     from tinode import Tinode
5
6     class ChatScreen(BoxLayout):
7         pass
8
9     class ChatApp(App):
10         def build(self):
11             self.tinode = Tinode()
12             self.tinode.connect('wss://api.tinode.co')
13             self.topic = self.tinode.get_topic('general')
14             self.topic.subscribe().then(self.on_subscribe)
15             return ChatScreen()
16
17         def on_subscribe(self, *args):
18             self.topic.on_data = self.receive_message
19
20         def send_message(self):
21             message = self.root.ids.message_input.text
22             self.topic.publish_message(message)
23             self.root.ids.message_input.text = ''
24             self.add_message_to_ui(message, 'right')
25
26         def receive_message(self, msg):
27             self.add_message_to_ui(msg.content, 'left')
28
29         def add_message_to_ui(self, message, alignment):
30             label = Label(text=message, size_hint_y=None, height=40)
31             if alignment == 'right':
32                 label.halign = 'right'
33             self.root.ids.message_container.add_widget(label)
34
35     if __name__ == '__main__':
36         ChatApp().run()
By following these steps, you can implement the main controls for your Tinode-based messaging application on various platforms. These controls will allow users to send and receive messages, attach files, and use other interactive features within the chat. In the next part, we will implement the participant view, which will display the list of users in the chat.

Get Free 10,000 Minutes Every Months

No credit card required to start.

Step 5: Implement Participant View

The participant view is an essential component of your messaging application, allowing users to see who else is in the chat. This section will guide you through implementing the participant view for your Tinode-based messaging application across JavaScript, Swift, Android, and Python.

Creating the Participant View

Let's break down the implementation of the participant view for each platform.

JavaScript (React)

[a] Create the Participant Component

First, create a new file named Participants.js in your components directory.
Add the following code to create the participant view:

JavaScript

1     import React, { useState, useEffect } from 'react';
2     import Tinode from 'tinode-sdk';
3
4     const Participants = ({ tinode }) => {
5       const [participants, setParticipants] = useState([]);
6
7       useEffect(() => {
8         const topic = tinode.getTopic('general');
9         topic.subscribe().then(() => {
10           topic.onMetaDesc = (desc) => {
11             setParticipants(desc.sub);
12           };
13         });
14       }, [tinode]);
15
16       return (
17         <div className="participants-container">
18           <h3>Participants</h3>
19           <ul>
20             {participants.map((participant, index) => (
21               <li key={index}>{participant.user}</li>
22             ))}
23           </ul>
24         </div>
25       );
26     };
27
28     export default Participants;

[b] Styling the Component

Create a CSS file named Participants.css and add some basic styles:

CSS

1     .participants-container {
2       border-left: 1px solid #ddd;
3       padding: 10px;
4     }
5
6     .participants-container h3 {
7       margin-top: 0;
8     }
9
10     .participants-container ul {
11       list-style: none;
12       padding: 0;
13     }
14
15     .participants-container li {
16       padding: 5px 0;
17     }

[c] Integrate the Component

In your main application file (e.g., App.js), import and use the Participants component alongside the Chat component:

JavaScript

1     import React, { useState } from 'react';
2     import Join from './components/Join';
3     import Chat from './components/Chat';
4     import Participants from './components/Participants';
5
6     const App = () => {
7       const [tinode, setTinode] = useState(null);
8
9       return (
10         <div className="App">
11           {!tinode ? (
12             <Join onLogin={setTinode} />
13           ) : (
14             <div className="chat-container">
15               <Chat tinode={tinode} />
16               <Participants tinode={tinode} />
17             </div>
18           )}
19         </div>
20       );
21     };
22
23     export default App;

Swift (iOS)

[a] Create the Participants View Controller

In your Xcode project, create a new Swift file named ParticipantsViewController.swift.
Add the following code to create the participant view:

Swift

1     import UIKit
2     import TinodeSDK
3
4     class ParticipantsViewController: UIViewController, UITableViewDataSource {
5       @IBOutlet weak var tableView: UITableView!
6
7       var tinode: Tinode!
8       var topic: DefaultComTopic!
9       var participants: [Subscription<TheType>] = []
10
11       override func viewDidLoad() {
12         super.viewDidLoad()
13         tableView.dataSource = self
14
15         topic = tinode.getTopic(for: "general") as? DefaultComTopic
16         topic.subscribe().then {
17           self.topic.onMetaDesc = { desc in
18             self.participants = desc.sub
19             self.tableView.reloadData()
20           }
21         }
22       }
23
24       func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
25         return participants.count
26       }
27
28       func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
29         let cell = tableView.dequeueReusableCell(withIdentifier: "ParticipantCell", for: indexPath)
30         cell.textLabel?.text = participants[indexPath.row].user
31         return cell
32       }
33     }

[b] Design the Participants Screen in Interface Builder

  • Open Main.storyboard.
  • Add a ViewController and set its class to ParticipantsViewController.
  • Add a UITableView for displaying participants.
  • Connect the outlet to the ParticipantsViewController.

Android

[a] Create the Participants Activity

In your Android Studio project, create a new Activity named ParticipantsActivity.java.
Add the following code to create the participant view:

Java

1     package com.example.chatapp;
2
3     import android.os.Bundle;
4     import androidx.appcompat.app.AppCompatActivity;
5     import androidx.recyclerview.widget.LinearLayoutManager;
6     import androidx.recyclerview.widget.RecyclerView;
7     import java.util.ArrayList;
8     import java.util.List;
9     import co.tinode.tinodesdk.Tinode;
10     import co.tinode.tinodesdk.model.Subscription;
11
12     public class ParticipantsActivity extends AppCompatActivity {
13         private Tinode tinode;
14         private RecyclerView participantsRecyclerView;
15         private ParticipantsAdapter adapter;
16         private List<Subscription> participants;
17
18         @Override
19         protected void onCreate(Bundle savedInstanceState) {
20             super.onCreate(savedInstanceState);
21             setContentView(R.layout.activity_participants);
22
23             tinode = (Tinode) getIntent().getSerializableExtra("tinode");
24             participantsRecyclerView = findViewById(R.id.participantsRecyclerView);
25
26             participants = new ArrayList<>();
27             adapter = new ParticipantsAdapter(participants);
28             participantsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
29             participantsRecyclerView.setAdapter(adapter);
30
31             Tinode.Topic topic = tinode.getTopic("general");
32             topic.subscribe().thenApply(ignored -> {
33                 topic.onMetaDesc = desc -> {
34                     participants.addAll(desc.sub);
35                     runOnUiThread(() -> adapter.notifyDataSetChanged());
36                 };
37                 return null;
38             }).exceptionally(e -> {
39                 runOnUiThread(() -> Toast.makeText(ParticipantsActivity.this, "Subscription failed: " + e.getMessage(), Toast.LENGTH_SHORT).show());
40                 return null;
41             });
42         }
43     }

[b] Design the Participants Screen Layout

In res/layout, create a new XML layout file named activity_participants.xml:

XML

1     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2         android:layout_width="match_parent"
3         android:layout_height="match_parent"
4         android:orientation="vertical">
5
6         <androidx.recyclerview.widget.RecyclerView
7             android:id="@+id/participantsRecyclerView"
8             android:layout_width="match_parent"
9             android:layout_height="match_parent"/>
10     </LinearLayout>

[c] Create the Adapter for Participants

Create a new Java class named ParticipantsAdapter.java:

Java

1     package com.example.chatapp;
2
3     import android.view.LayoutInflater;
4     import android.view.View;
5     import android.view.ViewGroup;
6     import android.widget.TextView;
7     import androidx.annotation.NonNull;
8     import androidx.recyclerview.widget.RecyclerView;
9     import java.util.List;
10     import co.tinode.tinodesdk.model.Subscription;
11
12     public class ParticipantsAdapter extends RecyclerView.Adapter<ParticipantsAdapter.ParticipantViewHolder> {
13         private List<Subscription> participants;
14
15         public ParticipantsAdapter(List<Subscription> participants) {
16             this.participants = participants;
17         }
18
19         @NonNull
20         @Override
21         public ParticipantViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
22             View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_participant, parent, false);
23             return new ParticipantViewHolder(view);
24         }
25
26         @Override
27         public void onBindViewHolder(@NonNull ParticipantViewHolder holder, int position) {
28             Subscription participant = participants.get(position);
29             holder.usernameTextView.setText(participant.user);
30         }
31
32         @Override
33         public int getItemCount() {
34             return participants.size();
35         }
36
37         public static class ParticipantViewHolder extends RecyclerView.ViewHolder {
38             TextView usernameTextView;
39
40             public ParticipantViewHolder(@NonNull View itemView) {
41                 super(itemView);
42                 usernameTextView = itemView.findViewById(R.id.usernameTextView);
43             }
44         }
45     }

[d] Create the Item Layout for Participants

In res/layout, create a new XML layout file named item_participant.xml:

XML

1     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2         android:layout_width="match_parent"
3         android:layout_height="wrap_content"
4         android:orientation="horizontal"
5         android:padding="10dp">
6
7         <TextView
8             android:id="@+id/usernameTextView"
9             android:layout_width="wrap_content"
10             android:layout_height="wrap_content"
11             android:textSize="16sp"/>
12     </LinearLayout>

Python (Kivy)

[a] Create the Participants Screen

In your Kivy project, create a new file named participants.kv for the Kivy language:

Kivy

1     BoxLayout:
2         orientation: 'vertical'
3         spacing: 10
4         padding: 10
5
6         Label:
7             text: 'Participants'
8             size_hint_y: None
9             height: '40dp'
10
11         ScrollView:
12             BoxLayout:
13                 id: participants_container
14                 orientation: 'vertical'
15                 size_hint_y: None
16                 height: self.minimum_height

[b] Implement the Participants

Logic in Python In your main application file (e.g., main.py), add the following code:

Pyhton

1     from kivy.app import App
2     from kivy.uix.boxlayout import BoxLayout
3     from kivy.uix.label import Label
4     from tinode import Tinode
5
6     class ParticipantsScreen(BoxLayout):
7         pass
8
9     class ChatApp(App):
10         def build(self):
11             self.tinode = Tinode()
12             self.tinode.connect('wss://api.tinode.co')
13             self.topic = self.tinode.get_topic('general')
14             self.topic.subscribe().then(self.on_subscribe)
15             return ParticipantsScreen()
16
17         def on_subscribe(self, *args):
18             self.topic.on_meta_desc = self.receive_participants
19
20         def receive_participants(self, desc):
21             participants = desc['sub']
22             for participant in participants:
23                 self.add_participant_to_ui(participant['user'])
24
25         def add_participant_to_ui(self, username):
26             label = Label(text=username, size_hint_y=None, height=40)
27             self.root.ids.participants_container.add_widget(label)
28
29     if __name__ == '__main__':
30         ChatApp().run()
By following these steps, you can implement the participant view for your Tinode-based messaging application on various platforms. This view will allow users to see who else is in the chat, enhancing the collaborative experience. In the next part, we will focus on running your code to test the full functionality of your application.

Step 6: Run Your Code Now

After setting up the server, implementing the join screen, adding controls, and creating the participant view, it's time to run your Tinode-based messaging application and see it in action. This section provides instructions for running the application on different platforms and some debugging tips to ensure everything works smoothly.

Running the Application

Let's go through the steps to run your application on each platform:

JavaScript (React)

[a] Start the Development Server

Navigate to your project directory and run the following command to start the development server:

sh

1     npm start
This command will compile your React application and open it in your default web browser.

[b] Test the Application

  • Open your browser and navigate to http://localhost:3000 if it doesn't open automatically.
  • You should see the join screen where you can enter a username and password to log in.
  • After logging in, you will be directed to the chat screen where you can send messages and view participants.

Swift (iOS)

Build and Run in Xcode

  • Open your project in Xcode.
  • Select your target device or simulator.
  • Click the "Run" button or press Cmd + R to build and run the application.

Test the Application

  • The app will launch on your selected device or simulator.
  • You should see the join screen where you can enter a username and password to log in.
  • After logging in, you will be directed to the chat screen where you can send messages and view participants.

Android

Build and Run in Android Studio

  • Open your project in Android Studio.
  • Select your target device or emulator.
  • Click the "Run" button or press Shift + F10 to build and run the application.

Test the Application

  • The app will launch on your selected device or emulator.
  • You should see the join screen where you can enter a username and password to log in.
  • After logging in, you will be directed to the chat screen where you can send messages and view participants.

Python (Kivy)

Run the Application

Navigate to your project directory and run the following command:

sh

1     python main.py
This command will start your Kivy application.

Test the Application

  • The app will open in a new window.
  • You should see the join screen where you can enter a username and password to log in.
  • After logging in, you will be directed to the chat screen where you can send messages and view participants.

Debugging Tips

If you encounter any issues while running your application, here are some common debugging tips:

Check Console Logs

  • For JavaScript, open the browser's developer console to check for any error messages.
  • For Swift, check the Xcode console for logs.
  • For Android, use Logcat in Android Studio to view logs.
  • For Python, check the terminal output for any errors.

Verify Server Connectivity

  • Ensure that your Tinode server is running and accessible.
  • Check the server logs for any errors or connection issues.

Verify Configuration Settings

  • Ensure that all configuration settings (e.g., server URL, database connection) are correct and properly set up.

Review Code for Errors

  • Double-check your code for any syntax errors or logical mistakes.
  • Ensure that all necessary dependencies are installed and properly configured.
By following these steps, you can run and test your Tinode-based messaging application on various platforms. This completes the development process, and your application should now be fully functional. Next, we will provide a conclusion and address some frequently asked questions (FAQs) to wrap up the guide.

Conclusion

Congratulations! You have successfully built a Tinode-based messaging application that leverages WebRTC for real-time communication. This guide covered the setup of the server, creation of the join screen, implementation of chat controls, and development of the participant view. By following these steps, you now have a functional messaging app that you can further customize and expand upon.

Want to level-up your learning? Subscribe now

Subscribe to our newsletter for more tech based insights

FAQ