🎉 New! Web Push Notifications for Chatkit. Learn more in our latest blog post.
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Step by step guide to migrating an Android app from Layer to Pusher

  • Neo Ighodaro

August 5th, 2019
If you want to follow along, you will need an Android development environment on your machine.

Step by step guide to migrating an Android app from Layer to Pusher

Introduction

As software developers, keeping your code up to date with recent technology is vital. You must always be ready to do so when the need arises. Today, we will learn how we can migrate the chat functionalities of our app from Layer to Pusher Chatkit. For our migration use case, we will be looking at an existing Android app built with Layer.

Creating a Chatkit app

Before you go ahead, you need to create a Chatkit instance. Open your Chatkit dashboard and create a new instance. Name it migrate-layer.

Updating dependencies

First things first, you have to remove the Layer dependency and replace it with Chatkit’s dependencies. Open your app module build.gradle file and add this:

    implementation 'com.pusher:chatkit-android:1.3.3'

Remember to sync your gradle files so that the Chatkit dependency will be downloaded for you.

Authentication

The next part of the Layer application we will migrate is the authentication part. As it is common with messaging apps, there must be a way you authenticate and authorize your users. In Layer, you would do that by calling this method:

    layerClient.authenticate()

But before you call this method, it is expected that your layerClient object is initialized with your Layer app keys and there has been a successful connection. Let’s say you have a login activity where you perform the authentication, you are expected to implement these methods in the activity:

    override fun onConnectionConnected(p0: LayerClient?) {}

    override fun onConnectionError(p0: LayerClient?, p1: LayerException?) {}

    override fun onConnectionDisconnected(p0: LayerClient?) {}

    override fun onAuthenticated(p0: LayerClient?, p1: String?) {}

    override fun onDeauthenticated(p0: LayerClient?) {}

    override fun onAuthenticationError(p0: LayerClient?, p1: LayerException?) {}

    override fun onAuthenticationChallenge(p0: LayerClient?, p1: String?) {}

And so, you are expected to call the layerClient.authenticate() method inside the onConnectionConnected callback because that is the callback that guarantees that layerClient has connected successfully.

Once the layerClient.authenticate() method is called, the onAuthenticationChallenge callback is triggered. Inside this callback, you will perform a request to your server to generate a token for you. You will need to pass the user’s id and a nonce (the p1 in the callback).

Here is a sample implementation of a Node.js server for your token. When you get your token, you can call:

    layerClient.answerAuthenticationChallenge(tokenFromServer);

And your authentication should be successful.

Now, let us see how you can reduce this hassle and callbacks when using Chatkit. When authenticating users in Chatkit, it is best to do so with your custom written backend.

The first thing you will do is pass the user ID to your server. The server will create a new user with the Chatkit server SDK. Here is an endpoint that creates a new user:

    app.post("/users", (req, res) => {
      const userId = req.body.userId;
      chatkit
        .createUser({
          id: userId,
          name: userId
        })
        .then(() => {
          res.sendStatus(200);
        })
        .catch(err => {
          console.log(err);
          if (err.error === "services/chatkit/user_already_exists") {
            console.log(`User already exists: ${userId}`);
            res.sendStatus(200);
          } else {
            res.status(err.status).json(err);
          }
        });
    });

Notice that if we notice that the user already exists, we still return success. The main aim of this endpoint is to be sure that Chatkit has a reference or recognizes that user ID. If it does not, we won’t be able to obtain a token.

After you’ve confirmed that the user exists on Chatkit, you can now connect to Chatkit and request a token like so:

    val chatManager = ChatManager(
            instanceLocator = "CHATKIT_INSTANCE_LOCATOR",
            userId = yourUserId,
            dependencies = AndroidChatkitDependencies(
                    tokenProvider = ChatkitTokenProvider(
                            endpoint = "http://localhost:3000/token",
                            userId = yourUserId
                    )
            )
    )

    chatManager.connect { result ->
      when (result) {
        is com.pusher.util.Result.Success -> {
          // result.value
        }

        is com.pusher.util.Result.Failure -> {

        }
      }
    }

You will have to replace CHATKIT_INSTANCE_LOCATOR with your instance locator on your Chatkit dashboard.

You will notice that the ChatManager for Chatkit is the equivalent of LayerClient. The two objects are responsible for initializing the chat functions with their respective dashboard keys. Unlike the LayerClient, the ChatManager not only initializes the app with your ChatKit keys, it fetches the token required for that user and then returns a CurrentUser object. With Chatkit, you don’t have to override so many authentication callbacks.

Here, we created a chat manager instance and then made a connection to get the user’s details. This is something similar to what you have on Layer. Notice that you are passing the instance locator, your user ID variable (yourUserId) and your token endpoint to your chat manager instance.

Your token endpoint should be handled by your custom created server too. If you’re using Node.js, the endpoint will look just like this:

    app.post("/token", (req, res) => {
      const result = chatkit.authenticate({
        userId: req.query.user_id
      });
      res.status(result.status).send(result.body);
    });

The result of a successful connection (result.value) is an instance of CurrentUser. You should store this object globally, maybe the Application class so that you can access it across your whole application.

Conversations vs Rooms

While you have conversations in Layer, in Chatkit, they are rooms. After successful authentication, ideally, the user should see whom they can chat with, it could be a group or an individual.

To see the conversations a user belong to in Layer, you would do something like this:

    val list = LayerSampleApp.instance.layerClient.conversations

While in Chatkit, you can view the rooms a user belong to by using this:

    // fetch all  user's room
    val roomlist = currentUser.rooms

    // get rooms user can join
    currentUser.getJoinableRooms { 
        // handle result
    }

And if you want to get rooms a user can join, you use this:

    currentUser.joinRoom(room!!,callback = { result ->
        // handle result
    })

Fetching messages

In Layer, to fetch previous messages, you would have something like this:

    val messageList = LayerSampleApp.instance.layerClient.getMessages(LayerSampleApp.instance.conversation)

This snippet fetches all messages in a particular conversation. To do this in Pusher, you will have a snippet like so:

    PusherSampleApp.instance.currentUser.fetchMultipartMessages(
        roomId = PusherSampleApp.instance.room.id,
        callback = {
            when (it){
                is Result.Success -> {
                    var messageList = it.value
                }    
            }    
        }
    )

This snippet fetches previous messages in the room. By default, the limit is 10 messages, but you can update it by using the limit parameter in the fetchMultipartMessages method.

In Chatkit, you can subscribe to a room to get notified of notable events that occur in the room. One of which is when a new message comes in. You can do so like so:

    val room = PusherSampleApp.instance.room
    PusherSampleApp.instance.currentUser.subscribeToRoomMultipart(
        roomId = room.id,
        listeners = RoomListeners(
            onMultipartMessage = {
                runOnUiThread {
                    // add message to adapter
                }
            }
        ),
        messageLimit = 20, // Optional
        callback = { subscription ->

        }
    )

The advantage of using this over the fetchMultipartMessages method is that subscribeToRoomMultipart method fetches previous messages based on your limit and still listens for new messages for you.

Sending messages

In Layer, for you to send a message, you need to send the message to a conversation (somewhat similar to a room in Pusher). Here is a snippet of how that is done:

    val messageText = "Yipee! We're migrating"
    val messagePart = layerClient.newMessagePart("text/plain", messageText.toByteArray())

    LayerSampleApp.instance.conversation..send(messagePart?.message,object:LayerProgressListener {
        override fun onProgressUpdate(p0: MessagePart?, p1: LayerProgressListener.Operation?, p2: Long) {}

        override fun onProgressStart(p0: MessagePart?, p1: LayerProgressListener.Operation?) {}

        override fun onProgressComplete(p0: MessagePart?, p1: LayerProgressListener.Operation?) {}

        override fun onProgressError(p0: MessagePart?, p1: LayerProgressListener.Operation?, p2: Throwable?) {}

    })

In this snippet, you are just sending a plain text to the conversation in question.

To do this with ChatKit, you need the CurrentUser object that was returned after successful authentication. You can either send a usual text or a message that has some attachments. Here is an example of sending a simple message:

    PusherSampleApp.instance.currentUser.sendSimpleMessage(
        roomId = room.id,
        messageText = "We just migrated!!!",
        callback = { result ->
            when (result){
                is Result.Success -> {
                    // MessageID -- result.value
                }

                is Result.Failure -> { }
            }
        }
    )

In this snippet, we are just sending a mere text message to a room. If you want to send a more complex message, you can do use the sendMultipartMessage method like so:

    PusherSampleApp.instance.currentUser.sendMultipartMessage(
        roomId = room.id,
        parts = listOf(
            NewPart.Inline("This is a regular text part", "text/plain"),
            NewPart.Url("https://pusher.com/", "text/html"),
            NewPart.Attachment(
                type = "image/jpeg",
                file = File("/path/to/myImage.jpg").inputStream(),
                name = "myImage.jpg",
                customData = mapOf("source" to "camera")
            )
        ),

        callback = { result ->

        }
    )

In this snippet, we are sending a message with three different parts:

  • A normal text. We added this using the NewPart.Inline class and by passing a text/plain type.
  • A URL. We added this using the NewPart.Url class and passing the text/html type.
  • An attachment. We added this using the NewPart.Attachment class. In this case, the attachment is an image.

    Conclusion

In this tutorial, we have seen how we can migrate the main features of a Layer app to Chatkit. Chatkit has a bunch of other features such as typing indicators, read receipts that you can use and make your chat apps seamless. You can checkout the GitHub repo for further exploration.

  • Chat
  • JavaScript
  • Node.js
  • Android
  • Chatkit

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.