There are a few different options for installing the Nakama Java SDK in your project depending on whether you’re using Gradle, Maven or relying on the JAR package directly.
repositories{maven{url'https://jitpack.io'}}dependencies{implementation'com.github.heroiclabs.nakama-java:nakama-java:<commit>'implementation'com.github.heroiclabs.nakama-java:satori-java:<commit>'// or, depend on the fat Jar which bundles all of the Nakama Java dependencies into a single Jar.
// implementation 'com.github.heroiclabs.nakama-java:nakama-java-all:<commit>'
// implementation 'com.github.heroiclabs.nakama-java:satori-java-all:<commit>'
}
The Nakama Java SDK uses the SLF4J logging API. You can find more information on how to use this API and how to use different logging bindings by reading the SLF4J User Manual.
All examples in this guide use an SLF4J Logger which can be created as follows:
1
2
// Where App is the name of your classLoggerlogger=LoggerFactory.getLogger(App.class);
Android uses a permissions system which determines which platform services the application will request to use and ask permission for from the user. The client uses the network to communicate with the server so you must add the “INTERNET” permission.
Many of the Nakama APIs are asynchronous and non-blocking and are available in the Java SDK using ListenableFuture objects which are part of the Google Guava library.
// Create a single thread executorExecutorServiceexecutor=Executors.newSingleThreadExecutor();// Get a ListenableFuture with a Session resultListenableFuture<Session>authFuture=client.authenticateDevice(deviceId);// Setup the success and failure callbacks, specifying which executor to useFutures.addCallback(authFuture,newFutureCallback<Session>(){@OverridepublicvoidonSuccess(@NullableDeclSessionsession){logger.debug("Authenticated user id: "+session.getUserId());executor.shutdown();}@OverridepublicvoidonFailure(Throwablethrowable){logger.error(throwable.getMessage());executor.shutdown();}},executor);// Wait for the executor to finish all taskstry{executor.awaitTermination(5,TimeUnit.SECONDS);}catch(InterruptedExceptione){logger.error(e.getMessage());}
If you wish to chain asynchronous calls together, you can do so using AsyncFunction<> objects and the Futures.transformAsync function.
// Get a ListenableFuture with a Session resultListenableFuture<Session>authFuture=client.authenticateDevice(deviceId);// Create an AsyncFunction to get the Account of a user using a Session objectAsyncFunction<Session,Account>accountFunction=session->client.getAccount(session);// Get a ListenableFuture from Futures.transformAsync by first passing the original Future, followed by the extended Future and finally an exectorListenableFuture<Account>accountFuture=Futures.transformAsync(authFuture,accountFunction,executor);// Setup the success and failture callbacks as usualFutures.addCallback(accountFuture,newFutureCallback<>(){@OverridepublicvoidonSuccess(@NullableDeclAccountaccount){logger.debug("Got account for user id: "+account.getUser().getId());executor.shutdown();}@OverridepublicvoidonFailure(Throwablethrowable){logger.error(throwable.getMessage());executor.shutdown();}},executor);
For brevity, the code samples in this guide will use the simpler but thread blocking .get() function instead.
Network programming requires additional safeguarding against connection and payload issues.
As shown above, API calls use a callback pattern with both a success and a failure callback being provided. If an API call throws an exception it is handled in the onFailure callback and the exception details can be accessed in the throwable object.
When sending and receiving data across the network it will need to be serialized and deserialized appropriately. The two most common ways to do this are using JSON and Binary data.
Both examples will show how to serialize and deserialize the Map object below but can be used with any serializable object.
Using the java.io.* package. Conversion to and from Base64 is only necessary if you wish to send and receive the serialized data as a String; otherwise you can serialize and deserialize using a byte[] array.
The Satori Client connects to a Satori Server and is the entry point to access Satori features. It is recommended to have one client per server per game.
To create a client pass in your server connection details:
1
2
3
4
5
6
7
8
9
10
StringapiKey="apiKey";Stringhost="127.0.0.1";intport=7450;booleanuseSsl=true;// Ensure you are using Client from the Satori namespaceClientclient=newDefaultClient(apiKey,host,port,useSsl);// Alternatively, you can create an HttpClient with the same parameters as opposed to the gRPC-based DefaultClientClientclient=newHttpClient(apiKey,host,port,useSsl);
Each request to Satori from the client must complete in a certain period of time before it is considered to have timed out. You can configure how long this period is (in seconds) by setting the Timeout value on the client:
Authenticate users using the Satori Client via their unique ID.
When authenticating, you can optionally pass in any desired defaultProperties and/or customProperties to be updated. If none are provided, the properties remain as they are on the server.
Sessions expire after five (5) minutes by default. Expiring inactive sessions is good security practice.
Satori provides ways to restore sessions, for example when players re-launch the game, or refresh tokens to keep the session active while the game is being played.
Use the auth and refresh tokens on the session object to restore or refresh sessions.
Restore a session without having to re-authenticate:
Specifying a default value ensures no exception will be thrown if the network is unavailable, instead a flag with the specified default value will be returned.
Specifying a default value ensures no exception will be thrown if the network is unavailable, instead a flag with the specified default value will be returned.
If you want to submit events to Satori before a user has authenticated with the game server backend (e.g. Nakama) and has a User ID, you should authenticate with Satori using a temporary ID, such as the device’s unique identifier or a randomly generated one.
Once a user has successfully authenticated, you should then call identity to enrich the current session and return a new session that should be used for submitting future events.