Building a Social Network with SwiftUI – Part 2 (Direct Messaging)
Leveraging our code from part 1, we'll modify the backend to generate a Stream Chat frontend token so our mobile application can communicate directly with Stream's Chat API. 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 part 1.
For this part, we'll use Stream's UI components instead of building the views ourselves to see the great UI experience we get out of the box.
For the mobile app, we'll build it with Swift wrapping the Stream Chat Swift libraries.
The app goes through these steps to allow a user to chat with another:
- User navigates to the user list and clicks on their name or chat icon. 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, most of this work happens in the Stream Chat UI Components. If you'd like to follow along, make sure you get both the backend and mobile app running part 1 before continuing. Please refer to the respective package management files (Podfile, package.json) to see what versions of the libraries we're using.
Configuring Stream Chat
To start, we need to configure Stream chat. We'll add on to the login flow that we created in part 1. Recall when we login we set up Stream. This instance of Stream is for the Stream Feed API. For chat, we need to set up a different client to interact with the Stream Chat API. Like before, we need an endpoint that creates our Stream Chat frontend token, so our mobile application can communicate directly with the Stream API. Let's check out our new endpoint:
This endpoint generates the Stream Chat frontend token. It also creates or updates the user to associate the token with the user. This is so Stream knows who's talking to it when actions are taken in the mobile application. Using this endpoint, we can modify our login flow to request these credentials as well. We modify
.setupFeed from part one to call to
.setupChat after we're done with the feed setup. Here's the new version of
This code is largely the same except for the last line where we call
.setupChat instead of setting the
isAuthed flag. Here's
Here we call the endpoint we set up previously and get our frontend token. We use this to initialize the Chat Client singleton for use later. We then indicate we're authenticated via the
isAuthed flag so the application knows we're all set up and ready to go.
Selecting a User to Chat
Step 1: Adding Navigation
For this app, the chat view is it's own screen, meaning there is no tab bar when chatting. To support this we first wrap our
TabView inside of
ContentView with a
NavigationView wrapper allows us to push new views on the stack and provides transitions and a back button. Since we have a nice navigation bar, we add a small title to the application. The list of people now looks like this (ignore the chat bubble for now as it's explained next):
Notice the default navigation bar at the top now. This is present on all screens now and automatically provides a back button if we push a new view on the navigation stack, such as our 1-on-1 chat. This allows the user to get back to the main view when they're done chatting. Let's see push a chat view on the stack.
Step 2: Starting a Chat
Now that we have a
NavigationView at the top of our hierarchy, we can build a
NavigationLink to show the chat. Since we'd like to preserve the follow action, we'll place a start chat icon next to the follow icon. We'll also make the whole row clickable. This means that unless the user explicitly clicks the follow icon (from part 1), we'll start a chat. Here's
PeopleView with these udpates:
The navigation is done by the
NavigationLink embedded in the row. Since we give the content of the
Spacer this forces the icons to align right. Each link is tagged with its index allowing us to "select" it when a user clicks the name or the message icon. This allows each element to be clickable by assigning the tag, which triggers the
NavigationLink to be selected.
NavigationLink is selected, by setting the tag or clicking on the
Spacer, it pushes a
PrivateChatView (defined next) onto the navigation stack. By using these built-in navigation views, we get a nice default transition (swipe animation) to the next view with a back button built for us. Now we simply add our chat view in and we're done!
Viewing a Chat
Step 1: Creating a Channel
First, we create a channel via Streams' library. The job of
PrivateChatChannel is to create the channel and feed it to our
StreamChatView (defined below). Let's look at the code for
We check if the channel has been initialized. If it's not, we show a loading screen and perform
loadChannel. This calls to
account.createPrivateChannel. Here's the implementation:
This function generates a unique channel id and sets up a private channel that contains only the two members that are chatting. It returns the channel via the completion.
Now that the view has a channel, it can boot a
StreamChatView. This is a view that wraps Steam's
Step 2: Wrapping Stream's UI Components
In the first part of this series, we built our timeline view from scratch. In this part, we'll wrap Stream's UI components to make our jobs easier. Stream provides convenient components to render a great chat experience with little work. There are no SwiftUI components yet, however, using Swift's built-in support for
UIControllers we can easily wrap Stream's library. Let's see the code for
We use SwiftUI's
UIViewControllerRepresentable which allows us to create and manage a
UIViewController. Since Stream's high-level components are
UIViewControllers this is exactly what we need to bring them into SwiftUI.
All Stream's component needs to render is a
ChannelPresenter. There's a lot of oppurtunities to customize this view, but we'll keep it simple for now. Since we instantiated this object when creating the channel, we simply use the one passed along to us by
PrivateChatChannel. With that object, we create a
ChatViewController instance, which comes from Stream's library, and give it the
ChannelPresenter. Now we get a full chat experience with only a few lines of work:
And we're done. Stream's built-in views makes it straightforward to build a great chat experience!