Flutter Quickstart Guide

Overview

This guide will walk you through simple instructions to create a video conferencing app using 100ms Flutter SDK and test it using an emulator or your mobile phone. Ensure that you use the latest versions of the SDKs to follow the instructions in this guide.

PackageVersion
hms_room_kitPub Version
hmssdk_flutterPub Version

Please check our basic concepts guide to understand the concepts like rooms, templates, peers, etc.

This guide contains instructions for two approaches to get you started with 100ms Flutter SDK:

  1. Create a sample app — instructions to create a flutter app quickly with a complete code sample.
  2. Building step-by-step — instructions to walk you through the implementation of the app in a step-by-step manner.

You can also check our basic sample app on GitHub.

Check out the full-fledged example app implementation in the 100ms Flutter SDK GitHub repository showcasing multiple features provided by 100ms. This uses the provider package as the state management library.

We also have other sample apps built using other popular state management libraries :

Create a sample app

This section contains instructions to create a simple Flutter video conferencing app. We will help you with instructions to understand the project setup and complete code sample to implement this quickly.

Prerequisites

To complete this implementation for the Android platform, you must have the following:

Users of iOS devices running Flutter versions 3.10.xx or earlier might experience crashes due to a known issue reported on GitHub link.

Create a Flutter app

Once you have the prerequisites, follow the steps below to create a Flutter app. This guide will use VS code, but you can use any IDE that supports Flutter.

  • Create a Flutter app using the terminal; you can get the Flutter SDK and use the below command:

    flutter create my_app
  • Once the app is created, open it in VS code.

Add 100ms SDK to your project

Once you have created a Flutter app, you must add the 100ms Flutter SDK and permission_handler package (to handle audio/video permissions from microphone and camera) to your app.

  • Add the below snippet to the pubspec.yaml.
# 100ms SDK and permissions_handler hmssdk_flutter: permission_handler:
  • Run flutter pub get to download these dependencies to your app.

Add permissions

Please follow the below instructions to test the app for the android target platform:

  1. Allow camera, recording audio and internet permissions by adding the below snippet to the AndroidManifest.xml file (at the application tag level).


    <uses-feature android:name="android.hardware.camera"/> <uses-feature android:name="android.hardware.camera.autofocus"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
  2. Add minimum SDK version (minSdkVersion 21) in "android/app/build.gradle" file (inside "defaultConfig").

... defaultConfig { ... minSdkVersion 21 ... } ...

You will also need to request camera and record audio permissions at runtime before you join a call or display a preview. Please follow the Android Documentation for runtime permissions.

Complete code example

Now that your project setup is complete let's replace the code in the lib/main.dart file with the complete code sample below.

import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:permission_handler/permission_handler.dart'; void main() => runApp(const MaterialApp(home: HomePage())); class HomePage extends StatelessWidget { const HomePage({super.key}); Future<bool> getPermissions() async { if (Platform.isIOS) return true; await Permission.camera.request(); await Permission.microphone.request(); await Permission.bluetoothConnect.request(); while ((await Permission.camera.isDenied)) { await Permission.camera.request(); } while ((await Permission.microphone.isDenied)) { await Permission.microphone.request(); } while ((await Permission.bluetoothConnect.isDenied)) { await Permission.bluetoothConnect.request(); } return true; } // UI to render join screen Widget build(BuildContext context) { return Scaffold( body: Container( color: Colors.black, child: Center( child: ElevatedButton( style: ButtonStyle( shape: MaterialStateProperty.all<RoundedRectangleBorder>( RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), ), ), // Function to push to meeting page onPressed: () async { await getPermissions(); Navigator.push( context, CupertinoPageRoute(builder: (_) => const MeetingPage()), ); }, child: const Padding( padding: EdgeInsets.symmetric(vertical: 20, horizontal: 20), child: Text( 'Join', style: TextStyle(fontSize: 20), ), ), ), ), ), ); } } class MeetingPage extends StatefulWidget { const MeetingPage({super.key}); State<MeetingPage> createState() => _MeetingPageState(); } class _MeetingPageState extends State<MeetingPage> implements HMSUpdateListener { //SDK late HMSSDK hmsSDK; // Variables required for joining a room String authToken = "APP_TOKEN_FROM_DASHBOARD"; String userName = "test_user"; // Variables required for rendering video and peer info HMSPeer? localPeer, remotePeer; HMSVideoTrack? localPeerVideoTrack, remotePeerVideoTrack; // Initialize variables and join room void initState() { super.initState(); initHMSSDK(); } void initHMSSDK() async { hmsSDK = HMSSDK(); await hmsSDK.build(); // ensure to await while invoking the `build` method hmsSDK.addUpdateListener(listener: this); hmsSDK.join(config: HMSConfig(authToken: authToken, userName: userName)); } // Clear all variables void dispose() { remotePeer = null; remotePeerVideoTrack = null; localPeer = null; localPeerVideoTrack = null; super.dispose(); } // Called when peer joined the room - get current state of room by using HMSRoom obj void onJoin({required HMSRoom room}) { room.peers?.forEach((peer) { if (peer.isLocal) { localPeer = peer; if (peer.videoTrack != null) { localPeerVideoTrack = peer.videoTrack; } if (mounted) { setState(() {}); } } }); } // Called when there's a peer update - use to update local & remote peer variables void onPeerUpdate({required HMSPeer peer, required HMSPeerUpdate update}) { switch (update) { case HMSPeerUpdate.peerJoined: if (!peer.isLocal) { if (mounted) { setState(() { remotePeer = peer; }); } } break; case HMSPeerUpdate.peerLeft: if (!peer.isLocal) { if (mounted) { setState(() { remotePeer = null; }); } } break; case HMSPeerUpdate.networkQualityUpdated: return; default: if (mounted) { setState(() { localPeer = null; }); } } } // Called when there's a track update - use to update local & remote track variables void onTrackUpdate( {required HMSTrack track, required HMSTrackUpdate trackUpdate, required HMSPeer peer}) { if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { switch (trackUpdate) { case HMSTrackUpdate.trackRemoved: if (mounted) { setState(() { peer.isLocal ? localPeerVideoTrack = null : remotePeerVideoTrack = null; }); } return; default: if (mounted) { setState(() { peer.isLocal ? localPeerVideoTrack = track as HMSVideoTrack : remotePeerVideoTrack = track as HMSVideoTrack; }); } } } } // More callbacks - no need to implement for quickstart void onAudioDeviceChanged( {HMSAudioDevice? currentAudioDevice, List<HMSAudioDevice>? availableAudioDevice}) {} void onSessionStoreAvailable({HMSSessionStore? hmsSessionStore}) {} void onChangeTrackStateRequest( {required HMSTrackChangeRequest hmsTrackChangeRequest}) {} void onHMSError({required HMSException error}) {} void onMessage({required HMSMessage message}) {} void onReconnected() {} void onReconnecting() {} void onRemovedFromRoom( {required HMSPeerRemovedFromPeer hmsPeerRemovedFromPeer}) {} void onRoleChangeRequest({required HMSRoleChangeRequest roleChangeRequest}) {} void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) {} void onUpdateSpeakers({required List<HMSSpeaker> updateSpeakers}) {} // Widget to render a single video tile Widget peerTile(Key key, HMSVideoTrack? videoTrack, HMSPeer? peer) { return Container( key: key, child: (videoTrack != null && !(videoTrack.isMute)) // Actual widget to render video ? HMSVideoView( track: videoTrack, ) : Center( child: Container( decoration: BoxDecoration( color: Colors.blue.withAlpha(4), shape: BoxShape.circle, boxShadow: const [ BoxShadow( color: Colors.blue, blurRadius: 20.0, spreadRadius: 5.0, ), ], ), child: Text( peer?.name.substring(0, 1) ?? "D", style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.w600), ), ), ), ); } // Widget to render grid of peer tiles and a end button Widget build(BuildContext context) { return WillPopScope( // Used to call "leave room" upon clicking back button [in android] onWillPop: () async { hmsSDK.leave(); Navigator.pop(context); return true; }, child: SafeArea( child: Scaffold( backgroundColor: Colors.black, body: Stack( children: [ // Grid of peer tiles Container( height: MediaQuery.of(context).size.height, child: GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( mainAxisExtent: (remotePeerVideoTrack == null) ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height / 2, crossAxisCount: 1), children: [ if (remotePeerVideoTrack != null && remotePeer != null) peerTile( Key(remotePeerVideoTrack?.trackId ?? "" "mainVideo"), remotePeerVideoTrack, remotePeer), peerTile( Key(localPeerVideoTrack?.trackId ?? "" "mainVideo"), localPeerVideoTrack, localPeer) ], ), ), // End button to leave the room Align( alignment: Alignment.bottomCenter, child: RawMaterialButton( onPressed: () { hmsSDK.leave(); Navigator.pop(context); }, elevation: 2.0, fillColor: Colors.red, padding: const EdgeInsets.all(15.0), shape: const CircleBorder(), child: const Icon( Icons.call_end, size: 25.0, color: Colors.white, ), ), ), ], ), ), ), ); } }

Fetch token to join the room

Fetch token using room-code method(Recommended)

We can get the authentication token using room-code from meeting URL.

Let's understand the subdomain and code from the sample URL In this URL: http://100ms-rocks.app.100ms.live/meeting/abc-defg-hij

  • Subdomain is 100ms-rocks
  • Room code is abc-defg-ghi

Now to get the room-code from meeting URL we can write our own logic or use the getCode method from here

To generate token we will be using getAuthTokenByRoomCode method of HMSSDK. This method has roomCode as a required parameter, userId & endPoint as optional parameter.

This method should be called after calling the build method.

Let's checkout the implementation:

//This returns an object of Future<dynamic> which can be either //of HMSException type or String? type based on whether //method execution is completed successfully or not dynamic authToken = await hmsSDK.getAuthTokenByRoomCode(roomCode: 'YOUR_ROOM_CODE'); if(authToken is String){ HMSConfig roomConfig = HMSConfig( authToken: authToken, userName: userName, ); hmsSDK.join(config: roomConfig); } else if(authToken is HMSException){ // Handle the error }

Get temporary token from dashboard

To test audio/video functionality, you need to connect to a 100ms room; please check the following steps for the same:

  1. Navigate to your 100ms dashboard or create an account if you don't have one.
  2. Use the Video Conferencing Starter Kit to create a room with a default template assigned to it to test this app quickly.
  3. Go to the Rooms page in your dashboard, click on the Room Id of the room you created above, and click on the Join Room button on the top right.
  4. In the Join with SDK section you can find the Auth Token for role ; you can click on the 'copy' icon to copy the authentication token and update the authToken in "lib/main.dart" file.

Token from 100ms dashboard is for testing purposes only, For production applications you must generate tokens on your own server. Refer to the Management Token section in Authentication and Tokens guide for more information.


Test the app

After adding the required code let's run the app

Build and run the app

  • Once you've made the above changes, your app is ready for testing. You can build the app and run it in an emulator or an actual android device.
  • Go to Run > Start debugging > select a device to use (android emulator or android phone).

Now, after you click join, you should be able to see yourself (android emulator doesn't support actual video, you can connect an actual device to see your video in real-time). You can join the room using a browser as the second peer to check audio/video transactions between two or more peers.


Building step-by-step

In this section, We'll walk through what the code does.

Add dependencies in pubspec.yaml

In your project pubspec.yaml dependencies add:

# 100ms SDK and permissions_handler hmssdk_flutter: permission_handler:

Add permissions for android and iOS

Add the permissions for microphone,camera and bluetooth for android and iOS follow the docs above

Handle device runtime permissions

We need permission from the user to access the media from the user's device. We must urge the user to grant permission to access camera, microphone, and bluetooth devices. We use the permission_handler package that provides a cross-platform (iOS, Android) API to request permissions and check their status.

Please ensure to update permissions in the AndroidManifest.xml file for android and info.plist file for iOS. Check Add Permission section for more information. getPermissions takes required permission for microphone, camera and bluetooth.

class HomePage extends StatelessWidget { const HomePage({super.key}); Future<bool> getPermissions() async { if (Platform.isIOS) return true; await Permission.camera.request(); await Permission.microphone.request(); await Permission.bluetoothConnect.request(); while ((await Permission.camera.isDenied)) { await Permission.camera.request(); } while ((await Permission.microphone.isDenied)) { await Permission.microphone.request(); } while ((await Permission.bluetoothConnect.isDenied)) { await Permission.bluetoothConnect.request(); } return true; } }

Implement join screen

This section will help you create the join screen user interface. To keep it simple for the quickstart, we have not created many UI elements; you can refer to the sample app implementation for a complete Preview/Join user interface. Add the below code in HomePage class.

// UI to render join screen Widget build(BuildContext context) { return Scaffold( body: Container( color: Colors.black, child: Center( child: ElevatedButton( style: ButtonStyle( shape: MaterialStateProperty.all<RoundedRectangleBorder>( RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), ), ), // Function to push to meeting page onPressed: () async { await getPermissions(); Navigator.push( context, CupertinoPageRoute(builder: (_) => const MeetingPage()), ); }, child: const Padding( padding: EdgeInsets.symmetric(vertical: 20, horizontal: 20), child: Text( 'Join', style: TextStyle(fontSize: 20), ), ), ), ), ), ); }

Implement meeting page

You can check the below snippet to create a widget as the user interface to show the video tiles of local and remote peers. HMSUpdateListener plays a significant role in rendering video or displaying any information regarding the room.

100ms SDK provides callbacks to the client app about any change or update happening in the room after a user has joined by implementing HMSUpdateListener.

To join a room, you need to create an HMSConfig instance and use that instance to call the join method of HMSSDK.

Note: An Auth token is required to authenticate a room join request from your client-side app. Please ensure to add the authToken by fetching it from your dashboard. Check fetch token to join a room section for more information.
Read more about authentication and tokens in this guide

class MeetingPage extends StatefulWidget { const MeetingPage({super.key}); State<MeetingPage> createState() => _MeetingPageState(); } class _MeetingPageState extends State<MeetingPage> implements HMSUpdateListener { //SDK late HMSSDK hmsSDK; // Variables required for joining a room String authToken = "APP_TOKEN_FROM_DASHBOARD"; String userName = "test_user"; // Variables required for rendering video and peer info HMSPeer? localPeer, remotePeer; HMSVideoTrack? localPeerVideoTrack, remotePeerVideoTrack; // Initialize variables and join room void initState() { super.initState(); initHMSSDK(); } void initHMSSDK() async { hmsSDK = HMSSDK(); await hmsSDK.build(); // ensure to await while invoking the `build` method hmsSDK.addUpdateListener(listener: this); hmsSDK.join(config: HMSConfig(authToken: authToken, userName: userName)); } // Clear all variables void dispose() { remotePeer = null; remotePeerVideoTrack = null; localPeer = null; localPeerVideoTrack = null; super.dispose(); } }

Now in the same class we will override the HMSUpdateListener methods to listen to updates.

Listen to room and peer updates

The 100ms SDK sends updates to the application about any change in HMSPeer and HMSRoom via the callbacks in HMSUpdateListener. Our application must listen to the corresponding updates in onPeerUpdate and onRoomUpdate. Check the Update Listeners documentation to understand the types of updates emitted by the SDK for room and peer updates. We will add these methods in MeetingPage class as they need to override the HMSUpdateListener methods.

// Called when peer joined the room - get current state of room by using HMSRoom obj void onJoin({required HMSRoom room}) { room.peers?.forEach((peer) { if (peer.isLocal) { localPeer = peer; if (peer.videoTrack != null) { localPeerVideoTrack = peer.videoTrack; } if (mounted) { setState(() {}); } } }); } // Called when there's a peer update - use to update local & remote peer variables void onPeerUpdate({required HMSPeer peer, required HMSPeerUpdate update}) { switch (update) { case HMSPeerUpdate.peerJoined: if (!peer.isLocal) { if (mounted) { setState(() { remotePeer = peer; }); } } break; case HMSPeerUpdate.peerLeft: if (!peer.isLocal) { if (mounted) { setState(() { remotePeer = null; }); } } break; case HMSPeerUpdate.networkQualityUpdated: return; default: if (mounted) { setState(() { localPeer = null; }); } } }

Listen to track updates

100ms SDK also sends updates to the application about any change in HMSTrack via the callbacks in HMSUpdateListener. Our application must listen to the corresponding updates in onTrackUpdate. Check the Update Listeners documentation to understand the types of updates emitted by the SDK for track updates.

// Called when there's a track update - use to update local & remote track variables void onTrackUpdate( {required HMSTrack track, required HMSTrackUpdate trackUpdate, required HMSPeer peer}) { if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { switch (trackUpdate) { case HMSTrackUpdate.trackRemoved: if (mounted) { setState(() { peer.isLocal ? localPeerVideoTrack = null : remotePeerVideoTrack = null; }); } return; default: if (mounted) { setState(() { peer.isLocal ? localPeerVideoTrack = track as HMSVideoTrack : remotePeerVideoTrack = track as HMSVideoTrack; }); } } } }

Other callbacks

100ms SDK provides various other callbacks to handle different scenarios in the app. For example, you can use onAudioDeviceChanged to get updates whenever a new audio device or an audio device is switched. Please check here for more information about these callbacks.

// More callbacks - no need to implement for quickstart void onAudioDeviceChanged( {HMSAudioDevice? currentAudioDevice, List<HMSAudioDevice>? availableAudioDevice}) {} void onSessionStoreAvailable({HMSSessionStore? hmsSessionStore}) {} void onChangeTrackStateRequest( {required HMSTrackChangeRequest hmsTrackChangeRequest}) {} void onHMSError({required HMSException error}) {} void onMessage({required HMSMessage message}) {} void onReconnected() {} void onReconnecting() {} void onRemovedFromRoom( {required HMSPeerRemovedFromPeer hmsPeerRemovedFromPeer}) {} void onRoleChangeRequest({required HMSRoleChangeRequest roleChangeRequest}) {} void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) {} void onUpdateSpeakers({required List<HMSSpeaker> updateSpeakers}) {}

Render video in a tile

We had initialized the HMSUpdateListener class in the Implement meeting page section; now, we can use the same to render video tracks in a tile.

To display a video track, first get the HMSVideoTrack & pass it on to HMSVideoView.

Ensure to add the HMSVideoView to your app's Widget tree. Check the render video guide for more information.

// Widget to render a single video tile Widget peerTile(Key key, HMSVideoTrack? videoTrack, HMSPeer? peer) { return Container( key: key, child: (videoTrack != null && !(videoTrack.isMute)) // Actual widget to render video ? HMSVideoView( track: videoTrack, ) : Center( child: Container( decoration: BoxDecoration( color: Colors.blue.withAlpha(4), shape: BoxShape.circle, boxShadow: const [ BoxShadow( color: Colors.blue, blurRadius: 20.0, spreadRadius: 5.0, ), ], ), child: Text( peer?.name.substring(0, 1) ?? "D", style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.w600), ), ), ), ); }

Render video tiles for remote peer

This section will help you build the user interface that renders the video tracks of local and remote peer in a grid. Add this in MeetingPage class.For more info about implementation check the complete code above

// Widget to render grid of peer tiles and a end button Widget build(BuildContext context) { return WillPopScope( // Used to call "leave room" upon clicking back button [in android] onWillPop: () async { hmsSDK.leave(); Navigator.pop(context); return true; }, child: SafeArea( child: Scaffold( backgroundColor: Colors.black, body: Stack( children: [ // Grid of peer tiles Container( height: MediaQuery.of(context).size.height, child: GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( mainAxisExtent: (remotePeerVideoTrack == null) ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height / 2, crossAxisCount: 1), children: [ if (remotePeerVideoTrack != null && remotePeer != null) peerTile( Key(remotePeerVideoTrack?.trackId ?? "" "mainVideo"), remotePeerVideoTrack, remotePeer), peerTile( Key(localPeerVideoTrack?.trackId ?? "" "mainVideo"), localPeerVideoTrack, localPeer) ], ), ), // End button to leave the room Align( alignment: Alignment.bottomCenter, child: RawMaterialButton( onPressed: () { hmsSDK.leave(); Navigator.pop(context); }, elevation: 2.0, fillColor: Colors.red, padding: const EdgeInsets.all(15.0), shape: const CircleBorder(), child: const Icon( Icons.call_end, size: 25.0, color: Colors.white, ), ), ), ], ), ), ), ); }

You can refer to the test the app section to test your app for android or iOS platforms.

Next steps

We have multiple example apps to get you started with 100ms Flutter SDK,

Basic example

For a basic example, see the sample app on GitHub.

Full-fledged example

You can also check out the full-fledged example app implementation in the 100ms Flutter SDK GitHub repository showcasing multiple features provided by 100ms. This uses the provider package as the state management library.

Examples with other state management libraries

For implementations with other state management libraries, visit :

App store / Play store

You can download & check out the 100ms Flutter app -

🤖 Flutter Android app from Google Play Store.

📱 Flutter iOS app from Apple App Store.


Have a suggestion? Recommend changes ->

Was this helpful?

1234