Getting Started with Android
This overview will guide you through the steps required to integrate the BlackBerry Spark Communications Services SDK for Android with your application.
Prerequisites
Before adding the SDK to your application, ensure that your project meets the minimum requirements. To build and run your application using the SDK, you will need:
- Android 7.0 or later with SDK API level 24 or later
- Java version 1.8 or later
- Android Studio
- Android Gradle plug-in 4.0.0 or later
- Gradle 6.1.1 or later
armeabi-v7a
,
arm64-v8a
and x86_64
. When testing using the Android emulator, you
must use an x86_64
image.
Add the SDK AAR to your Application
Download & Configure the latest version of the SDK for Android.
Copy sdk/bbm_sdk-release.aar
into the app/libs
directory of your application.
Edit your application's app/build.gradle
file to ensure the SDK
AAR is included as a dependency in your application's build additionally the SDK requires Google Guava.
android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { // The guava library is required by the SDK. implementation 'com.google.guava:guava:27.1-android' // This indicates that your application depends on the SDK. implementation files('libs/bbm_sdk-release.aar') // ...any other dependencies your application might have. }
Your application's build.gradle
file requires that the Java source and target compatibility to be set to Java 1.8. If not set the SDK may crash on startup.
Configure the SDK with your Domain
You provide the ID of your newly created domain to the SDK by adding the
com.bbm.sdk.UserDomain
entry to the application manifest. The
manifest also lets you select between the sandbox and production
environments with the com.bbm.sdk.environment.sandbox
flag.
Add the following lines to the application
section of the
manifest. Replace YOUR_DOMAIN_ID
with the ID of your
sandbox domain.
<!-- Configure the Spark Communications Services domain. Replace 'YOUR_DOMAIN_ID' with the ID domain you created for your application. --> <meta-data android:name="com.bbm.sdk.UserDomain" android:value="YOUR_DOMAIN_ID"/> <!-- This is used to configure the SDK to use either the Production or the Sandbox environment. By default, the SDK is configured to use the Production environment. Use this directive to configure your application to use the Sandbox environment. --> <meta-data android:name="com.bbm.sdk.environment.sandbox" android:value="true"/>
The Observer Pattern
On Android, the SDK uses an observer pattern to notify your
application about events. Items requested from the SDK are returned
as ObservableValues
.
You can listen for changes to an ObservableValue
by adding
an Observer
to the observable. When the value of an Observable
is modified
its Observable.changed()
method is called.
ObservableValue<String> example; example.addObserver(new Observer() { @Override public void changed() { String changedValue = example.get(); } }); //Call changed to trigger the observer example.changed()
The SDK includes helpers to simplify the use of the
Observable
class. Use
ObservableMonitor
and SingleShotMonitor
to easily track any changes to Observable
objects.
These classes track changes to any Observer
objects accessed within the monitor.
//Example Observable Monitor ObservableValue<String> exampleObservableValue; ObservableMonitor exampleMonitor = new ObservableMonitor() { @Override protected void run() { String observedValue = exampleObservableValue.get(); } }; //Observable monitors can be activated in Activity#onResume and disposed in Activity#onPause exampleMonitor.activate();
SingleShotMonitors can be used to track Observables until a specific condition is met.
SingleshotMonitor.run(new SingleshotMonitor.RunUntilTrue() { @Override public boolean run() { String observedValue = exampleObservableValue.get(); //Continue observing until the value equals "finished" if (observedValue.equals("finished")) { return true; } return false; } });
Observer
when accessing
an ObservableValue
. ObservableValue
objects hold
weak references to their Observer
objects so you must
maintain a hard reference to your Observer
to ensure you
receive change notifications.
Start the SDK
On Android, the entry point to the SDK is the
BBMEnterprise
singleton. Before you can interact with the SDK, you must initialize it with an Android
Context
.
The initialization step prepares the native components of the SDK. After calling
BBMEnterprise.start()
,
the SDK is ready to receive messages from your application.
// Initialize the SDK BBMEnterprise.getInstance().initialize(this); //Start the SDK final boolean startSuccessful = BBMEnterprise.getInstance().start(); if (!startSuccessful) { //implies BBMEnterprise was already started. Call stop before trying to start again Toast.makeText(SetupActivity.this, "Service already started.", Toast.LENGTH_LONG).show(); }
BBMEnterprise.stop()
method when your application is finished using the SDK. If your
application needs to destroy all local SDK data associated with the user's
identity, use the
BBMEnterprise.wipe()
method.
Observe the SDK State
Your application can monitor the state of the SDK. For example, the code
below enables a sign-in button only when the state is STARTED
.
//Create an observer to monitor the SDK state private final Observer mEnterpriseStateObserver = new Observer() { @Override public void changed() { BBMEnterpriseState bbmEnterpriseState = BBMEnterprise.getInstance() .getState().get(); switch (bbmEnterpriseState) { case STARTING: break; case STARTED: mSignInButton.setVisibility(View.VISIBLE); break; case FAILED: //If the SDK state changes to FAILED read the failure reason. BBMEnterpriseFailureReason failReason = BBMEnterprise.getInstance().getFailureReason(); //Take action on the failure //... case STOPPED: default: mSignInButton.setVisibility(View.VISIBLE); break; } } }; //Add the observer to the state ObservableValue BBMEnterprise.getInstance().getState().addObserver(mEnterpriseStateObserver);
The Existence Idiom
When your application requests an item from the BbmdsProtocol
,
it receives an ObservableValue
as the result. However, the
true value of the item you requested might not be immediately known. For
example, when requesting a Chat
, the chat data might need to be
loaded from storage. The ObservableValue
that is immediately
returned will contain a placeholder value. To distinguish between the
placeholder value and the real value, there is the
the Existence
interface.
Existence Value | Description |
---|---|
MAYBE |
When the existence is MAYBE , the actual value is
unknown.
|
YES |
When the existence is YES , the value exists and can be
trusted.
|
NO |
When the existence is NO , the value does not exist in the
SDK.
|
Chat chat = BBMEnterprise.getInstance().getBbmdsProtocol().getChat("chatId").get(); switch (chat.exists){ case MAYBE: //Wait for the chat to be provided break; case YES: //A chat with this ID exists break; case NO: //A chat with this ID could not be found break; }
Generate an Authentication Token
Your application is responsible for providing access tokens to the SDK with
the AuthToken
APIs. These tokens are used to identify, authenticate, and authorize your
users.
As described in the Identity
Providers guide, you can configure your domain in the sandbox to have
user authentication disabled. Your application must still provide tokens
using the AuthToken
APIs, but instead of getting them from a real identity provider, your
application generates its own unsigned JWT tokens.
The Quick Start example application is simple enough that it doesn't use the Support library, so in it you will find this code that generates suitable tokens:
//User ID is hard-coded for convenience here String userId = "sampleUserId"; JSONObject header = new JSONObject(); header.put("alg", "none"); SecureRandom rand = new SecureRandom(); byte[] bytes = new byte[128]; //Get some random bytes rand.nextBytes(bytes); //Use the first 18 characters as the token ID String jti = Base64.encodeToString(bytes, base64Flags).substring(0, 18); JSONObject body = new JSONObject(); body.put("jti", jti); body.put("sub", userId); body.put("iat", System.currentTimeMillis() / 1000); //Expires in one hour. body.put("exp", System.currentTimeMillis() / 1000 + 60 * 60); String base64Header = Base64.encodeToString(header.toString().getBytes(), base64Flags); String base64Body = Base64.encodeToString(body.toString().getBytes(), base64Flags); token = base64Header + '.' + base64Body + '.';
Your application gives the authentication token to the SDK by sending an
AuthToken
message.
AuthToken authToken = new AuthToken(token, userId); BBMEnterprise.getInstance().getBbmdsProtocol().send(authToken);
Monitor the GlobalAuthTokenState
The GlobalAuthTokenState
tells you when the SDK needs an AuthToken
to perform
user
authentication. When the GlobalAuthTokenState
is Needed
, you must send an
AuthToken
to the SDK to start or maintain your application's authorization with the
BlackBerry Infrastructure.
Auth Token State | Description |
---|---|
Needed |
When the state is Needed , your application must send
an AuthToken to the SDK.
|
Ok |
When the state is Ok , your application does not need to
take any action.
|
Rejected |
When the state is Rejected , the SDK was unable to use
the AuthToken that your application gave it.
|
//Create an observer to monitor the auth token state private final Observer mAuthTokenStateObserver = new Observer() { @Override public void changed() { GlobalAuthTokenState authTokenState = BBMEnterprise.getInstance().getBbmdsProtocol() .getGlobalAuthTokenState().get(); if (authTokenState.getExists() != Existence.YES) { return; } switch (authTokenState.value) { case Ok: break; case Needed: //Generate an unsigned token to authenticate with the //BlackBerry Infrastructure generateAuthToken(); break; case Rejected: break; case Unspecified: break; } } };
GlobalAuthTokenState
and provide
an AuthToken
when the SDK needs one.
Monitor the GlobalSetupState
The
GlobalSetupState
indicates the state of the SDK endpoint setup process.
Setup State | Description |
---|---|
NotRequested |
When the setup state is NotRequested , your application
must register the local device as a new endpoint.
|
Ongoing |
When the setup state is Ongoing , the
progressMessage property of
the GlobalSetupState will indicate the current phase of
setup.
|
Full |
The SDK limits the number of endpoints of each type that an identity
can have registered with the BlackBerry Infrastructure simultaneously.
When the setup state is Full , the identity has reached
the maximum number of endpoints. Before continuing, at least one
endpoint must be deregistered. Your application can request the set
of existing endpoints and decide which endpoint to remove. The
identity's current set of endpoints can be requested with the
EndpointsGet
message.
|
//Create an observer to monitor the setup state global private final Observer mSetupStateObserver = new Observer() { @Override public void changed() { final GlobalSetupState setupState = BBMEnterprise.getInstance().getBbmdsProtocol() .getGlobalSetupState().get(); if (setupState.getExists() != Existence.YES) { return; } switch (setupState.state) { case NotRequested: //Register this device as a new endpoint registerDevice(); break; case Full: //Handle the case where this account has reached the maximum number //of registered endpoints handleFullState(); break; case Ongoing: //Ongoing has additional information in the progressMessage mSetupStateView.setText( setupState.state.toString() + ":" + setupState.progressMessage.toString()); break; case SyncRequired: //SyncRequired state is processed by the syncPasscodeStateObserver case Success: //Setup completed break; case Unspecified: break; } } };
Monitor for Endpoint Setup Errors
The SDK will send a SetupError
message if an error occurred
during endpoint setup. To listen to for a SetupError
, use an
InboundMessageObservable
of type SetupError
. The InboundMessageObservable
class listens for the specified inbound message type and triggers the
provided Observer
when a matching inbound message is received.
//Create an Observer to be notified when a SetupError occurs private final Observer mSetupErrorObserver = new Observer() { @Override public void changed() { SetupError setupError = mSetupErrorObservable.get(); //Take action on the endpoint setup error } }; //Create an InboundMessageObservable to listen for SetupError messages and notify //our Observer InboundMessageObservable<SetupError> setupErrorObservable = new InboundMessageObservable<>( new SetupError(), BBMEnterprise.getInstance().getBbmdsProtocolConnector()); mSetupErrorObservable.addObserver(mSetupErrorObserver);
InboundMessageObservable
can be used to observe
any inbound message from the SDK.
Monitor the GlobalSyncPasscodeState
When using the BlackBerry Key Management Service, your application must provide a passcode to the SDK during endpoint setup. The passcode could be obtained from the user or provided by the application. To recover existing security keys, your application must be able to provide the same passcode that was previously set.
When the
GlobalSetupState
is SyncRequired
, the SDK requires a passcode to complete
endpoint setup. The
GlobalSyncPasscodeState
should be checked to determine if you should provide a new passcode, or if
current security keys exist that can be recovered by providing the existing
passcode.
Passcode State | Description |
---|---|
New |
When the GlobalSyncPasscodeState value
is New , there are no security keys stored in
the BlackBerry Key Management
Service for this identity.
|
Existing |
When the
If your application discards existing keys, other endpoints will
resynchronize keys automatically, but not instantly. Whenever
possible, your application to should keep its existing keys.
To complete endpoint setup, your application sends
|
//Observes the GlobalSetupState and the GlobalSyncPasscodeState. //When the required a passcode is sent to complete endpoint setup using the //'SyncStart' message. private Observer mSyncPasscodeStateObserver = new Observer() { @Override public void changed() { GlobalSetupState setupState = BBMEnterprise.getInstance().getBbmdsProtocol() .getGlobalSetupState().get(); //When the GlobalSetupState is 'SyncRequired' then send the passcode //to the SDK to continue endpoint setup if (setupState.state == GlobalSetupState.State.SyncRequired) { GlobalSyncPasscodeState syncPasscodeState = BBMEnterprise.getInstance().getBbmdsProtocol() .getGlobalSyncPasscodeState().get(); //For simplicity, this example hard codes a passcode. //A passcode obtained from a user is a more secure solution. SyncStart syncStart = new SyncStart("user-passcode"); switch (syncPasscodeState.value) { case New: //No existing keys were found, so send the SyncStart with //action 'New' syncStart.action(SyncStart.Action.New); BBMEnterprise.getInstance().getBbmdsProtocol().send(syncStart); break; case Existing: //Existing keys stored in KMS were found, so send the SyncStart //with action 'Existing' syncStart.action(SyncStart.Action.Existing); BBMEnterprise.getInstance().getBbmdsProtocol().send(syncStart); break; default: //No action } } } };
BBMDS and Raw Protocol Events
BBMDS is the name of the JSON message passing protocol that's used inside the SDK on Android and iOS. On Android, each SDK BBMDS message has an equivalent Java class that handles serialization to and from JSON. Usually, your application does not to work directly with BBMDS.
The
BbmdsProtocol
class contains the accessors to retrieve BBMDS objects and send BBMDS
messages to the SDK. To listen for raw BBMDS messages register a
ProtocolMessageConsumer
with the
ProtocolConnector
.
//Example of sending a BBMDS message to mark a chat message as read BBMEnterprise.getInstance().getBbmdsProtocol().send(new ChatMessageRead("chatId", 1234)); //Example of requesting a chat from the BBMDS protocol, the Chat is returned //as an ObservableValue ObservableValue<Chat> chat = BBMEnterprise.getInstance().getBbmdsProtocol().getChat("chatId");
BbmdsProtocol
class is not thread
safe. When sending messages or requesting items your application must use
the main UI thread of your application. Using other threads can result in
undefined behavior.