Stream Flutter: Building a Social Network with Flutter — Direct Messaging
Tags: flutter, feeds, tutorial, chat
Published: 2020-03-03 22:25:03

In the second part of our series, we'll implement direct messaging between users by integrating [Stream Chat](https://getstream.io/chat/). This post assumes you've followed along with [part 1](https://getstream.io/blog/stream-flutter-building-a-social-network-with-flutter-activity-feeds). 

Leveraging our code from part 1, we'll modify the backend to generate a Stream Chat frontend token so we can do the rest of the work in the mobile application. The Stream Chat frontend token securely authenticates a client application with the Stream Chat so it can directly talk with the API without going through our backend. This token is created and stored as a part of the login process, which is defined in section 1.

For the backend, we'll add in the [Stream Chat JavaScript](https://www.npmjs.com/package/stream-chat) library. 

For the mobile app, we'll build it with Flutter wrapping the Stream Chat [Android](https://github.com/GetStream/stream-chat-android) and [Swift](https://github.com/GetStream/stream-chat-swift) libraries.

The app goes through these steps to allow a user to chat with another:

* User navigates to the user list, selects a user, and clicks "Chat". The mobile application joins a 1-on-1 chat channel between the two users.
* The app queries the channel for previous messages and indicates to Stream that we'd like to watch this channel for new messages. The mobile app listens for new messages.
* The user creates a new message and sends it to the Stream API. 
* When the message is created, or a message from the other user is received, the mobile application consumes the event and displays the message.

Since we're relying on the Stream mobile libraries to do the heavy lifting, such as creating a WebSocket connection with the Stream API, this process is a dance between the Flutter code and native (Swift/Kotlin) via [Platform Channels](https://flutter.dev/docs/development/platform-integration/platform-channels). If you'd like to follow along, make sure you get both the backend and mobile app running part 1 before continuing.

## Prerequisites

Basic knowledge of [Node.js](https://nodejs.org/en/) (JavaScript), [Flutter](https://flutter.dev/) ([Dart](https://dart.dev/)), and [Kotlin](https://kotlinlang.org/), is required to follow this tutorial. Knowledge of Swift is useful if you want to browse the iOS implementation. This code is intended to run locally on your machine. 

If you'd like to follow along, you'll need an account with [Stream](https://getstream.io/accounts/signup/). Please make sure you can run a Flutter app, at least on Android. If you haven't done so, make sure you have Flutter [installed](https://flutter.dev/docs/get-started/install). If you're having issues building this project, please check if you can create run a simple application by following the instructions [here](https://flutter.dev/docs/get-started/test-drive).

Once you have an account with Stream, you need to set up a development app (see part 1):

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/448284823bc0aed4196c5983d0701e01/create-app.png?auto=compress%2Cformat&ixlib=php-1.2.1)

You'll need to add the credentials from the Stream app to the source code for it to work. See both the `mobile` and `backend` READMEs. 

Let's get to building!

## User sends a message

### Step 1: Get Stream Chat frontend token

To communicate with the Stream Chat API from our mobile app, we need an authenticated frontend token. To get this, we'll add a step into our login process that requests this token, essentially replicating how we received our Stream Activity Feed API frontend token. First, in the mobile app, we add a step to the login that requests this token be generated by the `backend`:

https://gist.github.com/nparsons08/338dc97f392220cfc8e4af6876a7f549

This is identical to part 1's login flow except for the `chatToken` creation and `setupChat`. Here we hit a separate `backend` endpoint `/v1/stream-chat-credentials` to generate the token. Let's look at how the backend is implemented:

https://gist.github.com/nparsons08/06bd9f9052eefa38bda3e9a411586143

Here we use the same Stream credentials as the feed endpoint but use them to create a `StreamChat` instance. Using this object, we can create a token and update that user in Stream. We return this data to the mobile app so it can store it for use.

Once we've received the token, we can set up our chat objects by telling the native code to `setupChat`. Here is what's happening on the Android side:

https://gist.github.com/nparsons08/a225c9d3bc023a59f8edb6d5a84f3528

Here we're initializing the `StreamChat` singleton with our `API_KEY`. We set the user on this instance, so Stream knows who's talking to the API and how to authenticate them. This sets up future calls to this library to share the configured instance.

### Step 2: Start a chat

Once we have our token, the user can navigate to the user list and indicate which user they'd like to start a chat with. To do this, we'll add a new button, "Chat", next to "Follow" that starts the conversation.

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/71263ec8bfe748047510118e1e7d120d/start-chat.png?auto=compress%2Cformat&ixlib=php-1.2.1)

Let's look at how we add this button to this dialog:

https://gist.github.com/nparsons08/f560e6e36ec4330d07b7703c3a6657f3

Since we already have our dialog built, this is relatively straightforward. We simply create a `FlatButton` that pushes a `Chat` widget onto the navigation stack.

### Step 3: Sending a message

First, we need to create a basic layout that will display our chat messages and our input. The view looks like this:

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/a7dbf92a8b01f38441ba5edeb9f10898/chat-empty.png?auto=compress%2Cformat&ixlib=php-1.2.1)

And the Flutter code that produces this:

https://gist.github.com/nparsons08/55355625391f6fd5cb42adcb5fcf2602

Here we use a simple `Scaffold` layout to give us an `AppBar` with a back button to exit the chat. If the `_messages` variable is null, which it will be when we first navigate, we'll display a loading spinner. Once the initial messages are loaded, we show the chat widgets. For now, we're going to focus on sending a message.  We'll explore how messages are loaded and displayed via the `buildMessages` method later.

First, we'll need an input and a send button. Let's define `buildInput` which creates the new message input widget:

https://gist.github.com/nparsons08/e71866b1d2ae4bf95add993611152846

This is a `Container` that is a `Row` containing the message input and send button. The message input is `Flexible` meaning it will take up any remaining space in the `Row` not taken by the other widgets, in this case, the send button. The input is bound to `_messageController` which is a `TextEditingController`. This stores whatever is typed in. 

When a user is ready to send the message, they will press the send button which triggers `_postMessage`:

https://gist.github.com/nparsons08/1e4254cb5d7953890771d725ea9dff1c

If there's text in the `_messageController`, we take the text and send it to the native code with the account and user that is sending the message. The `ApiService` will call the native Stream Chat library to send the message to the channel. Once that's done, we clear the text to be ready for the next message.

Let's take a look at what's happening in `ApiService`:

https://gist.github.com/nparsons08/0216215acff6865454523780881c16c7

We simply send this message, along with the `chatToken` created during login, via the platform channel. The native side, implemented in Kotlin, is where we interact with Stream to send the message:

https://gist.github.com/nparsons08/48b8302f1fe7c69dfffe071317d0d96f

We start by grabbing the `StreamChat` instance that we created during login via `setupChat`. We connect to our channel, which is a `messaging` channel [type](https://getstream.io/chat/docs/initialize_channel/?language=js#channel_initialization_parameters). The channel id is the two user's ids sorted and joined together with `-`. So if we're `sara` chatting with `jeff` our channel id is `jeff-sara`. Using a unique, consistent id is essential, so our users are joining the correct channels.

Once we've got the correct channel initialized, we create a Stream `Message` add the text and send it to the channel. Stream is smart enough to lazily generate the channel if it does not exist when we send our first message. On success, we simply respond true. We could choose to send the message back to the Flutter side for display, but since we're listening to the Stream WebSocket for new messages, we'll see our new message come across there. We'll explore how this is done next.

If you're confused about how we call native code from Flutter, please refer to the Flutter [documentation](https://flutter.dev/docs/development/platform-integration/platform-channels).

## User views messages

### Step 1: Streaming messages

When the `Chat` widget first loads, we want to grab the initial messages from the channel. We'll trigger the message loading in `initState`:

https://gist.github.com/nparsons08/507e8ae01bb8afbc5d1f60ac160f9d4c

Here we call the `ApiService` to start a connection with the channel. The `listenToChannel` method will first send along historical messages to our callback then any subsequent messages that come across until we tell it to stop, whenever a new set of messages is generated. This happens whenever either user creates a message. 

This widget responds to new messages by setting a new state by merging the previously displayed messages with any new messages that have been sent along. 

This stream is open until we explicitly cancel the channel connection. The `listenToChannel` method returns a cancel function. We store this and call it when we dispose of the widget:

https://gist.github.com/nparsons08/13de274ed63c2d2b25a667f85e4cbfe6

Let's look at how we implement the `listenToChannel` method:

https://gist.github.com/nparsons08/acec73907076b1aa09dd189071908ec8

First, we call the native side to set up the channel. We'll look at this in a second, but it essentially starts the WebSocket connection with Stream's API and returns the channel id. Using this channel id, we bind to a Flutter [EventChannel](https://api.flutter.dev/flutter/services/EventChannel-class.html). This class allows us to create an event stream between the native code and our flutter code. We bind to the broadcast stream under that channel id and start it by calling `listen`. As messages come in, the native code will request our callback with a JSON string. We decode the results and pass them along to the `listener` callback given to this method. 

For the caller code to tell us when to cancel this subscription, we return a small void function which wraps our `subscription.cancel` call.

Now we go to the native Android implementation of `setupChannel` to see how we start a connection with Stream and listen for new messages.

https://gist.github.com/nparsons08/8ddc81c841ffa1a8ba850d5a714d5c3d

A lot is going on here, but we'll walk through it step by step. Essentially, this function does four things: 

1) Set up a Flutter Java [EventChannel](https://api.flutter.dev/javadoc/io/flutter/plugin/common/EventChannel.html).
2) Tells Stream that we're going to watch this channel (by starting a WebSocket connection) and sends the first page of messages to the event sink.
3) Binds to the channel's new messages event stream and sends them to the `EventChannel`.
4) Handles when the user cancels this stream and cleans everything up.

Walking through the code, the first thing the code does is sets up the channel with the correct `channelId` using our `StreamChat` singleton. We then build our Flutter `EventChannel` using that `channelId` so we can stream messages (events) back to the dart side. We store this so we can later unbind our event listeners.

To keep things all in one place, the code uses [object expressions](https://kotlinlang.org/docs/reference/object-declarations.html) to instantiate objects of an anonymous class.

The call to `setStreamHandler` takes an object that implements `EventChannel.StreamHandler`. This requires two callback methods `onListen` and `onCancel`. `onListen` happens when the `EventChannel` is listened to, which occurs on the dart side (shown above in the `api_service.dart`). `onCancel` is called when the listener tells us to stop (also shown above in `api_service.dart`).

Looking at the `onListen` method, we see this is where we tell the channel we'd like to watch it. This method does two things. First, it informs Stream that we're going to bind to the channel and listen for any new messages. It also queries the last set of messages and calls our `QueryWatchCallback` object's method `onSuccess` with those messages. We send these messages along to our `eventSink` established by our `EventChannel`, which streams them to the dart code.

After we've told Stream that we're listening, we then need to bind to the channel's event stream. We add an event handler that implements [`ChatChannelEventHandler`](https://getstream.github.io/stream-chat-android/com/getstream/sdk/chat/rest/core/ChatChannelEventHandler.html). We override the one event we care about, which is `onMessageNew`. Whenever we get a new event, we pull the message off and send it to the `eventSink`.

When the dart side has decided to cancel the stream, the `EventChannel`'s `onCancel` method will be called. We tell the channel we're done by calling `stopWatching`, remove the event handler, and forget about the `EventChannel`.

### Step 2: Displaying messages

Now that we're streaming messages to our Flutter code and storing them in `_messages`, we can show them to the user. First, we build a `ListView` that contains our messages:

https://gist.github.com/nparsons08/e05204ec5dd4e189937516d2b2ef36b6

We reverse the list to show the most recent message on the bottom. This also makes it so the `ListView` scrolls to the bottom of the list by default, which is the messaging UX users expect. Each message is then it's own `Widget` that shows our messages on the right in a blue bubble and the other user's messages in a grey bubble on the left. 

https://gist.github.com/nparsons08/66edf92acaf3df30c0cab6dd29069e69

The final result will look like this:

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/062288488062aa34950fbb99880d7688/chat-complete.png?auto=compress%2Cformat&ixlib=php-1.2.1)

And that's it! You now have basic direct messaging between users.

## Final Thoughts

Now our app has the ability for a user to post updates to their activity stream and direct message other users. Using both the Activity Feed and Chat products from Stream allowed us to build this with virtually no backend. 

A big part of Flutter's power is the ability to leverage native iOS and Android libraries easily. You can get more for free by integrating Stream's out the box views. Utilizing Flutter's [AndroidView](https://api.flutter.dev/flutter/widgets/AndroidView-class.html) or [UiKitView](https://api.flutter.dev/flutter/widgets/UiKitView-class.html) we can embed Stream Chat Android's [https://github.com/GetStream/stream-chat-android/blob/master/docs/MessageList.md]. Keep in mind this level of integration is still considered in preview and is quite expensive, but it provides an even faster way to get up and running with Stream and Flutter.

Stream is also working hard, building a pure [dart library](https://github.com/GetStream/stream-chat-dart). While it's not ready yet, be sure to check back and see when it's available for production!

> In addition to the tutorial above, Stream offers a comprehensive [Flutter SDK Tutorial](https://getstream.io/chat/flutter/tutorial/), so you can see how to get up and running quickly with Flutter.