Video Calling in Flutter
Introduction:
Don't have time to introduce video calling in your app from scratch? Don't worry. This blog is made for you.
Learn to integrate video calling with a few lines of code within 30 minutes.
Not believing it? No problem. Just go through the blog. You will learn easily.
ZEGOCLOUD: https://www.zegocloud.com/
Video call: https://www.zegocloud.com/product/video-call
Setup and Configuration:
Create a basic project in Flutter (name like: video_meet).
Add Packages
Go to the
pubspec.yaml
file of your project.Under the
dependencies
section, add the following three packages:
Add
.env
fileMake a file named as
.env
at the root of your project.Go to the ZegoCloud Admin Dashboard and an account. Then add
appId
andappSignKey
in.env
file. But how to do that? Don't worry. Click on the following video to see particularly the steps:%[https://youtube.com/watch?v=5mxaNizy35k&feature=shares&t=941]
Open
.gitignore
file and write.env
..env
file contains secret information. So we are adding this file under.gitignore
so that it will not add to the version control in the future.Include
.env
file under the assets section ofpubspec.yaml
file. That will load as assets when we run the project.
Add Permissions under the main AndroidManifest.xml file
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><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.CAMERA" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /><uses-permission android:name="android.permission.WAKE_LOCK" />
Go to the android -> app -> build.gradle, and change the following
compileSdkVersion 33 minSdkVersion 24 targetSdkVersion 33
Under the buildTypes->release of that file, add the following line:
minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
Under the android->app, make a file name as
proguard-rules.pro
Write the following line under that file
-keep class **.zego.** { *; }
Screen Making and Functionality Integration:
Go the
main.dart
file under thelib
folder.Delete all the things and paste the following code:-
import 'package:flutter/material.dart'; import 'package:video_meet/data_management.dart'; import 'main_screen.dart'; void main() async{ await DataManagement.loadEnvData; runApp(const EntryRoot()); } class EntryRoot extends StatelessWidget { const EntryRoot({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'ZegoCloud Testing', builder: (context, child) => MediaQuery( data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), child: child!, ), home: const MainScreen(), ); } }
๐ Explanation: Under the
runApp()
, we are calling theStateless
classEntryRoot
, where the common entry of every Flutter project exists. Very easy to understand.Now create a file name as
main_screen.dart
. Paste the following code in that file.import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:video_meet/video_call.dart'; class MainScreen extends StatefulWidget { const MainScreen({Key? key}) : super(key: key); @override State<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> { _beautifyScreen( {Color navigationBarColor = Colors.white, Color statusBarColor = Colors.transparent, Brightness? statusIconBrightness = Brightness.dark, Brightness? navigationIconBrightness = Brightness.dark}) { SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( systemNavigationBarColor: navigationBarColor, // navigation bar color statusBarColor: statusBarColor, // status bar color statusBarIconBrightness: statusIconBrightness, systemNavigationBarIconBrightness: navigationIconBrightness, )); } @override void initState() { _beautifyScreen(); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( body: SizedBox( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _commonButton('Start a Meeting', () { _joinMeeting( meetId: 'video_call_${DateTime.now().microsecondsSinceEpoch}'); }), const SizedBox(height: 30), _commonButton('Join Meeting', _joinMeeting), ], ), ), ); } _commonButton(String btnText, VoidCallback onTap) { return ElevatedButton( onPressed: onTap, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xff0155FE), padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), )), child: Text( btnText, style: const TextStyle(fontSize: 18, color: Colors.white), )); } void _joinMeeting({String meetId = ''}) async { final Map<Permission, PermissionStatus> _permissionStatuses = await [ Permission.camera, Permission.microphone, ].request(); final _permissionResultMap = _permissionStatuses.map((key, value) => MapEntry(key, value.isGranted)); final _allPermissionResult = _permissionResultMap.values.toList(); if (_allPermissionResult.contains(false)) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('All Permission Need to Proceed'))); return; } final TextEditingController _meetIdController = TextEditingController(); final TextEditingController _userNameController = TextEditingController(); _joinContentWidget() { return Container( width: MediaQuery.of(context).size.width, height: meetId.isEmpty ? 300 : 100, margin: const EdgeInsets.symmetric(vertical: 15), child: Column( children: [ if (meetId.isEmpty) TextFormField( controller: _meetIdController, cursorColor: const Color(0xff424242), decoration: InputDecoration( enabledBorder: _commonBorder, focusedBorder: _commonBorder, border: _commonBorder, filled: true, hintText: 'Meeting Id', fillColor: const Color(0xFFF3F2F2), contentPadding: const EdgeInsets.symmetric( vertical: 17, horizontal: 15), )), const SizedBox( height: 30, ), TextFormField( controller: _userNameController, cursorColor: const Color(0xff424242), decoration: InputDecoration( enabledBorder: _commonBorder, focusedBorder: _commonBorder, border: _commonBorder, filled: true, hintText: 'User Name', fillColor: const Color(0xFFF3F2F2), contentPadding: const EdgeInsets.symmetric(vertical: 17, horizontal: 15), )), ], ), ); } showDialog( context: context, builder: (_) => StatefulBuilder( builder: (_, __) => AlertDialog( insetPadding: const EdgeInsets.symmetric(horizontal: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), title: _joinTitle(), content: _joinContentWidget(), actionsAlignment: MainAxisAlignment.center, actions: [ _commonButton("Let's Join", () { if (meetId.isEmpty && _meetIdController.text.isEmpty) { return; } final _confId = meetId.isEmpty ? _meetIdController.text : meetId; Navigator.of(context).push(MaterialPageRoute( builder: (_) => VideoMeetScreen( conferenceId: _confId, userName: _userNameController.text, userId: DateTime.now() .microsecondsSinceEpoch .toString()))); }), ], ))); } _joinTitle() { return const Center( child: Text( 'Enter Meeting Id', style: TextStyle(fontSize: 25, fontWeight: FontWeight.w400), ), ); } get _commonBorder => OutlineInputBorder( borderRadius: const BorderRadius.all(Radius.circular(12)), borderSide: BorderSide( color: const Color(0xff424242).withOpacity(0.4), width: 2), ); }
๐ Explanation:
Make a Stateful Widget name as
MainScreen
.From the
initstate
, call_beautifyScreen()
, use to beautify screen layout(Optional).Under the
build
method, make aColumn
widget with two common buttons for Start Meeting and Join Meeting.First focus on what happens when we click two buttons.
When Join Meeting, a meeting id, and username are required. Take that two pieces of information from the user and with that, we will join the meeting.
When clicking on Start Meeting, only username we have to take. As the new fresh room is going to create, we have to give a new unique confId.
So, generate a unique id with epoch time as it is always unique.
Generate a unique confId with the following code:
final _confId = 'video_call_${DateTime.now().microsecondsSinceEpoch}'; // That _confId will unique everytime.
Create a file name as
data_management.dart
. Add the following code to that file.import 'package:flutter_dotenv/flutter_dotenv.dart'; class DataManagement{ static get loadEnvData async => await dotenv.load(fileName: ".env");/// MAke Sure There is a file named as '.env' in root dir static String? getEnvData(String key) => dotenv.env[key]; static get getAppId => getEnvData('appID'); static get getAppSigningKey => getEnvData('appSignKey'); }
Explanation:
loadEnvData is used for loading
.env
file, when the project starts. That's why it's called fromvoid main()
atmain.dart
file.getEnvData is used for getting the value from the
.env
file with the proper key name mentioned in.env
file.getAppId is used for getting the value
appId
mentioned in.env
file.getAppSigningKey is used for getting the value
appSignKey
mentioned in.env
file.
Now create the last file for that project name as
video_call.dart
and paste the following code.import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:video_meet/data_management.dart'; import 'package:zego_uikit_prebuilt_video_conference/zego_uikit_prebuilt_video_conference.dart'; class VideoMeetScreen extends StatelessWidget { final String conferenceId; final String userName; final String userId; const VideoMeetScreen( {Key? key, required this.conferenceId, required this.userName, required this.userId}) : super(key: key); @override Widget build(BuildContext context) { return SafeArea( child: ZegoUIKitPrebuiltVideoConference( appID: int.parse(DataManagement.getAppId), appSign: DataManagement.getAppSigningKey, conferenceID: conferenceId, userID: userId, userName: userName, config: ZegoUIKitPrebuiltVideoConferenceConfig( turnOnCameraWhenJoining: false, turnOnMicrophoneWhenJoining: false, useSpeakerWhenJoining: true, leaveConfirmDialogInfo: ZegoLeaveConfirmDialogInfo( title: "Leave the conference", message: "Are you sure to leave the conference?", cancelButtonName: "Cancel", confirmButtonName: "Confirm", ), topMenuBarConfig: ZegoTopMenuBarConfig( buttons: [ ZegoMenuBarButtonName.showMemberListButton, ZegoMenuBarButtonName.chatButton ], ), bottomMenuBarConfig: ZegoBottomMenuBarConfig(buttons: [ ZegoMenuBarButtonName.toggleCameraButton, ZegoMenuBarButtonName.toggleMicrophoneButton, ZegoMenuBarButtonName.switchAudioOutputButton, ZegoMenuBarButtonName.leaveButton, ZegoMenuBarButtonName.switchCameraButton, ], extendButtons: [ ElevatedButton( style: ElevatedButton.styleFrom( fixedSize: const Size(60, 60), shape: const CircleBorder(), backgroundColor: const Color(0xff2C2F3E).withOpacity(0.6), ), child: const Icon(Icons.copy), onPressed: () async { await Clipboard.setData(ClipboardData(text: conferenceId)); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('MeetId Copied to clipboard'))); }, ) ])), ), ); } }
Explanation:
Under that file first, create a stateless widget that will take three params
conferenceId
userName
userId
That class will be navigated from
Let's Join
the method undermain_screen.dart
file. Don't worry, you already wrote the code if you read the blog from the first step-by-step.Under that class
build
method, we will callZegoUIKitPrebuiltVideoConference
widget with some basic and custom configurations.appSign
andappID
will be called fromDataManagement
class.conferenceID
,userID
, anduserName
is coming as the params of that class.Want to know how to customize it further in your own way? Here is the clip for you.
%[https://youtube.com/watch?v=5mxaNizy35k&feature=shares&t=1219]
๐ Now Run the project and have fun.
๐โโ๏ธ Want to see the clip of the result? Here is the clip for you.
๐ Hope that blog helps you. Don't forget to like that blog.
๐ฎ See the full tutorial video of the project: https://youtube.com/watch?v=5mxaNizy35k&feature=shares
๐ฅ Source code of the tutorial: https://github.com/SamarpanCoder2002/Video-Calling-in-Flutter
๐ Get 10,000 free mins for the flutter app: https://bit.ly/3GiFEgL
๐ Find out more about ZEGOCLOUD: https://bit.ly/3vepTBl
๐ How to create a video conference app: https://bit.ly/3HYSNNd
Subscribe to my newsletter
Read articles from Samarpan Dasgupta directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Samarpan Dasgupta
Samarpan Dasgupta
I am a Software Developer in an Indian Startup. Here I am working on Flutter, Node, React, Firebase, Express, and some other technologies as well. In my workplace, I mostly work on Flutter for Mobile App Development. Till now I have worked with more than 5 apps that are available also play store as some of them in App Store. Sometimes I also work on making and managing APIs. I mostly work on node and express. Besides my office work, I also work on my personal projects. Most of them are open-source and can easily be found in my Github Profile.