Android Client SDK

The SDK is written in Kotlin, but aimed to be as Java-friendly as possible. Please report incompatibilities with Java as bugs.

Installation

You can install the SDK via Gradle. First add this to yourPROJECT_ROOT/app/build.gradle file.
Check the repository for the latest release version. We recommend specifying a patch release matcher, like 1.0.+.

1
2
3
4
dependencies {
  // ...
  implementation 'com.pusher:chatkit-android:$chatkit-version'
}

Usage

Instantiate Chatkit

To get started with Chatkit you will need to instantiate both a ChatManager instance as well as a TokenProvider instance to authenticate users.

We provide you with a sample token provider implementation. You can enable / disable it in the dashboard. To include it in your application, create it with your details, as such:

Note: All the code samples in this document are in Kotlin, but Java is also fully supported.
1
2
3
4
5
6
7
8
9
10
val chatManager = ChatManager(
  instanceLocator = "YOUR_INSTANCE_LOCATOR",
  userId = "YOUR_USER_ID",
  dependencies = AndroidChatkitDependencies(
    tokenProvider = ChatkitTokenProvider(
      endpoint = "YOUR_AUTH_URL",
      userId = "YOUR_USER_ID"
    )
  )
)

This is how we do it on our demo app: ChatkitDemoApp.kt

  • instanceLocator: You can find this in the "Keys" section of our dashboard: dash.pusher.com/chatkit

  • userId: Used to identify the user that will be connected with this ChatManager instance.

  • dependencies: Contains some requirements needed for ChatManager. We provide a ready made type for ChatkitDependencies for android, so all you have to do is provide a TokenProvider.

We also have available an implementation for tokenProvider which just needs the url to authorize users. If you have enabled the test token provider on the Settings section of our dashboard, you can get a test url for this purpose in there. For production applications you have to create your own server side. More information about this can be found here: docs.pusher.com/chatkit/authentication.

Token Provider

The implementation of ChatkitTokenProvider has the following properties:

PropertyTypeDescription
endpointStringUrl for the server that provides access tokens
userIdStringName of the user logging in
authDataMap<String, String> (Optional)Custom data sent to your server in the token request. If you need additional data to validate your user's session in your system, include it here.
clientOkHttpClient (Optional)Used for networking. If you want to share this with an existing instance in your app, or tweak the configuration, for example to use an HTTP proxy, you can inject an instance here.
tokenCacheTokenCache (Optional)By default we use an in memory but can provide a customTokenCache implementation

Although we provide a version of the TokenProvider that works with a url to a remote token provider (ChatkitTokenProvider), it is possible to create a custom one by implementing its interface.

Connecting to Chatkit

The best way to connect takes a callback returning a Result<CurrentUser, Error> which will provide either a CurrentUser or an Error. The CurrentUser object is your primary interface for interacting with the system as a client. It represents an authenticated connection to Chatkit.

1
2
3
4
5
6
7
8
9
10
11
12
13
chatManager.connect { result ->
  when (result) {
    is Result.Success -> {
      // We have connected!
      currentUser = result.value // CurrentUser
    }

    is Result.Failure -> {
      // Failure
      handleConnectionError(result.error)
    }
  }
}

Result

We've been referring to Result without any explanation.
A Result can be either if a Result.Success or Result.Failure type, where the success and error types are passed as generic types, like this: Result<CurrentUser, Error>.
We can get their corresponding values using result.value, and result.error respectively.

Concurrency

The default interface is non-blocking, with all methods which might block returning immediately and passing their results to their callback arguments when they complete.

If you would prefer an interface without callbacks, you can get an instance of SynchronousChatManager where all calls block the current thread and return their results directly. Just call ChatkManager.blocking() and use the returned SynchronousChatManager. This should allow you to handle your own concurrency.

Chat Events

When connecting to ChatManager we can also register for global events.

If you only care about a number of events you can provide a ChatManagerListeners implementation with the events you want:

1
2
3
4
5
6
7
8
9
chatManager.connect(
  listeners = ChatListeners(
    onPresenceChanged = { user, newState, prevState -> },
    // ...
  ),
  callback = { result ->
    //...
  }
)

The available events are:

EventPropertiesDescription
CurrentUserReceivedCurrentUserHappens when the logged user is available or updated
UserStartedTypingUser, RoomUser has started typing
UserStoppedTypingUser, RoomUser has stopped typing
UserJoinedRoomUser, RoomUser has joined the specified Room
UserLeftRoomUser, RoomUser has left the specified Room
PresenceChangeUser, Presence (now), Presence (before)A user's presence state changed
AddedToRoomRoomCurrent user was added to a room
RemovedFromRoomString (room id)Current user was removed from a room with the given id
RoomUpdatedRoomHappens when the logged user is available or updated
RoomDeletedString (room id)Happens when the logged user is available or updated
NewReadCursorCursorHappens when a new cursor is set forcursor.userIdin cursor.roomId
ErrorOccurredkotlin.ErrorAn error occurred, it does not mean the subscription has finished

Each of the events have a relevant listener that can be set on Chatisteners

Termination

When you are done using the ChatManager you can call the close function which will try to terminate any pending requests and/or subscriptions.

1
chatManager.close()

CurrentUser

When an initial connection is successfully made to Chatkit the client will receive a CurrentUser object. The CurrentUser object is the primary means of interacting with Chatkit.

PropertyTypeDescription
roomsList<Room>The rooms that the connected user is a member of.
idStringId of this user
nameString?This user's name
avatarUrlString?URL to this user's avatar

Rooms

There are a few important things to remember about Chatkit rooms; they are either public or private, users that are members of a room can change over time, all chat messages belong to a room.

PropertyTypeDescription
idStringThe global identifier for the room on the instance.
createdByIdIntThe id of the user that created this room
nameStringThe human readable name of the room (this needn’t be unique!)
customDataMap<String, Any?>?A map of arbitrary data which you may attach to the room
isPrivateBooleanTrue if the room is private, otherwise the room is public.
memberUserIdsSet<String>A set of ids for everyone on the room

Creating a Room

All that you need to provide when creating a room is a name. The user that creates the room will automatically be added as a member of the room.

The following code will create a public room called "lobby". Note that a room name must be no longer than 60 characters.

1
2
3
4
5
6
7
8
9
10
11
12
lateinit var lobby: Room

currentUser.createRoom(
  name = "lobby",
  private = false, // Optional, default false
  customData = mapOf("useful" to "info"), // Optional, default empty
  callback = { result ->
    when (result) {
      is Result.Success -> { lobby = result.value }
    }
  }
)

If you want to make a private room pass in private=true to the method call.

Also, you may choose to provide an initial number of users to be part of that room (i.e. one-to-one conversations), in which case you can also provide it with a list of users as an argument: userIds = listOf("sarah", "pusherino").

Fetching Messages for a Room

You can fetch up to the last 100 messages added to a room when you subscribe (Using messageLimit) but sometimes you’ll want to fetch older messages. For example, suppose you subscribe to a room and the oldest message you see has the ID 42. To see older messages, you can provide the initialId option to the fetchMessages method.

1
2
3
4
5
6
7
8
9
10
11
currentUser.fetchMessages(
  roomId = "lobby",
  initialId = 42, // Optional
  direction = Direction.NEWER_FIRST, // Optional - OLDER_FIRST by default
  limit = 20, // Optional - 10 by default
  callback = { result ->
    when (result) {
      is Result.Success -> toast(result.value) // List<Message>
    }
  }
)

The full set of options follows:

PropertyTypeDescription
initialIdInt (Optional)A message ID that defaults to the most recent message ID.
directionDirection (Optional)Defaults toOLDER_FIRST, dictates the direction of the messages being returned.
limitInt (Optional)Limits the number of messages that we get back, defaults to 10.

Add User to a Room

The current user can add users to rooms that they themselves are a member of.

1
2
3
4
5
6
7
8
9
currentUser.addUsersToRoom(
  roomId = "lobby",
  userIds = listOf("lovelace", "turing"),
  callback = { result ->
    when (result) {
      is Result.Success -> toast("🎉")
    }
  }
)

Remove User From a Room

The current user can remove users from rooms that they themselves are a member of.

1
2
3
4
5
currentUser.removeUsersFromRoom(
  roomId = "lobby",
  userIds = listOf("troll"),
  callback = { result -> toast("Bye! 👋")}
)

Get Joinable Rooms

To fetch a list of the rooms that a user is able to join (but isn’t yet a member of):

1
2
3
4
5
6
7
currentUser.getJoinableRooms { result ->
  when (result) {
    is Result.Success -> {
      // Do something with List<Room>
    }
  }
}

The rooms returned will be a list of the public rooms which the currentUser is not a member of.

Joining a Room

Join a room with ID lobby:

1
2
3
4
5
6
7
8
9
10
currentUser.joinRoom(
  roomId = "lobby",
  callback = { result ->
    when (result) {
      is Result.Success -> {
        // Joined the room!
      }
    }
  }
)

Leaving a Room

Leave a room with ID someRoomId:

1
2
3
4
5
6
7
8
9
10
currentUser.leaveRoom(
  roomId = "lobby",
  callback = { result ->
    when (result) {
      is Result.Success -> {
        // Left the room!
      }
    }
  }
)

Update a Room

Change the name, privacy or custom data of a room with Id someRoomId:

1
2
3
4
5
6
7
currentUser.updateRoom(
  roomId = "lobby",
  name = "New and improved Lobby, now much more private!", // Optional
  isPrivate = true, // Optional
  customData = mapOf("new" to "custom data"), // Optional
  callback = { result -> /* ... */ }
)

Optional arguments which are omitted lead to no change in the value of that field. In order to delete custom data, set it to an empty map.

All other connected members of the room will receive an event that informs them that the room has been updated. Note that the current user must have the room:update permission to use this method.

Note: This only returns whether the action is successful. To get the new room we have to handle the event that we get or fetch a new room.

Delete a Room

Delete a room with ID someRoomId:

1
2
3
4
currentUser.deleteRoom(
  roomId = "lobby",
  callback = { result -> /* ☠️... */}
)

All other connected members of the room will receive an event that informs them that the room has been deleted. Any attempts to interact with a deleted room will result in an error. Note that the current user must have the room:delete permission to use this method.

Note: Deleting a room will delete all the associated messages too.

Subscriptions

To be notified when new messages are added to a room, you’ll need to subscribe to it and provide a RoomListeners instance. (Too see the full list of possible hooks see Room Subscription Hooks). At most 100 recent messages can be retrieved on subscription, to fetch older messages see Fetching Messages From a Room. To receive only new messages, set the messageLimit to 0.

Using RoomSubscriptionListeners:

1
2
3
4
5
6
7
8
9
10
11
12
currentUser.subscribeToRoom(
  roomId = "lobby",
  listeners = RoomListeners(
    // ...
  ),
  messageLimit = 20, // Optional
  callback = { subscription ->
    // Called when the subscription has started.
    // You should terminate the subscription with subscription.unsubscribe()
    // when it is no longer needed
  }
)

Using RoomEvent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
currentUser.subscribeToRoom(
  roomId = "lobby",
  consumer = { event: RoomEvent ->
    when (event) {
      is RoomEvent.Message -> {
        // ...
      }
      is RoomEvent.ErrorOccurred -> {
        // ...
      }
      // ...
    }
  },
  callback = { subscription ->
    // ...
  }
)

Note: Subscribing implicitly joins a room if you aren’t already a member. Subscribing to the same room twice will cause the existing subscription to be cancelled and replaced by the new one.

By default when you subscribe to a room you will receive up to the 10 most recent messages that have been added to the room. The number of recent messages to fetch can be configured by setting the messageLimit parameter . These recent messages will be passed to theonNewMessagecallback (or asMessageevent) in the order they were sent, just as if they were being sent for the first time.

Room Subscription Events

This is the full list of available events from a room subscription:

EventPropertiesDescription
MessageMessageA new message has been added to the room.
UserStartedTypingUserUser has started typing
UserStoppedTypingUserUser has stopped typing
UserJoinedInt (userId)User has joined the room
UserLeftInt (userId)User has left the room
UserCameOnlineUserUser is now online
UserWentOfflineUserUser is now offline
NewReadCursorCursorA member of the room set a new read cursor.
RoomDeletedStringThe room with this ID was deleted
RoomUpdatedRoomThe room was updated
ErrorOccurredErrorAn error has occured

Each of the events have a relevant listener that can be set on RoomSubscriptionListeners

Cancel a Subscription

The subscribeToRoom function returns a Subscription that can be cancelled by calling subscription.unsubscribe() when the subscription is no longer needed.

Alternatively, it is possible to close all active subscriptions by calling chatManager.cancel(), which will close all these subscriptions.

Users

User objects can be found in various places: globally under currentUser.users or returned as the argument to some callbacks.

User Properties

PropertyTypeDescription
idStringThe unique identifier for the user on the instance.
nameStringThe human readable name of the user. This is not required to be unique.
avatarUrlStringThe location (url) of an avatar for the user.
customDataMap<String, Any?>?A map of arbitrary data which you may attach to the user
presencePresenceAn object containing information regarding the users presence state. See user presence.

Rooms contain a list of user ids, to resolve these you can use this:

1
2
3
4
5
6
currentUser.usersForRoom(
  room = lobby,
  callback = { result ->
    // List<User>
  }
)

Messages

Every message belongs to a Room and has an associated sender, which is represented by a User object. Files can be sent along with a messages by specifying an Attachment property.

Message Properties

PropertyTypeDescription
idIntThe Id assigned to the message by the Chatkit servers.
textStringThe text content of the message if present.
attachmentAttachment (Optional)The message’s attachment if present.
userIdStringThe id of user who sent the message.
userUser (Optional)The user who sent the message.
roomIdRoomThe Id of room to which the message belongs.
createdAtStringThe timestamp at which the message was created.
updatedAtStringThe timestamp at which the message was last updated.

Sending a Message

To send a message:

1
2
3
4
5
6
7
currentUser.sendMessage(
  roomId = "lobby",
  messageText = "Hello, world! 👋",
  callback = { result -> //Result<Int, Error>
    // The Int is the new message ID
  }
)

An attachment can be added when you send a message. This can be done in one of two ways:

  • Provide some data (of type [File], most likely) along with a name for the data that will be used as the name of the file that is stored by the Chatkit servers.

This is how you send a message with an attachment of this kind:

1
2
3
4
5
6
7
8
9
10
11
currentUser.sendMessage(
  roomId = "lobby",
  messageText = "Hello, world! 👋",
  attachment = DataAttachment(
    name = "My file", // Optional, "file" by default
    file = File("path/to/file")
  ),
  callback = { result -> // Result<Int, Error>
    //...
  }
)

Note that the resulting type will be inferred automatically by Chatkit servers. If the type of the file is unable to be determined then it will be given a type of file.

  • Provide a link along with a type that describes the attachment. As above, this would be one ofimage,video,audio, orfile.

This is how you send a message with an attachment of this kind:

1
2
3
4
5
6
7
8
9
10
11
currentUser.sendMessage(
  roomId = "lobby",
  messageText = "Hello, world! 👋",
  attachment = LinkAttachment(
    link = "https://path.to/file.jpg",
    type = AttachmentType.IMAGE
  ),
  callback = { result -> // Result<Int, Error>
    // ...
  }
)

Attachment

It is possible for users to attach files to messages.

Attachment Properties

PropertyTypeDescription
linkStringThe link representing the location of the attachment.
typeAttachmentTypeThe type of the attachment; one of image, video, audio, or file.

Typing Indicators

Sometimes it’s useful to be able to see if another user is typing. You can use Chatkit to let all the connected members of a room know when another user is typing.

Trigger a Typing Event

To send typing indicator events call isTypingIn with the id of the room the current user is typing in.

1
2
3
4
5
6
currentUser.isTypingIn(
  roomId = "lobby",
  callback = { result -> // Result<Unit, Error>
    // Only the error case is useful, there is no return value for success
  }
)

Receive Typing Indicators

To be notified when a user starts or stops typing in a room, provide a onUserStartedTyping and a onUserStoppedTyping function as part of the room subscription listener.

1
2
3
4
5
6
7
8
9
10
11
currentUser.subscribeToRoom(
  roomId = "lobby",
  listeners = RoomListeners(
    onUserStartedTyping = { user ->
      // ...
    },
    onUserStoppedTyping = { user ->
      // ...
    }),
  callback = { /* ... */ }
)

Alternatively, if you are using an event callback:

1
2
3
4
5
6
7
8
9
10
currentUser.subscribeToRoom(
  roomId = "lobby",
  consumer = { event ->
    when (event) {
      is RoomEvent.UserStartedTyping -> { /* ... */ }
      is RoomEvent.UserStoppedTyping -> { /* ... */ }
    }
  },
  callback = { /* ... */ }
)

User Presence

If a user has at least one active connection to the Chatkit service then they are considered online. When a user has no active connections they are considered offline. Each user object keeps track of whether a user is online or offline via the presence property.

1
2
3
if (user.presence is Presence.Online) {
  // The user is online! Show an online badge or something...
}

Additionally, to be notified when a user comes online or goes offline, you can provide the onPresenceChanged listener or match the PresenceChange and event. Either at the room level, which fires whenever a member of that room goes on or off line, or at the connection level, which fires whenever any users sharing a common room membership goes on or offline.

1
2
3
4
5
6
7
8
9
10
11
12
13
chatManager.connect(
  listeners = ChatListeners(
    onPresenceChanged = { user, newState, prevState ->
      // The prevState may be Online, Offline, or Unknown.
      // You can tell initial fetches of presence state from real time updates
      // because the prevState will be Unknown. If you use UI transitions when
      // presence states change, you might want to make these initial ones less
      // pronounced.
    },
    // ...
  ),
  callback = { /* ... */}
)

Cursors

Read cursors track how far a user has read through the messages in a room. Each read cursor belongs to a user and a room – represented by a Cursor object.

Cursor Properties

PropertyTypeDescription
positionStringThe message ID that the user has read up to.
updatedAtStringThe timestamp when the cursor was last set.
roomString (room id)The room that the cursor refers to.
userString (user id)The user that the cursor belongs to.
typeIntThe type of the cursor object, currently always 0 (representing a read cursor).

Setting a Cursor

When you are confident that the current user has “read” a message, call setReadCursor with a roomId and a position (the id of the newest message that has been “read”).

1
2
3
4
currentUser.setReadCursor(
  roomId = "lobby",
  position = lastMessage.id
)

Getting a Cursor

The current user’s read cursors are available immediately upon connecting. Access any existing cursors with the readCursor function. (A cursor that hasn’t been set yet is undefined.)

1
2
3
4
var cursorResult = currentUser.getReadCursor("lobby")
when (cursorResult) {
  is Result.Success -> /* cursorResult.value... */
}

Access Other User's Cursors

After subscribing to a room, read cursors for members of that room can be accessed by supplying a userId as the second parameter to the readCursor method.

1
2
3
4
var cursorResult = currentUser.getReadCursor("lobby", otherUserId)
when (cursorResult) {
  is Result.Success -> /* cursorResult.value... */
}

To be notified when any of the current user’s read cursors are changed, supply an onNewReadCursor listener on connection or match for NewReadCursor events.

1
2
3
4
5
6
7
8
9
currentUser.subscribeToRoom(
  roomId = "lobby",
  listeners = RoomListeners(
    onNewReadCursor = { cursor ->
      // e.g. toast("User ${cursor.user.name} has read up to message ${cursor.position}")
    }
  ),
  callback = { /* ... */ }
)

To be notified when any member of the room changes their read cursor, supply an onNewReadCursor listener when subscribing to the room or match the NewReadCursor event.

Logger

As part of ChatManager dependencies a custom logger can be provided:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
val customLogger = object: Logger {
  override fun debug(message: String, error: Error?) {
    // ...
  }
  override fun error(message: String, error: Error?) {
    // ...
  }
  override fun info(message: String, error: Error?) {
    // ...
  }
  override fun verbose(message: String, error: Error?) {
    // ...
  }
  override fun warn(message: String, error: Error?) {
    // ...
  }
}

var chatManager = ChatManager(
  instanceLocator = "YOUR_INSTANCE_LOCATOR",
  userId = "YOUR_USER_ID",
  dependencies = AndroidChatkitDependencies(
    tokenProvider = ChatkitTokenProvider(
      endpoint = "YOUR_AUTH_URL",
      userId = "YOUR_USER_ID"
    ),
    logger = customLogger
  )
)

Did you find this document useful?

We are always striving to create the most accurate and informative docs as possible. If there is something especially wrong (or right) here then please let us know.