1. Vonage Learn
  2. Blog
  3. 2021
  4. 04
  5. 01
  6. Iosとflutterを活用したアプリから電話へのコール
iOSとFlutterを活用したアプリから電話へのコール

< Tutorial />

iOSとFlutterを活用したアプリから電話へのコール

本文は英語版からの翻訳となります。日本語版において意味または文言に相違があった場合、英語版が優先するものとします。 https://learn.vonage.com/blog/2021/04/01/make-app-to-phone-call-using-ios-and-flutter/

本日は、Flutterを使用してiOSアプリケーションを構築し、VonageクライアントSDKを用いてVonage Conversation APIにより、モバイルアプリケーションから電話をかけられるようにします。アプリケーションは3つの画面(3つのUIステート)で構成されます。

UI states: logon, make a call, and end call

前提条件

Flutter iOSアプリケーションのソースコードは、GitHubで公開されています。

iOSデバイス向けにFlutterアプリケーションを構築する前に、以下の前提条件を満たす必要があります:

  • コールコントロールオブジェクト(NCCO)を作成
  • Vonage CLI(旧Nexmo CLI)をインストール
  • Vonageアプリケーションを設定
  • Flutter SDKをインストール
  • Flutterプロジェクトを作成

Vonageアプリケーション

NCCOを作成

コールコントロールオブジェクト(NCCO)は、Voice API callのフローをコントロールするために使用するJSON配列です。 NCCOは公開され、インターネットからアクセスできる必要があります。そのためにこのチュートリアルでは、GitHub Gistを使って構成をホストする便利な方法を紹介します。それでは新しいgistを追加しましょう:

  1. https://gist.github.com/(Githubにログイン)
  2. ncco.jsonをファイル名にして、新しいgistを作成します
  3. 以下のJSONオブジェクトをgistにコピー&ペーストします:
[
    {
        "action": "talk",
        "text": "Please wait while we connect you."
    },
    {
        "action": "connect",
        "endpoint": [
            {
                "type": "phone",
                "number": "PHONE_NUMBER"
            }
        ]
    }
]
  1. PHONE_NUMBERをあなたの電話番号に置き換えます(Vonageの番号はE.164形式で、+と-は有効ではありません。電話番号を入力する際には、必ず国コードを指定してください。例:US:14155550100、UK:447700900001)
  2. Create secret gistボタンをクリックします
  3. Rawボタンをクリックします
  4. 次のステップで使用するので、ブラウザに表示されているURLをメモします

Vonage CLIをインストール

Vonage CLIは、コマンドラインを使用して多くの操作を実行することができます。アプリケーションの作成、会話の作成、Vonage番号の購入などのタスクを実行したい場合は、Vonage CLIをインストールする必要があります。

Vonage CLIはNode.jsが必要ですので、まずNode.jsをインストールします。

npmでCLIのベータ版をインストールするには、以下のコマンドを実行します:

npm install nexmo-cli@beta -g

Vonage API KeyとAPI Secretを使用するためにVonage CLIを設定します。ダッシュボードの設定ページから設定できます。

以下のターミナルのコマンドを実行し、API_KEYとAPI_SECRETをダッシュボードの値にリプレースします:

nexmo setup API_KEY API_SECRET

Vonageアプリケーションを設定

  1. プロジェクトディレクトリを作成し、次のターミナルのコマンドを実行します:
mkdir vonage-tutorial
  1. プロジェクトディレクトリに移動します::
cd vonage-tutorial
  1. 下記のコマンドをターミナルにコピー&ペーストして、Vonageアプリケーションを作成します。GIST-URLを前のステップのgistのURLにリプレースすることで、引数--voice-answer-urlの値を変更します。
nexmo app:create "App to Phone Tutorial" --capabilities=voice --keyfile=private.key --voice-event-url=https://example.com/ --voice-answer-url=GIST-URL

アプリケーションの作成時に、ターミナルにエコーされるApplication IDをメモしておきます。

注:.nexmo-appという名前の隠しファイルがプロジェクトディレクトリに作成され、新しく作成されたVonage Application IDと秘密鍵が含まれます。また、private.keyという名前の秘密鍵ファイルが既存フォルダに作成されます。

ユーザーを作成

各参加者はUserオブジェクトで表され、Client SDKによって認証される必要があります。本番アプリケーションでは、通常、ユーザー情報をデータベースに保存します。

次のコマンドを実行してAliceというユーザーを作成します:

nexmo user:create name="Alice"

JWTを生成

JWTはユーザーの認証に使用されます。ターミナルで以下のコマンドを実行し、ユーザーAliceのJWTを生成します。以下のコマンドでは、APPLICATION_IDをアプリケーションのIDにリプレースしてください。

nexmo jwt:generate sub=Alice exp=$(($(date +%s)+86400)) acl='{"paths":{"/*/users/**":{},"/*/conversations/**":{},"/*/sessions/**":{},"/*/devices/**":{},"/*/image/**":{},"/*/media/**":{},"/*/applications/**":{},"/*/push/**":{},"/*/knocking/**":{},"/*/legs/**":{}}}' application_id=APPLICATION_ID

上記のコマンドでは、JWTの有効期限を最大の1日後に設定しています。

Alice用に生成したJWTをメモしておきます。

注:本番環境では、アプリケーションは、クライアントのリクエストごとにJWTを生成するエンドポイントを公開する必要があります。

Xcodeをインストール

AppStoreを開いてXcodeをインストールします。

Flutterを設定

Flutter SDKをインストール

Flutter SDKをダウンロードしてインストールします。

この手順は、MacOSWinLinuxで異なりますが、一般的には特定のOS用のFlutter SDKをダウンロードし、Flutter SDKファイルを解凍して、sdk╲binフォルダをシステムのPATH変数に追加します。プラットフォームごとの詳細な説明はこちらをご覧ください。

幸いなことに、Flutterには、SDKと全ての必要な「コンポーネント」が存在し、正しく構成されているか確認できるツールが付属しています。次のコマンドを実行してください:

flutter doctor

Flutter Doctorが、Flutter SDKがインストールされ、その他のコンポーネントもインストールされていて、正しく構成されているかどうか確認します。

Flutterプロジェクトを作成

ターミナルを使用してFlutterプロジェクトを作成します:

flutter create app_to_phone_flutter

上記のコマンドで、Flutterプロジェクトを含むapp_to_phone_flutterフォルダを作成しま

Flutterプロジェクトには、iOSプロジェクトを含むiosフォルダ、Androidプロジェクトを含むandroidフォルダ、そしてwebプロジェクトを含むwebフォルダがあります。」

pubspec.yamlファイルを開き、permission_handlerの依存関係を追加します(sdk: flutterのすぐ下):

dependencies:
  flutter:
    sdk: flutter
  
  permission_handler: ^6.0.1+1

yamlファイルではインデントが重要なので、permission_handlerがflutter: アイテムと同じインデントレベルであることを確認してください。」

ここで次のコマンドを実行して(パスはFlutterプロジェクトのルート)、上記の依存関係をダウンロードします。

flutter pub get

上記のコマンドはiosサブフォルダにPodfileも作成します。ios╲Podfileを開き、platformの行をアンコメントして、platformのバージョンを11にアップデートします:

platform :ios, '11.0'

同じファイルの終わりにpod 'NexmoClient'を追加します:

target 'Runner' do
  use_frameworks!
  use_modular_headers!
  pod 'NexmoClient'

ターミナルでapp_to_phone_flutter/iosフォルダを開き、ポッドをインストールします:

pod install

上記のコマンドは、Flutter、パーミッションハンドラー、Client SDKなど、必要な全ての依存関係をダウンロードします。

XcodeでRunner.xcworkspaceを開き、アプリを実行して、上記の設定が正しく行われたことを確認します。

Flutter/iOSの双方向コミュニケーション

現在、Client SDKはFlutterのパッケージとしては提供されていないので、AndroidネイティブのクライアントSDKを使用し、iOSFlutterの間でMethodChannelを使って通信する必要があります。これにより、FlutterはAndroidのメソッドを呼び出し、iOSはFlutterのメソッドを呼び出します。

Flutterのコードはlib/main.dartファイルに格納され、iOSのネイティブコードはios/Runner/AppDelegate.swiftファイルに格納されます。

Flutterアプリケーションを起動

Flutterアプリケーションは、Dartというプログラミング言語を使って構築されています。

lib/main.dartファイルを開き、コンテンツを全て以下のコードにリプレースします:

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: CallWidget(title: 'app-to-phone-flutter'),
    );
  }
}

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

  
  _CallWidgetState createState() => _CallWidgetState();
}

class _CallWidgetState extends State<CallWidget> {
  SdkState _sdkState = SdkState.LOGGED_OUT;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            SizedBox(height: 64),
            _updateView()
          ],
        ),
      ),
    );
  }

  Widget _updateView() {
    if (_sdkState == SdkState.LOGGED_OUT) {
      return ElevatedButton(
          child: Text("LOGIN AS ALICE")
      );
    }
  }

  Future<void> _loginUser() async {
      // Login user
  }

  Future<void> _makeCall() async {
      // Make call
  }

  Future<void> _endCall() async {
      // End call
  }
}

enum SdkState {
  LOGGED_OUT,
  LOGGED_IN,
  WAIT,
  ON_CALL,
  ERROR
}

上記のコードには、アプリケーションの状態を管理する役割(ユーザーのロギングとコールの管理)を担うカスタムCallWidgetが含まれています。SdkStateの列挙型はVonage Client SDKの可能な状態を表します。この列挙型は、Dartを使用したFlutter用とSwiftを使用したiOS用で2回定義されます。ウィジェットには、SdkStateの値に基づいてUIを変更する_updateViewメソッドが含まれています。

Xcodeからアプリケーションを実行します:

Running the application from xcode

Login as Aliceボタンが表示されます:

Logged out screen showing Login as Alice button

ログイン画面

The Login as Alice button is disabled so now add onPressed handler to the ElevatedButton to allow logging in:

Widget _updateView() {
    if (_sdkState == SdkState.LOGGED_OUT) {
      return ElevatedButton(
          onPressed: () { _loginUser(); },
          child: Text("LOGIN AS ALICE")
      );
    }
  }

ネイティブコードと通信し、ユーザーをログインさせるために、_loginUserメソッドのボディをアップデートします:

Future<void> _loginUser() async {
    String token = "ALICE_TOKEN";

    try {
      await platformMethodChannel.invokeMethod('loginUser', <String, dynamic>{'token': token});
    } on PlatformException catch (e) {
      print(e);
    }
  }

ALICE_TOKENを、先ほどVonage CLIから取得したJWTトークンにリプレースし、会話アクセスのためにユーザーAliceを認証します。FlutterloginUserメソッドを呼び出し、tokenを引数として渡します。loginUserメソッドはMainActivityクラスで定義されています(後ほど説明します)。このメソッドをFlutterから呼び出すには、MethodChannelを定義する必要があります。_CallWidgetStateクラスの先頭にplatformMethodChannelフィールドを追加します:

_CallWidgetStateクラスの先頭にplatformMethodChannelフィールドを追加します:

class _CallWidgetState extends State<CallWidget> {
  SdkState _sdkState = SdkState.LOGGED_OUT;
  static const platformMethodChannel = const MethodChannel('com.vonage');

com.vonageの文字列は、iOSのネイティブコード(AppDelegateクラス)でも参照される、固有のチャンネルIDを表しています。次に、このメソッドコールをiOSネイティブ側で処理する必要があります。

FlutterMethodChannelへの参照を保持するios/Runner/AppDelegateクラスとvonageChannelプロパティを開きます:

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  var vonageChannel: FlutterMethodChannel?
    
...

Flutterからのメソッドコールをリッスンするには、AppDelegateクラス(上記のapplicationメソッドと同じレベル)内のaddFlutterChannelListenerメソッドを追加します:

:

func addFlutterChannelListener() {
        let controller = window?.rootViewController as! FlutterViewController
        
        vonageChannel = FlutterMethodChannel(name: "com.vonage",
                                             binaryMessenger: controller.binaryMessenger)
        vonageChannel?.setMethodCallHandler({ [weak self]
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            guard let self = self else { return }
            
            switch(call.method) {
            case "loginUser":
                if let arguments = call.arguments as? [String: String],
                   let token = arguments["token"] {
                    self.loginUser(token: token)
                }
                result("")
            default:
                result(FlutterMethodNotImplemented)
            }
        })
    }

上記のメソッドは、FlutterのメソッドコールをAppDelegateクラスで定義されたメソッド(今回はloginUser)に「変換」します。

また、同じクラス内のloginUserメソッドがありません(まもなくボディを埋める予定です):

func loginUser(token: String) {

}

ここで、applicationメソッド内にaddFlutterChannelListenerメソッドコールを追加します:

override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        addFlutterChannelListener()
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

コードが正しく書かれています - Login As Aliceボタンを押すと、Flutterアプリは_loginUserメソッドを呼び出します。Flutterプラットフォームのチャネルを通じて、このメソッドがAppDelegateクラスで定義されたloginUserメソッドを呼び出します。

Xcodeからアプリケーションを実行して、コンパイルされていることを確認します。

ユーザーがログインできるようにする前に、Vonage SDK Clientを初期化する必要があります。

クライアントを初期化

AppDelegateクラスを開き、ファイルの先頭にNexmoClientのインポートを追加します:

import NexmoClient

同じファイルに、Vonage Clientへの参照を保持するclientプロパティを追加します。

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    var vonageChannel: FlutterMethodChannel?
    let client = NXMClient.shared

...

ここで、クライアントを初期化するために、initClientメソッドを追加します:

func initClient() {
        client.setDelegate(self)
    }

既存のapplicationメソッドからinitClientメソッドを呼び出すには、以下の例のようにinitClient()の行を追加する必要があります:

override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        initClient()
        addFlutterChannelListener()
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

会話を許可する前に、ユーザーが正しくログインしたことを知る必要があります。AppDelegateファイルで、Vonage Client SDKの接続状態の変更をリッスンするデリゲートを追加します:

extension AppDelegate: NXMClientDelegate {
    func client(_ client: NXMClient, didChange status: NXMConnectionStatus, reason: NXMConnectionStatusReason) {
        switch status {
        case .connected:
            notifyFlutter(state: .loggedIn)
        case .disconnected:
            notifyFlutter(state: .loggedOut)
        case .connecting:
            notifyFlutter(state: .wait)
        @unknown default:
            notifyFlutter(state: .error)
        }
    }
}

最後に、notifyFlutterメソッドを同じクラスに追加する必要があります:

    func client(_ client: NXMClient, didReceiveError error: Error) {
        notifyFlutter(state: .error)
    }
}

ユーザーをログイン

loginUserメソッドのボディを変更して、クライアントインスタンスのloginを呼び出します:

func loginUser(token: String) {
        self.client.login(withAuthToken: token)
    }

この方法により、Client SDKを使ってユーザー(Alice)がログインし、会話にアクセスすることができます。

クライアントSDKの状態変更をFlutterに通知

Client SDKの状態の変更をFlutterに通知するためには、Client SDKの状態を表すenumを追加する必要があります。すでにこれに相当するSdkState列挙型をmain.dartファイルに追加しました。MainActivity.ktファイルの下部に、以下のSdkState列挙型を追加します:

enum SdkState: String {
        case loggedOut = "LOGGED_OUT"
        case loggedIn = "LOGGED_IN"
        case wait = "WAIT"
        case onCall = "ON_CALL"
        case error = "ERROR"
    }

これらの状態を(上のデリゲートから)Flutterに送るためには、AppDelegateクラスにnotifyFlutterメソッドを追加する必要があります:

func notifyFlutter(state: SdkState) {
        vonageChannel?.invokeMethod("updateState", arguments: state.rawValue)
    }

列挙型に状態を保存していますが、それを文字列として送信していることに注目してください。

FlutterでSDKの状態の更新を取得

Flutterで状態の更新を取得するには、メソッドチャネルの更新をリッスンする必要があります。main.dartファイルを開き、カスタムハンドラーで_CallWidgetStateコンストラクターを追加します:

_CallWidgetState() {
    platformMethodChannel.setMethodCallHandler(methodCallHandler);
  }

同じクラス(_CallWidgetState)の中に、ハンドラーメソッドを追加します:

Future<dynamic> methodCallHandler(MethodCall methodCall) async {
    switch (methodCall.method) {
      case 'updateState':
        {
          setState(() {
            var arguments = 'SdkState.${methodCall.arguments}';
            _sdkState = SdkState.values.firstWhere((v) {return v.toString() == arguments;}
            );
          });
        }
        break;
      default:
        throw MissingPluginException('notImplemented');
    }
  }

これらのメソッドは、Androidから「シグナル」を受け取り、それを列挙型に変換します。次に、SdkState.WAITとSdkState.LOGGED_INの状態をサポートするために、_updateViewメソッドの内容を以下の例のように更新します:

Widget _updateView() {
    if (_sdkState == SdkState.LOGGED_OUT) {
      return ElevatedButton(
          onPressed: () { _loginUser(); },
          child: Text("LOGIN AS ALICE")
      );
    }  else if (_sdkState == SdkState.WAIT) {
      return Center(
        child: CircularProgressIndicator(),
      );
    } else if (_sdkState == SdkState.LOGGED_IN) {
      return ElevatedButton(
          onPressed: () { _makeCall(); },
          child: Text("MAKE PHONE CALL")
      );
    }
  }

SdkState.WAITの間は、プログレスバーが表示されます。ログインに成功すると、アプリケーションにMAKE PHONE CALLボタンが表示されます。

アプリを実行して、LOGIN AS ALICEと書かれたボタンをクリックします。MAKE PHONE CALLボタンが表示され、これはSdkState enum)に基づいたFlutterアプリの別の状態です。)以下はこの例の画像になります:

Make a phone call UI state

コールする

次に、電話をかけるための機能を追加する必要があります。main.dartファイルを開き、_makeCallメソッドのボディを以下のように更新します:

Future<void> _makeCall() async {
    try {
      await platformMethodChannel
          .invokeMethod('makeCall');
    } on PlatformException catch (e) {
      print(e);
    }
  }

上記のメソッドはiOSと通信するため、AppDelegateクラスのコードも更新する必要があります。addFlutterChannelListenerメソッド内のswitch文にmakeCall句を追加します:

func addFlutterChannelListener() {
        let controller = window?.rootViewController as! FlutterViewController
        
        vonageChannel = FlutterMethodChannel(name: "com.vonage",
                                             binaryMessenger: controller.binaryMessenger)
        vonageChannel?.setMethodCallHandler({ [weak self]
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            guard let self = self else { return }
            
            switch(call.method) {
            case "loginUser":
                if let arguments = call.arguments as? [String: String],
                   let token = arguments["token"] {
                    self.loginUser(token: token)
                }
                result("")
            case "makeCall":
                self.makeCall()
                result("")
            default:
                result(FlutterMethodNotImplemented)
            }
        })
    }

次に、同じファイルにonGoingCallプロパティを追加します。これは、コールが進行中であるかどうか、またいつ進行中であるかを定義するものです:

var onGoingCall: NXMCall?

注:現在、Client SDKは進行中のコールリファレンスを保存していないため、AppDelegateクラスに保存する必要があります。この参照は、後でコールを終了する際に使用します。

同じクラスにmakeCallメソッドを追加します:

func makeCall() {
        client.call("IGNORED_NUMBER", callHandler: .server) { [weak self] (error, call) in
            guard let self = self else { return }
            
            if error != nil {
                self.notifyFlutter(state: .error)
                return
            }
            
            self.onGoingCall = call
            self.notifyFlutter(state: .onCall)
        }
    }

T上記のメソッドは、Flutterアプリの状態をSdkState.WAITに設定し、Client SDKのレスポンス(エラーまたは成功)を待ちます。ここで、main.dartファイル内に両方の状態(SdkState.ON_CALLとSdkState.ERROR)のサポートを追加する必要があります。以下のように_updateViewメソッドのボディを更新します:

Widget _updateView() {
    if (_sdkState == SdkState.LOGGED_OUT) {
      return ElevatedButton(
          onPressed: () { _loginUser(); },
          child: Text("LOGIN AS ALICE")
      );
    } else if (_sdkState == SdkState.WAIT) {
      return Center(
        child: CircularProgressIndicator(),
      );
    } else if (_sdkState == SdkState.LOGGED_IN) {
      return ElevatedButton(
          onPressed: () { _makeCall(); },
          child: Text("MAKE PHONE CALL")
      );
    } else if (_sdkState == SdkState.ON_CALL) {
      return ElevatedButton(
          onPressed: () { _endCall(); },
          child: Text("END CALL")
      );
    } else {
      return Center(
        child: Text("ERROR")
      );
    }
  }

状態が変化するたびに、UIが変更されます。コールする前に、アプリケーションはマイクを使用するための特定の許可を必要とします。次のステップでは、これらの許可をリクエストするための機能をプロジェクトに追加します。

許可をリクエスト

アプリケーションはマイクにアクセスする必要があるので、マイクへのアクセスをリクエストしなければなりません(FlutterではPermission.microphone)。

ios/Runner/info.plistファイルを開き、Privacy - Microphone Usage DescriptionキーにMake a callの値を追加します:

Setting add microphone permission

すでにFlutterプロジェクトにpermission_handlerパッケージを追加しました。では、main.dartファイルの先頭で、以下の例のようにpermission_handlerパッケージをインポートする必要があります:

import 'package:permission_handler/permission_handler.dart';

特定の許可のリクエストを起動させるためには、main.dartファイル内の_CallWidgetStateクラスにrequestPermissions()メソッドを追加する必要があります。そこで、この新しいメソッドをクラス内に追加します:

Future<void> requestPermissions() async {
    await [ Permission.microphone ].request();
  }

上記のメソッドは、permission_handlerを使って許可をリクエストします。

同じクラスで、_makeCallクラスのボディを修正して、メソッドチャネル経由でメソッドを呼び出す前に許可をリクエストします:

Future<void> _makeCall() async {
    try {
      await requestPermissions();
 
      ...
  }

Rアプリを起動し、MAKE PHONE CALLをクリックしてコールを開始します。許可ダイアログが表示されるので、許可するとコールが開始されます。

リマインダー:NCCOでは以前に電話番号を定義しました。

アプリケーションの状態がSdkState.ON_CALLに更新され、UIが更新されます:

On call UI

コールを終了

コールを終了するには、platformMethodChannelを使ってネイティブiOSアプリケーション上でメソッドをトリガーする必要があります。main.dartファイル内で、_endCallメソッドのボディを更新します:

Future<void> _endCall() async {
    try {
      await platformMethodChannel.invokeMethod('endCall');
    } on PlatformException catch (e) {}
  }

上記のメソッドはiOSと通信するので、AppDelegateクラスのコードも更新する必要があります。addFlutterChannelListenerメソッド内のswitch文にendCall句を追加します:

func addFlutterChannelListener() {
        let controller = window?.rootViewController as! FlutterViewController
        
        vonageChannel = FlutterMethodChannel(name: "com.vonage",
                                             binaryMessenger: controller.binaryMessenger)
        vonageChannel?.setMethodCallHandler({ [weak self]
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            guard let self = self else { return }
            
            switch(call.method) {
            case "loginUser":
                if let arguments = call.arguments as? [String: String],
                   let token = arguments["token"] {
                    self.loginUser(token: token)
                }
                result("")
            case "makeCall":
                self.makeCall()
                result("")
            case "endCall":
                self.endCall()
                result("")
            default:
                result(FlutterMethodNotImplemented)
            }
        })
    }

同じクラスに endCall メソッドを追加します:

func endCall() {
        onGoingCall?.hangup()
        onGoingCall = nil
        notifyFlutter(state: .loggedIn)
    }

上記のメソッドは、Flutterアプリの状態をSdkState.WAITに設定し、Client SDKからのレスポンス(エラーまたは成功)を待ちます。両方のUIの状態は、Flutterアプリですでにサポートされています(_updateViewメソッド)。

コールの終了は、FlutterアプリケーションのUIにあるEND CALLボタンを押すことで処理しました。しかし、コールはFlutterアプリ以外でも終了することができます。例えば、(実際の電話で)コールを受ける側が通話を拒否したり、応答した後で終了させることができます。

これらのケースをサポートするためには、NexmoCallEventListenerリスナーをコールインスタンスに追加し、コール固有のイベントをリッスンする必要があります。

AppDelegares.swiftファイルに、NXMCallDelegateを追加します:

extension AppDelegate: NXMCallDelegate {
    func call(_ call: NXMCall, didUpdate callMember: NXMCallMember, with status: NXMCallMemberStatus) {
        if (status == .completed || status == .cancelled) {
            onGoingCall = nil
            notifyFlutter(state: .loggedIn)
        }
    }
    
    func call(_ call: NXMCall, didUpdate callMember: NXMCallMember, isMuted muted: Bool) {
        
    }
    
    func call(_ call: NXMCall, didReceive error: Error) {
        notifyFlutter(state: .error)
    }
}

上記のリスナーを登録するには、makeCallメソッド内のonSuccessコールバックを変更します:

func makeCall() {
        client.call("IGNORED_NUMBER", callHandler: .server) { [weak self] (error, call) in
            guard let self = self else { return }
            
            if error != nil {
                self.notifyFlutter(state: .error)
                return
            }
            
            self.onGoingCall = call
            self.onGoingCall?.setDelegate(self)
            self.notifyFlutter(state: .onCall)
        }
    }

アプリを起動して、モバイルアプリから物理的な電話番号に電話をかけます。

サマリ

これでアプリケーションの構築に成功しました。これにより、Vonage Client SDKを使用して、モバイルアプリケーションから電話に電話をかける方法を学びました。プロジェクト全体については、[GitHub] (https://github.com/nexmo-community/client-sdk-voice-app-to-phone-flutter)をご覧ください。このプロジェクトには、さらに`Android`のネイティブコード(`android`フォルダ)が含まれており、Android上でもこのアプリを実行することができます。

その他の機能については、他のチュートリアルVonage開発者センターをご覧ください。

関連資料

Comments currently disabled.