Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add setting a in-app event listener #147

Merged
merged 6 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/io.customer/android/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object Versions {
internal const val FIREBASE_MESSAGING = "22.0.0"
internal const val GRADLE_NEXUS_PUBLISH_PLUGIN = "1.1.0"
internal const val GRADLE_VERSIONS_PLUGIN = "0.39.0"
internal const val GIST = "2.1.5"
internal const val GIST = "2.2.0"
internal const val GOOGLE_PLAY_SERVICES_BASE = "17.6.0"
internal const val KLUENT = "1.68"
internal const val KOTLIN_BINARY_VALIDATOR = "0.10.1"
Expand Down
34 changes: 34 additions & 0 deletions messaginginapp/api/messaginginapp.api
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
public final class io/customer/messaginginapp/MessagingInAppModuleConfig : io/customer/sdk/module/CustomerIOModuleConfig {
public static final field Companion Lio/customer/messaginginapp/MessagingInAppModuleConfig$Companion;
public synthetic fun <init> (Lio/customer/messaginginapp/type/InAppEventListener;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getEventListener ()Lio/customer/messaginginapp/type/InAppEventListener;
}

public final class io/customer/messaginginapp/MessagingInAppModuleConfig$Builder : io/customer/sdk/module/CustomerIOModuleConfig$Builder {
public fun <init> ()V
public fun build ()Lio/customer/messaginginapp/MessagingInAppModuleConfig;
public synthetic fun build ()Lio/customer/sdk/module/CustomerIOModuleConfig;
public final fun setEventListener (Lio/customer/messaginginapp/type/InAppEventListener;)Lio/customer/messaginginapp/MessagingInAppModuleConfig$Builder;
}

public final class io/customer/messaginginapp/MessagingInAppModuleConfig$Companion {
Expand All @@ -21,3 +24,34 @@ public final class io/customer/messaginginapp/ModuleMessagingInApp : io/customer
public fun initialize ()V
}

public final class io/customer/messaginginapp/extension/GistExtensionsKt {
public static final fun getNewFromInApp (Lbuild/gist/data/model/Message;Lio/customer/messaginginapp/type/InAppMessage;)Lbuild/gist/data/model/Message;
public static final fun getNewRandom (Lbuild/gist/data/model/Message;)Lbuild/gist/data/model/Message;
}

public abstract interface class io/customer/messaginginapp/type/InAppEventListener {
public abstract fun errorWithMessage (Lio/customer/messaginginapp/type/InAppMessage;)V
public abstract fun messageActionTaken (Lio/customer/messaginginapp/type/InAppMessage;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public abstract fun messageDismissed (Lio/customer/messaginginapp/type/InAppMessage;)V
public abstract fun messageShown (Lio/customer/messaginginapp/type/InAppMessage;)V
}

public final class io/customer/messaginginapp/type/InAppMessage {
public static final field Companion Lio/customer/messaginginapp/type/InAppMessage$Companion;
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/customer/messaginginapp/type/InAppMessage;
public static synthetic fun copy$default (Lio/customer/messaginginapp/type/InAppMessage;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/customer/messaginginapp/type/InAppMessage;
public fun equals (Ljava/lang/Object;)Z
public final fun getDeliveryId ()Ljava/lang/String;
public final fun getInstanceId ()Ljava/lang/String;
public final fun getMessageId ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/customer/messaginginapp/type/InAppMessage$Companion {
}

Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
package io.customer.messaginginapp

import io.customer.messaginginapp.type.InAppEventListener
import io.customer.sdk.module.CustomerIOModuleConfig

/**
* In app messaging module configurations that can be used to customize app
* experience based on the provided configurations
*/
class MessagingInAppModuleConfig private constructor() : CustomerIOModuleConfig {
class MessagingInAppModuleConfig private constructor(
val eventListener: InAppEventListener?
) : CustomerIOModuleConfig {
class Builder : CustomerIOModuleConfig.Builder<MessagingInAppModuleConfig> {
private var eventListener: InAppEventListener? = null

fun setEventListener(eventListener: InAppEventListener): Builder {
this.eventListener = eventListener
return this
}

override fun build(): MessagingInAppModuleConfig {
return MessagingInAppModuleConfig()
return MessagingInAppModuleConfig(
eventListener = eventListener
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,16 @@ class ModuleMessagingInApp internal constructor(
override fun initialize() {
initializeGist(organizationId)
setupHooks()
configureSdkModule(moduleConfig)
setupGistCallbacks()
}

private fun configureSdkModule(moduleConfig: MessagingInAppModuleConfig) {
moduleConfig.eventListener?.let { eventListener ->
gistProvider.setListener(eventListener)
}
}

private fun setupGistCallbacks() {
gistProvider.subscribeToEvents(
onMessageShown = { deliveryID ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.customer.messaginginapp.extension

import build.gist.data.model.Message
import io.customer.messaginginapp.type.InAppMessage
import io.customer.sdk.extensions.random

fun Message.getNewFromInApp(
levibostian marked this conversation as resolved.
Show resolved Hide resolved
message: InAppMessage
): Message = Message(
instanceId = message.instanceId,
messageId = message.messageId,
properties = mapOf(
Pair(
"gist",
mapOf(
Pair("campaignId", message.deliveryId)
)
)
)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we aren't using the receiver Message, so this should have been on InAppMessage instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't understand your question.

Is the question you have a question that Shahroz asked in this PR or a different question?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what Rehan meant to say is, is an extension method on build.gist.data.model.Message and we aren't using the receiver "build.gist.data.model.Message" but we are using the InAppMessage model only.

So would it make more sense to have "InAppMessage" as a receiver rather than "build.gist.data.model.Message" which isn't being used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shahroz got this right. I was suggesting this method to be following instead:

fun InAppMessage.getNewMessage(): Message

As I don't see this receiver used anywhere in the extension.


fun Message.getNewRandom(): Message = getNewFromInApp(InAppMessage(String.random, String.random, String.random))
levibostian marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal interface GistApi {
fun setUserToken(userToken: String)
fun setCurrentRoute(route: String)
fun clearUserToken()
fun addListener(listener: GistListener)
fun subscribeToEvents(
onMessageShown: (deliveryId: String) -> Unit,
onAction: (deliveryId: String?, currentRoute: String, action: String) -> Unit,
Expand Down Expand Up @@ -41,6 +42,10 @@ internal class GistApiProvider : GistApi {
GistSdk.clearUserToken()
}

override fun addListener(listener: GistListener) {
GistSdk.addListener(listener)
}

override fun subscribeToEvents(
onMessageShown: (String) -> Unit,
onAction: (deliveryId: String?, currentRoute: String, action: String) -> Unit,
Expand All @@ -50,7 +55,7 @@ internal class GistApiProvider : GistApi {
override fun embedMessage(message: Message, elementId: String) {
}

override fun onAction(message: Message, currentRoute: String, action: String) {
override fun onAction(message: Message, currentRoute: String, action: String, name: String) {
val deliveryID = GistMessageProperties.getGistProperties(message).campaignId
onAction(deliveryID, currentRoute, action)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package io.customer.messaginginapp.provider

import android.app.Application
import build.gist.data.model.Message
import build.gist.presentation.GistListener
import io.customer.messaginginapp.type.InAppEventListener
import io.customer.messaginginapp.type.InAppMessage

internal interface InAppMessagesProvider {
fun initProvider(application: Application, organizationId: String)
fun setUserToken(userToken: String)
fun setCurrentRoute(route: String)
fun clearUserToken()
fun setListener(listener: InAppEventListener)
fun subscribeToEvents(
onMessageShown: (deliveryId: String) -> Unit,
onAction: (deliveryId: String, currentRoute: String, action: String) -> Unit,
Expand All @@ -18,7 +23,13 @@ internal interface InAppMessagesProvider {
* Wrapper around Gist SDK
*/
internal class GistInAppMessagesProvider(private val provider: GistApi) :
InAppMessagesProvider {
InAppMessagesProvider, GistListener {

private var listener: InAppEventListener? = null

init {
provider.addListener(this)
}

override fun initProvider(application: Application, organizationId: String) {
provider.initProvider(application, organizationId)
Expand All @@ -36,6 +47,10 @@ internal class GistInAppMessagesProvider(private val provider: GistApi) :
provider.clearUserToken()
}

override fun setListener(listener: InAppEventListener) {
this.listener = listener
}

override fun subscribeToEvents(
onMessageShown: (String) -> Unit,
onAction: (deliveryId: String, currentRoute: String, action: String) -> Unit,
Expand All @@ -55,4 +70,22 @@ internal class GistInAppMessagesProvider(private val provider: GistApi) :
}
)
}

override fun embedMessage(message: Message, elementId: String) {}

override fun onAction(message: Message, currentRoute: String, action: String, name: String) {
listener?.messageActionTaken(InAppMessage.getFromGistMessage(message), currentRoute = currentRoute, action = action, name = name)
}

override fun onError(message: Message) {
listener?.errorWithMessage(InAppMessage.getFromGistMessage(message))
}

override fun onMessageDismissed(message: Message) {
listener?.messageDismissed(InAppMessage.getFromGistMessage(message))
}

override fun onMessageShown(message: Message) {
listener?.messageShown(InAppMessage.getFromGistMessage(message))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.customer.messaginginapp.type

interface InAppEventListener {
fun messageShown(message: InAppMessage)
fun messageDismissed(message: InAppMessage)
fun errorWithMessage(message: InAppMessage)
fun messageActionTaken(message: InAppMessage, currentRoute: String, action: String, name: String)
}
Shahroz16 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.customer.messaginginapp.type

import build.gist.data.model.GistMessageProperties
import build.gist.data.model.Message

data class InAppMessage(
val instanceId: String,
val messageId: String,
val deliveryId: String? // (Currently taken from Gist's campaignId property). Can be null when sending test in-app messages
) {
companion object {
internal fun getFromGistMessage(gistMessage: Message): InAppMessage {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You already created Message extension earlier, do you think it would make more sense if we add it there?

fun Message.getFromGistMessage(): InAppMessage {
    val gistProperties = GistMessageProperties.getGistProperties(this)
    val campaignId = gistProperties.campaignId

    return InAppMessage(
        instanceId = this.instanceId,
        messageId = this.messageId,
        deliveryId = campaignId
    )
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do enjoy extensions and see value in creating a fun Message.getFromGistMessage(): InAppMessage extension function.

However, I do like encapsulating the construction of a InAppMessage object existing inside of the InAppMessage object.

If we want to leverage extensions, I vote we keep InAppMessage.getFromGistMessage() as-is but create an extension:

fun Message.getFromGistMessage() = InAppMessage.getFromGistMessage(this)

val gistProperties = GistMessageProperties.getGistProperties(gistMessage)
val campaignId = gistProperties.campaignId

return InAppMessage(
instanceId = gistMessage.instanceId,
messageId = gistMessage.messageId,
deliveryId = campaignId
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
package io.customer.messaginginapp

import androidx.test.ext.junit.runners.AndroidJUnit4
import build.gist.data.model.Message
import io.customer.commontest.BaseTest
import io.customer.messaginginapp.extension.getNewRandom
import io.customer.messaginginapp.provider.GistApi
import io.customer.messaginginapp.provider.GistInAppMessagesProvider
import io.customer.messaginginapp.provider.InAppMessagesProvider
import io.customer.messaginginapp.type.InAppEventListener
import io.customer.messaginginapp.type.InAppMessage
import io.customer.sdk.extensions.random
import org.amshove.kluent.internal.assertEquals
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.whenever

@RunWith(AndroidJUnit4::class)
internal class InAppMessagesProviderTest : BaseTest() {

private lateinit var gistInAppMessagesProvider: InAppMessagesProvider
private lateinit var gistInAppMessagesProvider: GistInAppMessagesProvider
private val gistApiProvider: GistApi = mock()
private val eventListenerMock: InAppEventListener = mock()

@Before
override fun setup() {
Expand Down Expand Up @@ -147,4 +154,44 @@ internal class InAppMessagesProviderTest : BaseTest() {
wasOnActionCalled shouldBeEqualTo false
wasOnErrorCalled shouldBeEqualTo true
}

@Test
fun eventListener_givenEventListenerSet_expectCallEventListenerOnGistEvents() {
val givenMessage = Message().getNewRandom()
val expectedInAppMessage = InAppMessage.getFromGistMessage(givenMessage)

gistInAppMessagesProvider.setListener(eventListenerMock)
verifyNoInteractions(eventListenerMock)

gistInAppMessagesProvider.onMessageShown(givenMessage)
verify(eventListenerMock).messageShown(expectedInAppMessage)

gistInAppMessagesProvider.onError(givenMessage)
verify(eventListenerMock).errorWithMessage(expectedInAppMessage)

gistInAppMessagesProvider.onMessageDismissed(givenMessage)
verify(eventListenerMock).messageDismissed(expectedInAppMessage)

val givenCurrentRoute = String.random
val givenAction = String.random
val givenName = String.random
gistInAppMessagesProvider.onAction(givenMessage, givenCurrentRoute, givenAction, givenName)
verify(eventListenerMock).messageActionTaken(expectedInAppMessage, currentRoute = givenCurrentRoute, action = givenAction, name = givenName)
}

@Test
fun eventListener_givenEventListenerSet_expectCallEventListenerForEachEvent() {
val givenMessage1 = Message().getNewRandom()
val expectedInAppMessage1 = InAppMessage.getFromGistMessage(givenMessage1)
val givenMessage2 = Message().getNewRandom()
val expectedInAppMessage2 = InAppMessage.getFromGistMessage(givenMessage2)

gistInAppMessagesProvider.setListener(eventListenerMock)
verifyNoInteractions(eventListenerMock)

gistInAppMessagesProvider.onMessageShown(givenMessage1)
verify(eventListenerMock).messageShown(expectedInAppMessage1)
gistInAppMessagesProvider.onMessageShown(givenMessage2)
verify(eventListenerMock).messageShown(expectedInAppMessage2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.customer.messaginginapp
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.customer.commontest.BaseTest
import io.customer.messaginginapp.provider.InAppMessagesProvider
import io.customer.messaginginapp.type.InAppEventListener
import io.customer.sdk.hooks.HookModule
import io.customer.sdk.hooks.HooksManager
import org.junit.Before
Expand All @@ -19,6 +20,7 @@ internal class ModuleMessagingInAppTest : BaseTest() {
private lateinit var module: ModuleMessagingInApp
private val gistInAppMessagesProvider: InAppMessagesProvider = mock()
private val hooksManager: HooksManager = mock()
private val eventListenerMock: InAppEventListener = mock()

@Before
override fun setup() {
Expand All @@ -27,7 +29,11 @@ internal class ModuleMessagingInAppTest : BaseTest() {
di.overrideDependency(InAppMessagesProvider::class.java, gistInAppMessagesProvider)
di.overrideDependency(HooksManager::class.java, hooksManager)

module = ModuleMessagingInApp(overrideDiGraph = di, organizationId = "test")
module = ModuleMessagingInApp(
moduleConfig = MessagingInAppModuleConfig.Builder().setEventListener(eventListenerMock).build(),
overrideDiGraph = di,
organizationId = "test"
)
}

@Test
Expand All @@ -42,5 +48,8 @@ internal class ModuleMessagingInAppTest : BaseTest() {

// verify events
verify(gistInAppMessagesProvider).subscribeToEvents(any(), any(), any())

// verify given event listener gets registered
verify(gistInAppMessagesProvider).setListener(eventListenerMock)
}
}