Introduction
One of the many APIs available in Google Play Services is the Nearby Connections API. Introduced in early 2015, this framework lets you set one device running your application as a host and have multiple other devices connect to it in order to communicate over a Local Area Network (LAN).
Use cases for this feature include connecting a phone to an Android TV for controlling an app and allowing users to participate in a multiplayer game. In this tutorial, you will learn how to set up applications for connecting multiple devices together over a network and how to send data over that connection. A working sample for this tutorial can be found on GitHub.
1. Project Setup
Once you have your initial program created in Android Studio, you will need to import the Play Services library into your app. To do this, place the following line of code under the dependency node of the build.gradle file. At the time of writing, Play Services 7.5.0 is the most recent release for development.
compile 'com.google.android.gms:play-services:7.5.0'
Once Play Services is included in your app, you can close build.gradle and open AndroidManifest.xml. Since this feature uses a LAN to communicate, you will need to include the ACCESS_NETWORK_STATE
permission in your manifest.
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Next, you will need to add a piece of meta-data
in the application
node that defines a service identifier that will be used by your app so that it can discover hosts advertising with that same identifier. In this example, our service identifier is defined in strings.xml as tutsplus_service_id
.
<meta-data android:name="com.google.android.gms.nearby.connection.SERVICE_ID" android:value="@string/service_id" />
When you're done with the manifest, you can move to MainActivity.java. This is the class where we will implement both advertising and discovery. In MainActivity
, you will also control sending messages between different devices.
In order to start using the Nearby Connections API, you must set up and connect to the Google API Client. Start by implementing ConnectionCallbacks
and OnConnectionFailedListener
at the top of your class. While we're adding our interfaces, let's also include the three that are needed by the API and an OnClickListener
.
public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, Connections.ConnectionRequestListener, Connections.MessageListener, Connections.EndpointDiscoveryListener, View.OnClickListener { ...
Let's now create the member variables that we'll need for this tutorial at the top of the MainActivity
class. For the sake of brevity, I'll simply mention that the layout for this class consists of a ListView
for displaying messages, an EditText
field with a Button
for sending messages after connected, a Button
for advertising, connecting, or disconnecting, depending on the device's role, and a TextView
for displaying basic state information.
You'll notice that there are two boolean flags for designating wether or not the device is connected and if it is the connection host, the GoogleApiClient
that is necessary for using the Nearby Connections API, and an array of integers for keeping track of the network connection types we will support for this API.
private GoogleApiClient mGoogleApiClient; private Spinner mTypeSpinner; private TextView mStatusText; private Button mConnectionButton; private Button mSendButton; private ListView mListView; private ViewGroup mSendTextContainer; private EditText mSendEditText; private ArrayAdapter<String> mMessageAdapter; private boolean mIsHost; private boolean mIsConnected; private String mRemoteHostEndpoint; private List<String> mRemotePeerEndpoints = new ArrayList<String>(); private static final long CONNECTION_TIME_OUT = 10000L; private static int[] NETWORK_TYPES = {ConnectivityManager.TYPE_WIFI, ConnectivityManager.TYPE_ETHERNET };
If you've worked with any Android Google API classes before, the last bit of setup should look fairly familiar. You need to initialize the GoogleApiClient
and connect to it in onCreate
.
mGoogleApiClient = new GoogleApiClient.Builder( this ) .addConnectionCallbacks( this ) .addOnConnectionFailedListener( this ) .addApi( Nearby.CONNECTIONS_API ) .build();
In onStart
and onStop
, we handle connecting and disconnecting.
@Override protected void onStart() { super.onStart(); mGoogleApiClient.connect(); } @Override protected void onStop() { super.onStop(); if( mGoogleApiClient != null && mGoogleApiClient.isConnected() ) { mGoogleApiClient.disconnect(); } }
2. Advertising and Accepting Connections
Once you have connected to the Google API Client, you can start working with nearby connections. The first component we'll go over is advertising, which allows a device to assume the role of host and manage connections between various peers for communication.
Advertising itself is fairly straightforward. You simply need to check that the device has an acceptable type of connection and then call Nearby.Connections.StartAdvertising
with the proper parameters. This will cause the device to advertise across your LAN that it is available for accepting connections from other applications.
In this example, we pass in a timeout of ten seconds for advertising. However, you can pass in a value of 0
to advertise indefinitely. In the following code, isConnectedToNetwork
is a helper method meant to check if advertising should occur.
private void advertise() { if( !isConnectedToNetwork() ) return; String name = "Nearby Advertising"; Nearby.Connections.startAdvertising( mGoogleApiClient, name, null, CONNECTION_TIME_OUT, this ) .setResultCallback( new ResultCallback<Connections.StartAdvertisingResult>() { @Override public void onResult( Connections.StartAdvertisingResult result ) { if( result.getStatus().isSuccess() ) { mStatusText.setText("Advertising"); } } }); } private boolean isConnectedToNetwork() { ConnectivityManager connManager = (ConnectivityManager) getSystemService( Context.CONNECTIVITY_SERVICE ); for( int networkType : NETWORK_TYPES ) { NetworkInfo info = connManager.getNetworkInfo( networkType ); if( info != null && info.isConnectedOrConnecting() ) { return true; } } return false; }
Once your host application is advertising, it will be able to receive connection requests from peers. When a device attempts to connect, the following method will be called:
public void onConnectionRequest(final String remoteEndpointId, final String remoteDeviceId, final String remoteEndpointName, byte[] payload)
Using this method, you can either accept or reject the connection. To accept the request, you call Nearby.Connections.acceptConnectionRequest
with a ResultsCallback
. You can then perform actions, depending on whether the connection is successfully accepted or not.
For this example, we'll simply add the remote endpoint to a list to keep track of it and broadcast to any connected peers that this new device has connected. If, for some reason, you determine that the device should not connect to your application, you can reject it by calling Nearby.Connections.rejectConnectionRequest
.
@Override public void onConnectionRequest(final String remoteEndpointId, final String remoteDeviceId, final String remoteEndpointName, byte[] payload) { if( mIsHost ) { Nearby.Connections.acceptConnectionRequest( mGoogleApiClient, remoteEndpointId, payload, this ).setResultCallback (new ResultCallback<Status>() { @Override public void onResult(Status status) { if( status.isSuccess() ) { if( !mRemotePeerEndpoints.contains( remoteEndpointId ) ) { mRemotePeerEndpoints.add( remoteEndpointId ); } mMessageAdapter.add(remoteDeviceId + " connected!"); mMessageAdapter.notifyDataSetChanged(); sendMessage(remoteDeviceId + " connected!"); mSendTextContainer.setVisibility( View.VISIBLE ); } } }); } else { Nearby.Connections.rejectConnectionRequest(mGoogleApiClient, remoteEndpointId ); } }
3. Discovery
Just like advertising, discovery relies on being connected to the GoogleApiClient
and having an acceptable network connection. You can start discovery by passing the application's service identifier into the Nearby.Connections.startDiscovery
method, which sets your user's device into discovery mode.
When the device detects a host that is currently advertising using the predefined service identifier, the onEndpointFound
callback will be triggered. It should be noted that this method can be called multiple times if there are multiple hosts broadcasting. In this situation, you can create a dialog for your users that displays all available hosts so that they can select which they would like to be connected to.
For this example, we'll assume that there's only one host advertising at a time, so we'll immediately request to connect by calling Nearby.Connections.sendConnectionRequest
. If the connection is accepted or rejected by the host, the sendConnectionRequest
result callback will be called. If the connection is accepted, the status will be set to successful and we can save the host endpoint identifier and prepare for sending messages across the connection channel.
private void discover() { if( !isConnectedToNetwork() ) return; String serviceId = getString( R.string.service_id ); Nearby.Connections.startDiscovery(mGoogleApiClient, serviceId, 10000L, this) .setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { if (status.isSuccess()) { mStatusText.setText( "Discovering" ); } else { Log.e( "TutsPlus", "Discovering failed: " + status.getStatusMessage() ); } } }); } @Override public void onEndpointFound(String endpointId, String deviceId, final String serviceId, String endpointName) { byte[] payload = null; Nearby.Connections.sendConnectionRequest( mGoogleApiClient, deviceId, endpointId, payload, new Connections.ConnectionResponseCallback() { @Override public void onConnectionResponse(String endpointId, Status status, byte[] bytes) { if( status.isSuccess() ) { mStatusText.setText( "Connected to: " + endpointId ); Nearby.Connections.stopDiscovery(mGoogleApiClient, serviceId); mRemoteHostEndpoint = endpointId; mSendTextContainer.setVisibility(View.VISIBLE); if( !mIsHost ) { mIsConnected = true; } } else { mStatusText.setText( "Connection to " + endpointId + " failed" ); if( !mIsHost ) { mIsConnected = false; } } } }, this ); }
In a situation where you listen for multiple endpoints to present a choice for your users, the onEndpointLost
method will let you know if a host has stopped advertising before your user has attempted to connect to it. The onDisconnected
callback is also available for client devices and can be used for reconnecting to advertising hosts in the event of an unexpected disconnect.
4. Sending Messages
Once your devices have connected together, it's time to start communicating. There are two kinds of messages that can be sent, reliable and unreliable. If you're familiar with networking technology, you can think of these in terms of TCP (Transmission Control Protocol) and UDP (User Datagram Protocol). A simplified explanation is that reliable messages will retry attempts to send a message if they fail, whereas unreliable messages will simply drop the data if it is not successfully sent and received.
For this tutorial, you will use reliable messages. When a message is received over the API, onMessageReceived
will be called. This method accepts the endpoint identifier, a payload, and a boolean indicating whether the connection is reliable or unreliable. The payload contains the message and the endpoint identifier is the identifier of whichever device sent the message.
In the sample application, you will use this to display the payload as a string in a ListView
and, if the device is the host, rebroadcast it out to every connected device.
@Override public void onMessageReceived(String endpointId, byte[] payload, boolean isReliable) { mMessageAdapter.add( new String( payload ) ); mMessageAdapter.notifyDataSetChanged(); if( mIsHost ) { sendMessage( new String( payload ) ); } }
The sendMessage
method is a helper method that demonstrates two versions of Nearby.Connections.sendReliableMessage
. For host applications, sendReliableMessage
will accept a list of endpoints to send the message to. This allows you to communicate to multiple devices with one line of code. For clients, messages only need to go to the host, so only the host name is needed as a parameter with the GoogleApiClient
and message byte array.
private void sendMessage( String message ) { if( mIsHost ) { Nearby.Connections.sendReliableMessage( mGoogleApiClient, mRemotePeerEndpoints, message.getBytes() ); mMessageAdapter.add( message ); mMessageAdapter.notifyDataSetChanged(); } else { Nearby.Connections.sendReliableMessage( mGoogleApiClient, mRemoteHostEndpoint, ( Nearby.Connections.getLocalDeviceId( mGoogleApiClient ) + " says: " + message ).getBytes() ); } }
5. Disconnecting
When you're ready to disconnect on either the host or client side of the application, there is a little bit of clean up that must take place. For hosts, you have to stop advertising and disconnect all of your endpoints.
In the sample code, you'll notice that a message is also sent that attempts to let peers know that the host has disconnected. In a more complete app, you would want your clients to listen for this kind of message so that they can handle the situation appropriately.
If you attempt to disconnect on a client that hasn't connected to a host yet, you simply need to stop discovery. If you've already connected to a host, you call disconnectFromEndpoint
and the API will handle severing the connection.
private void disconnect() { if( !isConnectedToNetwork() ) return; if( mIsHost ) { sendMessage( "Shutting down host" ); Nearby.Connections.stopAdvertising( mGoogleApiClient ); Nearby.Connections.stopAllEndpoints( mGoogleApiClient ); mIsHost = false; mStatusText.setText( "Not connected" ); mRemotePeerEndpoints.clear(); } else { if( !mIsConnected || TextUtils.isEmpty( mRemoteHostEndpoint ) ) { Nearby.Connections.stopDiscovery( mGoogleApiClient, getString( R.string.service_id ) ); return; } sendMessage( "Disconnecting" ); Nearby.Connections.disconnectFromEndpoint( mGoogleApiClient, mRemoteHostEndpoint ); mRemoteHostEndpoint = null; mStatusText.setText( "Disconnected" ); } mIsConnected = false; }
Conclusion
In this tutorial, you have learned how to implement communication between various Android devices over a Local Area Network using the Nearby Connections API. You should now be able to enhance your own apps by connecting devices together and keeping them in sync through various updates to each other.
While this tutorial walked you through a fairly simple chat client application, you can take what you've learned here to create polished multiplayer experiences, provide secondary screens for users, or make your apps more contextually aware by providing an additional communication channel for the environment around them.
As the Nearby Connections API continues to grow with the addition of Eddystone beacons and Bluetooth, this will prove to be an invaluable skill to have when developing Android applications.