gpt4 book ai didi

android - 将原生 Twilio Android SDK 与 Flutter 集成

转载 作者:行者123 更新时间:2023-12-03 13:26:49 26 4
gpt4 key购买 nike

我正在尝试使用flutter创建IP语音(VOIP)移动应用程序。我还没有看到twilio语音api的flutter插件的实现,所以我使用MethodChannel将我的应用程序与 native android语音api集成在一起。twilio SDK似乎没有就像它正确集成一样,我无法访问脚本中的 twilio 类和方法。这些是我得到的错误。

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

我按照此处显示的语音快速入门安卓指南 https://github.com/twilio/voice-quickstart-android

这是我的代码: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;
}
}

这是我的 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'
}

这是我的 gradle 文件夹中的 build.gradle
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
}

最佳答案

你还有这个问题吗?关注 Flutter platform channels guide ,我能够毫无问题地使用 Twilio Android SDK。我在这个基于 Twilio's Android quickstart 的演示中集成了 Twilio 所需的最少组件。 .
主要.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)
}
}
}
}
至于 Android 中的依赖项,我已经在 build.gradle 配置中添加了这些
安卓/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}"
}
这是我的 flutter doctor详细日志供引用
[✓] 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!
这是示例应用程序在运行时的外观。由于 API key 设置无效,日志会抛出“连接失败”和“Forbidden:403”错误,但这证明 Twilio Android SDK 通过 Flutter 平台 channel 正常运行。
Demo
您也可以查看 pub.dev对于社区制作的可能适合您的用例的 Twilio Flutter 插件。

关于android - 将原生 Twilio Android SDK 与 Flutter 集成,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57658011/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com