You’ve probably already read many how-tos and how-don’ts for web chats. I mean, we’re constantly chatting the days away (outside office hours, of course), so it’s a pretty common topic. But here’s my question: how many times can you implement a chat app without copy/paste, and how fast? And what if you could create a whole bunch of such apps with the ultimate component?
So here’s the thing: what if you could build a web chat component that you can reuse as many times as you want, that’s independent from specific scenarios, and that you can integrate in any application with only a few different configurations? Is this a dream? No, not at all. Let’s see how to make it happen.
Research Phase: Gathering Requirements
All good projects start with requirements gathering. For example, I forced my co-workers to talk to me online with the excuse of “research.” This allowed me to see the most common, the best and the worst behaviors of a lot of apps.
But first things first. What do we expect when we open the web version of a chat app?
- Replicated look and feel: The web and mobile versions must look (almost) the same. You want consistency.
- A need for speed: Think fast loading, fast file sending, and fast reactions to your inputs.
- Robustness is a must: The chat app must be robust. It's expected. Being a web version, the connection is always there and, most likely, it's a good connection too.
With these requirements in mind, you can start thinking about experience. Here’s what stood out in terms of behaviors.
This one’s easy: to chat with someone, you start typing.
The most common behavior in a new conversation is to hide the attachment (or other available options) and show the Send button. Since it’s a web app, the Enter key is always available and should always send the message, but the visual hint of when you can send the message is becoming the default.
Right, so then what happens when you receive more than one message? You need a hint to indicate the unread ones. From the different approaches I saw, the most used one is a separator (usually a colored line) between the messages you’ve already read and the ones you haven't. But when the messages are read, some separators fade away a few seconds after you open the conversation, others when you focus on the input, and others only when you reply.
The experience of sending and receiving files changes from app to app. But we all expect at least to know what we're being sent, regardless of the filename. We tend to recognize a file type by its icon and not its name or extension, so when transfering files, a visual representation, or an icon, is preferred. Most of us would also like to preview these files, but not all apps support that. The most common files that can be previewed are PDFs, Office docs, images, and videos. Other formats usually require a download.
And when it comes to uploading a file, some apps only allow uploads if they’re separate from the messages, while others allow for titles and comments attached to the uploaded file. Regardless of having a title or not, while uploading, the file type representation is a plus.
Research Delivers Results
I then gathered my notes (my co-workers no longer felt the dread of our fake chats), and made some informed decisions about my chat’s user interface:
- Conversations are listed vertically.
- The user photo is round and slightly separated from the message itself.
- Messages are displayed inside a “balloon” box.
- The user’s messages are aligned to the right, while the received messages are displayed on the left.
- For new messages, I went with the separator line.
- Images, gifs, and videos have inline preview.
- Other file types have a common representation (without file type recognition).
Without further ado, here’s the result:
This matches what you’re used to seeing in other apps. A reusable component allows you to easily meet the most common scenarios, and guarantees future implementations.
You only have to build a reusable component once, but you can use it as many times as you’d like. So, you’re building an object that will later be referenced by any number of applications, each with their own use cases, specifications, and settings. To make this possible, you need API functions so you can set the right configurations and put this baby to work. But before that, there’s some other work to do.
Creating a Data Model
The data model implementation allows us to have a unique identifier that we use on an extension entity, enabling a specific chat usage per record.
Imagine, for example, an online store. In this case, a unique identifier can be associated with a specific product, or it can be associated with the user who is currently logged in, to keep the chat enabled throughout the whole site. So, on the the application side, you only need to create an identifier once per associated “item.”
Let’s call this entity Subject. We’ll need to provide a function in our component that allows the creation of a new Subject item and return its identifier, which we call SubjectId. (More about this this later on.)
Now, we have a unique conversation. Cool, right?
To save conversations, we're going to create a Post entity. This entity includes:
- The message itself: 'Message'(Text)
- The sender information: 'PostedBy'(UserId)
- When the message was sent: 'PostedOn'(DateTime)
- The Subject it belongs to: 'SubjectId'(SubjectId)
To get that neat separator to work, we need to keep track of the last read message. Here’s what we’ll do: LastSeenPost(SubjectId, PostId, UserId).
Let’s take a closer look at this. To show the user which message was the last read, we must know which Subject it belongs to. And yes, you guessed it, that’s the SubjectId. We need to know which message it is, meaning PostId. And which user it belongs to, that’s the UserId. With all this information, the trick is then to update this entry when we send a message.
To store the files, we're creating the Attachment entity. Now, because we want the conversation to appear as smooth as possible, we're not storing the binary data of the files in this Attachment entity, but in a separate one called File.
While loading the messages, we're retrieving the Subject information down to the Attachment level, but we're fetching the file itself at a different time. Otherwise, the response to our request would return all the binary data content of each file, and that could take too long to load and frustrate the chat users.
So, the Attachment entity is:
PostId(PostId), Filename(Text), Filetype(Text), UploadedOn(DateTime).
And the File entity is:
The result is this data model:
Now that we have our data model, it’s coding time! Let’s connect some dots.
Sending a message is pretty basic. When the user clicks the Send button or the Enter key, all we need to do is save the Post entity in the database. Sending files is the same, but the Attachment entity needs a PostId, so we need to create an empty Post entry first.
Here, let me show you what that looks like.
(Disclaimer: The code you’re about to enjoy is all pseudo-code. Readers are advised to not use this code.)
This action is called when we click the Send button we talked about before.
Similarly, sending a file goes like this:
Remember when I said we would update that LastSeenPost entity? Well, we just sent a message or a file, so, the time is now.
Here’s the code:
We can now send messages and files while updating the last read message.
Our Send button is working!! Awesome!
This is all really pretty, but is it in real time? It’s about to be!
Enabling Real-Time Communication
While we were building a chat component, someone else built a component that enables real-time communication (thanks, random person). So, I went with Firebase, because its setup is quick and integration is easy. Firebase comes with a “notifier” and a “listener.” When sending new messages we trigger the notifier, and the listener’ will tell the component what to do; for example, refresh the list of messages.
Connect to Any Application
So, let’s recap. We talked about the data model, the actions, and real-time. There’s only one thing missing: how to get that unique identifier that we’re supposed to save on the application side. You know, that SubjectId.
Remember when I said that you should extend an entity to associate that SubjectId with your “item?” And do you remember that a Post needs to know to which SubjectId it belongs to?
That Subject entity is the key. On one hand, it is used by the component to identify which conversation a message belongs to. On the other hand, its identifier is what you use to know which item has that specific conversation.
To make that Id available, here’s what we need to do:
And this is how it works: If you want a chat per user, on your app, when a user logs in, you check if the user has an associated SubjectId. If not, who you gonna call? No, not the Ghostbusters, but the CreateSubjectId function, and save the provided SubjectId along with the UserId. Then, when using the component, all you need to do is provide that SubjectId to the newly built, amazing reusable component, so it knows exactly which Posts (messages) to show or save for this user.
Blend the Best, Deliver the Best
So, now we have the basic logic to build a chat component.
Regarding the UI, the choice is yours. Et voilà! You have a working chat app!
Want to see the result and try it out? Head over to the Silk UI website for a test-drive. There’s also the possibility of learning a bit more about the structure and how it works and, of course, you can try out the platform with which I built it.