Flutter video calling implementation by awesome Agora SDK

Might be Wherever you are in this digital world, Video call is the only channel that does not make you feel isolated in connecting with your friends and family. In this article, we are going to learn about the implementation of Flutter video calling.

We can enable real-time flutter video calling by using Agora RTC (Real-time Communications) SDK. Apart from basic communications, this SDK provides advanced functionalities such as screen sharing, audio mixing, modifying raw data… about Agora SDK refer link.

These are the following modules we are going to implement in this article
1. join/leave a channel
2. Mute/unmute
3. Switching camera
4. Multiple video views Layout

Final Output:

Flutter video calling by agora sdk

Let’s start with creating a project in Agora’s developer console

1. To begin, we must first create an account in Agora’s dashboard.
2.After we’ve completed account registration, we’ll need to establish a project. In the left Navigation menu, select Project Management and then the Create button.

Flutter video calling by agora sdk

3. We will get a pop-up window to take the project name and authentication mechanism type (It’s recommended to select Secure mode: APP ID + Token) and finally click on submit.

Flutter video calling by agora sdk

4. Get an App ID: Copy the App ID, which is a unique identifier and assigned automatically for every project

Flutter video calling by agora sdk


5. Generating a temporary token:

  • Agora uses a token to authenticate the users to join a channel for communication security.
  • To use it, go to the Project management page and click the key icon.
Flutter video calling by agora sdk
  • It will navigate to another page to take channel as an Input and after we need to click on Generate Temp Token button for generating the token.
Flutter video calling by agora sdk

When joining, the entered channel name should be the same as with we used to generate the Temporary token. Here we created a Temporary token for the testing purpose it will be valid only for 24 hrs. For production, we have to use our own server to generate tokens. Reference Link.

As of now, we completed the agora’s project setup.

Add flutter video calling

  1.  In your IDE, create a new Flutter project. You can refer to this creating new flutter project if you want to learn how to create a new Flutter project.
  2. Once project creation has done then add the latest versions of agora_rtc_engine, permission_handler, and provider dependencies to our pubspec.yaml file
 dependencies:
  flutter:
    sdk: flutter
  agora_rtc_engine: <"latest version">
  permission_handler: <"latest version">
  provider: <"latest version">
  • agora_rtc_engine: This plugin is a wrapper for Agora Video SDK which is to enables real-time flutter video call communications in our own application quickly. For more information refer to the link.
  • permission_handler: This plugin provides a cross-platform (iOS, Android) API to request permissions and check their status. For the flutter video calling, we need to get the microphone and camera permissions that will be used during the video call. For more information refer to the link
  • provider: is one of the famous package for providing dependency injection and state management for flutter apps. For more information refer to the link.

Implementation

Project Structure
The following Image shows our code structure under the lib

Flutter video calling by agora sdk
  1. Create a home page :
    Let’s Create a dart file named home_page.dart, which takes the channel name as Input and navigate to calling screen whenever user click on the join button.
//home_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'home_model.dart';
class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  /// create a channelController to retrieve text value
  final _channelController = TextEditingController();
  @override
  Widget build(BuildContext context) {
    return Consumer(builder: (context, HomeNotifier homeNotifier, child) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Video Call App'),
        ),
        body: Center(
          child: Padding(
            padding: const EdgeInsets.only(left: 16, right: 16),
            child: Container(
              height: 150,
              child: Column(
                children: <Widget>[
                  TextField(
                    controller: _channelController,
                    decoration: InputDecoration(
                      errorText: homeNotifier.validateError
                          ? 'Channel name is mandatory'
                          : null,
                      border: UnderlineInputBorder(
                        borderSide: BorderSide(width: 1),
                      ),
                      hintText: 'Channel name',
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.symmetric(vertical: 20),
                    child: Row(
                      children: [
                        Expanded(
                          child: MaterialButton(
                            onPressed: () => {
                              homeNotifier.onJoin(
                                  context, _channelController.text),
                            },
                            child: Text('Join'),
                            color: Colors.teal,
                            textColor: Colors.white,
                          ),
                        ),
                      ],
                    ),
                  )
                ],
              ),
            ),
          ),
        ),
      );
    });
  }
  @override
  void dispose() {
    _channelController.dispose();
    super.dispose();
  }
}
///flutter video calling by Agora SDK

2. Create a model class for the Home page
Since we using a provider for state management, creating a model class named home_model.dart.

//home_model.dart
///flutter video calling by Agora SDK
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_video_call_app/screens/call/call_screen.dart';
import 'package:permission_handler/permission_handler.dart';
class HomeNotifier extends ChangeNotifier {
  bool _validateError = false;
  bool get validateError => _validateError;
  setValue(bool value) {
    _validateError = value;
    notifyListeners();
  }
  Future<void> onJoin(BuildContext context, String chanelName) async {
    if (chanelName.isEmpty) {
      setValue(true);
    } else {
      setValue(false);
      // await for camera and mic permissions before pushing video page
      await _handleCameraAndMic(Permission.camera);
      await _handleCameraAndMic(Permission.microphone);
      await Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => CallScreen(
                    channelName: chanelName,
                  )));
    }
  }
  Future<void> _handleCameraAndMic(Permission permission) async {
    final status = await permission.request();
    print("Permissions status: $status");
  }
}
///flutter video calling by Agora SDK
  • _validateError: gives the boolean value whether the user entered the channel or not
  • onJoin(…): This method will be called when the user clicks on the join button on the home page. Which is used to pass the channel name to the CallScreen page after taking the camera and mic permissions from the user.
  • _handleCameraAndMic(…): This method requests the permissions.

3. Create a utils class
As we discussed before, for flutter video calling by agora’s SDK we need AppID and Token to link our app to the Agora Rtc engine and joining to a particular channel. Here I have kept my AppID and Token in utils.dart class

const APP_ID =  "your AppID"
const Token = "Your Token"

4. Create a calling page
Now, Create a dart file named call_screen.dart, which having all functionalities such as video view, toggle mute, switching camera, end call, and showing log on the screen.

//call_screen.dart
import 'package:agora_rtc_engine/rtc_local_view.dart' as RtcLocalView;
import 'package:agora_rtc_engine/rtc_remote_view.dart' as RtcRemoteView;
import 'package:flutter/material.dart';
import 'package:flutter_video_call_app/screens/base_widget.dart';
import 'package:flutter_video_call_app/screens/call/call_model.dart';
class CallScreen extends StatefulWidget {
  final String channelName;
  const CallScreen({Key key, this.channelName}) : super(key: key);
  @override
  _CallScreenState createState() => _CallScreenState();
}
class _CallScreenState extends State<CallScreen> {
  /// Helper function to get list of native views
  List<Widget> _getRenderViews(CallNotifier model) {
     ....
  }
///flutter video calling by Agora SDK
  /// Video view wrapper
  Widget _videoView(view) {
    return Expanded(child: Container(child: view));
  }
  /// Video view row wrapper
  Widget _expandedVideoRow(List<Widget> views) {
     ....
  }
  /// Video layout wrapper
  Widget _viewRows(CallNotifier notifier) {
     ....
  }
  /// Toolbar layout
  Widget _toolbar(CallNotifier notifier) {
     ....
  }
  /// Info panel to show logs
  Widget _panel(CallNotifier notifier) {
    return Container( ... );
  }
  void _onCallEnd(BuildContext context) {
     ....
  }
  void _onToggleMute(CallNotifier notifier) {
     ....
  }
  void _onSwitchCamera(CallNotifier model) {
     ....
  }
///flutter video calling by Agora SDK
  @override
  Widget build(BuildContext context) {
    return BaseWidget<CallNotifier>(
      model: CallNotifier(),
      onModelReady: (model) => model.init(widget.channelName),
      builder: (context, notifier, child) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Video Call'),
          ),
          backgroundColor: Colors.black,
          body: Center(
            child: Stack(
              children: <Widget>[
                _viewRows(notifier),
                _panel(notifier),
                _toolbar(notifier),
              ],
            ),
          ),
        );
      },
    );
  }
}
///flutter video calling by Agora SDK
  • _viewRows(…): the widget gives the Layout based on the number of users who joined in.
  • _panel(…): the widget prints the channel logs on the screen.
  • _toolbar(…): this widget gives interactive options like mute/unmute the microphone, switching cameras, and ending the call.
  • _onToggleMute(…): the method is used for muting or unmuting microphone
  • _onSwitchCamera(…): the method is used to switching the cameras.
  • _onCallEnd(..): to disconnect the call

5. Create a model class for the Call screen
Creating a model class named call_model.dart.

//call_model.dart. 
import 'package:agora_rtc_engine/rtc_engine.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_video_call_app/utils/utils.dart';
class CallNotifier extends ChangeNotifier {
  var _users = <int>[];
  var _infoStrings = <String>[];
  bool _isMuted = false;
  RtcEngine _engine;
  RtcEngine get engine => _engine;
  set setEngine(RtcEngine engine) {
    _engine = engine;
    notifyListeners();
  }
///flutter video calling by Agora SDK
  bool get isMuted => _isMuted;
  set isMuted(value) {
    _isMuted = !value;
    notifyListeners();
  }
  get users => _users;
  addUser(value) {
    _users.add(value);
    notifyListeners();
  }
  get infoStrings => _infoStrings;
  infoString(String value) {
    _infoStrings.add(value);
    notifyListeners();
  }
//flutter video calling by Agora SDK
  removeUser(value) {
    _users.remove(value);
    notifyListeners();
  }
  clearUsers() {
    _users.clear();
    notifyListeners();
  }
  Future<void> init(String channelName) async {
    if (APP_ID.isEmpty) {
      infoString('APP_ID missing, please provide your APP_ID in settings.dart');
      infoString('Agora Engine is not starting');
      return;
    }
    await _initAgoraRtcEngine();
    _addAgoraEventHandlers();
    await _engine.enableLocalVideo(true);
    VideoEncoderConfiguration configuration = VideoEncoderConfiguration();
    configuration.dimensions = VideoDimensions(1920, 1080);
    await _engine.setVideoEncoderConfiguration(configuration);
    await _engine.joinChannel(Token, channelName, null, 0);
  }
  /// Create agora sdk instance and initialize
  Future<void> _initAgoraRtcEngine() async {
    _engine = await RtcEngine.create(APP_ID);
    setEngine = _engine;
    await _engine.enableVideo();
    await _engine.setChannelProfile(ChannelProfile.LiveBroadcasting);
    await _engine.setClientRole(ClientRole.Broadcaster);
  }
  void _addAgoraEventHandlers() {
    _engine.setEventHandler(RtcEngineEventHandler(error: (code) {
      infoString('onError: $code');
    }, joinChannelSuccess: (channel, uid, elapsed) {
      infoString('onJoinChannel: $channel, uid: $uid');
    }, leaveChannel: (stats) {
      infoString('onLeaveChannel');
      _users.clear();
    }, userJoined: (uid, elapsed) {
      infoString('userJoined: $uid');
      addUser(uid);
    }, userOffline: (uid, elapsed) {
      infoString('userOffline: $uid');
      removeUser(uid);
    }, firstRemoteVideoFrame: (uid, width, height, elapsed) {
    }));
  }
  @override
  void dispose() {
    clearUsers();
    _engine.destroy();
    _engine.leaveChannel();
    super.dispose();
  }
}
///flutter video calling by Agora SDK
  • _users: The list contains the uid of the users who joined the channel
  • _infoStrings: List contains the logs of the call.
  • _isMuted: gives the boolean value whether the microphone is muted or not
  • _engine: Is a reference variable of RtcEngine.
  • init(..): This method will call automatically whenever a user enters the call screen. if the AppID is not empty agora Rtc engine will be initialized by calling the _initAgoraRtcEngine() method, call events will be added to our log by calling the _addAgoraEventHandlers() method and some video configurations also added
  • _initAgoraRtcEngine(): AgoraRtcEngine will be initialized and our app will links to Agora’s engine by using AppID.
  • _addAgoraEventHandlers(): This method is used to add an event handler so that we can get uid of users who joined in and we can build the logs based on the events.
  • _engine.joinChannel(Token, channelName, null, 0): It allows the user to join a particular channel based on channel name and Token.
  • dispose(): Finally, we have to destroy the RtcEngine in this method.

In this Flutter video calling article, we referred to most of the code from Agora’s repo link and tried to add state management to that code using the provider.

Thanks for reading..!!! 🙂

1 thought on “Flutter video calling implementation by awesome Agora SDK”

Leave a Comment