Versions Compared
Key
- This line was added.
- This line was removed.
- Formatting was changed.
Technical Documentation
The Phunware Mobile Engagement SDK allows you to easily integrate location based messaging and notifications into your app.
Initialization
When your app first starts up, you need to initialize the Mobile Engagement SDK using the Engagement.Builder
. This is where you provide your AppId, Access Key,
and Signature Key
from the Maas Portal. All production apps should use the production environment.
GCM Registration
When setting up your app, you also need to register with GCM in order to receive push notifications from the Mobile Engagement backend. To do so, follow the steps that can be found in Googles official documentation at:
https://developers.google.com/cloud-messaging/android/client
Note: All manfiest changes required in the setup have been done in the SDK's AndroidManfiest file.
Logging
By default, the Phunware Mobile Engagement SDK will log to the Android system log and a log file, and will not log in production environments. This is entirely configurable using the builder's addLogger(Logger logger)
method.
You can easily create your own Logger
implementation that will do whatever you'd like with log messages, or use one of the built in Logger
s.
Code Block | ||||
---|---|---|---|---|
| ||||
public class CrashReportingLogger { @Override public void v(String tag, String message, Throwable t) { // no op } @Override public void i(String tag, String message, Throwable t) { // no op } @Override public void d(String tag, String message, Throwable t) { // no op } @Override public void w(String tag, String message, Throwable t) { // no op } @Override public void e(String tag, String message, Throwable t) { CrashReportingLibrary.logError(tag, message, throwable); } @Override public void wtf(String tag, String message, Throwable t) { CrashReportingLibrary.logError(tag, message, throwable); } } |
This allows you to easily use or discard log messages as you see fit.
Managers
The Phunware Mobile Engagement SDK is broken up into 3 managers, LocationManager
, MessageManager
and AttributeManager
. Once the Engagement SDK has been initialized, these can be accessed using static accessors on the LocationMessaging
class.
Code Block | ||||
---|---|---|---|---|
| ||||
MessageManager messages = Engagement.messageManager(); |
If you ensure the Mobile Engagement SDK is initialized in your Application's onCreate
method, then you'll be able to access the managers from anywhere within your app (except ContentProviders).
LocationManager
The LocationManager
is your entry point into the Location components of the Mobile Engagement SDK. Through it you can start or stop the Location services, access current geozone state, and register for notifications when geozone state changes.
Starting and Stopping Location Services
By default, the Mobile Engagement SDK will monitor geozones and notify the server when the user enters or leaves them. App developers may want to allow users to opt out of location tracking, or user may deny the app location permissions on newer versions of Android, so it's important that app developers are able to start and stop the service.
To manage the location state, simply call Engagement.locationManager().start()
or Engagement.locationManager().stop()
. This setting will be persisted across app restarts and device reboots.
If your app targets API level 21 or higher, you will need to manage permissions at runtime. This means disabling the LocationManager until the user grants the ACCESS_FINE_LOCATION
permission.
Code Block | ||||
---|---|---|---|---|
| ||||
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); checkPermissions(); } private void checkPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // Stop the LocationManager until we have permission Engagement.locationManager().stop(); if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) { // Show an explanation to the user of why the permission is important } else { ActivityCompat.requestPermissions( this, new String[]{ Manifest.permission.ACCESS_FINE_LOCATION }, PERMISSIONS_REQUEST_LOCATION); } } } @Override public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) { switch(requestCode) { case PERMISSIONS_REQUEST_LOCATION: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The user granted permission, so start the LocationManager Engagement.locationManager().start(); } return; } } } |
The same starting and stopping of the LocationManager can be applied if you explicitly allow the user to opt out of location services.
Access Geozone State
The LocationManager
can be used to get the current state of Geozones. This can allow you to use the known Geozones to populate a map of locations, or determine whether the user is currently inside a location.
All calls to get Geozone information from the LocationManager
are asynchronous, so you need to provide a Callback
and display appropriate UI.
Code Block | ||||
---|---|---|---|---|
| ||||
public class MyStoreFragment extends Fragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final long geozoneId = getArguments().getLong(GEOZONE_ID); // Show the loading view until data has been loaded showLoadingView(); // Load details about the selected Geozone Engagement.locationManager().getGeozone(geozoneId, new Callback<Geozone>() { @Override public void onSuccess(Geozone data) { showStoreDetails(data); } @Override public void onFailed(Throwable e) { Log.e(TAG, "Failed to load geozone detail.", e); showError(e.getMessage()); } }); // Check if the user is currently inside the geozone, and update UI accordingly Engagement.locationManager().getInsideGeozones(new Callback<List<Geozone>>() { @Override public void onSuccess(List<Geozone> data) { showInsideStoreView(); } @Override public void onFailed(Throwable e) { Log.e(TAG, "Failed to load inside geozones.", e); } }); } } |
Register for Geozone State Changes
You may want to register to be notified when the user enters or leaves geozones, or when new geozones are added or removed from the list. There are two ways to do this with the Phunware Mobile Engagement SDK, depending on whether you need to be notified when your app is in the foreground or background.
Foreground Geozone Notifications
To be notified of geozone state changes when your app is in the foreground, for instance to update a your app's view state when the user enters a location, you can add one or more LocationListener
s to the LocationManager
. This listener will be notified any time the state of Geozones changes while it is still registered.
Code Block | ||||
---|---|---|---|---|
| ||||
public class InStoreCouponView extends View implements LocationListener { private Long geozoneId; @Override public void onAttachedToWindow() { super.onAttachedToWindow(); Engagement.locationManager().addLocationListener(this); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); Engagement.locationManager().removeLocationListener(this); } @Override public void onZoneEntered(Long id) { if (id == geozoneId) { setVisibility(View.VISIBLE); } } @Override public void onZoneExited(Long id) { if (id == geozoneId) { setVisibility(View.GONE); } } } |
Be sure to remove the LocationListener
when it is no longer needed to avoid leaks.
Background Geozone Notifications
It's also possible to register for notifications of Geozone events when the app isn't running. This can be useful if you want to process events with a service or add local notifications.
In order to receive background notifications of Geozone events, you simply need to register an IntentService
in your manifest that responds to at least one of the following intent filters:
Code Block |
---|
com.phunware.engagement.ZONE_ADDED com.phunware.engagement.ZONE_REMOVED com.phunware.engagement.ZONE_UPDATED com.phunware.engagement.ZONE_ENTERED com.phunware.engagement.ZONE_EXITED com.phunware.engagement.ZONE_CHECK_IN com.phunware.engagement.ZONE_CHECK_IN_FAILURE |
Your IntentService
will then be started and notified for any Geozone events you are interested whether your app is running or not.
MessageManager
The MessageManager allows you to interact directly with the LocationMessaging messages, including retrieving and modifying messages, and registering to be notified of message updates.
The Public Interface
To retrieve an instance of MessageManager, simply call `Engagement.messageManager()` after initializing the Engagement library.
Code Block | ||||
---|---|---|---|---|
| ||||
public interface MessageManager { void addMessageListener(@NonNull MessageListener); void removeMessageListener(@NonNull MessageListener); void getMessages(Callback<List<Message>> callback); void getAllMessages(Callback<List<Message>> callback); void getUnreadMessages(Callback<List<Message>> callback); void findMessageById(@NonNull String id, Callback<Message> callback); void setMessageDeleted(@NonNull String id, Callback<Message> callback); void setMessageRead(@NonNull String id, Callback<Message> callback); } |
Listening for Message Events
The MessageManager accepts multiple MessageListener objects which can be notified of events, like when a message is received, deleted or updated. This allows app developers to manage foreground components when message status changes, like updating an unread message count, or changing the display of messages in a list based on their status.
Code Block | ||||
---|---|---|---|---|
| ||||
public interface MessageListener { void onMessageAdded(MessageManager manager, Message message); void onMessageDeleted(MessageManager manager, Message message); void onMessageUpdated(MessageManager manager, Message message); } |
Receiving Background Events
All of the same events that can be consumed by a listener can also be consumed by a service so that app developers don't need to keep anything retained in memory to receive updates. By defining a non-exported service, app developers can have the app woken up when message events happen.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<manifest ...> <application> <service android:name=".MyMessageListenerService" android:exported="false"> <intent-filter> <action android:name="com.phunware.engagement.MESSAGE_ADD" /> <action android:name="com.phunware.engagement.MESSAGE_DELETE" /> <action android:name="com.phunware.engagement.MESSAGE_UPDATE" /> </intent-filter> </service> </application> </manifest> |
Setting exported to false means that the service is not accessible to any other apps on the system, reducing potential security vulnerabilities and ensuring the an app developer's app only receives messages destined for it.
The app developer can define any or all of the following actions:
com.phunware.engagement.MESSAGE_ADD
com.phunware.engagement.MESSAGE_DELETE
com.phunware.engagement.MESSAGE_UPDATE
By only subscribing to events that the developer cares about, we limit the amount of code that is needlessly run.
While the service implementation will be a standard Android IntentService, the SDK will provide a convenient abstract class that will take care of determining the appropriate action and also retrieving the Message from the Intent.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
public class MyMessageListenerService extends MessageListenerService { public MyMessageListenerService() { super("MyMessageListenerService"); } @Override public void onMessageAdded(Message message) { // ... } @Override public void onMessageDeleted(Message message) { // ... } @Override public void onMessageUpdated(Message message) { // ... } } |
Customizing Notifications
User notifications are entirely handled by the SDK, so you don't need to worry about when to show what notifications. There are times, however, when you may want to customize the notification message, perhaps to personalize it or make it more relevant to the user. This can be accomplished by implementing a bound service. Since the 3.2.0 release, we also provide the ability to customize the foreground notification that gets spawned in the same way that the user can customize all other notifications if the user is ranging for beacons. Additionally, in 8.0 and above, we provide a way to edit the various notification channels for each type of notification. Since we provide the message type with each channel, the user can have as many or as little channels as they like. If the user is okay with the default channels no change is needed and they can proceed with the default channel for each message type.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<manifest ...> <application> <service android:name=".MyNotificationEditService" android:exported="false"> <intent-filter> <action android:name="com.phunware.engagement.EDIT_NOTIFICATION" /> </intent-filter> </service> </application> </manifest> |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
public class MyNotificationEditService extends NotificationCustomizationService { @Override public void editNotification(Notification.Compat.Builder notificationBuilder) { notificationBuilder .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) .setSmallIcon(R.drawable.ic_motorcycle_black_24dp); } @Override public void editForegroundNotification(NotificationCompat.Builder notificationBuilder) { // Use the default notification sound for all notifications notificationBuilder .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) .setSmallIcon(R.drawable.ic_motorcycle_black_24dp); } @Override public void editNotificationChannel(NotificationChannel notificationChannel, MessageType messageType) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { switch (messageType) { case BROADCAST: notificationChannel.setName("SampleBroadcastChannel"); break; case GEOZONE: notificationChannel.setName("SampleGeozoneChannel"); break; case ONDEMAND: notificationChannel.setName("SampleOnDemandChannel"); break; case BEACON: notificationChannel.setName("SampleBeaconChannel"); break; case FOREGROUND: notificationChannel.setName("SampleForegroundNotificationChannel"); break; case DEFAULT: default: notificationChannel.setName("The default Channel name"); } } } } |
AttributeManager
The AttributeManager allows you to interact directly with the Mobile Engagement attributes, including retrieving and modifying attributes, and retrieving attribute metadata.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
public interface AttributeManager { void getAttributeMetadata(Callback<List<AttributeMetadataItem>> callback); void getProfileAttributes(Callback<ProfileAttribute> callback); void updateProfileAttribute(Map<String, Object> attributes, Callback<ProfileAttribute> callback); } |
Deep Linking
Deep linking into the SDK can be done taking advantage of met data sent on a campaign. For example:
First, register your intent filter in the AndroidManifest.xml where you want your intents to come in:
Code Block | ||||
---|---|---|---|---|
| ||||
<activity android:name=".activities.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="engagement/message" /> </intent-filter> </activity> |
Then, inside of the Activity that you want to receive these intents, handle the message metadata as you see fit.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
FragmentTransaction trans = getSupportFragmentManager().beginTransaction(); switch (getIntent().getAction()) { case Intent.ACTION_VIEW: trans.replace(R.id.content, new ConfigFragment()); Message intentMessage = getIntent().getParcelableExtra(MessageManager.EXTRA_MESSAGE); Engagement.analytics().trackCampaignAppLaunched(intentMessage.campaignId(), intentMessage.campaignType()); // Get metadata off of message to find out where to link to // It's important to know that we can send more than one MessageMetadata object on a message so when // we call intentMessage.metaData, we get a list of metadata objects. For this example, // we will just grab the first for examples sake MessageMetadata deepLinkMessageMetadata = intentMessage.metaData.get(0); // In this example, we assume the value is the fully qualified class name that matches // the intent filter of the activity we want to start such as 'packagename.classname'. // If using this way make sure you fully qualify a valid class name. String metaDataValue = deepLinkMessageMetadata.value(); Intent intent = new Intent(this, Class.forName(metaDataValue)); startActivity(intent); default: // Handle default case } |
Table of Contents | ||||||
---|---|---|---|---|---|---|
|
Campaign Messages
Message State Transitions
Campaign messages go through several states during their lifecycle..
Initially when a message is received it is in the UNREAD state. When the user marks it as read using Engagement SDK APIs , the state changes to READ. When the message is in the read state, if another new message is received for the same campaign, the message is marked as UNREAD again.
Finally when the users deletes the message it goes to the DELETED state whereupon it is deleted from the SDK storage.
Sending Notifications
Following diagram represents the complete flow of a campaign message after it is received