Building a Sample App with the Android App Switch SDK

15 Aug 2014 · By Matthew Gotteiner

The Venmo Android App Switch SDK enables anyone to easily add person-to-person payments to an app. After adding the required sample files to your project, all that's left is to add a few input fields and fire off a request. To demonstrate how simple it is to integrate the App Switch SDK, let's walk through a sample app that allows a user to send a Venmo payment to a contact by searching for a name or phone number. Integrating contacts into your app is not a requirement to use our SDK, but it is a common use case so I decided to go over that process here. Download the source code for the complete app here: https://github.com/mgottein/app-switch-demo

This tutorial uses an AutoCompleteTextView widget to search a user's contact list. AutoCompleteTextView is similar to EditText, except that it provides a list of suggestions to the user based on what they are typing and allows the user to click on one to complete their entry.

In order for AutocompleteTextView to know what suggestions to provide to the user, we must provide it a ListAdapter that implements Filterable. The ListAdapter allows the AutocompleteTextView to access your suggestions, and implementing Filterable filters the list by what the user is currently typing. We'll be using the SimpleCursorAdapter ListAdapter.

First, retrieve a user's contact information by adding the read contacts permission to AndroidManifest.xml. If we don't add this permission, we will be prevented from reading contact info. Add this line under the manifest tag:

<uses-permission android:name="android.permission.READ_CONTACTS" />

Contact information in Android is exposed by a ContentProvider, a class that allows applications to access content. The contact ContentProvider allows us to look up the contact information via a SQLite query. The contact information will be inside a Cursor, a class that encapsulates the query results. Since our data is inside a Cursor, we don't have to implement our own ListAdapter from scratch and can instead use a SimpleCursorAdapter which handles much of the adapter boilerplate code for us. We can access the contact ContentProvider by using a ContentResolver. ContentResolver provides a query method that looks like a SQLite query. Let's take a look at its definition:

ContentResolver.query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

This generates a SQLite query that will look something like this:

SELECT <projection> FROM <uri> WHERE <selection> ORDER BY <sortOrder>

Making the Projection Statement

There are many columns in the Android Contact entity, but we only need a few of them here. We care about Contacts.DISPLAY_NAME_PRIMARY, Phone.NUMBER, and Data._ID.

We can now set up our projection:

private static final String[] PROJECTION = new String[]{
    Data._ID,
    Contacts.DISPLAY_NAME_PRIMARY,
    Phone.NUMBER
};

Making the SQLite Selection Statement

Building SQLite queries in Java can be confusing, so let's walk through this step-by-step. - Check if either Contact.DISPLAY_NAME_PRIMARY or Phone.NUMBER start with the input text. Since the contact name and phone number are strings, we can use the LIKE operator to see if the input text matches.

private final String SELECTION = '(' + Contacts.DISPLAY_NAME_PRIMARY + " LIKE ? OR " + Phone.NUMBER + " LIKE ?) AND " +
Phone.TYPE + "='" + Phone.TYPE_MOBILE + "' AND " +
Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'";

Instantiating the Adapter

In order to instantiate the adapter, we need to provide a valid Context, a layout resource for a single list item, and a mapping between columns and view IDs. This mapping allows the SimpleCursorAdapter to automatically display a list item for us with very little code. We can choose to provide a cursor at instantiation, but for our purposes this is unnecessary. We are using a pre-existing layout resource for simplicity.

SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
    android.R.layout.simple_list_item_2,
    null,
    new String[]{
        Contacts.DISPLAY_NAME_PRIMARY,
        Phone.NUMBER},
    new int[]{
        android.R.id.text1,
        android.R.id.text2
    },
    0
);

Set a FilterQueryProvider so the adapter knows how to get content, given text to filter with. The query is run on a background thread, so we don't have to worry about blocking the main thread.

adapter.setFilterQueryProvider(new FilterQueryProvider() {
    @Override
    public Cursor runQuery(CharSequence constraint) {
        String constraintWithWildcard = constraint + "%";
        return getContentResolver().query(
            Data.CONTENT_URI,
            PROJECTION,
            SELECTION,
            new String[]{
                    constraintWithWildcard,
                    constraintWithWildcard
            },
            Contacts.DISPLAY_NAME_PRIMARY + " DESC"
        );
    }
});

We also need to provide a CursorToStringConverter so the adapter knows how to convert a contact row into a string the AutoCompleteTextView can use to complete a user's entry.

adapter.setCursorToStringConverter(new SimpleCursorAdapter.CursorToStringConverter() {
    @Override
    public CharSequence convertToString(Cursor cursor) {
        int phoneNumberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
        return cursor.getString(phoneNumberIndex);
    }
});

All that's left is to set the adapter on the AutoCompleteTextView itself:

mRecipient = (AutoCompleteTextView) findViewById(R.id.recipient);
mRecipient.setAdapter(adapter);

Now that we can get contact info, let's see how we can open the Venmo app and pay a contact. Let's walk through a sample method to open Venmo and make a transaction.

private void doTransaction(String recipient, String amount, String note, String txn) {
    try {
        Intent venmoIntent = VenmoLibrary.openVenmoPayment(APP_ID, APP_NAME, recipient, amount, note, txn);
        startActivityForResult(venmoIntent, 1); //1 is the requestCode we are using for Venmo. Feel free to change this to another number.
    } catch (android.content.ActivityNotFoundException e) //Venmo native app not install on device, so let's instead open a mobile web version of Venmo in a WebView
    {
        Intent venmoIntent = new Intent(MyActivity.this, VenmoWebViewActivity.class);
        String venmo_uri = VenmoLibrary.openVenmoPaymentInWebView(APP_ID, APP_NAME, recipient, amount, note, txn);
        venmoIntent.putExtra("url", venmo_uri);
        startActivityForResult(venmoIntent, VENMO_REQUEST_CODE);
    }
}

We are using an Intent, a class Android provides to launch other components, to launch the Venmo app in order to complete a transaction. Note that if the app is not installed, a WebView will be launched that uses the Venmo website to complete the transaction. To get results from the App Switch SDK, we can define onActivityResult in our Activity, like so:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
    switch(requestCode) {
        case 1: { //1 is the requestCode we picked for Venmo earlier when we called startActivityForResult
            if(resultCode == RESULT_OK) {
                String signedrequest = data.getStringExtra("signedrequest");
                if(signedrequest != null) {
                    VenmoResponse response = (new VenmoLibrary()).validateVenmoPaymentResponse(signedrequest, app_secret);
                    if(response.getSuccess().equals("1")) {
                        //Payment successful.  Use data from response object to display a success message
                        String note = response.getNote();
                        String amount = response.getAmount();
                    }
                }
                else {
                    String error_message = data.getStringExtra("error_message");
                    //An error occurred.  Display the error_message to the user.
                }                               
            }
            else if(resultCode == RESULT_CANCELED) {
                //The user cancelled the payment
            }
        break;
        }
    }
}

And that's it!