Agora Flutter Video Calling Example
In this article, we are going to integrate video calling functionality in mobile applications using agora flutter sdk. We are using agora_rtc_engine package to make video call in flutter apps.
Nowadays video call is important part of our daily life. In this example, we are connecting two mobile devices using agora flutter sdk though channel. We can also switch camera and mute/unmute mic in this flutter video calling app.
First we need to create an account on Agora website. Then we need to create project after registration process. You can create new project using dashboard page. After that you need to copy APP ID to connect our app with agora sdk.






Add required dependencies in your pubspec.yaml file
permission_handler: ^5.0.1
Make changes for Android Platform
First enable AndoidX in gradle.properties file in android studio. We need to declare below permissions in AndroidManifest.xml file to make video call.
<uses-permission android:name=”android.permission.INTERNET” />
<uses-permission android:name=”android.permission.RECORD_AUDIO” />
<uses-permission android:name=”android.permission.CAMERA” />
<uses-permission android:name=”android.permission.MODIFY_AUDIO_SETTINGS” />
<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” />
<uses-permission android:name=”android.permission.BLUETOOTH” />
Make Changes for iOS Platform
for iOS platform, we need to add camera and microphone usage description in Info.plist file under iOS -> Runner folder.
Create Join Channel UI for Agora Flutter
bool validateError = false;
ClientRole clientRole = ClientRole.Broadcaster;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
height: 400,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: channelController,
decoration: InputDecoration(
hintText: ‘Enter Channel Name’,
errorText: validateError ? ‘Channel name is required’ : null,
border: UnderlineInputBorder(
borderSide: BorderSide(width: 1),
),
),
))
],
),
Column(
children: [
ListTile(
title: Text(ClientRole.Broadcaster.toString()),
leading: Radio(
value: ClientRole.Broadcaster,
groupValue: clientRole,
onChanged: (ClientRole value) {
setState(() {
clientRole = value;
});
},
),
),
ListTile(
title: Text(ClientRole.Audience.toString()),
leading: Radio(
value: ClientRole.Audience,
groupValue: clientRole,
onChanged: (ClientRole value) {
setState(() {
clientRole = value;
});
},
),
)
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
onPressed: onJoinChannel,
child: Text(‘Join Channel’),
color: Colors.blueAccent,
textColor: Colors.white,
),
)
],
),
)
],
),
),
),
);
}
Join Agora Flutter Channel for Flutter Chat App
setState(() {
channelController.text.isEmpty
? validateError = true
: validateError = false;
});
if (channelController.text.isNotEmpty) {
await handlePermissions(Permission.camera);
await handlePermissions(Permission.microphone);
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoCallingScreen(
channelName: channelController.text,
clientRole: clientRole,
),
),
);
}
}
Future<void> handlePermissions(Permission permission) async {
final status = await permission.request();
print(status);
}
@override
void dispose() {
channelController.dispose();
super.dispose();
}
Pass Flutter Channel Name to Video Call Screen
final String channelName;
final ClientRole clientRole;
const VideoCallingScreen({Key key, this.channelName, this.clientRole}) : super(key: key);
@override
_VideoCallingScreenState createState() => _VideoCallingScreenState();
}
Initialize Agora Flutter SDK
Here you add your APP ID from your agora account. You can also generate temporary token from your agora account
final Token = ‘YOUR_TOKEN’;
final listUsers = <int>[];
final strInfo = <String>[];
bool muted = false;
RtcEngine rtcEngine;
@override
void initState() {
super.initState();
initializeAgoraSDK();
}
Future<void> initializeAgoraSDK() async {
if (APP_ID.isEmpty) {
setState(() {
strInfo.add(
‘Please add your APP_ID’,
);
});
return;
}
await initializeAgoraEngine();
addAgoraEventHandlers();
await rtcEngine.enableWebSdkInteroperability(true);
VideoEncoderConfiguration encoderConfiguration = VideoEncoderConfiguration();
encoderConfiguration.dimensions = VideoDimensions(1920, 1080);
await rtcEngine.setVideoEncoderConfiguration(encoderConfiguration);
await rtcEngine.joinChannel(Token, widget.channelName, null, 0);
}
Future<void> initializeAgoraEngine() async {
rtcEngine = await RtcEngine.create(APP_ID);
await rtcEngine.enableVideo();
await rtcEngine.setChannelProfile(ChannelProfile.LiveBroadcasting);
await rtcEngine.setClientRole(widget.clientRole);
}
void addAgoraEventHandlers() {
rtcEngine.setEventHandler(RtcEngineEventHandler(error: (code) {
setState(() {
final info = ‘onError: $code’;
strInfo.add(info);
});
}, joinChannelSuccess: (channel, uid, elapsed) {
setState(() {
final info = ‘onJoinChannel: $channel, uid: $uid’;
strInfo.add(info);
});
}, leaveChannel: (stats) {
setState(() {
strInfo.add(‘onLeaveChannel’);
listUsers.clear();
});
}, userJoined: (uid, elapsed) {
setState(() {
final info = ‘userJoined: $uid’;
strInfo.add(info);
listUsers.add(uid);
});
}, userOffline: (uid, elapsed) {
setState(() {
final info = ‘userOffline: $uid’;
strInfo.add(info);
listUsers.remove(uid);
});
}, firstRemoteVideoFrame: (uid, width, height, elapsed) {
setState(() {
final info = ‘firstRemoteVideo: $uid ${width}x $height’;
strInfo.add(info);
});
}));
}
Flutter Video Call using Agora SDK
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘Flutter Agora Video Call Sample’),
centerTitle: true,
),
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: <Widget>[
videoViewRowsWidget(),
panelWidget(),
toolbarWidget(),
],
),
),
);
}
@override
void dispose() {
listUsers.clear();
rtcEngine.leaveChannel();
rtcEngine.destroy();
super.dispose();
}
final List<StatefulWidget> list = [];
if (widget.clientRole == ClientRole.Broadcaster) {
list.add(RtcLocalView.SurfaceView());
}
listUsers.forEach((int uid) => list.add(RtcRemoteView.SurfaceView(uid: uid)));
return list;
}
Widget videoViewWidget(view) {
return Expanded(child: Container(child: view));
}
Widget expandedVideoViewWidget(List<Widget> views) {
final wrappedViews = views.map<Widget>(videoViewWidget).toList();
return Expanded(
child: Row(
children: wrappedViews,
),
);
}
Widget videoViewRowsWidget() {
final views = listRenderViews();
switch (views.length) {
case 1:
return Container(
child: Column(
children: <Widget>[videoViewWidget(views[0])],
));
case 2:
return Container(
child: Column(
children: <Widget>[
expandedVideoViewWidget([views[0]]),
expandedVideoViewWidget([views[1]])
],
));
case 3:
return Container(
child: Column(
children: <Widget>[
expandedVideoViewWidget(views.sublist(0, 2)),
expandedVideoViewWidget(views.sublist(2, 3))
],
));
case 4:
return Container(
child: Column(
children: <Widget>[
expandedVideoViewWidget(views.sublist(0, 2)),
expandedVideoViewWidget(views.sublist(2, 4))
],
));
default:
}
return Container();
}
Widget toolbarWidget() {
if (widget.clientRole == ClientRole.Audience) return Container();
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),
)
],
),
);
}
Widget panelWidget() {
return Container(
padding: const EdgeInsets.symmetric(vertical: 48),
alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
heightFactor: 0.5,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 48),
child: ListView.builder(
reverse: true,
itemCount: strInfo.length,
itemBuilder: (BuildContext context, int index) {
if (strInfo.isEmpty) {
return null;
}
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 3,
horizontal: 10,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 2,
horizontal: 5,
),
decoration: BoxDecoration(
color: Colors.yellowAccent,
borderRadius: BorderRadius.circular(5),
),
child: Text(
strInfo[index],
style: TextStyle(color: Colors.blueGrey),
),
),
)
],
),
);
},
),
),
),
);
}
void onCallEnd(BuildContext context) {
Navigator.pop(context);
}
void onToggleMute() {
setState(() {
muted = !muted;
});
rtcEngine.muteLocalAudioStream(muted);
}
void onSwitchCamera() {
rtcEngine.switchCamera();
}


Pingback: Twilio API Send SMS Flutter Example - CodingWithDhrumil
Pingback: Flutter Video Player Example - CodingWithDhrumil
Pingback: Chewie Flutter Video Player Example - CodingWithDhrumil