I was trying to integrate the Amazon Chime demo app that was provided by AWS on our project.
After getting everything from the demo app provided by AWS to our project, I was getting an error "MissingPluginException(No implementation found for method on channel com.amazonaws.services.chime.flutterDemo.methodChannel)".
I guess it's related to the method channels that have been used on the app for communicating from the native side to the flutter side.
Here is the code for Method channels on flutter side :
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*/
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'attendee.dart';
import 'interfaces/audio_video_interface.dart';
import 'interfaces/realtime_interface.dart';
import 'interfaces/video_tile_interface.dart';
import 'logger.dart';
import 'response_enums.dart';
import 'video_tile.dart';
import 'view_models/meeting_view_model.dart';
class MethodChannelCoordinator extends ChangeNotifier {
final MethodChannel methodChannel = const MethodChannel(
"com.amazonaws.services.chime.flutterDemo.methodChannel");
RealtimeInterface? realtimeObserver;
VideoTileInterface? videoTileObserver;
AudioVideoInterface? audioVideoObserver;
void initializeMethodCallHandler() {
methodChannel.setMethodCallHandler(methodCallHandler);
logger.i("Flutter Method Call Handler initialized.");
}
void initializeRealtimeObserver(RealtimeInterface realtimeInterface) {
realtimeObserver = realtimeInterface;
}
void initializeAudioVideoObserver(AudioVideoInterface audioVideoInterface) {
audioVideoObserver = audioVideoInterface;
}
void initializeVideoTileObserver(VideoTileInterface videoTileInterface) {
videoTileObserver = videoTileInterface;
}
void initializeObservers(MeetingViewModel meetingProvider) {
initializeRealtimeObserver(meetingProvider);
initializeAudioVideoObserver(meetingProvider);
initializeVideoTileObserver(meetingProvider);
logger.d("Observers initialized");
}
Future<MethodChannelResponse?> callMethod(String methodName,
[dynamic args]) async {
logger.d("Calling $methodName through method channel with args: $args");
try {
dynamic response = await methodChannel.invokeMethod(methodName, args);
return MethodChannelResponse.fromJson(response);
} catch (e) {
logger.e(e.toString());
return MethodChannelResponse(false, null);
}
}
Future<void> methodCallHandler(MethodCall call) async {
logger.d(
"Recieved method call ${call.method} with arguments: ${call.arguments}");
switch (call.method) {
case MethodCallOption.join:
final Attendee attendee = Attendee.fromJson(call.arguments);
realtimeObserver?.attendeeDidJoin(attendee);
break;
case MethodCallOption.leave:
final Attendee attendee = Attendee.fromJson(call.arguments);
realtimeObserver?.attendeeDidLeave(attendee, didDrop: false);
break;
case MethodCallOption.drop:
final Attendee attendee = Attendee.fromJson(call.arguments);
realtimeObserver?.attendeeDidLeave(attendee, didDrop: true);
break;
case MethodCallOption.mute:
final Attendee attendee = Attendee.fromJson(call.arguments);
realtimeObserver?.attendeeDidMute(attendee);
break;
case MethodCallOption.unmute:
final Attendee attendee = Attendee.fromJson(call.arguments);
realtimeObserver?.attendeeDidUnmute(attendee);
break;
case MethodCallOption.videoTileAdd:
final String attendeeId = call.arguments["attendeeId"];
final VideoTile videoTile = VideoTile.fromJson(call.arguments);
videoTileObserver?.videoTileDidAdd(attendeeId, videoTile);
break;
case MethodCallOption.videoTileRemove:
final String attendeeId = call.arguments["attendeeId"];
final VideoTile videoTile = VideoTile.fromJson(call.arguments);
videoTileObserver?.videoTileDidRemove(attendeeId, videoTile);
break;
case MethodCallOption.audioSessionDidStop:
audioVideoObserver?.audioSessionDidStop();
break;
default:
logger.w(
"Method ${call.method} with args ${call.arguments} does not exist");
}
}
}
class MethodChannelResponse {
late bool result;
dynamic arguments;
MethodChannelResponse(this.result, this.arguments);
factory MethodChannelResponse.fromJson(dynamic json) {
return MethodChannelResponse(json["result"], json["arguments"]);
}
}
Here is the MainActivity.kt code on android side :
package com.familyconnect.memberapp.memberapp
import androidx.annotation.NonNull
import com.amazonaws.services.chime.flutterdemo.MethodChannelCoordinator
import com.amazonaws.services.chime.flutterdemo.NativeViewFactory
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterActivity() {
var methodChannel: MethodChannelCoordinator? = null
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
methodChannel =
MethodChannelCoordinator(
flutterEngine.dartExecutor.binaryMessenger,
getActivity()
)
methodChannel?.setupMethodChannel()
flutterEngine
.platformViewsController
.registry
.registerViewFactory("videoTile", NativeViewFactory())
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissionsList: Array<String>,
grantResults: IntArray
) {
val permissionsManager = methodChannel?.permissionsManager ?: return
when (requestCode) {
permissionsManager.AUDIO_PERMISSION_REQUEST_CODE -> {
methodChannel?.permissionsManager?.audioCallbackReceived()
}
permissionsManager.VIDEO_PERMISSION_REQUEST_CODE -> {
methodChannel?.permissionsManager?.videoCallbackReceived()
}
}
}
}
And here is code for MethodChannelCoordinator on android side :
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*/
package com.amazonaws.services.chime.flutterdemo
import com.amazonaws.services.chime.sdk.meetings.device.MediaDevice
import com.amazonaws.services.chime.sdk.meetings.session.DefaultMeetingSession
import com.amazonaws.services.chime.sdk.meetings.session.MediaPlacement
import com.amazonaws.services.chime.sdk.meetings.session.MeetingSessionConfiguration
import com.amazonaws.services.chime.sdk.meetings.session.CreateMeetingResponse
import com.amazonaws.services.chime.sdk.meetings.session.Meeting
import com.amazonaws.services.chime.sdk.meetings.session.CreateAttendeeResponse
import com.amazonaws.services.chime.sdk.meetings.session.Attendee
import com.amazonaws.services.chime.sdk.meetings.utils.logger.ConsoleLogger
import com.amazonaws.services.chime.flutterdemo.MethodCall as MethodCallFlutter
import android.app.Activity
import android.content.Context
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import androidx.appcompat.app.AppCompatActivity
import com.example.demo_app.AudioVideoObserver
import io.flutter.plugin.common.MethodChannel
class MethodChannelCoordinator(binaryMessenger: BinaryMessenger, activity: Activity) :
AppCompatActivity() {
val methodChannel: MethodChannel
val context: Context
var permissionsManager: PermissionManager = PermissionManager(activity)
init {
methodChannel =
MethodChannel(binaryMessenger, "com.amazonaws.services.chime.flutterDemo.methodChannel")
context = activity.applicationContext
}
private val NULL_MEETING_SESSION_RESPONSE: MethodChannelResult =
MethodChannelResult(false, Response.meeting_session_is_null.msg)
fun setupMethodChannel() {
methodChannel.setMethodCallHandler { call, result ->
val callResult: MethodChannelResult
when (call.method) {
MethodCallFlutter.manageAudioPermissions.call -> {
permissionsManager.manageAudioPermissions(result)
return@setMethodCallHandler
}
MethodCallFlutter.manageVideoPermissions.call -> {
permissionsManager.manageVideoPermissions(result)
return@setMethodCallHandler
}
MethodCallFlutter.join.call -> {
callResult = join(call)
}
MethodCallFlutter.stop.call -> {
callResult = stop()
}
MethodCallFlutter.mute.call -> {
callResult = mute()
}
MethodCallFlutter.unmute.call -> {
callResult = unmute()
}
MethodCallFlutter.startLocalVideo.call -> {
callResult = startLocalVideo()
}
MethodCallFlutter.stopLocalVideo.call -> {
callResult = stopLocalVideo()
}
MethodCallFlutter.initialAudioSelection.call -> {
callResult = initialAudioSelection()
}
MethodCallFlutter.listAudioDevices.call -> {
callResult = listAudioDevices()
}
MethodCallFlutter.updateAudioDevice.call -> {
callResult = updateAudioDevice(call)
}
else -> callResult = MethodChannelResult(false, Response.method_not_implemented)
}
if (callResult.result) {
result.success(callResult.toFlutterCompatibleType())
} else {
result.error(
"Failed",
"MethodChannelHandler failed",
callResult.toFlutterCompatibleType()
)
}
}
}
fun callFlutterMethod(method: MethodCallFlutter, args: Any?) {
methodChannel.invokeMethod(method.call, args)
}
fun join(call: MethodCall): MethodChannelResult {
if (call.arguments == null) {
return MethodChannelResult(false, Response.incorrect_join_response_params.msg)
}
val meetingId: String? = call.argument("MeetingId")
val externalMeetingId: String? = call.argument("ExternalMeetingId")
val mediaRegion: String? = call.argument("MediaRegion")
val audioHostUrl: String? = call.argument("AudioHostUrl")
val audioFallbackUrl: String? = call.argument("AudioFallbackUrl")
val signalingUrl: String? = call.argument("SignalingUrl")
val turnControlUrl: String? = call.argument("TurnControlUrl")
val externalUserId: String? = call.argument("ExternalUserId")
val attendeeId: String? = call.argument("AttendeeId")
val joinToken: String? = call.argument("JoinToken")
if (meetingId == null ||
mediaRegion == null ||
audioHostUrl == null ||
externalMeetingId == null ||
audioFallbackUrl == null ||
signalingUrl == null ||
turnControlUrl == null ||
externalUserId == null ||
attendeeId == null ||
joinToken == null
) {
return MethodChannelResult(false, Response.incorrect_join_response_params.msg)
}
val createMeetingResponse = CreateMeetingResponse(
Meeting(
externalMeetingId,
MediaPlacement(audioFallbackUrl, audioHostUrl, signalingUrl, turnControlUrl),
mediaRegion,
meetingId
)
)
val createAttendeeResponse =
CreateAttendeeResponse(Attendee(attendeeId, externalUserId, joinToken))
val meetingSessionConfiguration =
MeetingSessionConfiguration(createMeetingResponse, createAttendeeResponse)
val meetingSession =
DefaultMeetingSession(meetingSessionConfiguration, ConsoleLogger(), context)
MeetingSessionManager.meetingSession = meetingSession
return MeetingSessionManager.startMeeting(
RealtimeObserver(this),
VideoTileObserver(this),
AudioVideoObserver(this)
)
}
fun stop(): MethodChannelResult {
return MeetingSessionManager.stop()
}
fun mute(): MethodChannelResult {
val muted = MeetingSessionManager.meetingSession?.audioVideo?.realtimeLocalMute()
?: return NULL_MEETING_SESSION_RESPONSE
return if (muted) MethodChannelResult(
true,
Response.mute_successful.msg
) else MethodChannelResult(false, Response.mute_failed.msg)
}
fun unmute(): MethodChannelResult {
val unmuted = MeetingSessionManager.meetingSession?.audioVideo?.realtimeLocalUnmute()
?: return NULL_MEETING_SESSION_RESPONSE
return if (unmuted) MethodChannelResult(
true,
Response.unmute_successful.msg
) else MethodChannelResult(false, Response.unmute_failed.msg)
}
fun startLocalVideo(): MethodChannelResult {
MeetingSessionManager.meetingSession?.audioVideo?.startLocalVideo()
?: return NULL_MEETING_SESSION_RESPONSE
return MethodChannelResult(true, Response.local_video_on_success.msg)
}
fun stopLocalVideo(): MethodChannelResult {
MeetingSessionManager.meetingSession?.audioVideo?.stopLocalVideo()
?: return NULL_MEETING_SESSION_RESPONSE
return MethodChannelResult(true, Response.local_video_on_success.msg)
}
fun initialAudioSelection(): MethodChannelResult {
val device =
MeetingSessionManager.meetingSession?.audioVideo?.getActiveAudioDevice()
?: return NULL_MEETING_SESSION_RESPONSE
return MethodChannelResult(true, device.label)
}
fun listAudioDevices(): MethodChannelResult {
val audioDevices = MeetingSessionManager.meetingSession?.audioVideo?.listAudioDevices()
?: return NULL_MEETING_SESSION_RESPONSE
val transform: (MediaDevice) -> String = { it.label }
return MethodChannelResult(true, audioDevices.map(transform))
}
fun updateAudioDevice(call: MethodCall): MethodChannelResult {
val device =
call.arguments ?: return MethodChannelResult(false, Response.null_audio_device.msg)
val audioDevices = MeetingSessionManager.meetingSession?.audioVideo?.listAudioDevices()
?: return NULL_MEETING_SESSION_RESPONSE
for (dev in audioDevices) {
if (device == dev.label) {
MeetingSessionManager.meetingSession?.audioVideo?.chooseAudioDevice(dev)
?: return MethodChannelResult(false, Response.audio_device_update_failed.msg)
return MethodChannelResult(true, Response.audio_device_updated.msg)
}
}
return MethodChannelResult(false, Response.audio_device_update_failed.msg)
}
}
Here is the exception i was getting
─────────────────────────────────────────────────
I/flutter (31320): │ 20:55:25.247 (+0:00:11.417959)
I/flutter (31320): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
I/flutter (31320): │ Calling manageAudioPermissions through method channel with args: null
I/flutter (31320): └─────────────────────────────────────────────────
I/flutter (31320): ┌─────────────────────────────────────────────────
I/flutter (31320): │ 20:55:25.257 (+0:00:11.427780)
I/flutter (31320): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
I/flutter (31320): │ ⛔ MissingPluginException(No implementation found for method manageAudioPermissions on channel com.amazonaws.services.chime.flutterDemo.methodChannel)
I/flutter (31320): └─────────────────────────────────────────────────
I/flutter (31320): ┌─────────────────────────────────────────────────
I/flutter (31320): │ 20:55:25.259 (+0:00:11.429589)
I/flutter (31320): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
I/flutter (31320): │ ⛔ null
I/flutter (31320): └─────────────────────────────────────────────────
I/flutter (31320): ┌─────────────────────────────────────────────────
I/flutter (31320): │ 20:55:25.260 (+0:00:11.431143)
I/flutter (31320): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
I/flutter (31320): │ Calling manageVideoPermissions through method channel with args: null
I/flutter (31320): └─────────────────────────────────────────────────
I/flutter (31320): ┌─────────────────────────────────────────────────
I/flutter (31320): │ 20:55:25.439 (+0:00:11.609861)
I/flutter (31320): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
I/flutter (31320): │ ⛔ MissingPluginException(No implementation found for method manageVideoPermissions on channel com.amazonaws.services.chime.flutterDemo.methodChannel)