Make and receive phone calls in Android apps with Firebase
Published on May 20, 2021

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.

DT API Account

To complete this tutorial, you will need a DT API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the DT API Dashboard.

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 Vonage 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 Vonage 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 Vonage Application.

To create an application, first Install the Vonage CLI globally with this command:

npm install @vonage/cli -g

Next, configure the CLI with your Vonage API key and secret. You can find this information in the Developer Dashboard.

vonage config:set --apiKey=VONAGE_API_KEY --apiSecret=VONAGE_API_SECRET

Now 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
vonage apps:create 
✔ Application Name … ruling_narwhal
✔ Select App Capabilities › Voice
✔ Create voice webhooks? … yes
✔ Answer Webhook - URL … https://your-project-name.cloudfunctions.net/answer
✔ Answer Webhook - Method › POST
✔ Event Webhook - URL … https://your-project-name.cloudfunctions.net/event
✔ Event Webhook - Method › POST
✔ Allow use of data for AI training? Read data collection disclosure - https://help.nexmo.com/hc/en-us/articles/4401914566036 … no
Creating Application... done
Application Name: ruling_narwhal

Your private key gets saved in the functions directory you created. The key will have the same name as your project.

Record the application ID and save the private key in the "functions" directory. I recommend you add the your_private_key_name.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 Vonage 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 Vonage application as a firebase config variable instead of uploading the entire your_private_key_name.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. We also have to link the number to our Vonage app.

You can rent a number from Vonage by using the following command (replacing the country code with your code). For example, if you are in the USA, replace GB with US:

vonage numbers:search US vonage numbers:buy [NUMBER] [COUNTRYCODE]

Now link the number to your app:

vonage apps:link --number=VONAGE_NUMBER APP_ID

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)
</firebasefunctionservice></userjwt>

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()
    }

}
</userjwt></userjwt></userjwt></user>

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")
                }

            }
        })
    }

}
</call></callevent></void></call>

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.

Chris GuzmanVonage Alumni

Chris is a Developer Advocate at Nexmo where he helps developers use their global communications platform. When he’s not at conferences you can find him roaming the world.

Ready to start building?

Experience seamless connectivity, real-time messaging, and crystal-clear voice and video calls-all at your fingertips.