Rxp module integration - rezolved/rezolve_sdk_sampleapp_android GitHub Wiki

RxpSdk

RxpSdk is a common SDK client which allows android developers to use following features:

  • authentication handling
  • updating access token
  • manage and handle geofence engagements
  • monitoring location updates
  • configuration of push notifications
  • and others

Prerequisites

Smart Triggers uses Firebase Cloud Messaging to send push notifications to users' devices.

Consumer of RxpSdk needs to provide Firebase Server Key in order to let Rezolve systems send push notifications. Please get in touch with your Rezolve contact in order to do so.

Dependencies

These dependencies are required by the Rxp module. They should be added in a project build.gradle:

dependencies {
   def rezolveSdk = "2616-bitplaces-5452b67" // TODO: Update it with proper, tagged version after we do the official release of new sdk.
   implementation "com.rezolve.sdk:rxp-android:$rezolveSdk"
}

Rxp manager helper classes and interfaces

Below the table describes Rxp interfaces are required to handle its state and features:

Interface or class name Description
Authenticator OkHttp3 interface which allows to update access token in case request fails with "token expired" error. Performs either preemptive authentication before connecting to a proxy server, or reactive authentication after receiving a challenge from either an origin web server or proxy server.
NotificationProperties Notification properties wrapper class which describes notification-related fields: channelId, smallIcon, color, priority, defaults, vibrationPattern, sound, autoCancel.
PushNotificationProvider Allows to manage and configure push notifications.
NotificationHelper Helper class to provide better control of notifications.
SspGeofenceDetector Abstraction class for geofence detector.
SspActManager A manager class that handles Rxp backend communication.

Key variables

Variable name Description
APP_CONTEXT Android application context
SDK_API_KEY Rezolve SDK Api Key
SMART_TRIGGERS_ENDPOINT Backend URL address which is used for networking communication

Rxp SDK initialization

RxpSdk should be initialized in Android application class onCreate() method.

Initialization of SspActManager was described here.

@Override 
public void onCreate() {
    super.onCreate();
    initRxpSdk()
}

fun initRxpSdk() {

        val geofenceEngagementAlerts = NotificationProperties(
            geofenceAlertsNotificationChannelId,               // channel id
            R.mipmap.ic_launcher,                              // small icon
            ContextCompat.getColor(APP_CONTEXT, R.color.blue), // color
            NotificationCompat.PRIORITY_HIGH,                  // priority
            Notification.DEFAULT_ALL,                          // notification options. The value should be one or more of the following fields combined
                                                               //    with bitwise-or: Notification.DEFAULT_SOUND, Notification.DEFAULT_VIBRATE, Notification.DEFAULT_LIGHTS.
                                                               //    For all default values, use Notification.DEFAULT_ALL.
            longArrayOf(1000, 1000, 1000, 1000, 1000),         // vibration pattern
            Settings.System.DEFAULT_NOTIFICATION_URI,          // sound
            true                                               // auto cancel
        )

        // you can find PushNotificationProvider example in the next section

        val pushNotificationProvider: PushNotificationProvider = object : PushNotificationProvider {
                override fun resetCache() {

                }

                override val messages: MutableSharedFlow<PushMessage>
                    get() = yourMessagesFlow

                override val pushToken: MutableSharedFlow<PushToken>
                    get() = yourPushTokenFlow
            }

        PushNotificationDIProvider.pushNotificationProvider = pushNotificationProvider

        val authenticator = object : Authenticator {
            override fun authenticate(route: Route?, response: Response): Request? {
                // your implementation of okhttp3 Authenticator that refreshes expired Rezolve Core SDK access token. Check section below for an example.
            }
        }

        val tokenHolder: TokenHolder = object : TokenHolder {
            // implement methods
        }

        val accessTokenFlowable = tokenHolder.observeAccessTokenAsFlow()

        val notificationHelper = object : NotificationHelper {
            // implement methods
        }

        val rxpSdk = RxpSdk.Builder(APP_CONTEXT)
            .authenticator(authenticator)
            .accessTokenFlowable(accessTokenFlowable)
            .notificationAlerts(geofenceEngagementAlerts)
            .pushNotificationProvider(pushNotificationProvider)
            .notificationHelper(notificationHelper) // you can use NotificationHelperImpl(APP_CONTEXT) or provide your own implementation
            .apiKey(SDK_API_KEY)
            .endpoint(SMART_TRIGGERS_ENDPOINT)
            .geofenceDetector(geofenceDetector)
            .sspActManager(sspActManager)
            .build()

        RxpSdkProvider.sdk = rxpSdk
    }

PushNotificationProvider

object FCMManagerProvider {
    lateinit var manager: FCMManager
}

Initialize FCMManager in Android application class onCreate() method.

class FCMManager constructor(context: Context) : PushNotificationProvider {

    private val _token = MutableSharedFlow<PushToken>(
        replay = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )

    private val _messages = MutableSharedFlow<PushMessage>(
        replay = 1,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )

    override val pushToken: Flow<PushToken>
        get() = _token.distinctUntilChanged()

    override val messages: Flow<PushMessage>
        get() = _messages.distinctUntilChanged()

    init {
        FirebaseApp.initializeApp(context)
        FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
            if (!task.isSuccessful) {
                Log.e(TAG, "Fetching FCM registration token failed", task.exception)
                _token.tryEmit(PushToken.None)
                return@OnCompleteListener
            }
            val token = task.result
            if( token != null ){
                _token.tryEmit(PushToken.FCM(token))
            } else {
                Log.e(TAG, "FCM token is null")
                _token.tryEmit(PushToken.None)
                return@OnCompleteListener
            }
        })
    }

    fun updateToken(newToken: String) {
        _token.tryEmit(PushToken.FCM(newToken))
    }

    fun onMessageReceived(message: RemoteMessage) {
        println("$TAG.onMessageReceived: $message")
        _messages.tryEmit(message.toPushMessage())
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    override fun resetCache(){
        _messages.resetReplayCache()
    }

    companion object {
        const val TAG = "FCM_Manager"
    }
}

class RezolveFirebaseMessagingService : FirebaseMessagingService() {

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)
        println("RezolveFirebaseMessagingService.onMessageReceived: $message")
        FCMManagerProvider.manager.onMessageReceived(message)
    }

    override fun onNewToken(token: String) {
        Log.d(TAG, "Refreshed token: $token")
        FCMManagerProvider.manager.updateToken(token)
    }

    companion object {
        const val TAG = "RezolveFMS"
    }
}

private fun RemoteMessage.toPushMessage() = PushMessage(this.data)

Don't forget to register messaging service in your manifest:

    <application>

        <service
            android:name="com.yourpackagename.RezolveFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

    </application>

Authenticator

An example of RezolveSDK.AuthRequestProvider was described here

        class RezolveAuthenticator(private val authRequestProvider: RezolveSDK.AuthRequestProvider) : Authenticator {
            override fun authenticate(route: Route?, response: Response): Request? {
                if (responseCount(response) > 1) {
                    return null // If it've already been tried, give up.
                }
                val getAuthRequest: RezolveSDK.GetAuthRequest = authRequestProvider.authRequest
                if (getAuthRequest.isSuccessful) {
                    val headersMap: MutableMap<String, String>? = getAuthRequest.headersMap
                    val builder = response.request.newBuilder()
                    if (headersMap != null) {
                        for ((key, value) in headersMap) {
                            builder.header(key, value)
                        }
                    }
                    return builder.build()
                }
                return null
            }

            private fun responseCount(response: Response?): Int {
                return if (response == null) 0 else 1 + responseCount(response.priorResponse)
            }
        }

SspGeofenceDetector

There are various providers of geofence detection. It's up to you to decide which one best suits your needs. The default implementation of SspGeofenceDetector, based on Google Geofencing Client was provided in ssp-google-geofence module.

dependencies {
   implementation "com.rezolve.sdk:ssp-google-geofence:$rezolveSdkVersion"
}

If you choose to use it, use provided Builder class to create an instance:

val geofenceDetector = GoogleGeofenceDetector.Builder()
                .transitionTypes(GoogleGeofenceDetector.TRANSITION_TYPE_ENTER or GoogleGeofenceDetector.TRANSITION_TYPE_EXIT)
                .build(context)

Required permissions

Certain system permissions are required to successfully detect geofences. Obviously to provide location updates you need to obtain device position:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Since Android Api Level 26 in order to register geofences access to background location is required:

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

Also, since Android 33 to show notifications in a system bar another permission is needed:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

Please keep in mind that these are runtime permissions, which means user has to opt-in after they start the app. For more information please refer to official android docummentation.

⚠️ **GitHub.com Fallback** ⚠️