Adobe Experience Platform Mobile SDKs – Adobe Target

This article is part of the Adobe Experience Platform Mobile SDKs mini-series. It is about tagging your app, more specifically about how to add Target into it.

You can find the overview here.

Target

Adobe Target is an application that allows you to target specific visitors with relevant content, to A/B test what relevant content might mean, and to generally replace content on the fly.

Targeting and A/B testing are fairly standard tools on web sites, and the different tools in the market usually use Javascript to directly replace or modify elements of the pages. In a native mobile app, the concept is a little bit different.

Target still manages who sees what, but it usually delivers JSON content which the app can render appropriately. That means that any targeting or A/B testing has to be planned before the app goes live.

In this article, I’ll show you how to add Target into your app, how to add simple A/B testing or targeting into it.

Adding Target

Let’s start with Launch, because that’s where we add the Target Extension into the Mobile Property for our app.

Go to “Extensions”, open the Catalog, then find the “Adobe Target” Extension and click the “Install” button.

[screenshot]
Add Target Extension
There shouldn’t be anything to add or edit in the configuration, so just hit “Save”

Your list of Extensions should now have 3 items.

[screenshot]
Mobile Property with 3 Extensions
Create a new Library (maybe called “Target”), add the Extension to it, then push it all the way through to production.

Now, when you go back to “Environments”, then open the “Production” install instructions, you will see some new stuff in there.

[screenshot]
Install Instructions with new elements
There is one more line for the build.gradle file, and some more code to put into your MainActivity.java file.

[screenshot]
New code for MainActivity.java
All in all, three new lines of code, and that’s it. You now have Target built into your app.

Unfortunately, it really doesn’t do anything for now… we need to use Target.

Calling Target

The simple template I used for my app comes with two UI elements: a text and a button.

[screenshot]
Simple app in emulator
I think that is almost perfect! I shall use the button to trigger Target, and the text field to show some results. I’ll also add a WebView into the mix, so I can load web pages into the app.

So, we have a button called “fab”, a TextView called “textView1”, and a WebView called “webView” (obviously) (got to love Java!)

My idea is this: when the app user taps the button, I want to ask Target what to display. I want Target to give me some text that I can put into “textView1”, and a URL for a page that I can load into “webView”

First step for that: head over to Target and create an Activity!

Target Activity

I’ll make it an “Experience Targeting” Activity, then on the next screen, select “Mobile App” and “Form” and hit the “Next” button.

[screenshot]
Create Experience Targeting Activity
Select “Mobile App” and “Form”, then click “Next”

[screenshot]
Make it a mobile, form based Activity
Note: if you have Target Premium, you might want to create a Workspace specifically for your app, and you might choose that here. If you do that, make sure you go back to Launch, find the Target Extension and configure the Workspace accordingly.

You now have an “untitled activity” with one audience “All Visitors” and one experience “Experience A”, and Target asks you to specify a location for the experience.

In my screenshot, you see that Target suggests the only location it knows at this point, “target-global-mbox”

[screenshot]
Target suggests “target-global-mbox”
I want my app to use a spcific, new location / mbox, though, so I will type a name for it: “w4dmobile-targeting1”

[screenshot]
Typed “w4dmobile-targeting1” instead
Keep that in mind! We’ll use it later in the code inside the app.

I want to target three groups of app users:

  • Those using the app in the Americas (or via Adobe VPN),
  • those using it in Switzerland, and
  • everyone else

I have built those audiences based on Target’s own geo location capabilities, to keep things simple.

Once I changed the audience for “Experience A” from “All Visitors” to “Americans (and Adobe VPN)”, I want to make it deliver something other than default content.

To do so, I click that little triangle, then “Create JSON Offer”

[screenshot]
Create a JSON Offer
The UI will open an editor window, into which I will type or paste the exact JSON that Target should deliver.

In my case, that’ll be this:

{
    "text": "Hi there!",
    "url": "https://webanalyticsfordevelopers.com/2019/10/15/launch-events-and-eddl-aka-jim-changed-my-mind/"
}

[screenshot]
The new JSON Offer
I will put the same structure, but with different text and URL, for the other two audiences.

Once I’ve done that, I will click into the “Untitled Activity” title and rename the Activity to “W4DMobile Geo Targeting”

[screenshot]
Renamed Activity
Clicking “Next” in the top right corner will bring me to the targeting screen, where I can see which audiences get which content. Those little “i” symbols next to the audiences can be used to check how those are defined.

[screenshot]
Targeting screen with Audience details
Once I saved the Activity and put it live, it’ll look like this:

[screenshot]
Live Activity
Note the “Activity Location” on the left. Again, we’ll need that later.

Back to the code…

Main Activity

We built a Target Activity with JSON Offers, meaning Target will deliver JSON. That means the app itself will have to do something with the JSON that Target sends back. We need to code.

At the heart of the code is a TargetRequest.

On line 71 of the code, we instantiate the targetRequest1 variable, and there are two things I want to point out:

  1. The TargetRequest constructor takes 4 parameters, of which 3 are necessary.
    1. The first parameter, mboxName is the location we “created” above, “w4dmobile-targeting1
    2. The second, targetParameters, can be used to pass parameters into Target. These can then be used within audience definitions, or in Activities, and they can be empty
    3. The third, defaultContent, is a String. It is what Target will deliver back to the app in case of issues, and so in our case, it must match the format of our JSON Offers
    4. The fourth, contentCallback is a function of type AdobeCallback. This function is what will be called once Target has sent content, or a timeout has occured. This is where the magic happens
  2. At this point, though, nothing has happened.

Once we add our TargetRequest to a List, we can call Target.retrieveLocationContent(), passing in all requests in one go, along with more parameters.

The Experience Cloud Mobile SDKs will see which requests need to be sent to Target, send them, handle the waiting and retrieval of a reply, then call all AdobeCallback functions.

Note that you don’t have to work exactly like this. You can “prefetch” Target content, and you can work with events.

Here is the code for MainActivity.java, in full, but you can also find it on github.

package com.webanalyticsfordevelopers.test.w4dmobile;

import android.os.Bundle;

import com.adobe.marketing.mobile.TargetPrefetch;
import com.adobe.marketing.mobile.TargetRequest;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.WebView;
import android.widget.TextView;

import com.adobe.marketing.mobile.AdobeCallback;
import com.adobe.marketing.mobile.Identity;
import com.adobe.marketing.mobile.InvalidInitException;
import com.adobe.marketing.mobile.Lifecycle;
import com.adobe.marketing.mobile.LoggingMode;
import com.adobe.marketing.mobile.MobileCore;
import com.adobe.marketing.mobile.Signal;
import com.adobe.marketing.mobile.Target;
import com.adobe.marketing.mobile.UserProfile;

import org.json.JSONException;
import org.json.JSONObject;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MobileCore.setApplication(this.getApplication());
        MobileCore.setLogLevel(LoggingMode.DEBUG);

        try {
            Target.registerExtension();
            UserProfile.registerExtension();
            Identity.registerExtension();
            Lifecycle.registerExtension();
            Signal.registerExtension();
            MobileCore.start(new AdobeCallback() {
                @Override
                public void call(Object o) {
                    MobileCore.configureWithAppID("3028746f70eb/d6b1ea7ebece/launch-e736742547f3");
                }
            });
        } catch (InvalidInitException e) {
            e.printStackTrace();
        }

        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View view) {
                Snackbar.make(view, "Creating Target request...", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
                TargetRequest targetRequest1 = new TargetRequest("w4dmobile-targeting1", null
                        , "{\"text\":\"default\",\"url\":\"https://webanalyticsfordevelopers.com/\""
                        , new AdobeCallback<String>() {
                    @Override
                    public void call(String jsonResponse) {
                        Snackbar.make(view, "Content received", Snackbar.LENGTH_LONG)
                                .setAction("Action", null).show();
                        // so this should be JSON content...
                        try {
                            JSONObject targetJSONResponse = new JSONObject(jsonResponse);
                            // replace content as needed
                            final String textForTextView = targetJSONResponse.getString("text");
                            final TextView textView1 = findViewById(R.id.textView1);
                            final String urlForWebViewAsText = targetJSONResponse.getString("url");
                            URL url = new URL(urlForWebViewAsText); // I like to check my URLs
                            final WebView webView = findViewById(R.id.webView);
                            if (urlForWebViewAsText.length() > 0) {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        textView1.setText(textForTextView);
                                        webView.loadUrl(urlForWebViewAsText);
                                        Snackbar.make(view, "Content replaced", Snackbar.LENGTH_LONG)
                                                .setAction("Action", null).show();
                                    }
                                });
                            }
                        } catch (JSONException e) {
                            Snackbar.make(view, "Content from Target not valid JSON", Snackbar.LENGTH_LONG)
                                    .setAction("Action", null).show();
                        } catch (MalformedURLException e) {
                            Snackbar.make(view, "Target returned invalid URL", Snackbar.LENGTH_LONG)
                                    .setAction("Action", null).show();
                        }
                    }
                });
                List<TargetRequest> requests = new ArrayList<>();
                requests.add(targetRequest1);
                // prep done, now retrieve content
                Snackbar.make(view, "Retrieving content from Target", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
                Target.retrieveLocationContent(requests, null);
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

When you let this run, and tap the button at the bottom, the TextView will update, and the WebView will load a page from my blog, both based on your (perceived) location.

You can sideload the APK from github here.

If you’re in the US, you should see “Hi there!”, and an article about EDDL and Jim Gordon. If you’re in Switzerland, you should see “Hoi!” and an article written by Urs Boller, and if you’re somewhere else, you should see “Hi!” and the “No Custom Code Challenge” article.

[screenshot]
App after successfully receiving content from Target
Next time, we’ll look at Analytics.

8 thoughts on “Adobe Experience Platform Mobile SDKs – Adobe Target

  1. Thanks a lot for share this example Jan, i’m trying implementing Target in Mobile, but its not totally clear for me the way on target behave or interacting with android content, specifically for a pretched API methods its necessarily some kind of logic on callback method, or it can works with a similar logic like your example, I mean, what its the core difference between Prefetched methods and this way to implement.
    Thanks in advance for your help.
    Best!

    Like

    1. Hi Arturo,

      I’m trying to answer what I think you need:

      For Mobile, Target delivers content as JSON. It is the job of the app to use that JSON.

      Example: the JSON could contain the URL of a hero banner. It would be up the the app code to load the correct banner.

      Does that answer your question at all?

      Like

  2. Hi Jan, i really appreciate your reply

    I’m checking more on deep your code and trying translate to Kotlin, also I’ve trying modified your key params Launch Id Enviroment and Target Location with my own keys but i’m still catching the exception “Content from Target not valid JSON” when run the listener, so looking backward on debug mode looks like the stack of retrieved jsonResponse are not getting populated from target, i’m wondering what could I’ve been missed, some ideas Jan about what could it be happening?
    and again, thanks for this amazing post, its really helpful.

    Like

      1. Hi again Jan, i really appreciate your reply, i resolve the issue and was about audience refinements, the Json works fine, but i don´t know if the first touch point behavior its correctly i mean, when i deploy any experiment A/B or Targeting Experience for first time the experiment never occurs until i re-launch the event or app, on your opinion, a recommendation would be start a Activity QA to populate the Adobe Target servers? And thanks again for your awesome post

        Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.