Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intergrating native Twilio Android SDK with Flutter

I am trying to create Voice Over IP (VOIP) mobile application using flutter.I haven't seen an implementation for a flutter plugin for twilio voice api so i intergrated my application with the native android voice api using MethodChannel.The twilio SDK doesnt seem like it intergrated correctly i cant access the twilio classes and methods in scripts. These are the errors i get.

Running Gradle task 'assembleDebug'...
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees  /voip20/MainActivity.java:23: error: package android.support.annotation does not exist
import android.support.annotation.NonNull;
                             ^
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees   /voip20/MainActivity.java:295: error: cannot find symbol
public void onRequestPermissionsResult(int requestCode, @NonNull   String[] permissions, @NonNull int[] grantResults) {
                                                         ^
symbol:   class NonNull
location: class MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:295: error: cannot find symbol
   public void onRequestPermissionsResult(int requestCode, @NonNull  String[] permissions, @NonNull int[] grantResults) {
                                                                                         ^
symbol:   class NonNull
location: class MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees   /voip20/MainActivity.java:117: error: cannot find symbol
    soundPoolManager =   SoundPoolManager.getInstance(this.MainActivity);
                                                        ^
 symbol: variable MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:186: error: cannot find symbol
        public void onReconnecting(@NonNull Call call, @NonNull   CallException callException) {
                                    ^
  symbol: class NonNull
  /home/kudziesimz/voip20/android/app/src/main/java/com/workerbees  /voip20/MainActivity.java:186: error: cannot find symbol
          public void onReconnecting(@NonNull Call call, @NonNull CallException callException) {
                                                        ^
           symbol: class NonNull
         /home/kudziesimz/voip20/android/app/src/main/java /com/workerbees/voip20/MainActivity.java:191: error: cannot find symbol
            public void onReconnected(@NonNull Call call) {
                                   ^
        symbol: class NonNull
  /home/kudziesimz/voip20/android/app/src/main/java/com/workerbees    /voip20/MainActivity.java:279: error: cannot find symbol
      int resultMic = ContextCompat.checkSelfPermission(this,    Manifest.permission.RECORD_AUDIO);
                    ^
   symbol:   variable ContextCompat
   location: class MainActivity
  /home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:284: error: method    shouldShowRequestPermissionRationale in class Activity cannot be applied  to given types;
         if (MainActivity.shouldShowRequestPermissionRationale(this,  Manifest.permission.RECORD_AUDIO)) {
                     ^
           required: String
    found: MainActivity,String
     reason: actual and formal argument lists differ in length
    /home/kudziesimz/voip20/android/app/src/main/java/com/workerbees   /voip20/MainActivity.java:287: error: method requestPermissions in class    Activity cannot be applied to given types;
             MainActivity.requestPermissions(
                    ^
           required: String[],int
           found: MainActivity,String[],int
           reason: actual and formal argument lists differ in length
          Note: /home/kudziesimz/voip20/android/app/src/main/java  /com/workerbees/voip20/MainActivity.java uses or overrides a deprecated   API.
                    Note: Recompile with -Xlint:deprecation for details.
                    10 errors

I followed the voice-quickstart-android guide shown here https://github.com/twilio/voice-quickstart-android

here is my code:main.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';

 //This is a test application which allows clients to make Voice Over    The Internet Cal
 void main() => runApp(MaterialApp(
   home: MyApp(),
    ));

 class MyApp extends StatefulWidget {
  @override
 _MyAppState createState() => _MyAppState();
  }

 class _MyAppState extends State<MyApp> {
 static const platform = const     MethodChannel("com.voip.call_management/calls");

  @override
  Widget build(BuildContext context) {
     return Scaffold(
     appBar: AppBar(
      title: Text("Call Management"),
       ),
     bottomNavigationBar: Center(
     child: IconButton(
        icon: Icon(Icons.phone),
        onPressed: () {
          _makeCall;
           }),
       ),
      );
     }

  Future<void> _makeCall() async {
     return showDialog<void>(
         context: context,
          barrierDismissible: false, // user must tap button!
          builder: (BuildContext context) {
          return AlertDialog(
          title: Row(
             children: <Widget>[
                Text('Call'),
                Icon(
                   Icons.phone,
                  color: Colors.blue,
                 )
             ],
           ),
          content: SingleChildScrollView(
           child: ListBody(
                children: <Widget>[
                 TextField(
                  decoration: InputDecoration(
                  hintText: "client identity or phone number"),
                  ),
                SizedBox(
                    height: 20,
                    ),
                 Text(
                     'Dial a client name or number.Leaving the field      empty will result in an automated response.'),
              ],
             ),
           ),
           actions: <Widget>[
            FlatButton(
               child: Text('Cancel'),
                   onPressed: () {
                        Navigator.of(context).pop();
                       },
                    ),
             IconButton(icon: Icon(Icons.phone), onPressed:()async {
                try {
                final result = await platform.invokeMethod("makecall");
          } on PlatformException catch (e) {
            print(e.message);
          }
        })
        ],
      );
    },
    );
   }
  }

MainActivity.java

package com.workerbees.voip20;

 import android.os.Bundle;

import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;

//javacode imports

import android.Manifest;
import android.content.Context;

import android.content.pm.PackageManager;

import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import android.support.annotation.NonNull;

import android.util.Log;


import com.google.firebase.iid.FirebaseInstanceId;
import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.ion.Ion;
import com.twilio.voice.Call;
import com.twilio.voice.CallException;
import com.twilio.voice.CallInvite;
import com.twilio.voice.ConnectOptions;
import com.twilio.voice.RegistrationException;
import com.twilio.voice.RegistrationListener;
import com.twilio.voice.Voice;

import java.util.HashMap;

//sound pool imports
import android.media.SoundPool;


import static android.content.Context.AUDIO_SERVICE;


public class MainActivity extends FlutterActivity {
   private static final String CHANNEL = "com.workerbees.voip/calls";        // MethodChannel Declaration
   private static final String TAG = "VoiceActivity";
   private static String identity = "alice";
   private static String contact;
   /*
    * You must provide the URL to the publicly accessible Twilio     access token server route
    *
    * For example: https://myurl.io/accessToken
    *
    * If your token server is written in PHP,    TWILIO_ACCESS_TOKEN_SERVER_URL needs .php extension at the end.
     *
     * For example : https://myurl.io/accessToken.php
     */
     private static final String TWILIO_ACCESS_TOKEN_SERVER_URL = "https://bd107744.ngrok.io/accessToken";

private static final int MIC_PERMISSION_REQUEST_CODE = 1;


private String accessToken;
private AudioManager audioManager;
private int savedAudioMode = AudioManager.MODE_INVALID;


// Empty HashMap, never populated for the Quickstart
HashMap<String, String> params = new HashMap<>();

private SoundPoolManager soundPoolManager;
private Call activeCall;

Call.Listener callListener = callListener();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);

    new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(

            new MethodCallHandler() {
                @Override
                public void onMethodCall(MethodCall call, Result result) {
                    // Note: this method is invoked on the main thread.
                    // TODO
                    if(call.method.equals("makecall")){

                        params.put("to", contact);
                        ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
                                .params(params)
                                .build();
                        activeCall = Voice.connect(MainActivity.this, connectOptions, callListener);

                    }
                    else if(call.method.equals("hangup")){
                        disconnect();
                    }
                    else if(call.method.equals("mute")){
                        mute();
                    }
                    else if (call.method.equals("hold")){
                        hold();
                    }
                    else{
                        Log.d(TAG,"invalid API call");
                    }
                }
            });


    soundPoolManager = SoundPoolManager.getInstance(this.MainActivity);

    /*
     * Needed for setting/abandoning audio focus during a call
     */
    audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    audioManager.setSpeakerphoneOn(true);

    /*
     * Enable changing the volume using the up/down keys during a conversation
     */
    setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);



    /*
     * Displays a call dialog if the intent contains a call invite
     */
    //handleIncomingCallIntent(getIntent());

    /*
     * Ensure the microphone permission is enabled
     */
    if (!checkPermissionForMicrophone()) {
        requestPermissionForMicrophone();
    } else {
        retrieveAccessToken();
    }

}


private Call.Listener callListener() {
    return new Call.Listener() {
        /*
         * This callback is emitted once before the Call.Listener.onConnected() callback when
         * the callee is being alerted of a Call. The behavior of this callback is determined by
         * the answerOnBridge flag provided in the Dial verb of your TwiML application
         * associated with this client. If the answerOnBridge flag is false, which is the
         * default, the Call.Listener.onConnected() callback will be emitted immediately after
         * Call.Listener.onRinging(). If the answerOnBridge flag is true, this will cause the
         * call to emit the onConnected callback only after the call is answered.
         * See answeronbridge for more details on how to use it with the Dial TwiML verb. If the
         * twiML response contains a Say verb, then the call will emit the
         * Call.Listener.onConnected callback immediately after Call.Listener.onRinging() is
         * raised, irrespective of the value of answerOnBridge being set to true or false
         */
        @Override
        public void onRinging(Call call) {
            Log.d(TAG, "Ringing");
        }

        @Override
        public void onConnectFailure(Call call, CallException error) {
            setAudioFocus(false);
            Log.d(TAG, "Connect failure");
            String message = String.format("Call Error: %d, %s", error.getErrorCode(), error.getMessage());
            Log.e(TAG, message);

        }

        @Override
        public void onConnected(Call call) {
            setAudioFocus(true);
            Log.d(TAG, "Connected");
            activeCall = call;
        }

        @Override
        public void onReconnecting(@NonNull Call call, @NonNull CallException callException) {
            Log.d(TAG, "onReconnecting");
        }

        @Override
        public void onReconnected(@NonNull Call call) {
            Log.d(TAG, "onReconnected");
        }

        @Override
        public void onDisconnected(Call call, CallException error) {
            setAudioFocus(false);
            Log.d(TAG, "Disconnected");
            if (error != null) {
                String message = String.format("Call Error: %d, %s", error.getErrorCode(), error.getMessage());
                Log.e(TAG, message);
            }
        }
    };
}


private void disconnect() {
    if (activeCall != null) {
        activeCall.disconnect();
        activeCall = null;
    }
}

private void hold() {
    if (activeCall != null) {
        boolean hold = !activeCall.isOnHold();
        activeCall.hold(hold);

    }
}

private void mute() {
    if (activeCall != null) {
        boolean mute = !activeCall.isMuted();
        activeCall.mute(mute);

    }
}

private void setAudioFocus(boolean setFocus) {
    if (audioManager != null) {
        if (setFocus) {
            savedAudioMode = audioManager.getMode();
            // Request audio focus before making any device switch.
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                AudioAttributes playbackAttributes = new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        .build();
                AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
                        .setAudioAttributes(playbackAttributes)
                        .setAcceptsDelayedFocusGain(true)
                        .setOnAudioFocusChangeListener(new AudioManager.OnAudioFocusChangeListener() {
                            @Override
                            public void onAudioFocusChange(int i) {
                            }
                        })
                        .build();
                audioManager.requestAudioFocus(focusRequest);
            } else {
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.FROYO) {
                    int focusRequestResult = audioManager.requestAudioFocus(
                            new AudioManager.OnAudioFocusChangeListener() {

                                @Override
                                public void onAudioFocusChange(int focusChange)
                                {
                                }
                                  }, AudioManager.STREAM_VOICE_CALL,
                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                }
            }
            /*
             * Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
             * required to be in this mode when playout and/or recording starts for
             * best possible VoIP performance. Some devices have difficulties with speaker mode
             * if this is not set.
             */
            audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
        } else {
            audioManager.setMode(savedAudioMode);
            audioManager.abandonAudioFocus(null);
        }
    }
}

private boolean checkPermissionForMicrophone() {
    int resultMic = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);
    return resultMic == PackageManager.PERMISSION_GRANTED;
}

private void requestPermissionForMicrophone() {
    if (MainActivity.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {

    } else {
        MainActivity.requestPermissions(
                this,
                new String[]{Manifest.permission.RECORD_AUDIO},
                MIC_PERMISSION_REQUEST_CODE);
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    /*
     * Check if microphone permissions is granted
     */
    if (requestCode == MIC_PERMISSION_REQUEST_CODE && permissions.length > 0) {
        if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {

            Log.d(TAG, "Microphone permissions needed. Please allow in your application settings.");
        } else {
            retrieveAccessToken();
        }
    }
}


/*
 * Get an access token from your Twilio access token server
 */
private void retrieveAccessToken() {
    Ion.with(this).load(TWILIO_ACCESS_TOKEN_SERVER_URL + "?identity=" + identity).asString().setCallback(new FutureCallback<String>() {
        @Override
        public void onCompleted(Exception e, String accessToken) {
            if (e == null) {
                Log.d(TAG, "Access token: " + accessToken);
                MainActivity.this.accessToken = accessToken;

            } else {
                Log.d(TAG, "Registration failed");
            }
        }
    });
           }
      }


class SoundPoolManager {

private boolean playing = false;
private boolean loaded = false;
private boolean playingCalled = false;
private float actualVolume;
private float maxVolume;
private float volume;
private AudioManager audioManager;
private SoundPool soundPool;
private int ringingSoundId;
private int ringingStreamId;
private int disconnectSoundId;
private static SoundPoolManager instance;

private SoundPoolManager(Context context) {
    // AudioManager audio settings for adjusting the volume
    audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE);
    actualVolume = (float) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    maxVolume = (float) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    volume = actualVolume / maxVolume;

    // Load the sounds
    int maxStreams = 1;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        soundPool = new SoundPool.Builder()
                .setMaxStreams(maxStreams)
                .build();
    } else {
        soundPool = new SoundPool(maxStreams, AudioManager.STREAM_MUSIC, 0);
    }

    soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
        @Override
        public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
            loaded = true;
            if (playingCalled) {
                playRinging();
                playingCalled = false;
            }
        }

    });
    ringingSoundId = soundPool.load(context, R.raw.incoming, 1);
    disconnectSoundId = soundPool.load(context, R.raw.disconnect, 1);
}

public static SoundPoolManager getInstance(Context context) {
    if (instance == null) {
        instance = new SoundPoolManager(context);
    }
    return instance;
}

public void playRinging() {
    if (loaded && !playing) {
        ringingStreamId = soundPool.play(ringingSoundId, volume, volume, 1, -1, 1f);
        playing = true;
    } else {
        playingCalled = true;
    }
}

public void stopRinging() {
    if (playing) {
        soundPool.stop(ringingStreamId);
        playing = false;
    }
}

public void playDisconnect() {
    if (loaded && !playing) {
        soundPool.play(disconnectSoundId, volume, volume, 1, 0, 1f);
        playing = false;
    }
}

public void release() {
    if (soundPool != null) {
        soundPool.unload(ringingSoundId);
        soundPool.unload(disconnectSoundId);
        soundPool.release();
        soundPool = null;
    }
    instance = null;
 }
    }

This my build.gradle

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
   localPropertiesFile.withReader('UTF-8') { reader ->
      localProperties.load(reader)
    }
  }

def flutterRoot = localProperties.getProperty('flutter.sdk')
 if (flutterRoot == null) {
   throw new GradleException("Flutter SDK not found. Define location      with flutter.sdk in the local.properties file.")
}

  def flutterVersionCode =       localProperties.getProperty('flutter.versionCode')
    if (flutterVersionCode == null) {
         flutterVersionCode = '1'
            }

def flutterVersionName =   localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
  }

    apply plugin: 'com.android.application'
    apply from: "$flutterRoot/packages/flutter_tools/gradle /flutter.gradle"

android {
     compileSdkVersion 28

  lintOptions {
    disable 'InvalidPackage'
}

compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
}

defaultConfig {
    // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
    applicationId "com.workerbees.voip20"
    minSdkVersion 16
    targetSdkVersion 28
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
    release {
        // TODO: Add your own signing config for the release build.
        // Signing with the debug keys for now, so `flutter run --release` works.
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        signingConfig signingConfigs.debug
    }
    // Specify that we want to split up the APK based on ABI
    splits {
        abi {
            // Enable ABI split
            enable true

            // Clear list of ABIs
            reset()

            // Specify each architecture currently supported by the Video SDK
            include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"

            // Specify that we do not want an additional universal SDK
            universalApk false
        }
    }
}
         }

    flutter {
         source '../..'
       }

   dependencies {
          testImplementation 'junit:junit:4.12'
          androidTestImplementation 'androidx.test:runner:1.1.1'
          androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
          implementation 'com.twilio:voice-android:4.5.0'
          implementation 'com.android.support:design:28.0.0'
          implementation 'com.android.support:support-media-compat:28.0.0'
         implementation 'com.android.support:animated-vector-drawable:28.0.0'
         implementation 'com.android.support:support-v4:28.0.0'
         implementation 'com.squareup.retrofit:retrofit:1.9.0'
         implementation 'com.koushikdutta.ion:ion:2.1.8'
         implementation 'com.google.firebase:firebase-messaging:17.6.0'
        implementation 'com.android.support:support-annotations:28.0.0'
        }

this is my build.gradle from my gradle folder

buildscript {
repositories {
    jcenter()
    maven {
        url 'https://maven.google.com/'
        name 'Google'
    }
    google()
}

  dependencies {
          classpath 'com.android.tools.build:gradle:3.2.1'
           }
        }

   allprojects {
        repositories {
            google()
            jcenter()
            mavenCentral()
            maven {
               url 'https://maven.google.com/'
               name 'Google'
             }
           }
        }

      rootProject.buildDir = '../build'
      subprojects {
          project.buildDir = "${rootProject.buildDir}/${project.name}"
         }
      subprojects {
            project.evaluationDependsOn(':app')
       }

    task clean(type: Delete) {
    delete rootProject.buildDir
    }
like image 903
Travis Mckean Avatar asked Aug 26 '19 12:08

Travis Mckean


1 Answers

Are you still having this issue? Following Flutter platform channels guide, I was able to use Twilio Android SDK without issues. I integrated the bare minimum components needed for Twilio in this demo based from Twilio's Android quickstart.

main.dart

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('samples.flutter.dev/twilio');

  Future<void> callTwilio() async{
    try {
      final String result = await platform.invokeMethod('callTwilio');
      debugPrint('Result: $result');
    } on PlatformException catch (e) {
      debugPrint('Failed: ${e.message}.');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Hello',
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => callTwilio(),
        tooltip: 'Call',
        child: Icon(Icons.phone),
      ),
    );
  }
}

android/app/src/main/kotlin/{PACKAGE_NAME}/MainActivity.kt

class MainActivity : FlutterActivity() {
    private val CHANNEL = "samples.flutter.dev/twilio"
    private val TAG = "MainActivity"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "callTwilio") {
                executeTwilioVoiceCall()
                result.success("Hello from Android")
            } else {
                result.notImplemented()
            }
        }
    }

    private val accessToken = ""
    var params = HashMap<String, String>()
    var callListener: Call.Listener = callListener()
    fun executeTwilioVoiceCall(){
        val connectOptions = ConnectOptions.Builder(accessToken)
                .params(params)
                .build()
        Voice.connect(this, connectOptions, callListener)
    }

    private fun callListener(): Call.Listener {
        return object : Call.Listener {
            override fun onRinging(call: Call) {
                Log.d(TAG, "Ringing")
            }

            override fun onConnectFailure(call: Call, error: CallException) {
                Log.d(TAG, "Connect failure")
            }

            override fun onConnected(call: Call) {
                Log.d(TAG, "Connected")
            }

            override fun onReconnecting(call: Call, callException: CallException) {
                Log.d(TAG, "onReconnecting")
            }

            override fun onReconnected(call: Call) {
                Log.d(TAG, "onReconnected")
            }

            override fun onDisconnected(call: Call, error: CallException?) {
                Log.d(TAG, "Disconnected")
            }

            override fun onCallQualityWarningsChanged(call: Call,
                                                      currentWarnings: MutableSet<CallQualityWarning>,
                                                      previousWarnings: MutableSet<CallQualityWarning>) {
                if (previousWarnings.size > 1) {
                    val intersection: MutableSet<CallQualityWarning> = HashSet(currentWarnings)
                    currentWarnings.removeAll(previousWarnings)
                    intersection.retainAll(previousWarnings)
                    previousWarnings.removeAll(intersection)
                }
                val message = String.format(
                        Locale.US,
                        "Newly raised warnings: $currentWarnings Clear warnings $previousWarnings")
                Log.e(TAG, message)
            }
        }
    }
}

As for the dependencies in Android, I've added these on the build.gradle configs

android/build.gradle

ext.versions = [
    'voiceAndroid'       : '5.6.2',
    'audioSwitch'        : '1.1.0',
]

android/app/build.grade

dependencies {
    ...
    implementation "com.twilio:audioswitch:${versions.audioSwitch}"
    implementation "com.twilio:voice-android:${versions.voiceAndroid}"
}

Here's my flutter doctor verbose logs for reference

[✓] Flutter (Channel master, 1.26.0-2.0.pre.281, on macOS 11.1 20C69 darwin-x64)
    • Flutter version 1.26.0-2.0.pre.281
    • Framework revision 4d5db88998 (3 weeks ago), 2021-01-11 10:29:26 -0800
    • Engine revision d5cacaa3a6
    • Dart version 2.12.0 (build 2.12.0-211.0.dev)

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    • Platform android-30, build-tools 29.0.2
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.0.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.0.1, Build version 12A7300
    • CocoaPods version 1.10.0

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)

[✓] VS Code (version 1.52.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.18.1

[✓] Connected device (2 available)
    • AOSP on IA Emulator (mobile) • emulator-5554 • android-x86    • Android 9 (API 28) (emulator)
    • Chrome (web)                 • chrome        • web-javascript • Google Chrome 88.0.4324.96

• No issues found!

Here's how the sample app looks when run. Logs throw "Connect failure" and "Forbidden:403" errors since the API keys set are invalid, but this proves that Twilio Android SDK is functional through Flutter platform channels.

Demo

You can also check pub.dev for Twilio Flutter plugins made by the community that may fit your use case.

like image 160
Omatt Avatar answered Nov 13 '22 17:11

Omatt