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.
Your list of Extensions should now have 3 items.
Now, when you go back to “Environments”, then open the “Production” install instructions, you will see some new stuff in there.
build.gradle
file, and some more code to put into your MainActivity.java
file.
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.
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.
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”
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”
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/" }
Once I’ve done that, I will click into the “Untitled Activity” title and rename the Activity to “W4DMobile Geo Targeting”
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:
- The
TargetRequest
constructor takes 4 parameters, of which 3 are necessary.- The first parameter,
mboxName
is the location we “created” above, “w4dmobile-targeting1 - 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 - The third,
defaultContent
, is aString
. 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 - The fourth,
contentCallback
is a function of typeAdobeCallback
. This function is what will be called once Target has sent content, or a timeout has occured. This is where the magic happens
- The first parameter,
- 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.
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!
LikeLike
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?
LikeLike
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.
LikeLike
Hm… to me that sounds as if the error was in the actual JSON content that you created in Target. I would check that, first.
LikeLike
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
LikeLike