Make App-To-Phone Call Using iOS and Flutter

Vonage Dev
10 min readApr 15, 2021

Today we will build an iOS application using Flutter and utilize Vonage Client SDK to make a call from a mobile application to the phone using Vonage Conversation API. The application will have 3 screens (3 UI states):

Prerequisites

The source code for our Flutter iOS application is available on GitHub.

Before we begin building the Flutter application for the iOS device, we'll need to prepare with the following prerequisites:

  • Create a Call Control Object (NCCO)
  • Install the Vonage CLI (previously Nexmo CLI)
  • Setup the Vonage application
  • Install the Flutter SDK
  • Create the Flutter project

Vonage Application

Create An NCCO

A Call Control Object (NCCO) is a JSON array that we use to control the flow of a Voice API call.

The NCCO needs to be public and accessible by the internet. To accomplish this, in this tutorial we'll be using GitHub Gist which provides a convenient way to host the configuration. Let's add a new gist:

  • Go to https://gist.github.com/ (we have to be logged into Github)
  • Create a new gist with ncco.json as the filename
  • Copy and paste the following JSON object into the gist:
  • Replace PHONE_NUMBER with your phone number (Vonage numbers are in E.164 format, + and - are not valid. Make sure to specify the country code when entering the number, for example, US: 14155550100 and UK: 447700900001)
  • Click the Create secret gist button
  • Click the Raw button
  • Take note of the URL shown in the browser, we will be using it in the next step

Install Vonage CLI

The Vonage CLI allows us to carry out many operations using the command line. If we want to carry out tasks such as creating applications, creating conversations, purchasing Vonage numbers, and so on, we will need to install the Vonage CLI.

Vonage CLI requires Node.js, so we will need to install Node.js first.

To install the Beta version of the CLI with npm, run this command:

Set up the Vonage CLI to use the Vonage API Key and API Secret. We can get these from the settings page in the Dashboard.

Run the following command in the terminal, while replacing API_KEY and API_SECRET with values from the Dashboard:

Setup Vonage Application

  • Create the project directory. Run the following command in the terminal:
  • Change into the project directory:
  • Create a Vonage application by copying and pasting the command below into the terminal. Make sure to change the value of --voice-answer-url argument by replacing GIST-URL with the gist URL from the previous step.

Make a note of the Application ID that is echoed in the terminal when the application is created.

NOTE: A hidden file named .nexmo-app is created in the project directory and contains the newly created Vonage Application ID and the private key. A private key file named private.key is also created in the current folder.

Create User

Each participant is represented by a User object and must be authenticated by the Client SDK. In a production application, we would typically store this user information in a database.

Execute the following command to create a user called Alice:

Generate JWT

The JWT is used to authenticate the user. Execute the following command in the terminal to generate a JWT for the user Alice. In the following command replace the APPLICATION_ID with the ID of the application:

The command above sets the expiry of the JWT to one day from now, which is the maximum.

Make a note of the JWT we generated for Alice.

NOTE: In a production environment, the application should expose an endpoint that generates a JWT for each client request.

Install Xcode

Open AppStore and install Xcode.

Flutter Setup

Install Flutter SDK

Download and install Flutter SDK.

This step will vary on MacOS, Win, and Linux, but in general, it boils down to downloading Flutter SDK for a given OS, extracting the Flutter SDK file, and adding the sdk\bin folder to the system PATH variable. Detailed instruction for all platforms can be found here.

Fortunately, Flutter comes with a tool that allows us to verify if SDK and all required "components" are present and configured correctly. Run this command:

Flutter Doctor will verify if Flutter SDK is installed and other components are installed and configured correctly.

Create Flutter Project

We will create a Flutter project using the terminal:

The above command creates app_to_phone_flutter folder containing the Flutter project.

Flutter project contains ios folder, which contains the iOS project; android folder containing the Android project; and web folder containing web project.

Open the pubspec.yaml file, and add permission_handler dependency (just below sdk: flutter):

Indentation matters in yaml files, so make sure permission_handler is at the same indentation level as the flutter: item.

Now run this command (path is the root of the Flutter project) to download the above dependency:

The above command will also create Podfile in ios subfolder. Open ios\Podfile uncomment platform line and update the platform version to 11:

At the end of the same file add pod 'NexmoClient':

Open app_to_phone_flutter/ios folder in the terminal and install pods:

The above command will download all required dependencies including Flutter, permissions handler, and Client SDK.

Open Runner.xcworkspace in Xcode and run the app to verify that the above setup was performed correctly.

Two-way Flutter/iOS Communication

Currently, Client SDK is not available as a Flutter package, so we will have to use Android native Client SDK and communicate between iOS and Flutter using MethodChannel - this way, Flutter will call Android methods, iOS will call Flutter methods.

Flutter code will be stored in the lib/main.dart file, while iOS native code will be stored in the ios/Runner/AppDelegate.swift file.

Init Flutter Application

Flutter applications are built using a programming language called Dart.

Open lib/main.dart file, and replace all of the contents with the following code:

The above code contains custom CallWidget which will be responsible for managing the application state (logging the user and managing the call). The SdkState enum represents possible states of Vonage Client SDK. This enum will be defined twice - one for the Flutter using Dart and one for iOS using Swift. The widget contains the _updateView method that will change the UI based on the SdkState value.

Run the application from the Xcode:

The Login as Alice button should be displayed:

Login Screen

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

Update body of _loginUser method to communicate with native code and login the user:

Replace the ALICE_TOKEN with the JWT token we obtained previously from Vonage CLI to authenticate the user Alice for the conversation access. Flutter will call the loginUser method and pass the token as an argument. The loginUser method is defined in the MainActivity class (we will get there in a moment). To call this method from Flutter we have to define a MethodChannel. Add platformMethodChannel field at the top of _CallWidgetState class:

Add platformMethodChannel field at the top of _CallWidgetState class:

The com.vonage string represents the unique channel id that we will also refer to the native iOS code ( AppDelegate class). Now we need to handle this method call on the native iOS side.

To listen for method calls originating from Flutter add addFlutterChannelListener method inside AppDelegate class (same level as above application method):

And missing the loginUser methods inside the same class (we will fill the body soon):

Now add addFlutterChannelListener method call inside the application method:

Run the application from Xcode to make sure it is compiling.

Before we will be able to log in user we need to initialize the Vonage SDK Client.

Initialize Client

Open AppDelegate class and add the NexmoClient import at the top of the file:

In the same file add client property that will hold a reference to Vonage Client.

Now add initClient method to initialize the client:

To call the initClient method from the existing application method, we're going to need to add the initClient() line as shown in the example below:

Before allowing conversation we need to know that the user has correctly logged in. In the AppDelegate file add a delegate to listen for Vonage Client SDK connection state changes:

Finally, the notifyFlutter method needs to be added to the same class:

Login the User

Modify loginUser method body to call login on the client instance:

This method will allow us to log-in the user ( Alice) using the Client SDK to access the conversation.

Notify Flutter About Client SDK State Change

To notify Flutter of any changes to the state in the Client SDK, we'll need to add an enum to represents the states of the Client SDK. We've already added the equivalent SdkState enum in the main.dart file. Add the following SdkState enum, at the bottom of the MainActivity.kt file:

To send these states to Flutter (from above delegate) we need to add notifyFlutter method in the AppDelegate class:

Notice that we store the state in the enum, but we are sending it as a string.

Retrieve SDK State Update By Flutter

To retrieve state updates in Flutter we have to listen for method channel updates. Open main.dart file and add _CallWidgetState constructor with custom handler:

Inside the same class (_CallWidgetState) add the handler method:

These methods receive the “signal” from Android and convert it to an enum. Now update the contents of the _updateView method to support SdkState.WAIT and SdkState.LOGGED_IN states, as shown in the example below:

During SdkState.WAIT the progress bar will be displayed. After successful login application will show the MAKE PHONE CALL button.

Run the app and click the button labeled LOGIN AS ALICE. The MAKE PHONE CALL button should appear, which is another state of the Flutter app based on the SdkState enum`). An example of this is shown in the image below:

We now need to add functionality to make a phone call. Open the main.dart file and update the body of _makeCall method as shown below:

Make A Call

We now need to add functionality to make a phone call. Open the main.dart file and update the body of _makeCall method as shown below:

The above method will communicate with iOS so we have to update code in the AppDelegate class as well. Add makeCall clauses to the switch statement inside addFlutterChannelListener method:

Now in the same file add the onGoingCall property, which defines if and when a call is ongoing:

NOTE: Currently the Client SDK does not store ongoing call reference, so we have to store it in the AppDelegate class. We will use it later to end the call.

Now in the same class add makeCall method:

The above method sets the state of the Flutter app to SdkState.WAIT and waits for the Client SDK response (error or success). Now we need to add support for both states ( SdkState.ON_CALL and SdkState.ERROR) inside main.dart file. Update body of the _updateView method to show the same as below:

Each state change will result in UI modification. Before making a call the application needs specific permissions to use the microphone. In the next step, we’re going to add the functionality in the project to request these permissions.

Request Permissions

The application needs to be able to access the microphone, so we have to request access to the microphone ( Permission.microphone for Flutter ).

Open ios/Runner/info.plist file and add Privacy - Microphone Usage Description key with Make a call value:

We already added the permission_handler package to the Flutter project. Now at the top of the main.dart file, we'll need to import the permission_handler package as shown in the example below:

To trigger the request for certain permissions, we’ll need to add the requestPermissions() method within the _CallWidgetState class inside the main.dart file. So add this new method inside the class:

The above method will request permissions using permission_handler.

In the same class, modify the body of the _makeCall class to request permissions before calling the method via the method channel:

Run the app and click MAKE PHONE CALL to start a call. The permissions dialogue will appear and, after granting the permissions, the call will start.

Reminder: we defined the phone number earlier in the NCCO

The state of the application will be updated to SdkState.ON_CALL and the UI will be updated:

End Call

To end the call we need to trigger the method on the native iOS application using platformMethodChannel. Inside main.dart file, update the body of the _endCall method:

The above method will communicate with iOS, so we have to update code in the AppDelegate class as well. Add endCall clauses to the switch statement inside the addFlutterChannelListener method:

Now in the same class add the endCall method:

The above method sets the state of the Flutter app to SdkState.WAIT and waits for the response from the Client SDK, which can be either error or success. Both UI states are already supported in the Flutter application (_updateView method).

We have handled ending the call by pressing the END CALL button in the Flutter application UI. However, the call can also end outside of the Flutter app, e.g. the call will be rejected or answered, and later ended by the callee (on the real phone).

To support these cases we have to add the NexmoCallEventListener listener to the call instance and listen for call-specific events.

In the AppDelegares.swift file add NXMCallDelegate:

To register the above listener modify onSuccess callback inside makeCall method:

Run the app and make a phone call from the mobile application to a physical phone number.

Summary

We have successfully built the application. By doing so we have learned how to make a phone call from a mobile application to the phone using Vonage Client SDK. For the complete project please check GitHub. This project additionally contains the Android native code ( android folder) allowing us to run this app on Android as well.

To familiarize yourself with other functionalities please check other tutorials and Vonage developer center.

References

--

--

Vonage Dev

Developer content from the team at Vonage, including posts on our Java, Node.js, Python, DotNet, Ruby and Go SDKs. https://developer.vonage.com