This guide shows you how to use the Nearby Connections API in your C++ application. This API enables your app to easily discover other devices on a local network, connect to them, and exchange messages with them in real time. This functionality is especially useful for two types of user experiences:
- Local multiplayer gaming: Allows one player to set up a local multiplayer game that other players on the network can join. Your app can also allow a player to start an in-game mission when enough nearby participants join.
- Multi-screen gaming: Allows players to use their phone or tablet as a game controller to play games displayed on a nearby large-screen Android device, such as Android TV. Your app can also enable players to see a customized game screen on their personal device while all nearby participants see a shared common view on a table-top Android device.
This API is part of the Play Games C++ SDK, and and lets you build games that can join multiple nearby Android devices together on a WiFi network. One device advertises on the network as the host, while other devices act as clients and send connection requests to the host.
Before you begin
Before you start to code using the Nearby Connections API:
- Install the Google Play Services SDK.
- Download and review the Nearby Connections API code sample.
- Connect multiple Android devices to the same WiFi network with multicast enabled.
Once the system builds and configures a NearbyConnections object, your game can use the Nearby Connections API.
Initializing the Nearby Connections API client for nearby connections
To access the Nearby Connections API, you first need to initialize it. Do this by using the Builder class to construct a NearbyConnections object. The following example shows you how to do this.
gpg::AndroidPlatformConfiguration platform_configuration;
platform_configuration.SetActivity(app_->activity->clazz);
gpg::NearbyConnections::Builder nbcBuilder;
nearby_connection_ =
nbcBuilder.SetDefaultOnLog(gpg::LogLevel::VERBOSE)
.SetOnInitializationFinished([this](gpg::InitializationStatus status) {
LOGI("NBCInitializationFinished: %d", (int)status);
if (status == gpg::InitializationStatus::VALID) {
LOGI("InitializationFinished() returned VALID.");
} else {
LOGE("InitializationFinished() returned an error.");
}
}).Create(platform_configuration);
Validating network connectivity
Before using the Nearby Connections API, you should check to ensure the devices are connected to a WiFi network. If they are not connected, prompt players to connect to a WiFi network.
To detect the network state, first add the following permission to your manifest:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
The following Java code example shows you how to determine whether a device is connected to WiFi:
ConnectivityManager cm =
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null &&
activeNetwork.isConnectedOrConnecting();
boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;
Call this method before you start to advertise your device or set your device to discovery mode as described in the sections below.
Advertising your device
To establish a connection between two or more devices, one device must advertise its service and one or more devices must request to connect to it. The advertising device is the 'host' in a multiplayer game, while the connecting device is the client.
To enable your app to advertise itself with the Nearby Connections API, add the following to your manifest:
<application>
<!-- Required for Nearby Connections API -->
<meta-data android:name="com.google.android.gms.nearby.connection.SERVICE_ID"
android:value="@string/service_id" />
<activity>
...
</activity>
</application>
The service_id
value allows client devices to discover the advertising
device. The value must uniquely identify your app. As a best practice, use the
package name of your app (for example, com.google.example.mygame.service
).
The following example shows you how to declare the service_id
value in the
strings.xml
file:
<?xml version="1.0" encoding="utf-8"?>
<resources>
...
<!--
This is the service identifier for Nearby Connections. Do NOT copy and paste this value
into your own application. Instead, choose a unique identifier that is appropriate for
your application.
-->
<string name="service_id"><!-- your service ID goes here, e.g. com.google.example.mygame.service --></string>
...
</resources>
When a player starts the game and chooses to host, call
startAdvertising()
to advertise the device. The following example shows you how you can advertise
the host player’s device:
std::vector<gpg::AppIdentifier> app_identifiers;
gpg::AppIdentifier tmp;
tmp.identifier = kAndroidPackageName; // Pass in the Android package name here.
app_identifiers.push_back(tmp);
nearby_connection_->StartAdvertising(
"", // Use an automatically-generated default name.
app_identifiers,
gpg::Duration::zero(),
[this](int64_t client_id, gpg::StartAdvertisingResult const &result) {
LOGV("StartAdvertisingResult(%lld, %s)", client_id, result.local_endpoint_name.c_str());
switch (result.status) {
case gpg::StartAdvertisingResult::StatusCode::SUCCESS:
LOGI("Advertising succeeded!");
break;
default:
LOGE("Advertising failed.");
return;
}
EnableUI(true);
},
[this](int64_t client_id, gpg::ConnectionRequest const &request) {
LOGI("ConnectionRequest(%lld)", client_id);
LOGI("remote info: req.endpoint_id=%s, req.device_id=%s, req_name = %s",
request.remote_endpoint_id.c_str(), request.remote_device_id.c_str(),
request.remote_endpoint_name.c_str());
std::string msg = "accepted";
std::vector<uint8_t> payload(msg.begin(), msg.end());
nearby_connection_->AcceptConnectionRequest(request.remote_endpoint_id, payload, msg_listener_);
BroadcastNewConnection(request.remote_endpoint_id);
// Adding this end point into the connected state:
AddConnectionEndpoint(client_id, request.remote_endpoint_id, true, true);
nbc_state_ |= nearby_connection_state::CONNECTED;
nbc_state_ &= ~nearby_connection_state::IDLE;
EnableUI(true);
LOGI("Accepting Request sending out (for %s)", request.remote_device_id.c_str());
});
The simplest way to allow devices to reconnect after disconnecting is to allow the host to continue advertising until a game ends.
Discovering other devices
The discovery process allows a device to find nearby devices that are advertising
connections for a specific service ID. The service ID parameter you pass into
startDiscovery()
should match the value provided in the manifest of the advertising app. See
Advertising your device for information on how apps
advertise nearby games.
The following example shows how to initiate the discovery process in your app:
nearby_connection_->StartDiscovery(service_id_, gpg::Duration::zero(), this);
In the following section, you learn how to handle the connections once the client device discovers the host device.
Identifying connected devices
There are two ways that the Nearby Connections API identifies devices:
- Device ID: This identifies the device uniquely and remains
the same even when the device is rebooted. Obtain a local device ID using
GetLocalDeviceID
- Endpoint ID: This identifies the device locally to a
NearbyConnections
instance. Obtain a local endpoint ID usingGetLocalEndpointID
.
The endpoint ID is required to establish connections and send messages. For example, if the player on device X is directing an attack at the player on device Y, you would send the message from device X to the host and include device X’s endpoint ID so that device Y can know where the attack originated.
You can use the device ID for cases such as:
- Identifying the player and restoring their appropriate data.
- Reconnecting to a host that the client has previously connected to during discovery (for example, reconnecting to your Android TV to continue your progress the following day).
Trying to reconnect to a host after losing connectivity with it (for example, returning to a multiplayer game after receiving a phone call).
In the next section, you learn how to send messages to connected devices.
Sending messages through nearby connections
Once connections are established between devices, they can send and receive messages to update game state and transfer input, data, or events from one device to another. Hosts can send messages to any number of clients, and clients can send messages to the host. If a client needs to communicate with other clients, you can send a message to the host to relay the information to the recipient client.
Sending the message
To send a reliable message, call
sendReliableMessage()
and pass in the appropriate endpoint ID. The payload parameter holds your message data.
Reliable messages are guaranteed to be received in the order they were sent, and the system retries
sending the message until the connection ends.
The following code snippet shows how you can send reliable messages from one device to another:
std::string msg_to_send = "message";
std::vector<uint8_t> payload(msg_to_send.begin(), msg_to_send.end());
nearby_connection_->SendReliableMessage(remote_endpoint_id, payload);
To send an unreliable message, call
sendUnreliableMessage()
.
This method allows the system to deliver messages quickly, but the messages may
be lost or sent out of order. Unreliable messaging is suitable for frequent and
less important game notifications, such as showing an opposing player’s character
position on a map.
Receiving the message
After the message is sent, the system receives the message through
IMessageListener::onMessageReceived()
.
Based on the requirements of your game, you can implement this method to update
game information such as player scores, on-screen drawings, or the current
player's turn.
The following code snippet shows how to implement this method to handle received messages:
void Engine::OnMessageReceivedCallback(int64_t receiver_id, std::string const &remote_endpoint,
std::vector<uint8_t> const &payload, bool is_reliable) {
std::string msg(payload.begin(), payload.end());
if (msg.size() > 0) {
switch (msg[0]) {
case PAYLOAD_HEADER_FINAL_SCORE:
{
std::string endpoint_id(msg.substr(1));
endpoint_id.resize(remote_endpoint.size());
msg = msg.substr(1 + remote_endpoint.size()); // score string
UpdatePlayerScore(endpoint_id, msg, false);
UpdateScoreBoardUI(true);
return;
}
default:
// Drop the message
{
LOGE("Unknown payload type: From(%s) with Payload(%s) in %s @ line %d",
remote_endpoint.c_str(), msg.c_str(), __FILE__, __LINE__);
return;
}
}
}
}
Disconnecting nearby connections
When a player quits the game, or if the host wants to force a player to
quit, call
Disconnect()
.
The following snippet shows you how to pass in the endpoint ID of the specific device that you wish to disconnect:
nearby_connection_->Disconnect(remote_endpoint_id);
If the host decides to stop advertising and disconnect all remote endpoints,
call
Stop()
.
More generally, it is a best practice to use this method when you are finished with a connection.
For example, when:
- You want a client device to stop discovery or disconnect from the host.
- Ending the activity.
- Exiting the app.
- Destructing the
NearbyConnections
object.
The following example shows how you might call
Stop()
:
nearby_connection_->Stop();
When the
NearbyConnections
object is disconnected on a device, and you call
Disconnect()
,
or
Stop()
,
the system triggers the
IMessageListener::onDisconnected()
callback method on the device it was connected to. Use this method to
gracefully handle disconnection.
Reconnecting nearby connections
Players and hosts can lose connectivity because of network interruptions, or by simply walking outside of the WiFi network range. Depending on your game design, you may want to reconnect a disconnected player while the game is still ongoing or pause the game and restart a player's turn when the player attempts to reconnect.
To enable your app to reconnect devices, you should store both a player's device ID and endpoint ID. When storing player data such as a position or score, identify the player by device ID so that the data can be restored after a disconnection even if the endpoint ID changes.
If the host is still advertising, the player can reconnect using the service ID.
Completing the connection
Once your app discovers another app that is advertising the requested service ID, you can initiate a connection between the devices. The following example shows you how to handle discovery of a device:
void Engine::OnEndpointFound(int64_t client_id, gpg::EndpointDetails const &endpoint_details) {
std::vector<uint8_t> payload;
std::string name;
nearby_connection_->SendConnectionRequest(
name,
endpoint_details.endpoint_id,
payload,
[this](int64_t client_id, gpg::ConnectionResponse const& response) {
OnConnectionResponse(client_id, response);
},
msg_listener_);
}
For apps such as multiplayer games, it is suitable to present a list of hosts to connect to. Alternatively, you could also choose to immediately connect the player, such as when a device connects to an Android TV.
Because devices cannot connect to more than one endpoint at a time, if a device tries to connect
to another one that is already connected, the result will be an ERROR_ALREADY_CONNECTED
response.
You can solve this problem by calling
Disconnect(remote_endpoint_id)
on the
connected device, and then re-attempting the connection.
On the host side, you need to handle connection requests from the connecting
devices. To keep track of and send messages to connected devices, store the
device endpoint IDs in a data construct of your choosing. To accept a request, call the
AcceptConnectionRequest()
method with the endpoint ID of the endpoint you wish to accept a connection to. Alternatively,
call the
RejectConnectionRequest()
method to reject a request.
The following code snippet shows how you might accept connection requests from devices:
std::string message = "accepted";
std::vector<uint8_t> payload(message.begin(), message.end());
nearby_connection_->AcceptConnectionRequest(request.remote_endpoint_id, payload, msg_listener_);
Once gameplay is underway, you can stop advertising your device by calling
stopAdvertising()
.
Handling lost connection with advertising device
During the nearby device discovery process, players may encounter disruptions
in their network connection. For example, a player might move out of the range
of the WiFi network. If your app presents a list of hosts to the user, you can
use the
IEndpointDiscoveryListener::onEndpointLost()
callback to simply remove that user from the list when the system detects this
scenario.
Keeping the device screens awake
Screens of the connected devices may go into sleep mode because you do not explicitly need to send input events to any of the devices. As a best practice from your Java code, call the following code in your activity to keep the devices awake:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
For more information, see
Keeping the Device Awake and the
reference documentation for
LayoutParams
.