...

what we think

Blog

Keep up with our latest news, tech advancements, and articles contributed by our staff. You will discover our official announcements, exciting events, technological insight, and our staff voice.
TECH

June 22, 2024

MVP Design Pattern in iOS programming

The MVP (Model-View-Presenter) pattern is a design pattern used to separate the user interface logic from the business logic in applications. MVP helps make code easier to maintain and test. In an iOS application using Objective-C, you can organize the MVP pattern as follows:
Key Components of MVP:

1. Model

Contains the data and business logic of the application.
 
Student.h
@interface Student : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end
 
Student.m
@implementation Student
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
self = [super init];
if (self) {
_name = name;
_age = age;
}
return self;
}
 

2. View Protocol

The View protocol will include methods to display data and may have methods to show errors or other statuses.
The presenter communicate to the related view through view protocol
 
StudentViewProtocol.h
 
@protocol StudentViewProtocol <NSObject>
- (void)showStudentName:(NSString *)name;
- (void)showStudentAge:(NSString *)age;
@end

 

3. Presenter

The Presenter communicates with the View via the StudentViewProtocol. Additionally, the Presenter needs a protocol for the View to notify user events.
 
StudentPresenter.h
 
#import "StudentViewProtocol.h"
@protocol StudentPresenterProtocol <NSObject>
- (void)onLoadStudentButtonClicked;
@end
 
@interface StudentPresenter : NSObject <StudentPresenterProtocol>
@property (nonatomic, weak) id<StudentViewProtocol> view;
- (instancetype)initWithView:(id<StudentViewProtocol>)view;
@end
 
StudentPresenter.m
 
#import "StudentPresenter.h"
#import "Student.h"
@implementation StudentPresenter
- (instancetype)initWithView:(id<StudentViewProtocol>)view {
self = [super init];
if (self) {
_view = view;
}
return self;
}
- (void)onLoadStudentButtonClicked {
// In a real application, you might fetch data from a service or database
Student *student = [[Student alloc] initWithName:@"Phuoc Nguyen" age:45];
// Format data before sending to the View
NSString *ageString = [NSString stringWithFormat:@"%ld years old", (long)student.age];
// Update the View
[self.view showStudentName:student.name];
[self.view showStudentAge:ageString];
}
@end

4. View

View contains only view-related logic
In iOS, ViewController acts as the View and is also the place where user events are notified to the Presenter.
StudentViewController.h
#import <UIKit/UIKit.h>
#import "StudentViewProtocol.h"
#import "StudentPresenter.h"
@interface StudentViewController : UIViewController <StudentViewProtocol>
@property (nonatomic, strong)id<StudentPresenterProtocol> presenter;
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *ageLabel;
@property (weak, nonatomic) IBOutlet UIButton *loadStudentButton;
@end

 

StudentViewController.m
#import "StudentViewController.h"
@implementation StudentViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.presenter = [[StudentPresenter alloc] initWithView:self];
[self.loadStudentButton addTarget:self.presenter action:@selector(onLoadStudentButtonClicked) forControlEvents:UIControlEventTouchUpInside];
}
- (void)showStudentName:(NSString *)name {
self.nameLabel.text = name;
}
- {
self.ageLabel.text = age;
}
@end
 
In this example, the View notifies the Presenter when the "Load Student" button is clicked. The Presenter then processes the request, fetches data from the Model, formatsit, and updates the View. This ensures that all handling logic is separated from the View, making the code more maintainable and testable.
The class diagram:

(The post is used free image from Pexels source)
View More
TECH

June 20, 2024

Setting Up MySQL Master-Slave Replication with Docker.

In today's data management environment, ensuring database availability and scalability is critical. MySQL master-slave replication is a popular technique to achieve this, helping to distribute the workload and enhance system reliability. Combined with Docker, the process of deploying and managing this system becomes easier and more flexible. In this article, we will explore how to set up MySQL master-slave replication using Docker.

What is MySQL Master-Slave Replication?

MySQL master-slave replication is a mechanism in which data from a "master" server is automatically replicated to one or more "slave" servers. This helps distribute read load, enhance resiliency, and provide easier backups.

Setup steps

1. We need to set up the directory structure as follows.

2. Edit my.cnf file information for master and slave.

master/my.cnf

[mysqld]
socket = /var/run/mysqld/mysqld.sock
lower_case_table_names = 2
log_error=/var/log/mysql/error.log
character-set-server=utf8
collation-server=utf8_general_ci
server-id=101
binlog_format=ROW
log-bin=/var/lib/mysql/mysql-bin.log

[mysql]
default-character-set=utf8

[client]
default-character-set=utf8

slave/my.cnf

[mysqld]
socket = /var/run/mysqld/mysqld.sock
lower_case_table_names = 2
log_error=/var/log/mysql/error.log
character-set-server=utf8
collation-server=utf8_general_ci
server-id=201
binlog_format=ROW
log-bin=/var/lib/mysql/mysql-bin.log

[mysql]
default-character-set=utf8

[client]
default-character-set=utf8

3. Edit docker-compose.yml file information.

version: '3'
services:
  mysql-master:
    image: percona:ps-8.0
    volumes:
      - ./master/data:/var/lib/mysql
      - ./master/my.cnf:/etc/my.cnf
    environment:
      TZ: Asia/Tokyo
      MYSQL_USER: master
      MYSQL_PASSWORD: Master123@
      MYSQL_ROOT_PASSWORD: Mastermaster123

  mysql-slave:
    image: percona:ps-8.0
    container_name: mysql-slave
    volumes:
      - ./slave/data:/var/lib/mysql
      - ./slave/my.cnf:/etc/my.cnf
    environment:
      TZ: Asia/Tokyo
      MYSQL_USER: slave
      MYSQL_PASSWORD: slave123@
      MYSQL_ROOT_PASSWORD: slaveslave123

4. Start 2 servers:

docker compose up -d

5. Wait for the process of building the container to be done successfully.

After that, check the process with the command:

docker-compose ps

6. Enter the "mysql-master" container to set the replication DB on the master server:

docker-compose exec mysql-master bash

7. Login to mySQL by the root account:

mysql -u root -pMastermaster123

8. Check the binary log whether enabled successfully or not:

SHOW VARIABLES LIKE 'log_bin';

You must make sure the status of the binary is ON before processing the next step.

9. Create a new user in order to the slave server can access to the master server and check the binary log.

Use the below command to create the 'replication' user.

CREATE USER 'replication'@'%' IDENTIFIED BY 'Slaverepl123';

10. Grant user replication access to allow creation for MySQL replications:

GRANT REPLICATION SLAVE ON *.* TO 'replication'@'%';

11. Confirm whether the grants of the user were successful or not:

SHOW GRANTS FOR replication@'%';

12. Confirm the status of the binary log on the master server:

SHOW MASTER STATUS\G

You need to save the File and Position.
We will use these values to set on the slave server.

13. Enter the "mysql-slave" container to set the replication DB on the slave server:

docker-compose exec mysql-slave bash

14. Login to mySQL by the root account:

mysql -u root -pslaveslave123

15. Execute the SQL command so it can read the binary log for any changes on the master server and make it the same on the slave server.

We will set the value of the MASTER_LOG_FILE and MASTER_LOG_POS by the value obtained from step 12.

CHANGE MASTER TO
MASTER_HOST='mysql-master',
MASTER_USER='replication',
MASTER_PASSWORD='Slaverepl123',
MASTER_LOG_FILE='binlog.000003',
MASTER_LOG_POS=1161;

16. Let’s start the slave on mysql:

START SLAVE;

17. Confirm the status of the replication on the slave server:

SHOW SLAVE STATUS\G

If the Slave_IO_State is "Waiting for source to send event" then we have set up successfully.

From now on, if the master server changes anything, it also changes on the slave server.

18. Confirm synchronization between the two databases.

Check all databases on the master/slave server.

SHOW DATABASES;

Create a new database on the master server and re-check all databases on the master server.

CREATE DATABASE replicate_db;

A new database has been created on the master server and it has also been created on the slave server.

Conclusion

With the use of Docker, setting up MySQL master-slave replication becomes much simpler and faster. This not only improves data management but also provides an easy environment for testing and development. Try implementing this system for your project to see its obvious benefits.

Using Docker to manage MySQL replication helps you take advantage of the benefits of containerization, such as easily deploying, scaling, and managing complex database systems efficiently.

References:

View More
TECH

June 20, 2024

Web Camera Issues on Devices: Troubleshooting Tips

Recently, projects involving device interactions, such as with cameras and audio, have become popular. I just
completed a project using a device's camera, and encountered some challenges that required significant effort to
overcome. To assist with your current or future projects that utilize a device's camera, I decided to write this
blog. It discusses some common issues encountered when working with cameras, and provides solutions to these
problems. I hope this will promptly resolve your current issues and prevent similar problems in your future
projects.

View More
TECH

April 28, 2024

How to implement automatic activation feature for Windows applications.

    While using a computer for work, you'll certainly have to work with licensed applications. One of the important feature of licensed applications is activating the license with a key, but I believe that if you're a first-time user of the application, grasping the activation process can be difficult to ensure the correct execution of the activation process, software developers need to provide detailed and simplified instructions. With a principle of prioritizing user experience and application security, developers often develop the "Auto-Activation" feature, where a single mouse click on the application link automatically activates it and makes it ready to use.

    In this article, I will guide you on how to implement the "Auto-Activation" feature via email for a Windows application. There are certainly many other ways to implement this feature, but I will try to provide the simplest instructions possible so that you can understand how this feature works and apply it to your project.

1. Development environment

For implementing the auto-activation feature via email for a Windows application, I will develop an environment with two main components:

    • ActivationServer:
      • Function: Manage data, send emails, redirect activation links
      • Email service: Gmail
      • Web Framework: ASP.NET
    • Application:
      • Function: Perform activation
      • Application Framework: Windows Form C#

2. Functional design

    Regarding the specific operation of the feature, I will describe it according to the diagram below. Through the Windows operating system allowing you to call commands to open applications via "application protocol" (reference "Registering an Application to a URI Scheme"), we will create an application protocol for our application. Then, we can pass the activation key to the application to activate it as a URI parameter.

Auto activation email sending

Picture 1. Auto activation email sending sequence diagram

Auto activation processing

Picture 2. Auto activation processing sequence diagram

3. Functional development

On the server side, I will implement the routes as follows:

    • GET Home/index: Display the email sending form.
    • POST Home/SendMail: Generate the activation key and send the email.
    • GET Home/AutoRedirect: Perform redirection to the application protocol.

HomeController.cs

using System;
using System.Net;
using System.Net.Mail;
using System.Web.Mvc;
using Utility;

namespace ActivationServer.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View("Index", new ViewDataDictionary { { "message", string.Empty } });
        }

        [HttpPost]
        public ActionResult SendMail(FormCollection form)
        {
            try
            {
                string emailAddress = form["email"];
                string host = "http://myserver.com";
                string mailAddress = "myemail@gmail.com";
                string mailPassword = "mypassword";
                string mailHost = "smtp.gmail.com";
                int maiPort = 587;
                string mailSubject = "Auto activation email";
                string mailBody = "<h2>Auto Activation Email</h2><a href="{{ActivationLink}}">Click here to active!</a>";
                mailBody = mailBody.Replace("{{ActivationLink}}",
                           $"{host}/Home/AutoRedirect?protocol=appdemo&param=myactivationkey}");

                using (SmtpClient client = new SmtpClient())
                {
                    client.Host = mailHost;
                    client.Port = maiPort;
                    client.EnableSsl = true;
                    client.Credentials = new NetworkCredential(mailAddress, mailPassword);

                    using (MailMessage message = new MailMessage())
                    {
                        message.From = new MailAddress(mailAddress);
                        message.To.Add(new MailAddress($"{emailAddress}"));
                        message.Subject = mailSubject;
                        message.Body = mailBody;
                        message.IsBodyHtml = true;

                        client.Send(message);
                    };
                };
                return View("Index", new ViewDataDictionary { { "message", "Activation mail has been sent!" } });
            }
            catch (Exception ex)
            {
                return View("Error", new ViewDataDictionary { { "message", ex.Message } });
            }
        }

        public ActionResult AutoRedirect(string protocol, string param)
        {
            return Redirect($"{protocol}://?key={param}");
        }
    }
}

In the example, I'm using email sending with an HTML code to embed a link in the email using an <a> tag.

    I'll further explain why we don't directly insert the application protocol into the activation link. The reason is that an application protocol inherently lacks the format of an HTML protocol, making it prone to being stripped out by email servers during the email sending process. Therefore, instead of directly inserting the application protocol, we'll use a route like "Home/AutoRedirect" to redirect to the application protocol.

Index.cshtml

@{
    ViewBag.Title = "Home Page";
}

<main>
    <section class="row" aria-labelledby="aspnetTitle">
        <h1 id="title">Auto Activation Demo</h1>
    </section>

    <div class="row">
        <section class="col-md-4" aria-labelledby="">
            <form method="post" action="@Url.Action("SendMail")">
                <div class="mb-3">
                    <label for="txtEmail" class="form-label">Email address</label>
                    <input type="email" class="form-control" name="email" id="txtEmail" required>
                </div>
                <button type="submit" class="btn btn-primary">Submit</button>
            </form>
        </section>

        <section class="row mt-3">
            <p id="message">@Model["message"]</p>
        </section>
    </div>
</main>

On the application side, I will implement 2 forms as follows:

    • Active: Activation screen

Active screen

Picture 3.  Active Screen

    • Main: Main screen of the application

Main screen

Picture 4 .  Main Screen

    I'll add modifications so that the application can be launched via CLI with a "key" parameter. This is aimed at inserting the activation key into the activation screen upon startup.

Programs.cs

using System.Web;
using Utility;
using AppSetting = ApplicationDemo.Properties.Settings;

namespace ApplicationDemo
{
    internal static class Program
    {
        private static Mutex? mutex = null;

        ///
        ///  The main entry point for the application.
        /// 
        [STAThread]
        static void Main()
        {
            const string appName = "ApplicationDemo";
            bool createdNew;

            mutex = new Mutex(true, appName, out createdNew);

            if (!createdNew)
            {
                MessageBox.Show("Application already running!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            ApplicationConfiguration.Initialize();

            Form startForm = new Main();
            if (string.IsNullOrWhiteSpace(AppSetting.Default.Key) || !CommonCode.IsValidKey(AppSetting.Default.Key))
            {
                string[] args = Environment.GetCommandLineArgs();
                string key = string.Empty;
                if (args.Length > 1)
                {
                    Uri uri = new Uri(args[1]);
                    key = $"{HttpUtility.ParseQueryString(uri.Query).Get("key")}";
                }
                startForm = new Active(key);
            }

            Application.Run(startForm);
        }
    }
}

Active.cs

using Utility;
using AppSetting = ApplicationDemo.Properties.Settings;

namespace ApplicationDemo
{
    public partial class Active : Form
    {
        public Active(string key)
        {
            InitializeComponent();

            txtActivationKey.Text = key;
        }

        private void btnActive_Click(object sender, EventArgs e)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(txtActivationKey.Text))
                {
                    throw new Exception("Please enter activation key!");
                }

                if (CommonCode.IsValidKey(txtActivationKey.Text))
                {
                    MessageBox.Show("Activation Successful", "Information", MessageBoxButtons.OK,
                                    MessageBoxIcon.Information);
                    AppSetting.Default.Key = txtActivationKey.Text;
                    AppSetting.Default.Save();
                    Main main = new();
                    main.Show();
                    Hide();
                }
                else
                {
                    MessageBox.Show("The activation key is invalid!", "Error", MessageBoxButtons.OK,
                                     MessageBoxIcon.Warning);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        private void Active_FormClosed(object sender, FormClosedEventArgs e)
        {
            Application.Exit();
        }

        private void Active_Shown(object sender, EventArgs e)
        {

            if (!string.IsNullOrWhiteSpace(txtActivationKey.Text))
            {
                btnActive.PerformClick();
            }
        }
    }
}

Main.cs

using AppSetting = ApplicationDemo.Properties.Settings;

namespace ApplicationDemo
{
    public partial class Main : Form
    {
        public Main()
        {
            InitializeComponent();
        }

        private void btnDeactive_Click(object sender, EventArgs e)
        {
            AppSetting.Default.Key = string.Empty;
            AppSetting.Default.Save();

            Active active = new Active(string.Empty);
            active.Show();
            Hide();
        }

        private void Main_FormClosed(object sender, FormClosedEventArgs e)
        {
            Application.Exit();
        }
    }
}

    To make the application operational with an application protocol, we'll register it through the registry as follows, with the "ExecFilePath" is the location where the application is installed.

AppDemo.reg

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\appdemo]
"URL Protocol"=""
@="URL:appdemo"

[HKEY_CLASSES_ROOT\appdemo\shell]

[HKEY_CLASSES_ROOT\appdemo\shell\open]

[HKEY_CLASSES_ROOT\appdemo\shell\open\command]
@="ExecFilePath"   \"%1\""

To perform registry registration during application setup, you'll need to use a third-party software to package the application.

4. Operational demo:

5. Advantages and disadvantages:

    • Advantages:
      • User Experience: Streamlined activation process enhances user experience, reducing user frustration and increasing satisfaction.
      • Automation: Automating the activation process saves time and effort for both users and administrators.
      • Security: By encrypting the activation key and embedding it in a link, security is enhanced compared to manual entry or transmission via insecure channels.
      • Ease of Use: One-click activation simplifies the process for users, especially for those who may not be technically savvy.
    • Disadvantages:
      • Complexity of Implementation: Setting up the infrastructure for automated activation, including server-side processing and email integration, can be complex and require additional development effort.
      • Compatibility Issues: Because the information of the application protocol needs to be registered with the exact path to the application to be opened, it will not work if the user moves the application to a different directory.

Cover image from freepik.com

Reference:

  1. Sending emails using Gmail in C#
  2. Sign in with app passwords
  3. Registering an Application to a URI Scheme
View More
TECH

June 19, 2024

What is Socket.IO? Building a real-time chat application with Socket.IO

In the current digital landscape, real-time features have become crucial for modern web applications. Real-time features allow applications to provide instantaneous feedback, live updates, and continuous data streams, creating a more dynamic and interactive user experience. There are many technologies available to implement real-time functionality in web applications. Each technology has its own strengths. Today, I will introduce you to one of the most popular and flexible options. It is Socket.IO.
 

What is Socket.IO?

Socket.IO is a JavaScript library that enables real-time, bidirectional, and event-based communication between web clients and servers. It simplifies the complexities of real-time transport protocols, providing a unified API that makes it straightforward to implement real-time features in web applications.
 

The features provided by Socket.IO

Socket.IO is a powerful library for implementing real-time, bidirectional communication in web applications. Here are some of the key features provided by Socket.IO:
  1. Real-Time Communication:
    Socket.IO enables real-time, low-latency communication between clients and servers, allowing for instant data exchange.
  2. Bidirectional Data Flow:
    Both clients and servers can send and receive data simultaneously, enabling interactive and responsive applications.
  3. Event-Driven Architecture:
    Socket.IO uses an event-driven model, where clients and servers can emit and listen for events. This simplifies the handling of real-time interactions.
  4. Cross-Platform Compatibility:
    Socket.IO is compatible with multiple platforms, including web browsers, Node.js servers, and mobile devices. This ensures consistent real-time communication across different environments.
  5. Automatic reconnection:
    Socket.IO automatically handles connection loss and attempts to reconnect the client to the server in the event of lost connection or network problems. This ensures a stable and reliable communication channel.
  6. Scalability:
    Socket.IO supports namespaces and rooms, allowing you to organize and scale real-time communication efficiently. A Namespace is a communication channel that allows you to split the logic of your application over a single shared connection, while rooms enable broadcasting messages to specific groups of clients.
  7. Broadcasting:
    Socket.IO allows you to broadcast messages to all connected clients or to specific groups of clients (rooms). This enables scenarios like sending chat messages to all users in a chat room or notifying specific users about relevant events.

How does Socket.IO work?

Socket.IO works by establishing a bidirectional communication channel between a client (such as a web browser) and a server (typically implemented using Node.js). This communication channel enables real-time data exchange, allowing both the client and server to send and receive messages instantly.
 
In Socket.IO, sending and receiving messages involves emitting and listening for events. Here's how you can send and receive messages using Socket.IO:
  1. Sending Messages (Emitting Events):
    To send a message from the server to the client, you use the emit() method provided by the Socket.IO server library within the context of a connection event. You need to specify the name of the event you want to emit.
    io.on('connection', (socket) => {
        // Sending a 'new message' event to the client
       socket.emit('new message', 'Welcome to the chat!');
    });
     
  2. Receiving Messages (Listening for Events):
    To receive a message on the client, you listen for the event emitted by the server using the on() method provided by the Socket.IO client library.
    // Listening for the 'new message' event on the client
    socket.on('new message', (msg) => {
        console.log('New message received:', msg);
    });
     
  3. Broadcasting events:
    Broadcasting events in Socket.IO allows a server to send a message to multiple clients simultaneously. Socket.IO provides several methods for broadcasting events, allowing developers to target specific groups of clients or all connected clients.
    // Server-side code
    io.on('connection', (socket) => {
        socket.on(new message', (msg) => {
            // Broadcast to all clients except the sender
            socket.broadcast.emit(new message', msg);
        });
    });
    In this example, when a client sends a ‘new message' event, the server broadcasts that message to all connected clients except the one that sent the message.
     
  4. Broadcasting to Specific Rooms:
    Socket.IO supports the concept of rooms, which allow you to broadcast messages to a subset of clients. A room is simply a named channel that sockets can join and leave.
    Joining a room: Clients can join a room using the join() method.
    // Server-side code
    io.on('connection', (socket) => {
        socket.on('join room', (roomName) => {
            socket.join(roomName);
        });
    });
     
    Broadcasting to a Room: To broadcast an event to all clients in a specific room, use the to() or in() method.
    // Server-side code
    io.on('connection', (socket) => {
        socket.on(new message', (roomName, msg) => {
            // Broadcast to all clients in the specified room
            io.to(roomName).emit(new message', msg);

            // Broadcast to all clients in room1 and room2
            io.to('room1').to('room2').emit('chat message', msg);
        });
    });

Building a real-time chat application with Socket.IO

Description: Building a real-time Chat application where users can join rooms, send, and receive messages within those rooms using Vue 3, Express, and Socket.io.
Server Setup (Node.js):
  1. Initialize a new project:
    mkdir socketio-chat
    cd socketio-chat
    npm init -y
  2. Install Express and Socket.IO:
    npm install express socket.io
  3. Create the server file (index.js):
    const express = require('express');
    const http = require('http');
    const socketIo = require('socket.io');

    const app = express();
    const server = http.createServer(app);
    const io = socketIo(server);
    app.use(express.static('public'));

    let socketsConected = new Set()
    io.on('connection', (socket) => {
        console.log('Socket connected', socket.id)
        socketsConected.add(socket.id)
        // Emit 'clients-total' event to clients to display numbers user in the room
        io.emit('clients-total', socketsConected.size)

        socket.on('disconnect', () => {
            console.log('Socket disconnected', socket.id)
            socketsConected.delete(socket.id)
            io.emit('clients-total', socketsConected.size)
        })

        socket.on('joinRoom', (room) => {
            // Subscribe the socket to a given channel
            socket.join(room);
            socket.room = room;
        });

        socket.on('chat-message', (data) => {
            data.sender = socket.id
            // Broadcast to all clients in the specified room
            io.to(socket.room).emit('chat-message', data)
        })
    })
    server.listen(4000, () => console.log(`💬 server on port 4000`))
    Explanation: The server is listening for two main events 'joinRoom' and 'chat-message'. The ‘joinRoom’ event will subscribe the socket to a given channel (The room name user selected). The ‘chat-message’ event will listen for the ‘chat-message’ event sent from the client, and then it will send the data to all clients in the specified room via the ‘chat-message’ event.
Client Setup (Vue 3 + Socket.io-client)
Create an HTML file (index.html):
 
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>App Chat</title>
    </head>
    <body>
        <div id="app" style="width: 416px;">
            <h1 class="title">App Chat 💬</h1>
            <!-- Join room -->
            <div v-if="!joinedRoom" class="join-room d-flex flex-column">
                <select v-model="selectedRoom" class="form-select">
                    <option v-for="room in rooms" :key="room" :value="room">{{ room }}</option>
                </select>
                <input v-model="nameInput" type="text" class="form-control mt-1 mb-1" placeholder="Please enter name">
                <button type="submit" class="btn w-25 btn-light" @click="joinRoom()">Join Room</button>
            </div>
            <!-- Box chat -->
            <div v-else class="main">
                <div class="name">
                    <span class="name-input w-75">{{ selectedRoom }}</span>
                    <span class="name-input w-25 text-end">{{ clientsTotal }} <i class="far fa-user"></i></span>
                </div>
                <ul class="message-container" id="message-container" >
                </ul>
                <form class="message-form" id="message-form">
                    <inputtype="text"
                        v-model="messageInput"
                        class="message-input"
                        ref="messageInputEl"
                    />
                    <div class="v-divider"></div>
                    <button type="submit" class="send-button" @click="sendMessage($event)">
                        send <span><i class="fas fa-paper-plane"></i></span>
                    </button>
                </form>
            </div>
        </div>
   
        <!-- Include Vue 3 from CDN -->
        <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
        <script src="/socket.io/socket.io.js"></script>
        <script>
        const {
            createApp,
            ref,
            onMounted
        } = Vue;
   
        createApp({
            setup() {
                const socket = io()
                const messageInput = ref('');
                const clientsTotal = ref(0)
                const nameInput = ref('')
                const selectedRoom = ref(null)
                const joinedRoom = ref(false)
                const socketId = ref(null)
                const rooms = ref(['Room 1', 'Room 2', 'Room 3'])
                onMounted(() => {
                    socket.on('connect', () => {
                        socketId.value = socket.id;
                    });
                });
   
                const sendMessage = async (e) => {
                    e.preventDefault()
                    if (messageInput.value === '') return
                    const data = {
                        name: nameInput.value,
                        message: messageInput.value,
                        dateTime: new Date(),
                    }
                    socket.emit('chat-message', data)
                    addMessageToUI(true, data)
                    messageInput.value = ''
                }
   
                const addMessageToUI = (isOwnMessage, data) => {
                    const element = `
                        <li class="${isOwnMessage ? 'message-right' : 'message-left'}">
                            <p class="message">
                                ${data.message}
                                <span>${data.name} ● ${moment(data.dateTime).fromNow()}</span>
                            </p>
                        </li>`;
                    const messageContainer = document.getElementById('message-container')
                    messageContainer.innerHTML += element
                }
   
                socket.on('chat-message', (data) => {
                    if (socketId.value != data.sender) {
                        addMessageToUI(false, data)
                    }
                })
                socket.on('clients-total', (value) => {
                    clientsTotal.value = value
                })
   
                const joinRoom = () => {
                    joinedRoom.value = true
                    socket.emit('joinRoom', selectedRoom.value);
                }
   
                return {
                    messageInput,clientsTotal, rooms,
                    selectedRoom,joinedRoom,nameInput,
                    sendMessage, joinRoom
                };
            }
        }).mount('#app');
        </script>
    </body>
    </html>

 

Explanation:

  • Vue 3 Setup: Use Vue 3 from the CDN to create a simple Vue application.
  • Socket.io-client: Include Socket.io-client from the CDN and establish a connection to the server.
  • Event Handling: Listen for 'chat-message' and 'clients-total' events from the server to update the message list and number of users in the room. Emit 'joinRoom' and 'chat-message' events to the server.
  • Joining Room: Allow users to join a room by emitting a 'joinRoom' event to the server.
  • Sending Messages: Allow users to send messages to the room by emitting a 'chat-message' event to the server when the Enter key is pressed.
 

Demo:

Image 1

 

Image 2

 

Conclusion

Socket.IO offers numerous advantages that make it an excellent choice for implementing real-time features in web applications. Its real-time communication capabilities, bidirectional data flow, event-driven architecture, and cross-platform compatibility provide a robust foundation for building interactive and responsive applications. Additionally, features like automatic reconnection, transport layer abstraction, scalability, and a rich ecosystem further enhance its appeal. By leveraging Socket.IO, developers can create engaging and highly responsive web applications that meet the needs of today's users.
 

References

 

 

 

View More
TECH

June 19, 2024

NFC and how to use it in the Android Studio Project

NFC stands for Near Field Communication, this is a technology that has emerged as a game changer in mobile application development, providing seamless interaction between devices with just a touch. Among its many capabilities, NFC tag reading stands out as a powerful feature that allows developers to create innovative and user-friendly experiences in their Android apps. In this article, we will explore NFC in detail while also understanding how to read NFC tags in Android Studio

What is NFC?

NFC is a wireless technology that allows data to be transferred between devices over very close distances, usually just a few centimeters. This technology works on the principle of creating an electromagnetic field and communicating through it to transmit data between devices. NFC is commonly used for a variety of purposes, including mobile payments, data exchange, device connectivity, and task automation. In the mobile industry, NFC has become an important technology, especially in creating utility applications and services such as mobile payments, smart cards, and device connectivity. The strength of NFC lies in its convenience, high security, and compatibility with many different types of devices.

The convenience that NFC brings

1. Quick and easy connection:

NFC allows connection between devices simply by bringing them close together without needing to set up or search for devices in Bluetooth or Wi-Fi lists. This reduces the time and complicated steps required to connect, providing a fast and convenient experience for users. We can easily see it through Sony devices, just turn on NFC on the Sony speaker and the Sony phone, and then put them close to each other and they will connect immediately.

2. Mobile payment:

NFC has changed the way payments are made by allowing users to make financial transactions simply by touching or placing their mobile phone near an NFC reader, such as a payment terminal or machine. POS. This creates a quick, convenient, and secure payment process. The most prominent include Google Pay, Samsung Pay, and Apple Pay

3. File and data transfer:

NFC provides a convenient means of transferring files and data between devices without the need for an internet connection. Users can share photos, videos, contacts, or other files simply by touching their phone to another device.

4. Connecting IoT devices (Internet of Things):

NFC provides a simple way to connect IoT devices to each other or to the user's mobile phone. This may include configuring and connecting to devices such as lights, door locks, or other smart control devices.

5. Bluetooth and Wi-Fi connection:

NFC can also be used to initiate a Bluetooth or Wi-Fi connection between devices. Instead of having to search and connect manually, NFC can start this process automatically, reducing inconvenience for users.

How to use NFC in the Android Studio Project

Here's a basic guide to setting up the NFC tag reading in the Android Studio project:

1. Check NFC availability:
Before integrating NFC into your app, ensure that the device is capable of reading NFC and that NFC is enabled. You can do this programmatically using the NfcAdapter class.


2. Add NFC permission and declare NFC feature to Manifest:
Open your app's AndroidManifest.xml file and add the necessary permissions:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"  xmlns:tools="http://schemas.android.com/tools">

              <uses-permission android:name="android.permission.NFC" />
              <uses-feature android:name="android.hardware.nfc" android:required="true" />


</manifest>

3. Define NFC Intent Filter:
Specify intent filters in your manifest to indicate which activities in your app should handle NFC-related actions, such as tag detection, reading, or writing.

For example:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.NFC" />
    <application
        android:name=".yourApplication">
        <activity
            android:name=".nfc.NfcActivity">
                <intent-filter>
                    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
        </activity>
    </application>
</manifest>


4. Handle Intent in Activity:
In your activity's onCreate() method, handle the NFC intent:

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
                  // Handle the NFC intent here
                  handleNfcIntent(getIntent());
         }
}

private void handleNfcIntent(Intent intent) {
        // Extract the NFC tag information from the intent
        Parcelable[] rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
        if (rawMessages != null) {
                NdefMessage[] messages = new NdefMessage[rawMessages.length];
                for (int i = 0; i < rawMessages.length; i++) {
                       messages[i] = (NdefMessage) rawMessages[I];
                 }
                // Process the NDEF messages
               // ...
        }
}

Conclusion

In conclusion, NFC tag reading offers a myriad of benefits for Android app developers, ranging from seamless interaction and information retrieval to authentication and security. By leveraging NFC technology in your Android Studio projects, you can create immersive and intuitive experiences that delight users

[Reference- resource]
- https://developer.android.com/reference/android/nfc/NfcAdapter#summary  (Docs)
- https://www.linkedin.com/pulse/how-use-nfc-your-flutter-app-ahmed-ghaith (Image)

View More
NEWS

June 18, 2024

IVC Wins the Motivation Team Award from LMI!

We are thrilled to announce that our company has been honored with the “Motivation Team Award 2024” by Link and Motivation Inc. (LMI).

This award is a testament to our unwavering commitment to fostering a motivated, engaged, and high-performing team culture.

The “Motivation Team Award” is an annual ceremony of LMI Company that recognizes companies & departments who have made significant achievements in improving employee engagement’s status.

This recognition not only validates our efforts but also serves as motivation to continue pushing the boundaries of innovation and delivering exceptional results in employee engagement activity.

Left: Mr. Duong Nguyen - The representative receiving the award on behalf of IVC

Right: Mr. Bao Nguyen - The representative presenting the award on behalf of LMI

Our journey towards winning this award has been marked by several key initiatives such as employee engagement surveys, professional development, open communication and many other activities.

While we are proud of this achievement, we see it as a stepping-stone towards greater success. We remain committed to continuously improving our workplace culture and ensuring that our team feels motivated and supported. Together, we will continue to collaborate, and strive for excellence.

Thank you to everyone for your contribution and commitment to our company’s employee engagement activity. Your passion and dedication are what make our company a great place to work.

View More
TECH

June 12, 2024

Exploring the role of "Cushion Words" in Japanese Language

Have you ever heard of the Japanese word "cushion words"? Today, I would like to introduce some of the politest Japanese words, "cushion words".

1. What is "cushion words"? 

“Cushion words” are also called business pillow words, and by adding them as an introduction to a conversation, they have the effect of softening the impression that the words convey to the other person than if they were said directly. It is just like a “cushion” to soften the impact of words.

You can also express your feelings of “sorry” (申し訳ない) or “I may be a nuisance to the other person, but…” (もしかしたら相手としては迷惑かもしれないけれど…) in a way that shows compassion for the other person.

2.Scenes and effects where "cushion words" should be used

It is mainly used in situations where you want to make a “request”, “refusal”, or “opinion/refutation”. By using a variety of words and using them appropriately, you should be able to express your words in a way that touches the other person’s heart. It also has the effect of making it easier to convey to the other person that “You are an important person who I want to take care of”.

You can use “cushion words” when talking to customers, seniors, superiors, etc.

When making an offer or asking something, when offering something that you don’t know if the other person needs, or when you want to ask something, cushion words such as “If you don’t mind” (もしよろしければ), or “If it’s not a problem” (差し支えなければ) are used. This phrase is useful when you want to extract information from the other party during a business negotiation or meeting, or when making a proposal.

The cushion words are used not only in face-to-face communication, but also in emails and documents where it is difficult to convey nuances and feelings. This will avoid giving the impression that you are bossy or one-sided, and it will show that you are considerate.  Cushion words are essential for building trusting relations.

Here are some cases to use cushion words.

a. When asking something.

「お尋ねしてよろしいでしょうか」 (May I ask?)

「失礼ですが」 (Excuse me)

「差し支えなければ」 (If it’s not a problem)

「お教えいただきたいのですが」 (I would like to ask you to tell me.)

Ex:失礼ですが、お名前をフルネームでお聞かせいただけますでしょうか。

b. When requesting something.

「恐縮ですが」 (I’m sorry)

「ご面倒をお掛けしますが」 (I apologize for the inconvenience.)

「ご迷惑とは存じますが」 (I understand this is nuisance.)

「こちらの都合で恐れ入りますが」 (We apologize for the inconvenience.)

Ex:ご面倒をお掛けしますが、お引き受けいただけないでしょうか。

c. When you want cooperation.

「恐れ入りますが」 (Excuse me)

「お手数をおかけしますが」 (I apologize for the inconvenience)

「ご面倒をおかけします」 (I'm sorry for the inconvenience)

「お忙しいところ恐れ入りますが」 (I apologize for the inconvenience, but I'm busy)

Ex:恐れ入りますが、〇日までにメールでお返事をいただいてもよろしいでしょうか。

d. When to refuse.

「あいにく」(Unfortunately)

「残念なのですが」(Unfortunately)

「お気持ちはとてもよくわかるのですが」 (I know exactly how you feel.)

「せっかくのお申し出をいただき、大変ありがたいのですが」 (I would like to thank you very much for your request.)

「ご期待に添えず、大変申し訳ないのですが」 (I am very sorry that I did not meet your expectations.)

「私どもの力不足で、大変恐縮なのですが」 (I am very sorry that I'm lacking in ability.) 

Ex: ご希望のデザインには添えず、申し訳ございません。再度、見直しを行います。

e. When giving an opinion or counterargument.

「僭越ながら」 (Please allow me (to say something))

「おっしゃることは重々承知をしておりますが」 (I am fully aware of what you are saying.)

「余計なこととは存じますが」 (I know it's unnecessary, but...)

「私の考え過ぎかもしれませんが」 (Maybe I'm thinking too much)

Ex: おっしゃることは重々承知をしておりますが、今回はA案をご提案させていただけないでしょうか。

f. When a request cannot be met.

「せっかくお声をかけていただいたのですが」 (Thank you for taking time to contact me but)

「ぜひご期待にお応えしたかったのですが」 (I really wanted to meet your expectations but)

「身に余るお話、光栄なのですが」 (It's an honor to hear your story)

「申し上げにくいのですが」 (It's hard to tell)

Ex: 身に余るお話、光栄なのですが、今回は辞退させていただいてもよろしいでしょうか?

g. When you want something to be improved.

「細かいことを言ってしまい恐縮ですが」 (I apologize for mentioning such details)

「こちらの都合ばかりで申し訳ございませんが」 (I apologize for the inconvenience)

「説明が足りず失礼いたしました」 (I apologize for not explaining enough)

Ex: 説明が足りず失礼いたしました。決定の理由を2つ以上あげていただけると助かります。

h. When offering assistance.

「もしよろしければ」 (If you do not mind)

「私でよければ」 (If you're okay with me)

「差し支えなければ」 (If it's not a problem)

「お力になれるのであれば」 (If I can help)

Ex: もしよろしければ〇〇までご提案させていただきます。

3. Examples of NG usage of cushion words that are easy to mistake.

  • 「申し訳ございません」 (I’m sorry)

「申し訳ございません」 (I’m sorry) is a cushion word used when you can’t meet someone’s request or when you apologize. Therefore, if you use it casually when it is not needed, it will end up sounding too light in the situation where you originally wanted to use it.

NG example:

「申し訳ございませんが、お名前をお聞かせいただけますか」

「申し訳ございませんが、こちらにご記入いただいてもよろしいでしょうか」

OK example:

「申し訳ございませんが、セール品につきましては返品をご遠慮いただいております」

  • 「差し支えなければ」 (If you don’t mind)

「差し支えなければ」 (If you don’t mind) includes the meaning of “Please decline the request if it is inconvenient.” Therefore, it can only be used in situations where there is no inconvenience caused even if the other party refuses. For example, it cannot be used when you are responding to a telephone call and need to know the person’s name to be connected to the agent, so be careful.

NG example:

「差し支えなければ、 お名前を伺ってもよろしいでしょうか」

→「差し支えなければ」を「恐れ入りますが」に変更しましょう。

OK example:

「差し支えなければ資料をご自宅にお送りしましょうか」

4. Precautions when using cushion words

Cushion words are soft expressions that give a polite impression, but if used too often, they can give a dull impression and make it difficult to get the main point across.

Cushion words can be used in a variety of situations, such as emails, chats, telephone calls, and face-to-face meetings, but especially in phone calls and face-to-face situations where sentences cannot be confirmed in text, it is likely that your intentions will be difficult to convey, so avoid using cushion words frequently.

5. How to respond to 「差し支えなけれ」 (If you don’t mind)

So far, we have looked at the expressions 「差し支えなけれ」 (If you don’t mind) used when requesting someone to do something, but there may be times when the other makes a request “If you don’t mind” to us. When accepting a request, a response such as 「承知しました」 (I understand) or 「かしこまりました」 (I understand) is appropriate.

Also, as mentioned above, 「差し支えなけれ」 (If you don’t mind) is an expression that leaves the decision on whether or not to accept the request to you, so of course you can decline it.

When you say “No”, be sure to show that you are sorry.

For example, if you can respond with something like 「大変申し訳ないですが、〇〇があるため、行うことが難しい状況です。お役に立てず恐縮でございます」 (I’m very sorry, but due to 〇〇, I’m unable to help you, I’m sorry that I can’t help you.)

This will leave a good impression.

6. Use cushion words appropriately to facilitate communication.

Cushion words are used in a variety of business situations to show consideration for others and facilitate communication.

It’s good idea to understand multiple phrases and appropriate situations, as they will be useful in situations where you need to convey something that is difficult to convey, such as when declining a request from someone or making a counterargument.

Make sure to use cushion words appropriately depending on the situation and maintain smooth communication and good relationships.

Reference Image and Document Source:

  • https://www.pexels.com/
  • https://go.chatwork.com/
  • https://allabout.co.jp/
View More
NEWS

June 12, 2024

Celebrating International Children's Day at IVC!

At IVC, we believe that children are the future; their well-being and happiness are paramount to creating a better world tomorrow.

As we celebrate International Children's Day, we take this opportunity to honor the joy, innocence, and potential of all IVC children!

 

 

We understand that the smiles of children are great joy and motivation every day for IVC employees, who have little angels in their family.

Hence, the company always places great emphasis on caring and taking care of IVC's children and our commitment to children is shown very specifically in the company's welfare packages. Especially, on the 1st of June every year - International Children's Day - is the occasion that the company and Union coordinate to give lovely gifts and send best wishes to IVC children.

We believe that by investing in the well-being of children today, we are building a better world tomorrow.

 

 

Thank you for joining us in this celebration.

Children are the bright stars of tomorrow, we are so proud of each and every one of them. May our children's day be filled with fun, love, and wonderful surprises.

Let’s work together to make every day a day that children can smile about.

Happy International Children's Day!

View More
ARTICLE

June 11, 2024

Life's Turning Points and Embracing Change

Life is full of twists, unexpected turns, and surprising moments of clarity. At the heart of this journey lies what we often refer to as "turning points" — those pivotal moments that change our lives. Sometimes it comes sweetly and excitedly, sometimes it's unexpected changes that weren't planned, or sometimes it's mental shocks that can make people collapse. These turning points challenge us, shape us, and ultimately define who we are and who we become.

In the midst of life's upheavals, it's natural to feel a mix of emotions: fear, uncertainty, excitement, and anticipation. Yet, it's how we respond to these turning points that truly matters. Do we shrink back in fear, clinging to the familiar, or do we embrace the unknown, stepping forward with courage and resilience?

How to handle life's turning points with grace and wisdom?

Embrace change: Change is inevitable. It's the only constant in life. Instead of resisting it, embrace it. Recognize that change often brings growth, new opportunities, and fresh perspectives. Embracing change allows us to adapt, evolve, and thrive in the face of uncertainty.

Stay flexible: Rigidity breeds resistance. Stay flexible and open-minded in your approach to life's turning points. Be willing to pivot, adjust your plans, and explore new possibilities. Flexibility enables us to flow with the currents of change rather than against them.

Trust yourself: In moments of uncertainty, trust yourself. Trust your intuition, inner wisdom and trust your judgment and decision-making abilities. Remember that you have the strength, resilience, and resourcefulness to navigate life's challenges. Trust that you have what it takes to overcome the storms and emerge stronger on the other side.

Seek support: You don't have to navigate life's turning points alone. Reach out to friends, family, mentors, or anyone that you trust for guidance and encouragement. Surround yourself with people who uplift and inspire you, who believe in your potential, and who offer a listening ear and a shoulder to lean on when needed.

Embrace growth: Every turning point is an opportunity for growth and self-discovery. Lean into the discomfort, lean into the unknown, and embrace the journey of personal and spiritual growth. Allow yourself to learn, evolve, and become the best version of yourself through life's twists and turns.

Find meaning: In the midst of chaos and uncertainty, seek meaning and purpose. Reflect on the lessons learned, the wisdom gained, and the deeper truths revealed through life's turning points. Find meaning in the challenges you face, the obstacles you overcome, and the journey you're on.

Practice gratitude: Gratitude is a powerful antidote to fear and uncertainty. Cultivate gratitude even when life challenges you. In challenges, there are always hidden opportunities for optimistic people. Take time each day to acknowledge your blessings, embrace the little good things around you, and express gratitude for the journey, with all its twists and turns.

Life's turning points may be unpredictable and at times challenging, but they also hold the promise of growth, transformation, and new beginnings. By embracing change, staying flexible, trusting yourself, seeking support, embracing growth, finding meaning, and practicing gratitude, you can navigate life's turning points with grace, resilience, and wisdom. Embrace the journey, embrace the unknown, and trust that the best is yet to come.

Image source: freepik.com

View More
1 2 3 18
Let's talk about your project! CONTACT US


At ISB Vietnam, we believe in making a positive difference to our clients through our software development outsourcing services.

As a prestigious offshore software development company in Vietnam, we've been providing top-notch solutions to numerous clients for over two decades since 2003.

Add the attachment
*Up to 10MB
...