Instantly Send Audio Messages With Stream Chat and Flutter

Many chat applications today allow users to send voice notes as messages. In this tutorial, you’ll learn how to send voice notes, or audio attachments, in your Stream Chat Flutter app. By the end, your app will feature a chat experience similar to the shown here.

This tutorial will cover the following sections in detail:

  • Set Up Your Stream Account
  • Create Demo User Accounts
  • Disable Authentication for Development
  • Set Up Your Flutter Account
  • Create a Channel List Page
  • Create a Channel Page to View Messages
  • Add a Custom Action Widget to Record a Voice Note
  • Add a Custom Attachment Builder

Set Up Your Stream Account

To get started, you’ll need a Stream account to access the Stream Chat Messaging API. If you don’t have a Stream account already, you can sign up for a free 30-day trial.

If you’re working on a personal project or own a small business, you can register for a Stream Maker Account and access Stream Chat for free indefinitely.

After creating your account, create an app and name it:

  1. Go to your Stream dashboard.
  2. Select Create App.
  3. Enter an App Name (like Audio Attachment Demo).
  4. Set your Feeds Server Location.
  5. Set the Environment to Development.
  6. Select Create App.

After creating your app, you should see it listed in your Stream dashboard with your app’s respective API KEY and Secret.

Your API Key is only an app identifier and safe to share publicly. Your Secret helps generate authenticated user tokens and should be kept private.

From this dashboard, you can edit the app, access data, and create new apps.

Create Demo User Accounts

Stream offers many methods to create user accounts. In a production environment, you’d ideally manage user account creation and token generation server side.

However, for demo purposes, it’s easier to create accounts on your Stream dashboard.

To create demo accounts for your Steam app:

  1. Go to your Audio Attachment Demo app.
  2. Select Options.
  3. Select Open in Chat Explorer. (This will direct you to the Explorer dashboard, where you can create channels and users.)
  1. Select Users, then Create New User.
  2. In the Create New User window, enter a User Name and User Id.
  3. In the User Application Role dropdown menu, select user.

You can create as many users as you’d like for your demo app.

Disable Authentication for Development

Any user accounts you create will require authentication tokens to access the Stream API. For demo purposes, you should disable these authentication checks and use developer tokens instead.

To disable authentication for your app:

  1. Go to your Audio Attachment Demo app.
  2. In the dashboard nav menu, select Chat.
  3. From the Chat dropdown, select Overview.
  4. Scroll to the Authentication section.
  5. Select the Disable Auth Checks toggle button.

If you don’t want to disable authentication, you can easily generate tokens using Stream’s User JWT Generator.

⚠️Note: In a production scenario, you must generate a token using your server and one of Stream's server SDKs. You should never hardcode user tokens in a production application.

Set Up Your Flutter App

If you’re new to Stream, see the Stream Flutter Chat tutorial for a more thorough introduction to all the basic components available to you.

Otherwise, go ahead and create a new Flutter application from your terminal or preferred IDE with the following command:

Loading code sample...

Note: This tutorial was tested using version 2.2.3 of Flutter.

Open the project and add the following code to your pubspec.yaml file:

Loading code sample...

Note: Future versions may have breaking changes.

Create a config.dart file with the following code:

Loading code sample...

In the snippet above, you:

  1. Set your unique App Key to streamKey. (You can get this unique key from your app's dashboard on Stream.)
  2. Created a DemoUser model to store user information.
  3. Created two demo users. These should use the same ids that you set on your Stream dashboard. (Note that you’re hardcoding a name and image; ideally, these values should be set using your server and one of Stream's server SDKs.)

Replace the code inside main.dart with the following:

Loading code sample...

In the above snippet, you:

  1. Instantiated a StreamChatClient using Stream’s Flutter SDK.
  2. Created a MyApp widget with the home attribute set to SelectUserPage, and wrapped the application with a builder that creates a StreamChat widget, which handles a lot of the chat logic out of the box.
  3. Created the SelectUserPage and SelectUserButton widgets, which show two demo accounts to select from.
  4. Created an onPressed handler to connect a user using the Stream client. (You can only call client.devToken(user.id) - devToken if you disabled Authentication.)
  5. Navigate the user to the ChannelListPage after connecting them. The ChannelListPage lists all the channels for your Stream app.

Create a Page to List All Channels

Next, you’ll display a list of channels where the current user is a member. It’s better to separate the code into multiple files so that it’s easier to maintain as the code grows.

Create a new file called channel_list_page.dart and add the following code:

Loading code sample...

In the snippet above, you:

  1. Created a Scaffold with the body set to the list of channels and a button in the actions attribute to disconnect the current user and navigate back to the SelectUserPage.
  2. Created ChannelsBloc and ChannelListView widgets (provided by the Stream package) that will display all channels for your Stream app.
  3. Created an emptyBuilder, which will return if there are no channels for your app. (In the emptyBuilder, you return a button that creates a new channel and sets the members to the users you created.)
  4. Specified the page to open when a channel is selected in the channelWidget attribute, setting it to ChannelPage (a custom widget you’ll create next).
  5. Added a filter to only show channels where the current user is a member.
  6. Added sort and pagination, which you can customize as needed.

Create a Channel Page to View Messages

To display the message list view, create a new file called channel_page.dart and add the following code:

Loading code sample...

In this snippet, you:

  1. Created a Scaffold for the new page.
  2. Set the appBar to be a ChannelHeader (this shows the channel name and image).
  3. Created a Column with an expanded MessageListView (a list that displays all channel messages, images, and custom attachments).
  4. Created a MessageInput at the bottom of the Column, which is used to send new messages and attachments to the channel.

If you run the app now, you’ll find–with only a small amount of code–that you have a pretty robust messaging app complete with all the necessary functionality.

Now, you’re at the point where you can add the functionality to support audio messaging.

Add a Custom Action Widget to Record a Voice Note

To support audio messaging, you’ll add a custom action widget so users can record a voice note and send it as a message. At the end of this section, your widget should look like this:

In your MessageInput provide the following custom action:

Loading code sample...

You’ll create the _recordingFinishedCallback method later. First, create a new file called record_button.dart and add the following code:

Loading code sample...

In this widget, you:

  1. Created an instance of Record called _audioRecorder, which uses the Record package to make it easy to capture audio recordings in Flutter. (The record package requires some minimal iOS and Android set up to use; read the Flutter Package docs for more information.)
  2. Created _start and _stop methods to control the audio recording.
  3. Created a build method that uses a GestureDetector to start stop a recording.
  4. Used a RecordCallback type definition to send back the string path of the recorded file (called in the _stop method).

Go back to the channel_page.dart and create the _recordingFinishedCallback method in the _ChannelPageState class:

Loading code sample...

When the recording is finished, the _recordingFinishedCallback will be called. It does the following:

  1. Parses the path to the URI.
  2. Creates a new File from the uri.path.
  3. Uses the then callback on file.length to handle the file length as it’s retrieved (the file length is needed to upload the attachment to Stream).
  4. Gets the current channel using StreamChannel.of(context).channel.
  5. Calls sendMessage on the channel and provides a Message with the Attachment.
  6. Sets the type to voicenote (this can be any identifier) and creates an AttachmentFile with the path and size of the file.

Now, you have the necessary functionality to record an audio file and upload it to Stream.

Add a Custom Attachment Builder

With the current code, the MessageListView still doesn’t know how to render attachments with the type: ‘voicenote’. You must tell it how with the messageBuilder argument.

In channel_page.dart, change your MessageListView to the following:

Loading code sample...

The messageBuilder gets the defaultMessage, which has a copyWith method to override the customAttachmentBuilders for the list view. You're creating a custom builder for the voicenote type (a type you specified).

In the builder, you check to see that the first attachment's assetUrl isn’t null. If it is null, you return AssetLoadingMessage. Otherwise, you return AudioPlayerMessage and specify the AudioSource. (The AudioSource is a class that comes from the just_audio package and uses the attachment URL to load the audio.)

Next, create a new file called audio_loading_message.dart and add the following:

Loading code sample...

This code will show a loading indicator as the asset sends.

Finally, create a file called audio_loading_message.dart and add the following code:

Loading code sample...

This widget controls the audio playback for a voice note, allowing you to play, pause, and skip to different parts of the audio file with a slider.

For more information on how to use this package, see the just_audio package documentation.

By the end, your channel_page.dart imports should look similar to this:

Loading code sample...

Wrapping Up

That's it 🎉! You should see a channel page like the one below that allows you to record and send voice notes:

To see the full source code for this Stream Chat Flutter app, see the Stream Audio Attachment Tutorial GitHub.

There are several other Stream Flutter packages that provide various levels of UI and low-level chat control, including offline support and localization. See the Stream Chat Flutter GitHub for more information.

Lastly, you can also subscribe to the Stream Developers YouTube channel for more exclusive dev content.

Happy coding!