Agora in Flutter- navigating to the video chat screen more than one time keeps local video loading forever

134

I think the problem is that when pressing back button you are just being taken to the previous screen and the call session is not being end. You can try by leaving the channel when pressing back button like :

_engine.leaveChannel();

End Call button sample

             ElevatedButton(
                    onPressed: () {
                      _rtcEngine.leaveChannel();
                      Navigator.pop(context);
                    },
                    style: ButtonStyle(
                      shape: MaterialStateProperty.all(CircleBorder()),
                      backgroundColor: MaterialStateProperty.all(Colors.red),
                      padding: MaterialStateProperty.all(
                          EdgeInsets.fromLTRB(15, 15, 15, 12)),
                    ),
                    child: Icon(
                      Icons.phone,
                      size: 30,
                    ),
                  )

Back Button override using WillPopScope

return WillPopScope(
      onWillPop: () async {
        _rtcEngine.leaveChannel();
        return true;
      },
      child: Scaffold(
        body: Container(),
      ),
    );
Share:
134
Istiaque Ahmed
Author by

Istiaque Ahmed

A programmer anyway

Updated on December 19, 2022

Comments

  • Istiaque Ahmed
    Istiaque Ahmed over 1 year

    I am using Agora for a one-to-one video chat purpose in Flutter. User1 has an app to go online and user2 has another app to go online. After both of them go online, they can do video chat with one another. Both apps have almost similar codebase.

    I have a screen or activity (say screen1) where an alert dialog is shown on tapping a button (say button1). On tapping the Continue button in the alert dialog, the dialog disappears and the user is taken to the screen (say screen2) where the video chat takes place. But after going to the video chat screen successfully, if the user taps on the back button on the mobile set then s/he is taken to screen1 and after tapping on button1, if the user taps on the Continue button in the popped up alert dialog, the user is again taken to screen2 but this time the local video (i.e. video of the user using the app) keeps loading for ever. Obviously I want the local video to load as it did for the first time.

    I am gonna put my code here in such a way that you can easily run that.

    Following code is for user1. For user2, no alert box is there in the app. Same code from user1 is used for user2 app, except the value of remoteUid is set to be 2 for user2 while this value is set to be 1 for user1. These are just two values identifying 2 users.

    For user1:

    main.dart:

    import 'dart:async';
    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'livesession1to1.dart';
    
    void main()  {
    
      runApp(MessagingExampleApp());
    }
    
    class NavigationService {
      static GlobalKey<NavigatorState> navigatorKey =
      GlobalKey<NavigatorState>();
    }
    
    
    /// Entry point for the example application.
    class MessagingExampleApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Messaging Example App',
          navigatorKey: NavigationService.navigatorKey, // set property
          theme: ThemeData.dark(),
          routes: {
            '/': (context) => Application(),
            '/liveSession1to1': (context) =>LiveSession1to1(),
          },
        );
      }
    }
    
    int _messageCount = 0;
    
    /// The API endpoint here accepts a raw FCM payload for demonstration purposes.
    String constructFCMPayload(String? token, String server_key) {
      _messageCount++;
      return jsonEncode({
        'token': token,
        'to':token,
        'data': {
          'via': 'FlutterFire Cloud Messaging!!!',
          'count': _messageCount.toString(),
        },
        'notification': {
          'title': 'Hello FlutterFire!',
          'body': 'This notification (#$_messageCount) was created via FCM! =============',
        },
        "delay_while_idle" : false,
        "priority" : "high",
        "content_available" : true
    
      });
    }
    
    /// Renders the example application.
    class Application extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => _Application();
    }
    
    class _Application extends State<Application> {
      String? _token;
    
      @override
      void initState() {
        super.initState();
    
    
    
      }
    
    
      showAlertDialog() {
        BuildContext context=NavigationService.navigatorKey.currentContext!;
        // set up the buttons
        Widget cancelButton = TextButton(
          child: Text("Cancel"),
          onPressed:  () {},
        );
        Widget continueButton = TextButton(
          child: Text("Continue"),
          onPressed:  () {
    
            Navigator.of(context, rootNavigator: true).pop();
    
            Navigator.of(context).pushNamed('/liveSession1to1');
          },
        );
    
    
    
    
    
        Timer? timer = Timer(Duration(milliseconds: 5000), (){
    
          Navigator.of(context, rootNavigator: true).pop();
        });
    
        showDialog(
            context: context,
            builder: (BuildContext builderContext) {
    
              return AlertDialog(
                backgroundColor: Colors.black26,
                title: Text('One to one live session'),
                content: SingleChildScrollView(
                  child: Text('Do you want to connect for a live session ?'),
                ),
    
                actions: [
                  cancelButton,
                  continueButton,
                ],
              );
            }
        ).then((value){
          // dispose the timer in case something else has triggered the dismiss.
          timer?.cancel();
          timer = null;
        });
    
    
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('My App'),
    
          ),
          floatingActionButton: Builder(
            builder: (context) => FloatingActionButton(
              onPressed: showAlertDialog,
    
              backgroundColor: Colors.white,
              child: const Icon(Icons.send),
            ),
          ),
          body: SingleChildScrollView(
            child: Text(
                'Trigger Alert'
    
            ),
          ),
        );
      }
    
    
    }
    

    livesession1to1.dart:

    import 'dart:async';
    import 'package:agora_rtc_engine/rtc_engine.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:permission_handler/permission_handler.dart';
    
    // const appId = "<-- Insert App Id -->";
    // const token = "<-- Insert Token -->";
    
    
    const appId = "......";// Put Agora App ID from Agora site here
    const token = "....";// Put token ( temporary token avilable from Agora site)
    
    
    void main() => runApp(MaterialApp(home: LiveSession1to1()));
    
    class LiveSession1to1 extends StatefulWidget {
      @override
      _LiveSession1to1State createState() => _LiveSession1to1State();
    }
    
    class _LiveSession1to1State extends State<LiveSession1to1> {
    
      int _remoteUid=1;
      bool _localUserJoined = false;
      late RtcEngine _engine;
    
      @override
      void initState() {
        super.initState();
        setState(() {});
        initAgora();
      }
    
    
    
    
    
      Future<void> initAgora() async {
        // retrieve permissions
        await [Permission.microphone, Permission.camera].request();
    
    
    
    
        // Create RTC client instance
        RtcEngineContext context = RtcEngineContext(appId);
        _engine = await RtcEngine.createWithContext(context);
    
    
        await _engine.enableVideo();
    
        _engine.setEventHandler(
          RtcEngineEventHandler(
            joinChannelSuccess: (String channel, int uid, int elapsed) {
              print("local user $uid joined");
              setState(() {
                _localUserJoined = true;
              });
            },
            userJoined: (int uid, int elapsed) {
              print("remote user $uid joined");
              setState(() {
                _remoteUid = uid;
              });
            },
            userOffline: (int uid, UserOfflineReason reason) {
              print("remote user $uid left channel");
              setState(() {
                // _remoteUid = null;
                _remoteUid = 0;
              });
            },
          ),
        );
    
        try {
          await _engine.joinChannel(token, "InstaClass", null, 0);
    
        } catch (e) {
          print("error with agora = ");
          print("$e");
          print("error printeddddd");
        }
      }
    
      // Create UI with local view and remote view
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Agora Video Call'),
          ),
          body: Stack(
            children: [
              Center(
                child: _remoteVideo(),
              ),
    
    
              Align(
                alignment: Alignment.topLeft,
                child: Container(
                  width: 100,
                  height: 150,
                  child: Center(
                    child: _localUserJoined
                        ? RtcLocalView.SurfaceView()
                        : CircularProgressIndicator(),
                  ),
                ),
              ),
    
    
            ],
          ),
        );
      }
    
      // Display remote user's video
      Widget _remoteVideo() {
    
        if (_remoteUid != 0) {
          return RtcRemoteView.SurfaceView(
            uid: _remoteUid,
            channelId: "InstaClass",
          );
        }else {
          print("'Please wait for remote user to join',");
          return Text(
            'Please wait for remote user to join',
            textAlign: TextAlign.center,
          );
        }
      }
    }
    

    For user2:

    main.dart:

    import 'dart:async';
    import 'package:agora_rtc_engine/rtc_engine.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:permission_handler/permission_handler.dart';
    
    
    const appId = "....."; // Same as user1 app
    const token = "....."; // same as user1 app
    
    void main() => runApp(MaterialApp(home: MyApp()));
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      // int? _remoteUid=1;
    
      int _remoteUid=2;
      bool _localUserJoined = false;
      late RtcEngine _engine;
    
      @override
      void initState() {
        super.initState();
        initAgora();
      }
    
      Future<void> initAgora() async {
        // retrieve permissions
        await [Permission.microphone, Permission.camera].request();
    
        //create the engine
        _engine = await RtcEngine.create(appId);
        await _engine.enableVideo();
        _engine.setEventHandler(
          RtcEngineEventHandler(
            joinChannelSuccess: (String channel, int uid, int elapsed) {
              print("local user $uid joined");
              setState(() {
                _localUserJoined = true;
              });
            },
            userJoined: (int uid, int elapsed) {
              print("remote user $uid joined");
              setState(() {
                _remoteUid = uid;
              });
            },
            userOffline: (int uid, UserOfflineReason reason) {
              print("remote user $uid left channel");
              setState(() {
                // _remoteUid = null;
                _remoteUid = 0;
              });
            },
          ),
        );
    
        // await _engine.joinChannel(token, "test", null, 0);
        await _engine.joinChannel(token, "InstaClass", null, 0);
    
      }
    
      // Create UI with local view and remote view
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Agora Video Call'),
          ),
          body: Stack(
            children: [
              Center(
                child: _remoteVideo(),
              ),
              Align(
                alignment: Alignment.topLeft,
                child: Container(
                  width: 100,
                  height: 150,
                  child: Center(
                    child: _localUserJoined
                        ? RtcLocalView.SurfaceView()
                        : CircularProgressIndicator(),
                  ),
                ),
              ),
            ],
          ),
        );
      }
    
      // Display remote user's video
      Widget _remoteVideo() {
        /*if (_remoteUid != null) {
              return RtcRemoteView.SurfaceView(uid: _remoteUid!);
            }*/
    
        if (_remoteUid != 0) {
          return RtcRemoteView.SurfaceView(
            uid: _remoteUid,
            channelId: "InstaClass",
          );
        }else {
          return Text(
            'Please wait for remote user to join',
            textAlign: TextAlign.center,
          );
        }
      }
    }
    

    In order to get the app ID and token, login to Agora site. After logging in, go to the 'Project Management' section to see the projects already created there. Under the Functions column, click on the key symbol and you will be taken to a page where you can generate a temporary token. On that page, give the channel name input the value 'InstaClass' as I have used this name in my code.

    How to make the video chat work smoothly after the first time it works well ?

  • Istiaque Ahmed
    Istiaque Ahmed over 2 years
    How to detect the back button pressing event in Flutter ?
  • Diwyansh
    Diwyansh over 2 years
    For that you can use custom end call button or use WillPopScope widget .
  • Istiaque Ahmed
    Istiaque Ahmed over 2 years
    I was looking for an way to use the custom end call button. Any suggestion ?
  • Diwyansh
    Diwyansh over 2 years
    As you know the that we can customize full UI of call page so just put a button to end the call I'm adding that to my answer.
  • Istiaque Ahmed
    Istiaque Ahmed over 2 years
    I'm gonna test and let you know the result.
  • Diwyansh
    Diwyansh over 2 years
    Sure let me know when you tried.
  • Istiaque Ahmed
    Istiaque Ahmed over 2 years
  • Istiaque Ahmed
    Istiaque Ahmed over 2 years
    Your answer works in the case of an end button in video screen. Can you please add the default back button pressing event handling as you said to do it with WillPopScope ? Then I can accept your answer.
  • Istiaque Ahmed
    Istiaque Ahmed over 2 years
    I can find the leaveChannel method here (docs.agora.io/en/Video/leave_android?platform=Android) for Android platform. But for flutter, where is it documented ? I cannot event find it even in the agora_rtc_engine API doc (pub.dev/packages/agora_rtc_engine/example) .
  • Diwyansh
    Diwyansh over 2 years
    Please go through this docs.agora.io/en/Voice/API%20Reference/flutter/index.html and I will add a WillPopScope functions as well.
  • Istiaque Ahmed
    Istiaque Ahmed over 2 years
    The link you gave is for voice call. 1) Still where is it said in the doc that we have to call the leaveChannel method when coming out of the chat screen ? Besides I am using agora_rtc_engine plugin. Found no mention of it in the plugin doc. 2) Shouldn't the plugin doc have mentioned it ?
  • Diwyansh
    Diwyansh over 2 years
    Sorry I mistaken check this docs.agora.io/en/Video/API%20Reference/flutter/index.html but not much difference because for voice we also use same plugin
  • Istiaque Ahmed
    Istiaque Ahmed over 2 years
    I have invited you to a chat room auto generated from here
  • Diwyansh
    Diwyansh over 2 years
    No chat room found
  • Istiaque Ahmed
    Istiaque Ahmed over 2 years