1. Vonage Learn
  2. Blog
  3. 2018
  4. 07
  5. 03
  6. Make and Receive Phone Calls in Android Apps With Nexmo in App Voice and Firebase Dr
Make and receive phone calls in Android apps with Firebase

< Tutorial />

Make and receive phone calls in Android apps with Firebase

With Nexmo In-App Voice, you can easily make and receive phone calls with the Nexmo Stitch Android SDK and WebRTC technology. In this tutorial we'll show you how to make a simple Android app that can make outbound phone calls and receive incoming phone calls. This functionality could be used where customers want to make and receive calls to customer service, without leaving the app.

To get the app up and running, we'll use Firebase Functions to host the NCCO and return a JWT for users to login with.

To follow along with this blog post, you should have some knowledge of JavaScript and be able to build an Android app using Kotlin.

Before we get started

There are a few things you’ll need before we get started.

Set up the Firebase Function.

First, we'll want to create a new directory for our Firebase functions project and to store our config files.

mkdir firebase-functions-nexmo-in-app-calling
cd firebase-functions-nexmo-in-app-calling

Now that a new directory has been made, we can follow the for setup instructions provided by Firebase and create a new project.

npm install -g firebase-tools
firebase login
firebase init functions

For this project I chose to use JavaScript instead of TypeScript.

If like me, you were not able to create a firebase function project from the command line you can visit the Firebase Console to create a new project, then run firebase use --add from your command line.

Edit the Firebase Functions

You can see the final version of the methods we'll use for the Firebase Functions in the repo for this demo on GitHub. In short we need three endpoints:

  1. An Answer URL that will host the NCCO
  2. An Event URL to capture events from the Voice API
  3. A URL to return a JWT for users to login with

To edit the Firebase functions, we'll need to edit the index.js file in the new functions/ directory that firebase created after we ran firebase init functions

Let's start by taking a look at the Answer method

exports.answer = functions.https.onRequest((request, response) => {
  //use the `to` query parameter that Nexmo gives us to make a call.
  //if `to` is null, then we are receiving a call.
  var to = request.query.to
  var from = request.query.from

  var ncco = [];

  if (to) {
    ncco.push(
      {
        action: "talk",
        text: "Thank you for calling, you are now being connected."
      },
      {
        "action": "connect",
        "from": functions.config().nexmo.from_number,
        "endpoint": [
          {
            "type": "phone",
            "number": `${to}`
          }
        ]
      }
    )
  } else {
    ncco.push(
      {
        action: "talk",
        text: "You are being connected to the Customer."
      },
      {
        "action": "connect",
        "from": from,
        "endpoint": [
          {
            "type": "app",
            "user": "Customer"
          }
        ]
  
      })
  }
  response.json(ncco);
});

Since Nexmo's Voice API uses one answer URL per application, we can dynamically show the NCCO for accepting an inbound call if the to query parameter is null. If the to query parameter is not null, then we'll show the NCCO for making an outbound phone call.

Our endpoint for providing a JWT for users is fairly straightforward. We'll use the Nexmo Node library to generate a JWT. In order to use the library we'll need to install the package.

#Ensure you're in the /functions directory where the package.json and index.js files are
npm install nexmo

After installing the library, you can ensure everything is working correctly by inspecting the package.json file. It should look like so:

  "dependencies": {
    "firebase-admin": "~5.12.1",
    "firebase-functions": "^1.0.3",
    "nexmo": "^2.3.2"
  }

Once the Nexmo Node library is installed, we can use it in the jwt endpoint like so to return a valid user_jwt.

exports.jwt = functions.https.onRequest((request, response) => {
    response.json({
      user_jwt: Nexmo.generateJwt("private.key", {
        application_id: functions.config().nexmo.application_id,
        sub: "Customer",
        exp: new Date().getTime() + 86400,
        acl: adminAcl
      })
    });
});

Now that we've created our Firebase functions, we will create a Nexmo application.

Deploy the Firebase function

Now that the Firebase Functions have been written, we can deploy the Firebase project. After deploying the Functions, Firebase will give us the URLs for our answer and event endpoints. We can use these URLs to create our Nexmo application.

#Ensure you're in the firebase-functions-nexmo-in-app-calling/ directory we created at the beginning of this tutorial
firebase deploy --only functions

✔  functions[answer]: Successful create operation.
Function URL (answer): https://your-project-name.cloudfunctions.net/answer
✔  functions[event]: Successful create operation.
Function URL (event): https://your-project-name.cloudfunctions.net/event
✔  functions[jwt]: Successful create operation.
Function URL (jwt): https://your-project-name.cloudfunctions.net/jwt

Set up the Nexmo Application.

To create an application, we'll use the answer and event url that Firebase provided us in the previous section.

#Ensure you're in the firebase-functions-nexmo-in-app-calling/ directory we created at the beginning of this tutorial
nexmo app:create "Firebase Functions Nexmo In-App Calling" https://your-project-name.cloudfunctions.net/answer https://your-project-name.cloudfunctions.net/event --keyfile=functions/private.key --type=rtc

Application created: aaaaaaaa-bbbb-cccc-dddd-0123456789ab
Credentials written to /firebase-functions-nexmo-in-app-calling/.nexmo-app
Private Key saved to: functions/private.key

Note: I'm saving the private key in the functions/ directory so that the file can be referenced when the function is published

Record the application ID and save the private key in the "functions" directory. I recommend you add the private.key and .nexmo-app files with your credentials to your .gitignore.

Following best practices, we'll store some environment variables Firebase config via the firebase CLI. The Firebase docs contain an overview about environment configuration.

Now we need to store the Nexmo application ID in the firebase config via the firebase CLI.

firebase functions:config:set nexmo.application_id="aaaaaaaa-bbbb-cccc-dddd-0123456789ab"

Note: Firebase requires config variable keys to be lowercase, so we'll use snake case for our variable names.

If you wish, you could upload the private key string found in the .nexmo-app file from your Nexmo application as a firebase config variable instead of uploading the entire private.key file to the firebase functions.

Now that our functions have been written, we need to buy a number so that our users can make outbound calls from that number and receive inbound calls in their Android app whenever someone dials that number. After the number is bought we'll link the number to the Nexmo application and set it as the from_number in the Firebase Config variables.

#Search for a number and buy it
nexmo number:buy --country_code US
#Link the number to our Nexmo application
nexmo link:app 12013753230 aaaaaaaa-bbbb-cccc-dddd-0123456789ab
#Set the `from_number` in the Firebase Config variables.
firebase functions:config:set nexmo.from_number="12013753230"

Nexmo In-App Voice also requires us to create a user for authentication. By creating a user, we can route incoming phone calls to them. We can do that simply with the beta version of the Nexmo CLI

#Ensure you have the beta version of the CLI installed
npm install nexmo-cli@beta -g
#Create a user that will make a receive phone calls
nexmo user:create name="Customer"

Set up the Android project

If you'd like to view the finished project for our Android app you can view the source code on GitHub.

First we need to add the necessary libraries. We're going to use the Nexmo Stitch Android SDK, Retrofit, and Retrofit's Moshi Converter.

dependencies {
  implementation 'com.nexmo:stitch:1.8.0'
  implementation 'com.squareup.retrofit2:retrofit:2.4.0'
  implementation "com.squareup.retrofit2:converter-moshi:2.4.0"
}

We'll use Retrofit to make a GET request the Firebase function's JWT endpoint that Firebase CLI provided us.

//FirebaseFunctionService.kt
interface FirebaseFunctionService {
    @GET("jwt")
    fun getJWT(): Call<UserJWT>
}


//RetrofitClient.kt
var retrofitClient = Retrofit.Builder()
        .baseUrl("https://your-project-name.cloudfunctions.net/")
        .addConverterFactory(MoshiConverterFactory.create())
        .build()

var retrofitService = retrofitClient.create<FirebaseFunctionService>(FirebaseFunctionService::class.java)

Add the Login Activity

Having set up Retrofit, we can use it to retrieve a JWT from the JWT Firebase function endpoint in the LoginActivity. Here I'll show the happy path when we retrieve the user JWT and use it to login to the Nexmo Stitch Android SDK. The layout for this Activity is available in GitHub.

class LoginActivity : BaseActivity(), RequestHandler<User>, Callback<UserJWT> {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        loginBtn.setOnClickListener {
            login()
        }
    }

    private fun login() {
        showProgress(true)
        retrofitService.getJWT().enqueue(this)
    }

    //Successfully retrieved a JWT from the Firebase Function endpoint
    override fun onResponse(call: Call<UserJWT>?, response: Response<UserJWT>?) {
        val jwt = response?.body()?.user_jwt
        client.login(jwt, this)
    }

    //User successfully logged in with the Nexmo Stitch SDK
    override fun onSuccess(result: User?) {
        goToCallActivity()
    }

}

Add the Call Activity

We'll add a simple layout for inputting phone numbers, starting, and ending phone calls. The layout for this Activity is available in GitHub. Like before I'll show the happy path of making and receiving a call.

class CallActivity : BaseActivity(), RequestHandler<Call> {
    private var currentCall: Call? = null
    private lateinit var client: ConversationClient

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        client = Stitch.getInstance(this).conversationClient

        attachIncomingCallListener()
        callControlBtn.setOnClickListener { callPhone() }
    }

    private fun attachIncomingCallListener() {
        //Listen for incoming calls
        client.callEvent().add({ incomingCall ->
            logAndShow("answering Call")
            //Answer an incoming call
            incomingCall.answer(object : RequestHandler<Void> {
                override fun onError(apiError: NexmoAPIError) {
                    logAndShow("Error answer: " + apiError.message)
                }

                override fun onSuccess(result: Void) {
                    currentCall = incomingCall
                    attachCallStateListener(incomingCall)
                    showHangupButton()
                }
            })
        })
    }

    private fun attachCallStateListener(incomingCall: Call) {
        //Listen for incoming member events in a call
        val callEventListener = ResultListener<CallEvent> { message ->
            logAndShow("callEvent : state: " + message.state + " .content:" + message.toString())
        }
        incomingCall.event().add(callEventListener)
    }


    private fun callPhone() {
        val phoneNumber = phoneNumberInput.text.toString()

        client.callPhone(phoneNumber, object : RequestHandler<Call> {
            override fun onError(apiError: NexmoAPIError) {
                logAndShow("Cannot initiate call: " + apiError.message)
            }

            override fun onSuccess(result: Call) {
                currentCall = result
                showHangupButton()

                when (result.callState) {
                    Call.CALL_STATE.STARTED -> logAndShow("Started")
                    Call.CALL_STATE.RINGING -> logAndShow("Ringing")
                    Call.CALL_STATE.ANSWERED -> logAndShow("Answered")
                    else -> logAndShow("Error attaching call listener")
                }

            }
        })
    }

}

As you can see the Nexmo Stitch SDK handles the hard work of placing and answering phone calls.

How does it work?

Receive a Phone Call

When a user makes a phone call to the number we bought in the previous section, Nexmo will look up the NCCO in our Answer URL at https://your-project-name.cloudfunctions.net/answer. The NCCO will look like this:

[  
   {  
      "action":"talk",
      "text":"You are being connected to the Customer."
   },
   {  
      "action":"connect",
      "endpoint":[  
         {  
            "type":"app",
            "user":"Customer"
         }
      ]
   }
]

This NCCO will direct the call to the "Customer" user we created in our app with the Stitch SDK. The app will handle answering the call by attaching the Call Listener in attachIncomingCallListener() For the sake of simplicity, we'll automatically answer the call, but you could implement a UI and logic to allow the user to answer or reject() the call.

Make a Phone Call

If the user choose to make a call, we'll handle that in the callPhone() method. For example if our method was called like so:

client.callPhone("14155550100", callback)

Then the client.callPhone(phoneNumber, callback) method in the Nexmo Stitch Android SDK will use the Stitch API to make GET a request to your answer url https://your-project-name.cloudfunctions.net/answer with the following query parameters:

?from=16625461410\
&to=14155550100\
&conversation_uuid=CON-4e977dab-2abc-42b5-bf64-d468d4763e54\
&uuid=0666edbe58077d826944a7c1913da2b0

We can use the to parameter to dynamically return a NCCO that tells Nexmo which phone number to call. The Stitch API will see the following NCCO response:

[  
   {  
      "action":"talk",
      "text":"Thank you for calling, you are now being connected."
   },
   {  
      "action":"connect",
      "from":"12013753230",
      "endpoint":[  
         {  
            "type":"phone",
            "number":"14155550100"
         }
      ]
   }
]

In this case, the from number is the number that we rented from Nexmo and set as the nexmo.from_number with Firebase config variables. The value in number key in the endpoint array is the number that our user wants to call.

Try it for yourself

The repo for this sample project contains the source code for both the Firebase Functions and the Android Sample app. Clone the project for yourself to check it out!

What's next?

Now that you've learned how to make and receive phone calls with Nexmo In-App Voice you can also learn more about In-App Messaging.

For more in depth details of what the Nexmo Stitch Android SDK covers, you can read the SDK documentation online.

Our Voice API also offers other possibilities beyond just connecting and receiving calls! Visit our documentation to learn more about recording calls, playing audio stream to calls, and more.

Comments currently disabled.