Skip to content

Android Usage

This page explains how to use Context Decision to calibrate your custom model, and how to make decisions based on the predictions.

  • Step 1: Add ContextSDK to your app
  • Step 2: Ship app into production in Calibration Mode
  • Step 3: Once the model is ready, you can start making decisions based on the SDK's predictions

Usage

  • Use a different, unique flowName for each upsell funnel (e.g. upsell_prompt, upsell_onboarding, etc.)
    • If you have the same purchase flow, but from different parts of your app, please use different a different flowName for each.
    • We automatically analyze the overlap between different flows, to decide if there should be one, or more models.
  • The timing is crucial: You need to create the context object right before showing your prompt, and then log the outcome of the user interaction after the user has either accepted or dismissed the prompt.
  • For each context object you create, be sure to log at least one outcome, even if the user dismisses the prompt.

We recommend using this approach for most apps, unless you have a specific reason to use the advanced methods.

In most cases, ContextSDK will execute your block instantly. Only if your app was recently launched, or resumed from the background, it will take up to a few seconds to get a full context.

ContextSDK.optimize("upsell") { context ->
  // [Show the upgrade prompt here right after fetching the context]
  // Once you know if the user purchased or dismissed the upsell, log the outcome:
  context.log(EventOutcome.POSITIVE) // or EventOutcome.NEGATIVE
}
ContextSDK.Companion.optimize("upsell", null, null, context -> {
  // [Show the upgrade prompt here right after fetching the context]
  // Once you know if the user purchased or dismissed the upsell, log the outcome:
  context.log(EventOutcome.POSITIVE);
  return Unit.INSTANCE;
});

It's critical you trigger your prompt inside the block, which behaves as follows:

  • The callback is instantly executed if your app has been running for at least 3 seconds
  • The callback being executed within 3 seconds if your app was just put into the foreground recently

Use Instant Callbacks if you want to show a prompt right after a user action (e.g. opening a screen)

There are cases where you may prefer to always instantly have your callback executed. For example, when you want to show a prompt right after a certain user-action, to prevent the prompt showing up when the user already left the screen.

ContextSDK.optimize("upsell", maxDelayS = 0) { context ->
  // [Show the upgrade prompt here right after fetching the context]
  // Once you know if the user purchased or dismissed the upsell, log the outcome:
  context.log(EventOutcome.POSITIVE) // or EventOutcome.NEGATIVE
}
ContextSDK.Companion.optimize("upsell", 0, null, context -> {
  // [Show the upgrade prompt here right after fetching the context]
  // Once you know if the user purchased or dismissed the upsell, log the outcome:
  context.log(EventOutcome.POSITIVE);
  return Unit.INSTANCE;
});

Using maxDelayS = 0 means that the context object may not be complete, which may reduce data quality. We recommend not using this approach right after the app start. Use in places where the app usually has already been running for a few seconds.

If you want full control, you can use the instantContext method to get a Context object instantly. This comes with some extra complexity, as you also need to check the .shouldUpsell property yourself to decide if you should show the prompt or not.

val context = ContextSDK.instantContext("upsell", durationS = 3)
if (context.shouldUpsell) {
  // [Show the upgrade prompt here right after fetching the context]
  // Once you know if the user purchased or dismissed the upsell, log the outcome:
  context.log(EventOutcome.POSITIVE) // or EventOutcome.NEGATIVE
} else{
  context.log(EventOutcome.SKIPPED)
}
RealWorldContext context = ContextSDK.Companion.instantContext("upsell", 3, null);
if (context.getShouldUpsell()) {
  // [Show the upgrade prompt here right after fetching the context]
  // Once you know if the user purchased or dismissed the upsell, log the outcome:
  context.log(EventOutcome.POSITIVE); // or EventOutcome.NEGATIVE
} else{
  context.log(EventOutcome.SKIPPED);
}

During calibration phase, .shouldUpsell will always be true, so your app's behavior won't change. Once your custom model is rolled out, the .shouldUpsell property will reflect the prediction of your model.

Helpers

Some apps may not have an easy way to pass the context object from where you initially generate it, to where you know the outcome of the user interaction. In those cases, you can use the recentContext method to get the most recent context object you created based on the unique flowName.

  • This approach requires you to create the context object first.
  • Only the most recent context for each flowName name will be stored, and older context objects will be discarded.
  • The context object must already be created before you show the prompt.
OnboardingActivity.kt
ContextSDK.optimize("upsell") { context ->
  // [Prompt the user for an upgrade right after getting the context]
  // Don't log any outcomes here
}
UpsellActivity.kt
// Once the user either finished the purchase, or dismissed the upgrade:
val outcome = EventOutcome.POSITIVE // or EventOutcome.NEGATIVE

ContextSDK.recentContext("upsell")?.let { context ->
  context.log(outcome)
}
OnboardingActivity.java
ContextSDK.Companion.optimize("upsell", null, null, context -> {
  // [Prompt the user for an upgrade right after getting the context]
  // Don't log any outcomes here
  return Unit.INSTANCE;
});
UpsellActivity.java
// Once the user either finished the purchase, or dismissed the upgrade:
EventOutcome outcome = EventOutcome.POSITIVE; // or EventOutcome.NEGATIVE
RealWorldContext context = ContextSDK.Companion.recentContext("upsell");
if (context != null) {
  context.log(outcome);
}

You can provide additional outcome details, for example to indicate which type of product the user has purchased.

ContextSDK.optimize("show_all_deals") { context ->
  // [Show the user a list of deals]
  val selectedDeal = user.selectedDeal
  if (selectedDeal != null) {
    context.outcomeMetadata["deal_selected"] = selectedDeal.number
    context.outcomeMetadata["deal_value_in_usd"] = selectedDeal.usdValue
    context.log(EventOutcome.POSITIVE)
  } else {
    context.log(EventOutcome.NEGATIVE)
  }
}
ContextSDK.Companion.optimize("show_all_deals", null, null, context -> {
  // [Show the user a list of deals]
  Deal selectedDeal = user.selectedDeal;
  if (selectedDeal != null) {
    context.getOutcomeMetadata().set("deal_selected",selectedDeal.number);
    context.getOutcomeMetadata().set("deal_value_in_usd", selectedDeal.usdValue);
    context.log(EventOutcome.POSITIVE);
  } else {
    context.log(EventOutcome.NEGATIVE);
  }
  return Unit.INSTANCE;
});

The information provided via the outcomeMetadata will not be used to determine if it's currently a good moment, you can use Custom Signals for that.

The information provided is used to help you decide on variants. For example, you may have different types of offers, or different price points, and you want to train ContextSDK to recommend the best one based on the current user context.

Custom Signals

ContextSDK uses more than 200 signals based on various built-in sensors to make its predictions. However, you can improve its performance by providing additional data to the ContextSDK that's specific to your app. Some examples of what you could provide: in-game progress, number of friends/messages, number of entries in a list, etc.

Global signals will be used across all ContextSDK calls, and are an easy way to provide additional data to the ContextSDK.

ContextSDK.globalCustomSignals["number_of_levels_completed"] = 21
ContextSDK.Companion.getGlobalCustomSignals().set("number_of_levels_completed", 21);

You can easily override the global custom signals by providing the same key again using a different value. To remove a global custom signal, call the globalCustomSignals method with a null value.

In general, we recommend using global signals. But if you have custom signals that are specific to a certain flow, you can provide them as a parameter when getting your context object:

val customSignals = CustomSignals(
  mapOf(
    "number_of_friends" to SignalValue.IntValue(4)
  )
)

ContextSDK.optimize("onboarding_upsell", customSignals = customSignals) { context ->
  // ...
}
CustomSignals customSignals = new CustomSignals();
customSignals.set("number_of_friends", 4);
ContextSDK.Companion.optimize("onboarding_upsell", 0, customSignals, realWorldContext -> {
    // ...
});

Please be sure to consider the user's privacy and don't include any sensitive data. You are responsible for ensuring that you have the necessary rights to provide the data to us. Please at no point include any PII (including but not limited to emails, user IDs, IP addresses, exact locations, etc.) in your custom signals.

Additional Entry Points

Enhance user engagement by strategically adding new entry points for prompts, paywalls, or ads based on user context. ContextSDK enables you to add new triggers that respond dynamically to users’ real-world activities, helping you introduce upsell opportunities that are either too annoying to users or require a lot of effort to create a custom decision logic manually.

See below some examples of where you and your team could add additional prompts:

After a user completes a key action — such as signing up, finishing a level, editing a photo, or completing a workout — you can use ContextSDK to determine if this is an ideal time for a prompt. ContextSDK’s model evaluates each situation based on user context, allowing you to replace rigid conditions with intelligent, context-aware timing.

Example: Replace "every 5 levels completed, if it’s a weekend and if the user hasn’t been prompted in the last 2 hours, show a paywall" with ContextSDK’s shouldUpsell boolean. Simple as that.

Identify natural pauses in your app, like loading screens or cooldowns, where users may be more receptive to an ad or prompt, all while keeping user churn in mind. ContextSDK’s contextual understanding can evaluate which idle moments are opportune to prompt them, aiming best conversion timing.

Implementation Best Practices

  • Every app is different: different app types (e.g., fitness, gaming, dating, entertainment) may benefit from unique entry points. Identify key moments in your user’s journey and leverage ContextSDK’s on-device signals to time interactions naturally.
  • Experiment and iterate: after identifying new entry points, reach out to our team to get experiments going. We will constantly fine-tune your ML models improving prompt timing as you gather more data.

Using these strategies, you can introduce additional, relevant entry points that enhance the user experience while maximizing conversion opportunities.

Logging Custom Events

Optionally, you can log custom events for analytics purposes, to get insights into the real-world context of your users.

The events you log using this code is for informational purposes only and will not influence your custom model.

Use this to track users navigating through different screen in your app. It allow us to provide you insights into which screen is most commonly used in which real world context.

ContextSDK.trackPageView("page_identifier");

Use this to track certain user actions, like when a user enabled a certain feature, when a user tapped a button, when the user created an account, or when the user shared something. This allows us to provide you insights into which user actions are most commonly done in which real world context.

ContextSDK.trackUserAction("user_tapped_share_button");

Use this to track certain events in your app. This can be used generically to track any type of event. For example, you can add this to your existing analytics code to log all your existing events into ContextSDK. This allows us to provide you insights into which events are most commonly triggered in which real world context.

ContextSDK.trackEvent("custom_event");

Done with the integration? Header over to the Release Section to go live with your app!