Development Guide
v4 Docs
Chat SDK Development Guide
Contents
Interacting with the server - how to make requests and receive responses
Handing entities - Creating, modifying and deleting entities
Custom Authentication Authenticate with Firebase using an existing app server
Code Examples - How to perform common tasks
UI Customization - How to customize the UI and network interactions
Architecture and getting started
The easiest way to get started is by understanding the core principles that are used in Chat SDK. Once you understand these principles and design patterns, it will make customization much easier.
High level Architecture
The Chat SDK is broken down into the following major parts:
Core: This includes interface definitions and common services.
CoreData: This contains the ORM which stores all the user, thread and message data
UI: This component contains all the app's user interface
NetworkAdapter: This component handles communication with the network.
Now that you know the basic structure, we're going to go into some detail about some important classes and design patterns that can be used to manipulate the Chat SDK.
Interacting With The Server
In this section an explanation will be provide of how to interact with the messaging server. Performing tasks like creating threads, sending messages, working with users.
Core Concepts
The client server interaction generally looks like this:
The user performs some action -> A request is made to the server -> The server responds -> The UI is updated
For example, if a user writes a message and clicks "send", the message needs to be sent to the server. Once that's done, it needs to be displayed in the chat view.
To perform these actions, you need to understand the Chat SDK service architecture.
NetworkManager: A singleton that makes the services available to the whole app
NetworkAdapter: A wrapper class that contains references to all the possible services
Handler: A service that contains a group of related functions
Function: An individual action that can be performed.
This can be illustrated with some simple examples:
Creating a public thread
To create a public thread, the UI calls the following:
iOS
Here we have: Network Manager -> Adapter -> Handler -> Function
Or a more concise form:
Android
Here we have: Network Manager -> Adapter -> Handler -> Function
Or the concise form:
Note: The NM class is just a convenience class that contains static getter functions to make calls to the NetworkManager more concise. The NM class should always be used unless you want to set a new handler.
Takeaway
The most important point is that if you want to find out which services are available, you should start by looking at the handler classes. These are documented and their names give you a good idea as to what they do.
You can find a full list of handler classes in the BNetworkFacade
protocol for iOS and the BaseNetworkAdapter
class for Android.
Case Study Imagine you wanted to find out how to send an image message. First you would look at the
BNetworkFacade
or theBaseNetworkAdapter
and you would see the following propertyImageMessageHandler
. If you open that interface you would see the functionsendMessageWithImage
. Calling this would cause the image to be uploaded to the server and then the image message would be added to the thread.
Handling the response
After we've made a request to the server, we will need to wait some time for the server to respond. Generally speaking there are two ways to handle the response:
Using the Promise or Observable returned by the function
Listening for app level notifications
Promises and Observables
This document won't go into a full explanation of promises and observables because they are very common design patterns and there are plenty of excellent explanations available online. The basic idea is that the function will return an object which will allow you to register a callback to receive a notification when the function server response comes back.
Note A common mistake when using Observables is to forget to call
subscribe()
. Unless you callsubscribe()
, the method won't actually be executed! For example,pushUser()
will do nothing. You have to callpushUser().subscribe()
and then the method will be executed.
In our example of creating a public thread:
iOS
Android
In each example, we can define a function that should be called if the request is successful and another that will be called if there is an error.
Note You can use the
observeOn
function to tell the observable to execute the result on the main thread.
App level events
There are some events that don't happen as a result of a function that we have called. For example, if another user sends us a message. These events are handled slightly differently in iOS and Android:
iOS
In iOS these events are handled by notifications. You can find a full list in the BNetworkFacade.h
file. For example, if we wanted to receive notifications whenever a new message is received:
A couple of key points:
All notifications are dispatched on a background thread so if you want to update the UI, you will need to use GSD to run your code on the main thread.
Sometimes the notification will contain a payload that's stored in the
userInfo
dictionary. In the example above, you can access the message. You can see what will be available by looking at theBNetworkFacade
. Below the name of the notification, there will be one or more keys that can be used to get the user info objects.
Android
In Android events are sent through an event bus using a PublishSubject
. You can access the event bus using the following:
or
When you subscribe to this source, you will receive a stream of NetworkEvent
objects. You can also apply filters. For example:
Here we are only listening to the event types: MessageAdded
and ThreadReadReceiptUpdated
for a particular thread.
To get a stream of all incoming messages, the following could be used:
To stop listening we can use the disposable:
The Chat SDK also includes a helper class called DisposableList
. You can add multiple disposables to this list and then call list.dispose()
to dispose of them all at one time.
Note: It's important to dispose of all of your observables when you destroy an activity. Otherwise, the observer will persist and may try to perform actions on an activity which no longer exists. This will cause the app to crash.
Handling Entities
Instant Messaging basics
In an instant messenger there are three core entities:
User (
BUser
,co.chatsdk.core.dao.User
)Thread (
BThread
,co.chatsdk.core.dao.Thread
)Message (
BMessage
,co.chatsdk.core.dao.Message
)
Note: In iOS, the entities are hidden behind protocol. For example, rather than dealing with a
BUser
object directly, we would always use theid<PUser>
protocol. Because of platform differences, this isn't possible in Android so we use the database object directly.
User has a many-to-many relationship with thread and thread has a one-to-many relationship with message. We will go into more detail about how to create and request these entities later in this guide.
These entites exist both on the server and locally in the app's database. Both iOS and Android use an Object Relational Maping (ORM) to simplify data persistence. iOS uses CoreData and Android uses GreenDAO.
Common tasks are handled by the BStorageManager
singleton iOS and co.chatsdk.core.session.StorageManager
singleton in Android.
Creating a new Entity
iOS
Android
ChatSDK.db()
Saving an entity
iOS
Android
Fetching an entity using it's entity ID
iOS
Android
There is also a useful
fetchOrCreate
method which will try to fetch an entity and if it doesn't exist, return a new entity.
Deleting entities
iOS
Android
Queries
More advanced queries are also possible.
iOS
Get the current user's contacts.
Android
For more advanced queries it's recommended to look at the documentation for CoreData and GreenDAO.
Code Examples
In this section concrete examples will be provided of how to perform common tasks.
Authentication
Authentication is handled by the BAuthenticationHandler
in iOS and the AuthenticationHandler
in Android.
It is very important to authenticate your user before you load up any of the Chat SDK views. Failure to do this will cause the app to crash.
Authenticate a new user
To authenticate a user you need to pass an account details object to the authenticate method in the authentication handler.
iOS
Android
Registering a new user
To register a new user, just use the Register type.
iOS
Android
Authenticate using cached details
The Chat SDK will automatically cache the user's login details saving them from logging in each time the app opens.
iOS
Android
Logging out
iOS
Android
Custom Authentication
With Firebase, you can also authenticate using a custom token that's been generated on your server. It works like this:
The user authenticates with your server
The server generates a new authentication token based on the user's unique ID
The token is passed back to the client and into the Chat SDK
Generating the token
To generate a token, you should follow the Firebase custom authentication guide.
Firebase also has an Admin SDK for Node.js, Java, Python and Go which makes the process more straightforward. you can install it using this guide.
In PHP, an implementation may look like this:
The id
should be the id
your server uses to identify the user who is currently logged in. This token should be passed back to the app.
Authenticating on the client
iOS
Android
Users
User meta data
The user entity is designed to be customisable. For that reason most of the user's properties are stored as key-value pairs. Some of the more common properties also have getters and setters for convenience. Custom data can be set and retrieved by doing the following:
iOS
Android
When you push a user, these values will automatically be synchronized with the server and to all other devices have subscribed to that user.
Pushing a user's details to the server
To synchronize the current user with the server the following method can be used:
iOS
Android
Subscribing to a user
In most cases, the Chat SDK will update the local database automatically if a user's details change on the remote server. By default, the Chat SDK will monitor the following users:
Contacts
Users who are members of our threads
In case you want to handle this manually, you can use the following methods:
iOS
Android
Getting a user given the user's entity ID
In some cases, you may have a user's entity ID and want to access the user object. To do this, you need to use the user wrapper object.
iOS
Android
The user wrapper object is used to synchronize the local user object which is stored in the database with the remote user data stored in Firebase. When we call metaOn
we are adding listeners so whenever the user's meta data changes on the server, the local database object will be automatically updated. onlineOn
does a similar thing but with the user's presence (online/offline) state.
Contacts
Adding a contact
iOS
Android
Getting a list of contacts
iOS
Android
Adding a contact from a user ID
If you want to manage your contact list on your own server, you can use the following code to display these users on the contacts screen.
You would need to download the list of contacts from your server. The list should be composed of the entity IDs of the users.
Download the user object using the entity ID. See instructions here.
Add the user to contacts:
iOS
Android
We only need to do the above steps once. Once the user is added to contacts, whenever the app launches, all the necessary listeners will be added and the user will be displayed in the contacts view.
Threads
In the Chat SDK, a thread represents a conversation between a number of users. Threads can have different types: private, public, group etc...
In iOS, threads are handled by the Core Handler. In Android, they are handled by the Thread Handler.
Creating a private thread
To create a thread, you need a list of user entities that you want to add. The following code will create a new thread and then display it in the chat view.
iOS
Swift
Android
Adding or removing a user to a thread
iOS
Android
Creating a public thread
Public threads are visible to everyone who is logged into the app. They are more like public chat rooms.
iOS
Android
Getting a list of threads for a user
Sometimes it's useful to get a full list of threads for a particular type.
Public Threads
iOS
Android
Private Threads
iOS
Android
Messaging
Send a text message to a user:
iOS
Android
Customizing the User Interface
There are two main ways to customize the user interface.
Modify the UI module directly
Change the UI by subclassing and using the Interface Manager
Modifying the UI directly
Since the Chat SDK is open source, you could modify the user interface files directly. This has some benefits as well as some disadvantages. The main advantage is that this method is quick and doesn't require any configuration and allows you to see your changes immediately. The problem comes when it's time to upgrade. If you just replaced the UI module with the latest version from Github, all of your customizations would be lost and you would go back to the vanilla Chat SDK UI.
The way to get around this is to fork the project using Git. You would make all of your customizations on a separate branch. When it was time to update the UI module, you would need to merge the latest version with your branch and resolve any conflicts that may have arisen.
So here is an outline of the procedure.
Create a fork of the Chat SDK project on Github
Clone the fork to your computer using
git clone [link to your fork]
. You would replace the square brackets with the actual URL of your Github forkOpen your Podfile and include the Chat SDK as development pods
You should find where you downloaded the Chat SDK files and locate the ChatSDK.podspec file. Right click this file and click Get Info. Then click drag to highlight the path after it says Where:. Press Command + C to copy this path to the clipboard. Then replace the square brackets with the path you copied.
Run
pod install
Modify the Chat SDK directly - you can do this from within Xcode
Upgrading the Chat SDK
In the future you may want to upgrade the Chat SDK library. To do this, you need to complete the following steps:
Find the location where you saved the Chat SDK library
Open this location in the terminal app
Add the original version of the Chat SDK as a remote
Merge the latest version of the Chat SDK with your fork
Resolve any conflicts
Using the interface manager
The second method is a little more complex to set up initially but it more robust over the longer term. This method involves subclassing the UI element that you need to modify. After you've made your changes, you need to find a way to tell the Chat SDK to use your subclass rather than the default class. That can be achieved using the ui
service.
iOS
Android
Notice that the calling class just requests the profile view controller or activity. It has no idea what class will actually be returned. That is decided by the interface manager.
The first step to add your own custom UI is to subclass the interface adapter. So we create a new class called MyAppInterfaceAdapter
which inherits from the DefaultInterfaceAdapter
in iOS and the BaseInterfaceAdapter
in Android.
iOS
Android
Now we need to tell the Chat SDK to use our custom interface adapter. In the main app start method where you initialize the Chat SDK add the following:
iOS
Android
Next, we need to subclass the view we want to change. For example, If we wanted to modify the profile view, the first step would be to create a subclass. We could call this MyAppProfileViewController
for iOS or MyAppProfileActivity
for Android.
Finally, we need to override the method that provides this view in our interface adapter. To do that, add the following to your MyAppInterfaceAdapter
.
iOS
Android
So now, let's see what happens. When the app requests the profile view from the interface manager, the interface manager will ask its adapter to provide the view. Since we replaced the standard adapter with a custom version, the getProfile method that you just created will be called. It will return your customized profile view which will be used by the Chat SDK.
This method may seem a little more complex to setup initially, but it's more convenient in the long term. It means that you don't need to make any modifications to the Chat SDK library and updates can be installed without worrying about losing your changes.
Customizing message cells
Since table views work very differently in Android and iOS, it's helpful to look at each separately.
iOS
The chat view uses a UITableView
and the cell type that is used when rendering a specific message type is setup in the registerMessageCells
method.
First we setup the standard message types associating the cell class with a string identifier (in this case the integer value message type).
Then we loop over the custom cell types. This means that if you want to register your own cell type, you would need to override the customCellTypes
method:
Android
Currently, there isn't a way to define a completely custom message cell for Android. However, you can define a custom message handler which can modify the standard message cell view. To do this first you need to make a class that implements the CustomMessageHandler
interface.
Then you need to register your new class with the interface manager:
Then you need to implement the updateMessageCellView
method. This method will be called for every cell and cells can be reused so if we're not careful we can run into problems. Imagine we want to add an icon to text message cells.
If we used something like layout.addView
to add the icon, this would add an icon to every text view. But the second time the view was displayed, it would add a second icon! And when the cell was reused for an image message, that message would also have the icon.
To avoid this, we need to do the following:
Check the cell type - is it text or image?
If it's a text view, check to see if we've already added an icon
If we haven't, add the icon
If it's not a text type and we have added an icon (it's been resued) remove the icon
Note If you display a custom view in the cell, it's recommended to use create your custom class that extends
LinearLayout
. Then make a property calledView view
. Then add a method calledgetView()
which can be called by the below code. This is useful because it makes it easier to retrieve your custom view. You can loop over the message cell layout and check if any sub view is aninstanceOf
your custom view. Then you can get the actual custom view usinggetView()
.
Modifying the database from your server
In some cases it may be necessary to access the Firebase database directly from your server. To do this, you can use the Firebase Admin SDK.
Last updated