got an AgoraRtcException(20, Make sure you call RtcEngine.initialize first) even after initializing the engine - flutter

trying to make a 1:1 video meeting with agora with flutter and after following the docs i got
AgoraRtcException(20, Make sure you call RtcEngine.initialize first) exception although I am sure I am initializing it first however this the initialize code
void initState() {
super.initState();
setupVideoSDKEngine();
join();
the setupVideoSDKEngine() method code is
Future<void> setupVideoSDKEngine() async {
// retrieve or request camera and microphone permissions
await [Permission.microphone, Permission.camera].request();
//create an instance of the Agora engine
agoraEngine = createAgoraRtcEngine();
await agoraEngine
.initialize(RtcEngineContext(appId: Environment.agoraAppId));
await agoraEngine.enableVideo();
// Register the event handler
agoraEngine.registerEventHandler(
RtcEngineEventHandler(
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
showMessage(
"Local user uid:${connection.localUid} joined the channel");
setState(() {
_isJoined = true;
});
},
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
showMessage("Remote user uid:$remoteUid joined the channel");
setState(() {
_remoteUid = uid;
player.stop();
customTimer!.resetAndStart();
});
},
onUserOffline: (RtcConnection connection, int remoteUid,
UserOfflineReasonType reason) {
showMessage("Remote user uid:$remoteUid left the channel");
callEnded = true;
setState(() {
_remoteUid = null;
});
print('stats ${reason.name}');
if (!userOffline) {
Future.delayed(Duration(seconds: 1), () => Navigator.pop(context));
}
userOffline = true;
},
),
);
}
I am expecting to join the channel but nothing happens and it throws this error
I tried to delete the app and reinstall it but nothing happens
and got this exception too AgoraRtcException(-17, null)

you can to call this before using any other agora function i.e. You are trying to use agora sdk function without initializing it so here it is
This will be written globally
late final RtcEngineEx _engine;
this will be written in initState
_engine = createAgoraRtcEngineEx();
await _engine.initialize(const RtcEngineContext(
appId: APP_ID,
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));

This piece of advice I'm giving is for the correct usage of async and await keywords.
In your initState() function, you are calling two functions one after another; out of which the first function setupVideoSDKEngine() is an async function, and another function join() chould also be an async because the Agora join channel code returns a Future.
await engine.joinChannel(agoraToken, channelId, '', 0,);
Your code right now does not wait for the engine initialization and starts joining channel. Thus, the error. So you got to await your initialization and then write your join channel code.
For eg.
/* permission stuffs */
await agoraEngine.initialize(RtcEngineContext(appId: Environment.agoraAppId));
/* do event handling tasks */
// Now join the channel.
await engine.joinChannel(agoraToken, channelId, '', 0,);
It is recommended that you put the whole code into one async function.

Related

Flutter - an async function returns before really finishing?

I have a function scanAndConnect() that should scan for BLE devices and connect to the device with the specified service ID. This function should be async and should return Future.
The problem is that scanAndConnect() prints 99999 and returns without waiting for flutterReactiveBle.statusStream.listen() to finish although I use await before it.
Future scanAndConnect(Uuid serviceId, Uuid charctId) async {
StreamSubscription<BleStatus>? bleStatusStreamSubscription;
StreamSubscription<DiscoveredDevice>? deviceStreamSubscription;
Stream<DiscoveredDevice> stream;
bleStatusStreamSubscription =
await flutterReactiveBle.statusStream.listen((bleStatus) async {
print("new listen ${bleStatus.toString()}");
if (bleStatus == BleStatus.ready) {
await bleStatusStreamSubscription!.cancel();
connectionStatus = BLEConnectionStatus.Connecting;
stream = await flutterReactiveBle.scanForDevices(
withServices: [serviceId],
scanMode: ScanMode.lowLatency,
);
}
});
print("9999999");
}
....
Future connectToDevice() async {
await ble.scanAndConnect(BLE_SERVICE_UUID, BLE_CHAR_UUID)
print("Statement after await in main");
setState(() {
loading = false;
print("Changing state to ${loading.toString()}");
});
}
This is the output I get in Xcode:
flutter: 9999999
flutter: Statement after await in main
flutter: Changing state to false
flutter: new listen BleStatus.unknown
flutter: new listen BleStatus.ready
How can I make scanAndConnect doesn't return before really finishing?
According to the documentation, FlutterReactiveBle.scanForDevices() returns a Stream, not a Future, so await will not work here. You can use
await for
listen()
await stream.first()
to wait for data from a Stream.

Future function returns value before executing body

I need help. I have looked everywhere but have no solution.
I have a function to get data from the server (REST API). Let's call this function retrieveProfile(). So when the user navigates to their profile page. I call retrieveProfile() in initState() which works very fine.
Now, I implemented access token and refresh token. So if the access token is expired and the user navigates to their profile page, initState() is fired, retrieveProfile() is fired too. The user is informed that their access token is expired but also that the app is generating a new one. Of course, after generating it, the API is called again through the interceptor. But between this process, the value is returned which is zero. retrieveProfile() itself calls a function that returns a future. I expect all the body to be executed before returning the value. See below:
The function that calls the API and retrieves data from backend
Future<List<AllUsers>> getAllUsersData({
BuildContext? context,
}) async {
_isLoading = true;
notifyListeners();
List<AllUsers> usersList = [];
try {
DatabaseProvider databaseProvider = DatabaseProvider();
final String? token =
await databaseProvider.readAccessToken("accessToken");
final String? refreshToken =
await databaseProvider.readRefreshToken("refreshToken");
if (token == null || token.isEmpty) {
await databaseProvider.saveAccessToken('');
}
http.Response response = await http.get(
Uri.parse('$uri/user/all'),
headers: {
'Authorization': 'Bearer $token',
},
);
if (jsonDecode(response.body)["message"] ==
'Access expired. Try refreshing token.') {
showSnackBar(context!, jsonDecode(response.body)["message"]);
print('refreshing token')
recursively(
message: jsonDecode(response.body)['message'],
token: refreshToken!,
maxRetry: maxRetry,
incrementCounter: setMaxRetry,
action: () async {
await getAllUsersData(context: context);
});
} else {
_maxRetry = 1;
responseHandler(
response: response,
context: context!,
onSuccess: () async {
showSnackBar(
context,
jsonDecode(response.body)['message'],
success: true,
);
_isLoading = false;
_resMessage = jsonDecode(response.body)['message'];
notifyListeners();
// Save all users data
for (int i = 0; i < jsonDecode(response.body)['data'].length; i++) {
usersList.add(
allUsersFromJson(
jsonEncode(
jsonDecode(response.body)['data'][i],
),
),
);
}
},
);
print(usersList.length);
_isLoading = false;
notifyListeners();
}
} on SocketException catch (_) {
_isLoading = false;
_resMessage = 'Connection broken! Number of users may not be up-to-date.';
showSnackBar(context!, _resMessage);
notifyListeners();
} catch (e) {
_isLoading = false;
showSnackBar(context!, e.toString());
_resMessage = e.toString();
notifyListeners();
}
return usersList;
}
The function that is executed in initState to call the async function
List<AllUsers>? allUsers;
void retrieveAllUsers() async {
allUsers = await adminService.getAllUsersData(context: context);
print(allUsers!.length);
setState(() {});
}
#override
void initState() {
super.initState();
retrieveAllUsers();
}
When I print allUsers.length, it returns 0.
When I print usersList.length, it returns 6.
What I expect:
I expect the whole body and if statement executed before returning a value.
On printing, I expect the printing sequence to be:
refreshing token. // This is printed while refreshing token.
6 // from usersList.length after refreshing token and recalling api. Printed when recalling api.
6 // from allUsers.length because the correct value has been returned from the async function. Printed from retrieveProfiles() > initState().
What I get instead:
refreshing token. // This is printed while refreshing token.
0 // from allUsers.length because the correct value has been returned from the async function. Printed from retrieveProfiles() > initState().
6 // from usersList.length after refreshing token and recalling api. Printed when recalling api.
This means that it returns the value immediately after refreshing token without waiting for
getAllUsersData({}) to be recalled. getAllUsersData({}) is recalled in the action callback which is an argument in recursively().
Note that the refreshToken function itself returns a future. And I await it too. The recursively() function calls the refreshToken function.

Agora Flutter video call stops after some time with PlatformException(-7, , null, null)

I am working on a Flutter app that uses a video call feature. I have set up engine initialization and everything correctly and am able to make the video call on both ends successfully. Following is my current version of Agora RTC Engine:
agora_rtc_engine: ^5.3.1
Following is the code I used for engine initialization and initiating video calls.
RtcEngine engine;
var _localUid = 0;
await [Permission.camera, Permission.microphone].request();
engine = await RtcEngine.create(Constants.AGORA_APP_ID);
engine.setEventHandler(
RtcEngineEventHandler(
joinChannelSuccess: (channelId, uid, elapsed) { _localUid = uid; /* my_own_logic */ },
leaveChannel: (RtcStats stats) { /* my_own_logic */ },
userJoined: (int remoteUid, int reason) { /* my_own_logic */ },
userOffline: (remoteUid, reason) { /* my_own_logic */ },
));
await engine.enableVideo();
await engine.enableAudio();
await engine.setVideoEncoderConfiguration(VideoEncoderConfiguration(
dimensions: const VideoDimensions(width: 360, height: 640),
frameRate: VideoFrameRate.Fps60,
bitrate: 0,
));
await engine.setDefaultAudioRouteToSpeakerphone(true);
agoraToken = await fetchAgoraToken(uid: _localUid, channelId: "generatedChannelId");
await engine.setChannelProfile(ChannelProfile.LiveBroadcasting);
await engine.setClientRole(ClientRole.Broadcaster);
await engine.startPreview();
await engine.joinChannel(agoraToken, "generatedChannelId", '', _localUid,);
I'm generating call joining tokens with channel ID, Agora APP ID and Certificate from Firebase Functions and the channel name is always unique between the same two callers.
The video call is working well and I can see/hear other people. But after a few seconds, not more than a minute, the video call stops suddenly. Following is the exact error message I receive on my console
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(-7, , null, null)
E/flutter (25342): #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:653:7)
E/flutter (25342): #1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:296:18)
I'm stuck here and unable to work further. Could anybody suggest me how should I fix this?
It turned out that I had earlier placed a timer to schedule cancellation of call if there's no response from the callee. The calling screen was closing after the specified time without leaving channel and destroying the engine, and that was causing the exception.
Timer? callEndTimer;
void scheduleCallEnd() {
callEndTimer = Timer(const Duration(seconds: Constants.INCOMMING_CALL_TIME), () {
if (mounted) {
setState(() {
_onCallEnd(context);
});
}
});
}
Then I cancelled the timer if the call was responded by callee.
void cancelScheduledCallEnd() {
if (callEndTimer != null) callEndTimer!.cancel();
}

Flutter Bloc Error : emit was called after an event handler completed normally - between two functions

I have the following problem...
emit was called after an event handler completed normally. This is
usually due to an unawaited future in an event handler. Please make
sure to await all asynchronous operations with event handlers and use
emit.isDone after asynchronous operations before calling emit() to
ensure the event handler has not completed.
BAD on((event, emit) {
future.whenComplete(() => emit(...)); });
GOOD on((event, emit) async {
await future.whenComplete(() => emit(...)); }); )
What happens is that in a function called _onLogIn, if the user has changed the language, it goes from there to another function inside the bloc, these two functions do not depend on each other, I mean that each function is called in different pages of the application, but still _onLogIn checks the _onChangeLanguage function.
UserBloc({this.usecases}) : super(UserInitial()) {
on<LogInEvent>(_onLogIn);
on<ChangeLanguageEvent>(_onChangeLanguage);
}
_onLogIn function :
void _onLogIn(
LogInEvent event,
Emitter<StateA> emit,
) async {
emit(UserLoading());
final userOrFailure = await services.logIn(
x: event.x,
y: event.y,
);
await userOrFailure.fold((user) async {
/// If the user is logging in for the first time and does not
/// have a preferred language.
if (user.preferredLanguage == null) {
emit(UserSuccess());
emit(UserAlreadyLogged(connectedUser));
} else {
/// An ChangeLanguageEvent object
ChangeLanguageEvent event = ChangeLanguageEvent(
user.preferredLanguage,
user.someId,
);
/// Call the other function in the same bloc
this._onChangeLanguage(
event,
emit,
isFromLogin: true,
);
}
}, (failure) {
emit(UserError(failure.message));
});
}
_onChangeLanguage function :
void _onChangeLanguage(
ChangeLanguageEvent event,
Emitter<StateA> emit, {
bool isFromLogin = false,
}) async {
final successOrFailure = await services.updateLanguage(
event.language,
event.someId,
);
await successOrFailure.fold( // ! HERE THE ERROR WHEN I LOG IN; but when i changed the language from the application i don't have an error
(language) async {
emit(ChangeAppLanguage(language));
final sessionOrFailure = await services.getSession();
sessionOrFailure.fold(
(session) {
/// I need this condition to know if the language comes from login
if (isFromLogin) {
emit(UserSuccess());
}
emit(UserAlreadyLogged(session));
},
(failure) => emit(UserError(failure.message)),
);
},
(failure) {
emit(UserError(failure.message));
},
);
}
Any idea why? Thank you
void _onChangeLanguage(
ChangeLanguageEvent event,
Emitter<StateA> emit, {
bool isFromLogin = false,
}) async
This should be a major red flag. A call marked as async, but not returning a Future<>. There is no way, the caller could possibly await this call. Or even know that they should await this call.
Make it return a proper Future<void> instead of just void and your bloc should pick up on that and properly await the call.
There even is a linter rule for this: avoid_void_async. Did you turn off your linter? Don't do that. Turn your linter on and listen to it. Your other function has the same problem.
In my case I had to return Future<...>, but have not done it in the 'switch' statement, rather I've used the 'break' on all cases. So a compiler have not pointed me the lack of the 'return' statement.
After I have put 'return' on all cases, the error disappeared.

Is there a way to get notified when a dart stream gets its first result?

I currently have an async function that does the following:
Initializes the stream
Call stream.listen() and provide a function to listen to the stream.
await for the stream to get its first result.
The following is some pseudo code of my function:
Future<void> initStream() async {
// initialize stream
var stream = getStream();
// listen
stream.listen((result) {
// do some stuff here
});
// await until first result
await stream.first; // gives warning
}
Unfortunately it seems that calling stream.first counts as listening to the stream, and streams are not allowed to be listened by multiple...listeners?
I tried a different approach by using await Future.doWhile()
Something like the following:
bool gotFirstResult = false;
Future<void> initStream() async {
var stream = getStream();
stream.listen((result) {
// do some stuff here
gotFirstResult = true;
});
await Future.doWhile(() => !gotFirstResult);
}
This didn't work for me, and I still don't know why. Future.doWhile() was successfully called, but then the function provided to stream.listen() was never called in this case.
Is there a way to wait for the first result of a stream?
(I'm sorry if I didn't describe my question well enough. I'll definitely add other details if needed.)
Thanks in advance!
One way is converting your stream to broadcast one:
var stream = getStream().asBroadcastStream();
stream.listen((result) {
// do some stuff here
});
await stream.first;
Another way, without creating new stream, is to use Completer. It allows you to return a Future which you can complete (send value) later. Caller will be able to await this Future as usual.
Simple example:
Future<int> getValueAsync() {
var completer = Completer<int>();
Future.delayed(Duration(seconds: 1))
.then((_) {
completer.complete(42);
});
return completer.future;
}
is equivalent of
Future<int> getValueAsync() async {
await Future.delayed(Duration(seconds: 1));
return 42;
}
In your case:
Future<void> initStream() {
var stream = getStream();
var firstValueReceived = Completer<void>();
stream.listen((val) {
if (!firstValueReceived.isCompleted) {
firstValueReceived.complete();
}
// do some stuff here
});
return firstValueReceived.future;
}