Back to Blog

Flutter Video Call: Add Video Chat to Your App using Agora

Flutter Video Call: Add Video Chat to Your App using Agora

This blog was written by Meherdeep Thakur, an Agora Superstar. The Agora Superstar program empowers developers around the world to share their passion and technical expertise, and create innovative real-time communications apps and projects using Agora’s customizable SDKs.


How do you ensure that your customers are happy with your service? How can you provide a better way to engage with your team, so that productivity is never compromised? If these are questions that have crossed your mind, then you’ll be pleased to know that I have an answer for you and that answer is really simple: Real Time Engagement (RTE). So what does RTE mean and how can it help your business?

In today’s digital economy everyone is connected with each other virtually and it bridges the distance between two distant users. Your company can be based in the North Pole but it shouldn’t stop you from reaching out to the users in the other corners of the world. RTE ensures that you’re always connected to the people that matter to you and your business. It comes without saying that in today’s world, RTE has a great impact on every field like education, politics, social media, art, and science.

Building your own RTE software can be a little troublesome: maintaining a server, load-balancing and at the same time providing low-latency can make even the best go nuts.

via GIPHY

So how can you add RTE to your Flutter Android or iOS application without losing your mind and your time? Answer: Agora’s RTE SDK. In this tutorial we’ll learn how to add group calling functionality to your Flutter app.

How Do I Start?

Before we start making our Flutter video call app let's make sure that we have all the required things:

  1. Flutter SDK for video calling in Flutter: Quite obvious isn’t it?
  2. Agora’s developer account: To build an app that is using Agora’s SDK you will need an AppID. So to grab your own AppID go to https://console.agora.io/ and quickly create an account and log in to your dashboard.
  3. Now navigate to the project management tab and click on the create button. This will let you give a suitable name for your project and give you an AppID linked with it.

Let's Get Going!

Now that we have all the things that we need, let’s make a simple group video calling app. We first need to start with creating a new Flutter project:

flutter create agora_video_call

Import Packages

Jump to pubspec.yaml file and under dependencies add agora_rtc_engine package available over here.

dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
agora_rtc_engine: 1.0.5
permission_handler: '^4.4.0+hotfix.2'
view raw pubspec.yaml hosted with ❤ by GitHub

dependencies in pubspec.yaml

Next we will add permission_handler to get the permission of microphone and camera features that will be used during video calls.

After adding all these packages in your pubspec.yaml run the command below from your project’s directory to get the packages:

flutter pub get

Code Structure

This would be the structure of the lib folder that we are developing:

File Structure

To start development, let’s just remove the default counter code from main.dart such that our dart file looks something like:

import 'package:agora_video_call/pages/HomePage.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Agora Group Video Call',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.blueAccent,
),
home: MyHomePage(),
);
}
}
view raw main.dart hosted with ❤ by GitHub

main.dart

For our Flutter video chat app home page, we will be building a form that takes the channel name as an input. So let’s jump to HomePage.dart in the pages folder.

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'CallPage.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final myController = TextEditingController();
bool _validateError = false;
final PermissionHandler _permissionHandler = PermissionHandler();
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
centerTitle: true,
title: Text('Agora Group Video Call'),
elevation: 0,
),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 50),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(padding: EdgeInsets.only(top:100)),
Image(image: NetworkImage('https://www.agora.io/en/wp-content/uploads/2019/07/agora-symbol-vertical.png'),height: 100,),
Padding(padding: EdgeInsets.only(top:20)),
Text('Agora Group Video Call Demo',
style: TextStyle(color: Colors.black, fontSize: 20, fontWeight: FontWeight.bold),
),
Padding(padding: EdgeInsets.symmetric(vertical: 20)),
Container(
width: 300,
child: TextFormField(
controller: myController,
decoration: InputDecoration(
labelText: 'Channel Name',
labelStyle: TextStyle(color: Colors.blue),
hintText: 'test',
hintStyle: TextStyle(color: Colors.black45),
errorText: _validateError ? 'Channel name is mandatory' : null,
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue),
borderRadius: BorderRadius.circular(20),
),
),
),
),
Padding(padding: EdgeInsets.symmetric(vertical: 30)),
Container(
width: 90,
child: MaterialButton(
onPressed: onJoin,
height: 40,
color: Colors.blueAccent,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Join', style: TextStyle(color: Colors.white)),
Icon(Icons.arrow_forward,color: Colors.white),
],
),
),
)
],
),
),
);
}
view raw HomePage.dart hosted with ❤ by GitHub

HomePage.dart

So we have successfully made a form that takes the channel name as input. Now we need to create a logic for that join button such that it passes the instructions to the CallPage.dart and also asks for users' microphone and camera permission.

Future<void> onJoin() async{
setState(() {
myController.text.isEmpty
? _validateError = true
: _validateError = false;
});
await _permissionHandler.requestPermissions([PermissionGroup.camera, PermissionGroup.microphone]);
Navigator.push(context,
MaterialPageRoute(
builder: (context)=> CallPage(channelName: myController.text),)
);
}
view raw HomePage2.dart hosted with ❤ by GitHub

onJoin() function

Now, before we actually start with building our Flutter video call screen let’s just keep our AppID separate in utils.dart under the settings folder.

const appID = '<----Add Your APP ID Here----->';
view raw AppID.dart hosted with ❤ by GitHub

Building the Call Screen

Finally! After all the setup and permission handling, we have everything that we will be needing to build our CallPage.dart. Let’s just declare some variables that will be used to manage the state of the call.

static final _users = <int>[];
final _infoStrings = <String>[];
bool muted = false;
view raw CallPage.dart hosted with ❤ by GitHub
  • The list _users[] contains the uid of the users who join a particular channel.
  • We will use _infoStrings[] to store the logs of the call.
  • We will use muted to save the state of the microphone. We will be using it to disable and enable the microphone.

Once we have created all the variables, we can go ahead and create an initState() to initialize our AgoraRtcEngine through which we can call the APIs provided by Agora.

@override
void initState() {
super.initState();
// initialize agora sdk
initialize();
}
Future<void> initialize() async {
if (appID.isEmpty) {
setState(() {
_infoStrings.add(
'APP_ID missing, please provide your APP_ID in settings.dart',
);
_infoStrings.add('Agora Engine is not starting');
});
return;
}
await _initAgoraRtcEngine();
_addAgoraEventHandlers();
await AgoraRtcEngine.enableWebSdkInteroperability(true);
VideoEncoderConfiguration configuration = VideoEncoderConfiguration();
configuration.dimensions = Size(1920, 1080);
await AgoraRtcEngine.joinChannel(null, widget.channelName, null, 0);
}
view raw CallPage.dart hosted with ❤ by GitHub
  • _initAgoraRtcEngine() initializes the AgoraRtcEngine and links your app to Agora’s engine using AppID.
Future<void> _initAgoraRtcEngine() async {
await AgoraRtcEngine.create(appID);
await AgoraRtcEngine.enableVideo();
}
view raw CallPage.dart hosted with ❤ by GitHub
  • _addAgoraEventHandlers() is a function that we will be using to add event handlers to our code so that we can build our call logs and get the uid of people as they join.
void _addAgoraEventHandlers() {
AgoraRtcEngine.onError = (dynamic code) {
setState(() {
final info = 'onError: $code';
_infoStrings.add(info);
});
};
AgoraRtcEngine.onJoinChannelSuccess = (
String channel,
int uid,
int elapsed,
) {
setState(() {
final info = 'onJoinChannel: $channel, uid: $uid';
_infoStrings.add(info);
});
};
AgoraRtcEngine.onLeaveChannel = () {
setState(() {
_infoStrings.add('onLeaveChannel');
_users.clear();
});
};
AgoraRtcEngine.onUserJoined = (int uid, int elapsed) {
setState(() {
final info = 'userJoined: $uid';
_infoStrings.add(info);
_users.add(uid);
});
};
AgoraRtcEngine.onUserOffline = (int uid, int reason) {
setState(() {
final info = 'userOffline: $uid';
_infoStrings.add(info);
_users.remove(uid);
});
};
AgoraRtcEngine.onFirstRemoteVideoFrame = (
int uid,
int width,
int height,
int elapsed,
) {
setState(() {
final info = 'firstRemoteVideo: $uid ${width}x $height';
_infoStrings.add(info);
});
};
}
view raw CallPage.dart hosted with ❤ by GitHub
  • AgoraRtcEngine.enableWebSdkInteroperability(true) enables interoperability between a mobile device and a web browser or app.
  • AgoraRtcEngine.joinChannel(null, widget.channelName, null, 0) allows a user to join a channel. Here you can pass optional parameters such as token (used for authentication) or additional information about the channel.

Now it's time to build the UI for our Flutter video call. As an example, I will be going with a grid view for this group call.

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Agora Group Calling'),
),
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: <Widget>[
_viewRows(),
_panel(),
_toolbar(),
],
),
),
);
}
view raw CallPage.dart hosted with ❤ by GitHub
  • _viewRows() widget is used here to provide that stack view.
  • _panel() Flutter chat widget simply prints the channel logs.
  • _toolbar() provides interactive options like muting the microphone, switching cameras, and ending the call.

Before we build that grid we will need a list of users in the call so that our app can auto adjust the screen ratio for each user.

List<Widget> _getRenderViews() {
final List<AgoraRenderWidget> list = [
AgoraRenderWidget(0, local: true, preview: true),
];
_users.forEach((int uid) => list.add(AgoraRenderWidget(uid)));
return list;
}
view raw CallPage.dart hosted with ❤ by GitHub

Using this list let's define the view:

  • View for a single user
Widget _videoView(view) {
return Expanded(child: Container(child: view));
}
view raw CallPage.dart hosted with ❤ by GitHub

Using this list let's define the view:

  • View for multiple users
Widget _expandedVideoRow(List<Widget> views) {
final wrappedViews = views.map<Widget>(_videoView).toList();
return Expanded(
child: Row(
children: wrappedViews,
),
);
}
view raw CallPage.dart hosted with ❤ by GitHub

Now that we have defined our view for users in our group call, let's put this inside the _viewRows() widget to form the grid.

Widget _viewRows() {
final views = _getRenderViews();
switch (views.length) {
case 1:
return Container(
child: Column(
children: <Widget>[_videoView(views[0])],
));
case 2:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow([views[0]]),
_expandedVideoRow([views[1]])
],
));
case 3:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow(views.sublist(0, 2)),
_expandedVideoRow(views.sublist(2, 3))
],
));
case 4:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow(views.sublist(0, 2)),
_expandedVideoRow(views.sublist(2, 4))
],
));
default:
}
return Container();
}
view raw CallPage.dart hosted with ❤ by GitHub

Here we built cases based on the number of people in the call so we can align our grid accordingly.

Well, we are done with our grid. Now let's quickly define the _toolbar to add buttons for muting the call, switching the camera, and disconnecting from the call.

Widget _toolbar() {
return Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(vertical: 48),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RawMaterialButton(
onPressed: _onToggleMute,
child: Icon(
muted ? Icons.mic_off : Icons.mic,
color: muted ? Colors.white : Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: muted ? Colors.blueAccent : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: () => _onCallEnd(context),
child: Icon(
Icons.call_end,
color: Colors.white,
size: 35.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.redAccent,
padding: const EdgeInsets.all(15.0),
),
RawMaterialButton(
onPressed: _onSwitchCamera,
child: Icon(
Icons.switch_camera,
color: Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.white,
padding: const EdgeInsets.all(12.0),
)
],
),
);
}
view raw CallPage.dart hosted with ❤ by GitHub
  • Muting the call
void _onToggleMute() {
setState(() {
muted = !muted;
});
AgoraRtcEngine.muteLocalAudioStream(muted);
}
view raw CallPage.dart hosted with ❤ by GitHub

_onToggleMute()

  • Switching the camera
void _onSwitchCamera() {
AgoraRtcEngine.switchCamera();
}
view raw CallPage.dart hosted with ❤ by GitHub
  • Disconnecting from the call
void _onCallEnd(BuildContext context) {
Navigator.pop(context);
}
view raw CallPage.dart hosted with ❤ by GitHub

Done!

Output

It wasn’t that hard, was it? Now you can easily go ahead with group calling integration into your Flutter video chat app. In case you weren’t coding along or just want to see the complete code then you can refer to this GitHub repository over here.

RTE Telehealth 2023
Join us for RTE Telehealth - a virtual webinar where we’ll explore how AI and AR/VR technologies are shaping the future of healthcare delivery.

Learn more about Agora's video and voice solutions

Ready to chat through your real-time video and voice needs? We're here to help! Current Twilio customers get up to 2 months FREE.

Complete the form, and one of our experts will be in touch.

Try Agora for Free

Sign up and start building! You don’t pay until you scale.
Try for Free