Kotlin frontend integration

This page describes an example Froomle integration using the Kotlin programming language. We’ll give examples on how to request personalized recommendations and register events through the Froomle APIs. We’ll assume this is a frontend integration where no authentication is required, for authenticated access please refer to the page on authentication first.

Registering events and requesting recommendations is the bulk of the integration work, leaving only the synchronization of your item metadata, which should be handled on the backend for security reasons.

You can find an executable Kotlin file containing all code on this page here.

Setup

When you sign up with Froomle you will be assigned a subdomain and one or more environments (for example one for production and development). These determine the API url, which we’ll assign to a variable for easy reuse:

val froomleApi = "https://{subdomain}.froomle.com/api/{environment}"

Each API call expects some contextual information, namely what type of page the user is currently on, how it is being served (desktop, app, ..) and who the user is. We’ll also assign these once for reuse across requests:

val channel = "..."
val page_type = "..."
val context_item = "..."
val context_item_type = "..."
val device_id = "..."
val user_id = "..."

The channel is how the page is being served. Should be set to "www-desktop", "www-mobile", "mobile-app" or "test". The page_type identifies a unique location where you’ve integrated one or more lists with Froomle recommendations that is distinctly different from other locations and in addition, this page-type is useful to differentiate on in our reporting. If the current page is a detail page for one of your items then include the item id as the context_item. If not, omit this field. The device_id is a unique identifier for the user that persists between sessions. If the user does not consent to tracking a value of "anonymous" can be used here. The user_id is an identifier for logged-in users that is stable across devices. If the user is not logged in it can be omitted.

Requesting recommendations

Below is example code for requesting recommendations from the Froomle API. We’re using the standard Java HttpClient for making http requests and the kotlinx.serialization library for converting json bodies to and from kotlin objects, but any library can be used here. The object attributes match the fields expected by the recommendations API:

val jsonFormat = Json { explicitNulls = false; ignoreUnknownKeys = true }

@Serializable
data class RecommendationRequest(
    val channel: String,
    val page_type: String,
    val context_item: String? = null,
    val context_item_type: String? = null,
    val device_id: String,
    val user_id: String? = null,
    val lists: List<RequestList>,
)

@Serializable
data class RequestList(
    val list_name: String,
    val list_size: Int,
    val configuration_id: String? = null,
)

@Serializable
data class Recommendations(
    val request_id: String,
    val user_group: String,
    val lists: List<ResponseList>,
)

@Serializable
data class ResponseList(
    val list_name: String,
    val list_size: Int,
    val configuration_id: String? = null,
    val items: List<Item>,
)

@Serializable
data class Item(
    val item_id: String,
    val rank: Int,
)

fun fetchRecommendations(recommendationRequest: RecommendationRequest): Recommendations {
    val client = HttpClient.newBuilder().build()

    val body = jsonFormat.encodeToString(recommendationRequest)
    val httpRequest = HttpRequest.newBuilder()
        .uri(URI.create("$froomleApi/recommendations/requests"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(body))
        .build()
    val httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString())

    if (httpResponse.statusCode() >= 300) {
        throw Exception("Unexpected ${httpResponse.statusCode()}: ${httpResponse.body()}")
    }

    return jsonFormat.decodeFromString(httpResponse.body())
}

Here’s how you might use the above function to request recommendations and iterate over the recommended items in order:

val list_name = "..."

val recommendationRequest = RecommendationRequest(
    channel = channel,
    page_type = page_type,
    context_item = context_item,
    context_item_type = context_item_type,
    device_id = device_id,
    user_id = user_id,
    lists = listOf(
        RequestList(
            list_name = list_name,
            list_size = 5,
        )
    ),
)
val recommendations = fetchRecommendations(recommendationRequest)
val responseList = recommendations.lists[0]
for (item in responseList.items) {
    println("Recommended item ${item.rank}: ${item.item_id}")
}

If requesting multiple recommendation lists at once please make sure each list_name is unique. When you sign up with Froomle you can specify what type of recommendations you’d like to appear in each list.

Rendering recommendations

To render the recommended items you can either fetch relevant display information such as title and thumbnail from your own backend, or ask Froomle to include this data in the recommendation response. To read the extra fields from the response remember to add them to the kotlin data class:

@Serializable
data class Item(
    val item_id: String,
    val rank: Int,

    val your_custom_field: String,
    // ...
)

Sending events

Using our events API is very similar. In this case we chose to store the request body in a single Event data class that includes fields for multiple types of events, but you could split the class up by event type for stricter static typing if desired.

For an overview of different types of events and fields please refer to the events concept page. The news and e-commerce example pages have an overview of required events and how they are used in those settings.

@Serializable
data class Events(
    val events: List<Event>
)

@Serializable
data class Event(
    val event_type: String,
    val channel: String,
    val page_type: String,
    val device_id: String,
    val user_id: String? = null,
    val action_item: String,
    val action_item_type: String,

    val request_id: String? = null,
    val user_group: String? = null,
    val list_name: String? = null,
)

fun sendEvents(events: Events) {
    val client = HttpClient.newBuilder().build()

    val body = jsonFormat.encodeToString(events)
    val httpRequest = HttpRequest.newBuilder()
        .uri(URI.create("$froomleApi/events"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(body))
        .build()
    val httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString())

    if (httpResponse.statusCode() >= 300) {
        throw Exception("Unexpected ${httpResponse.statusCode()}: ${httpResponse.body()}")
    }
}

Here’s how you might use the function above to send a number of different events:

  • detail_pageview

  • impression

  • click_on_recommendation

Sends a detail_pageview event for an item with id viewedItem

val viewedItem = "..."

val detailPageviewEvents = Events(
    events = listOf(
        Event(
            event_type = "detail_pageview",
            channel = channel,
            page_type = page_type,
            device_id = device_id,
            user_id = user_id,
            action_item = viewedItem,
            action_item_type = "article",
        )
    )
)
sendEvents(detailPageviewEvents)

Sends impression events for all recommended items, articles in this case. We’re using the response from the fetchRecommendations call in the previous section here. Note that ideally impression events are only sent once the recommendations become visible to the user (they might initially be hidden below the fold for example).

val impressionEvents = Events(events = responseList.items.map {
    Event(
        event_type = "impression",
        channel = channel,
        page_type = page_type,
        device_id = device_id,
        user_id = user_id,
        action_item = it.item_id,
        action_item_type = "article",

        request_id = recommendations.request_id,
        user_group = recommendations.user_group,
        list_name = list_name,
    )
})
sendEvents(impressionEvents)

Sends a click_on_recommendation event for a recommended item, the first one in this case. We’re using the response from the fetchRecommendations call in the previous section here.

val clickedItem = responseList.items[0].item_id  // assuming the 1st recommendation was clicked

val clickOnRecommendationEvents = Events(
    events = listOf(
        Event(
            event_type = "click_on_recommendation",
            channel = channel,
            page_type = page_type,
            device_id = device_id,
            user_id = user_id,
            action_item = clickedItem,
            action_item_type = "article",

            request_id = recommendations.request_id,
            user_group = recommendations.user_group,
            list_name = list_name,
        )
    )
)
sendEvents(clickOnRecommendationEvents)