AgoraIO-Extensions / Agora-Flutter-SDK

Flutter plugin of Agora RTC SDK for Android/iOS/macOS/Windows
https://pub.dev/packages/agora_rtc_engine
MIT License
733 stars 381 forks source link

Unhandled Exception: LateInitializationError: Field 'requestPort' has not been initialized. Agora flutter #860

Closed FaizanAhmad127 closed 1 year ago

FaizanAhmad127 commented 1 year ago

I am working on a large-scale project and integrating video calls into it. So I made a separate project just for practice and I achieved good results. The group calling worked perfectly on both android and IOS. But then I integrated the same code in my large-scale project which uses firebase as a backend and when I navigate to the video screen it gives me an error saying "Unhandled Exception: LateInitializationError: Field 'requestPort' has not been initialized". The Agora console is on Testing for now and the channel wasn't expired just in case you guys are wondering. As I said it works perfectly in a separate project.

The app uses agora_rtc_engine: ^6.1.0 and defined in pubspec.yaml file


import 'package:flutter/material.dart';
import 'package:logger/logger.dart';

class VideoCallPage extends StatefulWidget {
  const VideoCallPage({Key? key}) : super(key: key);

  @override
  VideoCallPageState createState() => VideoCallPageState();
}

class VideoCallPageState extends State<VideoCallPage> {
  static final _users = <int>[];
  Logger logger = Logger();
  bool muted = false;
  late RtcEngine _engine;
  bool isRigning = true;
  bool isSpeakerOn = true;
  final String channelName = 'video';
  final String appID = 'xxx';
  final String tokenAudio ='xxx';

  @override
  void dispose() {
    _dispose();
    super.dispose();
  }

  Future<void> _dispose() async {
    _users.clear();
    await _engine.leaveChannel();
    await _engine.stopPreview();
    await _engine.release();
  }

  @override
  void initState() {
    super.initState();
    // initialize agora sdk
    initialize();
  }

  Future<void> initialize() async {
    logger.i('Initialize');
    if (appID.isEmpty) {
      setState(() {
        logger.e(
          'APP_ID missing, please provide your APP_ID in settings.dart',
        );
        logger.e('Agora Engine is not starting');
      });
      return;
    }
    await _initAgoraRtcEngine();
    _addAgoraEventHandlers();
    onOffSpeaker();
    await _engine.joinChannel(
        token: tokenAudio,
        channelId: channelName,
        uid: 0,
        options: const ChannelMediaOptions(
            channelProfile: ChannelProfileType.channelProfileCommunication,
            clientRoleType: ClientRoleType.clientRoleBroadcaster));

  }

  Future<void> _initAgoraRtcEngine() async {
    logger.i('_initAgoraRtcEngine');
    //create the engine
    _engine = createAgoraRtcEngine();
    logger.i('RtcEngineContext'); //this is printed 
    await _engine.initialize(RtcEngineContext(
      appId: appID,
    ));
    logger.i('enablbing video'); //this isn't printed
    await _engine.enableVideo();
    // await _engine.setVideoEncoderConfiguration(
    //   const VideoEncoderConfiguration(
    //     dimensions: VideoDimensions(width: 640, height: 360),
    //     frameRate: 15,
    //     bitrate: 0,
    //   ),
    // );
    await _engine.startPreview();
  }

  void _addAgoraEventHandlers() {
    _engine.registerEventHandler(RtcEngineEventHandler(
      onError: (ErrorCodeType errorCodeType, String value) {
        if (mounted) {
          setState(() {
            final info = 'onError: ${errorCodeType.name}';
            logger.e(info);
          });
        }
      },
      onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
        setState(() {
          final info =
              'onJoinChannel: ${connection.channelId}, uid: ${connection.localUid}';
          logger.i(info);
        });
      },
      onLeaveChannel: (RtcConnection rtcConnection, RtcStats rtcStats) {
        setState(() {
          logger.i('onLeaveChannel');
          _users.clear();
        });
      },
      onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
        setState(() {
          isRigning = false;
          final info = 'remoteUserJoined: $remoteUid';
          logger.i(info);
          _users.add(remoteUid);
        });
      },
      onUserOffline: (RtcConnection connection, int remoteUid,
          UserOfflineReasonType reason) {
        setState(() {
          final info =
              'remoteUserOffline: $remoteUid , reason: ${reason.index}';
          logger.i(info);
          _users.remove(remoteUid);
        });
      },
      onFirstRemoteVideoFrame: (connection, uid, width, height, elapsed) {
        setState(() {
          final info = 'firstRemoteVideoFrame: $uid';
          logger.i(info);
        });
      },
    ));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Agora Group Video Calling'),
      ),
      backgroundColor: Colors.black,
      body: Center(
        child: Stack(
          children: <Widget>[
            Opacity(
              opacity: isRigning ? 0.2 : 1,
              child: _viewRows(),
            ),
            _toolbar(),
            if (isRigning)
              Positioned(
                  top: 100,
                  left: MediaQuery.of(context).size.width * 0.3,
                  right: MediaQuery.of(context).size.width * 0.3,
                  child: Center(
                    child: Text(
                      'Ringing...',
                      style: TextStyle(color: Colors.white, fontSize: 30),
                    ),
                  ))
          ],
        ),
      ),
    );
  }

  /// Helper function to get list of native views
  List<Widget> _getRenderViews() {
    final List<StatefulWidget> list = [];
    list.add(AgoraVideoView(
        controller: VideoViewController(
      rtcEngine: _engine,
      canvas: const VideoCanvas(uid: 0),
    )));
    _users.forEach((int uid) => list.add(AgoraVideoView(
          controller: VideoViewController.remote(
            rtcEngine: _engine,
            canvas: VideoCanvas(uid: uid),
            connection: RtcConnection(channelId: channelName),
          ),
        )));
    return list;
  }

  /// Video view wrapper
  Widget _videoView(view) {
    return Expanded(child: Container(child: view));
  }

  /// Video view row wrapper
  Widget _expandedVideoRow(List<Widget> views) {
    final wrappedViews = views.map<Widget>(_videoView).toList();
    return Expanded(
      child: Row(
        children: wrappedViews,
      ),
    );
  }

  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();
  }

  Widget _toolbar() {
    return Container(
      alignment: Alignment.bottomCenter,
      padding: const EdgeInsets.symmetric(vertical: 48),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          RawMaterialButton(
            onPressed: onOffSpeaker,
            child: Icon(
              isSpeakerOn ? Icons.volume_up_sharp : Icons.volume_off,
              color: isSpeakerOn ? Colors.white : Colors.blueAccent,
              size: 20.0,
            ),
            shape: CircleBorder(),
            elevation: 2.0,
            fillColor: isSpeakerOn ? Colors.blueAccent : Colors.white,
            padding: const EdgeInsets.all(12.0),
          ),
          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),
          )
        ],
      ),
    );
  }

  void _onToggleMute() {
    setState(() {
      muted = !muted;
    });
    _engine.muteLocalAudioStream(muted);
  }

  void _onCallEnd(BuildContext context) {
    Navigator.pop(context);
  }

  void _onSwitchCamera() {
    _engine.switchCamera();
  }

  Future onOffSpeaker() async {
    setState(() {
      isSpeakerOn = !isSpeakerOn;
    });
    await _engine.setEnableSpeakerphone(isSpeakerOn);
  }
}
JimmyGoGoGo commented 1 year ago

same here

littleGnAl commented 1 year ago

@FaizanAhmad127 Can you share the more stack trace of the error log?

tselmeggkh commented 1 year ago

Having the same issue here. It's working fine after one call, this exception is throwing only on the first initialization for me.


[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: LateInitializationError: Field 'requestPort' has not been initialized.
#0      _ApiCallExecutor.requestPort (package:agora_rtc_engine/src/impl/api_caller.dart)
#1      _ApiCallExecutor.callIrisApiWithUin8ListAsync (package:agora_rtc_engine/src/impl/api_caller.dart:273:5)
#2      ApiCaller.callIrisApiWithUin8ListAsync (package:agora_rtc_engine/src/impl/api_caller.dart:110:10)
#3      ApiCaller.callIrisApi (package:agora_rtc_engine/src/impl/api_caller.dart:118:12)
#4      RtcEngineImpl.enableAudio (package:agora_rtc_engine/src/binding/agora_rtc_engine_impl.dart:723:25)
#9      AgoraRtmChannel._eventListener (package:agora_rtm/src/agora_rtm_channel.dart:60:33)
#10     _RootZone.runUnaryGuarded (dart:async/zone.dart:1586:10)
#11     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:339:11)
#12     _DelayedData.perform (dart:async/stream_impl.dart:515:14)
#13     _PendingEvents.handleNext (dart:async/stream_impl.dart:620:11)
#14     _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:591:7)
#15     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
#16     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)
FaizanAhmad127 commented 1 year ago

@FaizanAhmad127 Can you share the more stack trace of the error log?

E/flutter (10089): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: LateInitializationError: Field 'requestPort' has not been initialized.
E/flutter (10089): #0      _ApiCallExecutor.requestPort (package:agora_rtc_engine/src/impl/api_caller.dart)
package:agora_rtc_engine/…/impl/api_caller.dart:1
E/flutter (10089): #1      _ApiCallExecutor.callIrisApiWithUin8ListAsync
package:agora_rtc_engine/…/impl/api_caller.dart:273
E/flutter (10089): #2      ApiCaller.callIrisApiWithUin8ListAsync
package:agora_rtc_engine/…/impl/api_caller.dart:110
E/flutter (10089): #3      ApiCaller.callIrisApi
package:agora_rtc_engine/…/impl/api_caller.dart:118
E/flutter (10089): #4      RtcEngineImpl.setupLocalVideo
package:agora_rtc_engine/…/binding/agora_rtc_engine_impl.dart:706
E/flutter (10089): #5      VideoViewControllerBaseMixin.setupView
package:agora_rtc_engine/…/impl/video_view_controller_impl.dart:125
E/flutter (10089): #6      _AgoraRtcRenderPlatformViewState._setupVideo
package:agora_rtc_engine/…/impl/agora_video_view_impl.dart:111
E/flutter (10089): <asynchronous suspension>
FaizanAhmad127 commented 1 year ago

I think the method createAgoraRtcEngine() should be async just like in previous versions we had await RtcEngine.create(appID). When the app gets big then it only gives the error else it's fine.

FaizanAhmad127 commented 1 year ago

Solved. I created a service class in which I put this piece of code

class Foo
{
    late RtcEngine engine;

    Foo()
    {
        engine = createAgoraRtcEngine();
        engine.initialize(RtcEngineContext(
        appId: 'your_app_id', 
        logConfig: LogConfig(level: LogLevel.logLevelError))); 
      }

}

and used get it package to achieve a singleton pattern which is also suggested by Agora while creating RTCEngine so we can have only one instance of it. And then register the service class at the very beginning of main() like this:

GetIt locator = GetIt.instance;
locator.registerLazySingleton(() => Foo()); //Foo should be replace by your service class

Then I used the engine variable inside my video call screen,like this

final foo=locator.get<Foo>();
RtcEngine engine=foo.engine;

and the rest of the code remains the same. I don't really get the depth of the issue. A proper solution must be provided.

littleGnAl commented 1 year ago

You should call the RtcEngine APIs after initialize, cause the initialize is async function, so you need check the initialized state:

class VideoCallPageState extends State<VideoCallPage> {

  late RtcEngine _engine;
  bool _isInitialized = false;

  @override
  void initState() {
    super.initState();
    // initialize agora sdk
    initialize();
  }

  Future<void> initialize() async {
    _engine = createAgoraRtcEngine();
    await _engine.initialize(RtcEngineContext(
    appId: 'your_app_id', 
    logConfig: LogConfig(level: LogLevel.logLevelError))); 
    setState((){
      _isInitialized = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!_isInitialized) {
      return Container();
    }

    // Only show AgoraVideoView after initialized

    ...
  }
}
FaizanAhmad127 commented 1 year ago

Thanks, yes I know that and I was doing the exact same thing but it led me to a response port error in my large-scale project. I'm doing the same now but in a service class that is run before other service classes. If I run your code after other services it gives me an error, which means I have to create and initialize the engine before everything else in my app.

littleGnAl commented 1 year ago

This can be enhanced, but you should control the initializing order on your own at this time.

FaizanAhmad127 commented 1 year ago

Yes, I have done that. But any luck on finding why we get responsePort error?

littleGnAl commented 1 year ago

Since the responsePort is initiated in the RtcEngine.initialize function, if any APIs are called before RtcEngine.initialize will be caused this error.

qaTomosia commented 1 year ago

i'm facing the same problem. if you can fix, let me know. thanks man

adiShinwari commented 1 year ago

@littleGnAl iam getting the same error and the same steps to reproduce it. If initialization is the case, then it should give the error in the first call also.

johnhcolani commented 1 year ago

Please same here, after upgrading my Flutter today, [VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: LateInitializationError: Field 'deviceName' has not been initialized.

0 _BluetoothPageState.deviceName (package:phillips_smart_charger/presentation/bluetoothpage/bluetoothPage.dart)

1 _BluetoothPageState._startDiscovery (package:phillips_smart_charger/presentation/bluetoothpage/bluetoothPage.dart:57:19)

2 _BluetoothPageState.initState (package:phillips_smart_charger/presentation/bluetoothpage/bluetoothPage.dart:41:5)

3 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5101:55)

4 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4944:5)

5 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3953:16)

6 Element.updateChild (package:flutter/src/widgets/framework.dart:3682:18)

7 SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6377:14)

github-actions[bot] commented 1 year ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please raise a new issue.