diff --git a/README.md b/README.md
index ccc518c2..b3e3e86c 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ To start using the SDK, include this in your app *build.gradle*
```java
dependencies {
- compile 'it.near.sdk:nearit:2.2.5'
+ compile 'it.near.sdk:nearit:2.2.6'
}
```
diff --git a/docs/in-app-content.md b/docs/in-app-content.md
index ec18dc19..38fc2b0d 100644
--- a/docs/in-app-content.md
+++ b/docs/in-app-content.md
@@ -1,21 +1,21 @@
# Handle In-app Content
-NearIT takes care of delivering content at the right time, you will just need to handle content presentation.
-
-## Foreground vs Background
-
-Recipes either deliver content in background or in foreground but not both. Check this table to see how you will be notified.
+After an user **taps on a notification**, you will receive content through an intent to your app launcher activity.
+If you want to just check if the intent carries NearIT content use this method:
+```java
+boolean hasNearItContent = NearUtils.carriesNearItContent(intent);
+```
+To extract the content from an intent use the utility method:
+```java
+NearUtils.parseCoreContents(intent, coreContentListener);
+```
-| Type of trigger | Delivery |
-|----------------------------------|--------------------|
-| Push (immediate or scheduled) | Background intent |
-| Enter and Exit on geofences | Background intent |
-| Enter and Exit on beacon regions | Background intent |
-| Enter in a specific beacon range | Proximity listener (foreground) |
-## Foreground Content
+If you want to customize the behavior of background notification see [this page](custom-bkg-notification.md).
-To receive foreground content (e.g. ranging recipes) set a proximity listener with the method
+## Beacon Interaction Content
+Beacon interaction (beacon ranging) is a peculiar trigger that works only when your app is in the foreground.
+To receive this kind of content set a **proximity listener** with the method:
```java
{
...
@@ -32,33 +32,31 @@ public void foregroundEvent(Parcelable content, TrackingInfo trackingInfo) {
NearUtils.parseCoreContents(content, trackingInfo, coreContentListener);
}
```
+**Warning:** For this kind of content you will need to write the code for **Trackings** and to eventually show an **In-app notification**.
-## Background Content
-
-Once you have added at least one of the receivers for any background working trigger you will be delivered the actual content through an intent that will call your app launcher activity and carry some extras.
-To extract the content from an intent use the utility method:
-```java
-NearUtils.parseCoreContents(intent, coreContentListener);
-```
-If you want to just check if the intent carries NearIT content, without having to eventually handle the actual content, use this method
-```java
-boolean hasNearContent = NearUtils.carriesNearItContent(intent);
-```
-
-If you want to customize the behavior of background notification see [this page](custom-bkg-notification.md)
## Trackings
+NearIT allows to track user engagement events on recipes. Any recipe has at least two default events:
+
+ - **Notified**: the user *received* a notification
+ - **Engaged**: the user *tapped* on the notification
+
+Usually the SDK tracks those events automatically, but if you write custom code to show notification or content (i.e. to receive Beacon interaction content) please make sure that at least the "**notified**" event is tracked.
+
**Warning:** Failing in tracking this event cause some NearIT features to not work.
-NearIT analytics on recipes are built from trackings describing the status of user engagement with a recipe. The two recipe states are "Notified" and "Engaged" to represent a recipe delivered to the user and a recipe that the user responded to.
-Built-in background recipes track themselves as notified and engaged.
-Foreground recipes don't have automatic tracking. You need to track both the "Notified" and the "Engaged" statuses when it's the best appropriate for you scenario.
+You can track **default or custom events** using the "**sendTracking**" method:
+
```java
+// notified - notification received
NearItManager.getInstance().sendTracking(trackingInfo, Recipe.NOTIFIED_STATUS);
-// and
+
+// engaged - notification tapped
NearItManager.getInstance().sendTracking(trackingInfo, Recipe.ENGAGED_STATUS);
+
+// custom recipe event
+NearItManager.getInstance().sendTracking(trackingInfo, "my awesome custom event");
```
-The recipe cooldown feature uses tracking calls to hook its functionality, so failing to properly track user interactions will result in the cooldown not being applied.
## Content Objects
@@ -70,11 +68,9 @@ Here are the public fields for every other one:
- `message` returns the notification message (it is the same as `notificationMessage`)
- `Content` for the notification with content, with the following getters and fields:
+ - `title` returns the content title
- `contentString` returns the text content
- - `video_link` returns the video link string
- - `getImages_links()` returns a list of *ImageSet* object containing the source links for the images
- - `upload` returns an Upload object containing a link to a file uploaded on NearIT if any
- - `audio` returns an Audio object containing a link to an audio file uploaded on NearIT if any
+ - `getImageLink()` returns an *ImageSet* object containing the links for the image
- `Feedback` with the following getters and fields:
- `question` returns the feedback request string
@@ -111,16 +107,16 @@ We handle the complete emission and redemption coupon cycle in our platform, and
You can ask the library to fetch the list of all the user current coupons with the method:
```java
NearItManager.getInstance().getCoupons(new CouponListener() {
- @Override
- public void onCouponsDownloaded(List list) {
+ @Override
+ public void onCouponsDownloaded(List list) {
- }
+ }
- @Override
- public void onCouponDownloadError(String s) {
+ @Override
+ public void onCouponDownloadError(String s) {
- }
- });
+ }
+});
```
The method will also return already redeemed coupons so you get to decide to filter them if necessary.
diff --git a/docs/installation.md b/docs/installation.md
index 442bb0b2..ad62e6cd 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -7,7 +7,7 @@ To start using the SDK, include this in your app *build.gradle*
```java
dependencies {
- compile 'it.near.sdk:nearit:2.2.5'
+ compile 'it.near.sdk:nearit:2.2.6'
}
```
diff --git a/docs/location-based-notifications.md b/docs/location-based-notifications.md
index 7d533736..d1b8c611 100644
--- a/docs/location-based-notifications.md
+++ b/docs/location-based-notifications.md
@@ -1,5 +1,12 @@
# Location Based Notifications
+The SDK automatically includes the permission for location access in its manifest (necessary for beacon and geofence monitoring). When targeting API level 23+, please ask for and verify the presence of ACCESS_FINE_LOCATION permissions at runtime.
+If you are also manually including ACCESS_COARSE_LOCATION in your manifest please be sure to specify a `maxSdkVersion` of _22_.
+```java
+
+```
+
When you want to start the radar for **geofences and beacons** call this method:
```java
@@ -8,13 +15,6 @@ NearItManager.getInstance().startRadar()
// to stop the radar call the method nearItManager.stopRadar()
```
-The SDK automatically includes the permission for location access in its manifest (necessary for beacon and geofence monitoring). When targeting API level 23+, please ask for and verify the presence of ACCESS_FINE_LOCATION permissions at runtime.
-If you are also manually including ACCESS_COARSE_LOCATION in your manifest please be sure to specify a `maxSdkVersion` of _22_.
-```java
-
-```
-
The SDK creates a system notification for every background recipe. On the notification tap, your launcher activity will start.
To learn how to deal with in-app content once the user taps on the notification, see this [section](in-app-content.md).
diff --git a/docs/user-profiling.md b/docs/user-profiling.md
index 6ce34367..19952ff8 100644
--- a/docs/user-profiling.md
+++ b/docs/user-profiling.md
@@ -32,16 +32,16 @@ userDataMap.put("name", "John");
userDataMap.put("age", "23"); // set everything as String
userDataMap.put("saw_tutorial", "true") // even booleans, the server has all the right logic
NearItManager.getInstance().setBatchUserData(userDataMap, new UserDataNotifier() {
- @Override
- public void onDataCreated() {
- // data was set/created
- }
+ @Override
+ public void onDataCreated() {
+ // data was set/created
+ }
- @Override
- public void onDataNotSetError(String error) {
+ @Override
+ public void onDataNotSetError(String error) {
- }
- });
+ }
+});
```
If you try to set user data before creating a profile the error callback will be called.
diff --git a/nearit/src/androidTest/java/it/near/sdk/reaction/content/ContentTest.java b/nearit/src/androidTest/java/it/near/sdk/reaction/content/ContentTest.java
index 04747dc3..566ba9be 100644
--- a/nearit/src/androidTest/java/it/near/sdk/reaction/content/ContentTest.java
+++ b/nearit/src/androidTest/java/it/near/sdk/reaction/content/ContentTest.java
@@ -13,6 +13,7 @@
import it.near.sdk.reactions.contentplugin.model.Audio;
import it.near.sdk.reactions.contentplugin.model.Content;
+import it.near.sdk.reactions.contentplugin.model.ContentLink;
import it.near.sdk.reactions.contentplugin.model.ImageSet;
import it.near.sdk.reactions.contentplugin.model.Upload;
@@ -35,6 +36,7 @@ public void contentIsParcelable() {
content.video_link = "video_link";
content.setId("content_id");
content.updated_at = "updated@whatever";
+ content.title = "title";
Audio audio = new Audio();
HashMap audioMap = Maps.newHashMap();
audioMap.put("url", "a.mp3");
@@ -46,6 +48,8 @@ public void contentIsParcelable() {
upload.uploadMap = uploadMap;
content.upload = upload;
content.notificationMessage = "fejrf";
+ ContentLink contentLink = new ContentLink("a", "b");
+ content.setCta(contentLink);
Parcel parcel = Parcel.obtain();
content.writeToParcel(parcel, 0);
@@ -59,6 +63,9 @@ public void contentIsParcelable() {
assertThat(content.audio.audioMap, is(actual.audio.audioMap));
assertThat(content.upload.uploadMap, is(actual.upload.uploadMap));
assertThat(content.notificationMessage, is(actual.notificationMessage));
+ assertThat(content.title, is(actual.title));
+ assertThat(content.getCta(), is(actual.getCta()));
+ assertThat(content.getImageLink(), is(actual.getImageLink()));
}
}
diff --git a/nearit/src/androidTest/java/it/near/sdk/reactions/contentplugin/model/ContentLinkTest.java b/nearit/src/androidTest/java/it/near/sdk/reactions/contentplugin/model/ContentLinkTest.java
new file mode 100644
index 00000000..9079c80b
--- /dev/null
+++ b/nearit/src/androidTest/java/it/near/sdk/reactions/contentplugin/model/ContentLinkTest.java
@@ -0,0 +1,31 @@
+package it.near.sdk.reactions.contentplugin.model;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static junit.framework.Assert.assertNotNull;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+@RunWith(AndroidJUnit4.class)
+public class ContentLinkTest {
+
+ @Test
+ public void isParcelable() {
+ ContentLink contentLink = new ContentLink("a", "b");
+ Parcel parcel = Parcel.obtain();
+ contentLink.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ ContentLink actual = ContentLink.CREATOR.createFromParcel(parcel);
+ assertNotNull(actual);
+ assertThat(actual.label, is(contentLink.label));
+ assertThat(actual.url, is(contentLink.url));
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/nearit/src/main/java/it/near/sdk/reactions/contentplugin/ContentReaction.java b/nearit/src/main/java/it/near/sdk/reactions/contentplugin/ContentReaction.java
index 620c1b88..3837ecdd 100644
--- a/nearit/src/main/java/it/near/sdk/reactions/contentplugin/ContentReaction.java
+++ b/nearit/src/main/java/it/near/sdk/reactions/contentplugin/ContentReaction.java
@@ -16,11 +16,16 @@
import it.near.sdk.reactions.CoreReaction;
import it.near.sdk.reactions.contentplugin.model.Audio;
import it.near.sdk.reactions.contentplugin.model.Content;
+import it.near.sdk.reactions.contentplugin.model.ContentLink;
import it.near.sdk.reactions.contentplugin.model.Image;
import it.near.sdk.reactions.contentplugin.model.ImageSet;
import it.near.sdk.reactions.contentplugin.model.Upload;
import it.near.sdk.recipes.NearNotifier;
+import static it.near.sdk.reactions.contentplugin.model.ContentLink.CTA_LABEL_KEY;
+import static it.near.sdk.reactions.contentplugin.model.ContentLink.CTA_URL_KEY;
+import static it.near.sdk.utils.NearUtils.safe;
+
public class ContentReaction extends CoreReaction {
// ---------- content notification plugin ----------
public static final String PLUGIN_NAME = "content-notification";
@@ -34,6 +39,7 @@ public class ContentReaction extends CoreReaction {
public static final String RES_AUDIOS = "audios";
public static final String RES_UPLOADS = "uploads";
+
ContentReaction(Cacher cacher, NearAsyncHttpClient httpClient, NearNotifier nearNotifier, Type cacheType) {
super(cacher, httpClient, nearNotifier, Content.class, cacheType);
}
@@ -76,7 +82,7 @@ protected void injectRecipeId(Content element, String recipeId) {
protected void normalizeElement(Content element) {
List images = element.images;
List imageSets = new ArrayList<>();
- for (Image image : images) {
+ for (Image image : safe(images)) {
try {
imageSets.add(image.toImageSet());
} catch (Image.MissingImageException ignored) {
@@ -84,6 +90,19 @@ protected void normalizeElement(Content element) {
}
}
element.setImages_links(imageSets);
+
+ try {
+ if (element.link != null &&
+ element.link.containsKey(CTA_LABEL_KEY) &&
+ element.link.containsKey(CTA_URL_KEY)) {
+ String label = (String) element.link.get(CTA_LABEL_KEY);
+ String url = (String) element.link.get(CTA_URL_KEY);
+
+ ContentLink contentLink = new ContentLink(label, url);
+ element.setCta(contentLink);
+ }
+ } catch (Throwable ignored) {}
+
}
@Override
diff --git a/nearit/src/main/java/it/near/sdk/reactions/contentplugin/model/Content.java b/nearit/src/main/java/it/near/sdk/reactions/contentplugin/model/Content.java
index a69dab0c..1e5f84ec 100644
--- a/nearit/src/main/java/it/near/sdk/reactions/contentplugin/model/Content.java
+++ b/nearit/src/main/java/it/near/sdk/reactions/contentplugin/model/Content.java
@@ -2,38 +2,81 @@
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import it.near.sdk.morpheusnear.annotations.Relationship;
import it.near.sdk.recipes.models.ReactionBundle;
public class Content extends ReactionBundle implements Parcelable {
+
+ @Nullable
+ @SerializedName("title")
+ public String title;
+
+ @Nullable
@SerializedName("content")
public String contentString;
- @SerializedName("video_link")
- public String video_link;
+
+ @Nullable
+ @SerializedName("link")
+ public HashMap link;
+
@SerializedName("updated_at")
public String updated_at;
+
+ @Deprecated
@Relationship("images")
public List images;
+
+ @Deprecated
@Relationship("audio")
public Audio audio;
+
+ @Deprecated
@Relationship("upload")
public Upload upload;
+ @Deprecated
+ @SerializedName("video_link")
+ public String video_link;
+
private List images_links;
+ private ContentLink cta;
public Content() {
}
+ @Nullable
+ public ImageSet getImageLink() {
+ if (images_links == null || images_links.isEmpty())
+ return null;
+ return images_links.get(0);
+ }
+
+ /**
+ * @deprecated please use {@link #getImageLink()}
+ */
+ @Deprecated
public List getImages_links() {
return images_links;
}
+ @Nullable
+ public ContentLink getCta() {
+ return cta;
+ }
+
+ public void setCta(ContentLink cta) {
+ this.cta = cta;
+ }
+
+ @Deprecated
public void setImages_links(List images_links) {
this.images_links = images_links;
}
@@ -46,11 +89,13 @@ public int describeContents() {
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
+ dest.writeString(title);
dest.writeString(contentString);
dest.writeString(video_link);
dest.writeString(updated_at);
dest.writeString(getId());
dest.writeTypedList(getImages_links());
+ dest.writeParcelable(cta, flags);
dest.writeParcelable(audio, flags);
dest.writeParcelable(upload, flags);
}
@@ -68,6 +113,7 @@ public Content[] newArray(int size) {
public Content(Parcel in) {
super(in);
+ title = in.readString();
contentString = in.readString();
video_link = in.readString();
updated_at = in.readString();
@@ -75,6 +121,7 @@ public Content(Parcel in) {
List list = new ArrayList();
in.readTypedList(list, ImageSet.CREATOR);
setImages_links(list);
+ cta = in.readParcelable(ContentLink.class.getClassLoader());
audio = in.readParcelable(Audio.class.getClassLoader());
upload = in.readParcelable(Upload.class.getClassLoader());
}
diff --git a/nearit/src/main/java/it/near/sdk/reactions/contentplugin/model/ContentLink.java b/nearit/src/main/java/it/near/sdk/reactions/contentplugin/model/ContentLink.java
new file mode 100644
index 00000000..64280a87
--- /dev/null
+++ b/nearit/src/main/java/it/near/sdk/reactions/contentplugin/model/ContentLink.java
@@ -0,0 +1,65 @@
+package it.near.sdk.reactions.contentplugin.model;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class ContentLink implements Parcelable {
+
+ public static final String CTA_LABEL_KEY = "label";
+ public static final String CTA_URL_KEY = "url";
+
+ public String label;
+ public String url;
+
+ public ContentLink(String label, String url) {
+ this.label = label;
+ this.url = url;
+ }
+
+ protected ContentLink(Parcel in) {
+ label = in.readString();
+ url = in.readString();
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public ContentLink createFromParcel(Parcel in) {
+ return new ContentLink(in);
+ }
+
+ @Override
+ public ContentLink[] newArray(int size) {
+ return new ContentLink[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeString(label);
+ parcel.writeString(url);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ContentLink that = (ContentLink) o;
+
+ if (label != null ? !label.equals(that.label) : that.label != null) return false;
+ return url != null ? url.equals(that.url) : that.url == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = label != null ? label.hashCode() : 0;
+ result = 31 * result + (url != null ? url.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/nearit/src/test/java/it/near/sdk/reactions/contentplugin/ContentReactionTest.java b/nearit/src/test/java/it/near/sdk/reactions/contentplugin/ContentReactionTest.java
index fc76bbd4..8e9c7302 100644
--- a/nearit/src/test/java/it/near/sdk/reactions/contentplugin/ContentReactionTest.java
+++ b/nearit/src/test/java/it/near/sdk/reactions/contentplugin/ContentReactionTest.java
@@ -1,5 +1,7 @@
package it.near.sdk.reactions.contentplugin;
+import com.google.common.collect.Maps;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -11,6 +13,7 @@
import it.near.sdk.reactions.BaseReactionTest;
import it.near.sdk.reactions.contentplugin.model.Audio;
import it.near.sdk.reactions.contentplugin.model.Content;
+import it.near.sdk.reactions.contentplugin.model.ContentLink;
import it.near.sdk.reactions.contentplugin.model.Image;
import it.near.sdk.reactions.contentplugin.model.ImageSet;
import it.near.sdk.reactions.contentplugin.model.Upload;
@@ -19,8 +22,12 @@
import static it.near.sdk.reactions.contentplugin.ContentReaction.RES_CONTENTS;
import static it.near.sdk.reactions.contentplugin.ContentReaction.RES_IMAGES;
import static it.near.sdk.reactions.contentplugin.ContentReaction.RES_UPLOADS;
+import static it.near.sdk.reactions.contentplugin.model.ContentLink.CTA_LABEL_KEY;
+import static it.near.sdk.reactions.contentplugin.model.ContentLink.CTA_URL_KEY;
+import static junit.framework.Assert.assertNull;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsCollectionContaining.hasItems;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -93,6 +100,40 @@ public void normalizeElementShouldNormalizeImages() throws Image.MissingImageExc
assertThat(content.getImages_links(), hasItems(imageSet1, imageSet2));
}
+ @Test
+ public void normalizeElementShouldNormalizeContentLink() {
+ Content content = new Content();
+ HashMap ctaMap = Maps.newHashMap();
+ String label = "label";
+ ctaMap.put(CTA_LABEL_KEY, label);
+ String url = "url";
+ ctaMap.put(CTA_URL_KEY, url);
+ content.link = ctaMap;
+
+ reaction.normalizeElement(content);
+
+ ContentLink actualContentLink = content.getCta();
+ assertNotNull(actualContentLink);
+ assertThat(actualContentLink.label, is(label));
+ assertThat(actualContentLink.url, is(url));
+ }
+
+ @Test
+ public void incompleteCtaShouldNotCreateContentLink() {
+ Content content = new Content();
+ HashMap ctaMap = Maps.newHashMap();
+ String label = "label";
+ ctaMap.put(CTA_LABEL_KEY, label);
+ // String url = "url";
+ // ctaMap.put(CTA_URL_KEY, url);
+ content.link = ctaMap;
+
+ reaction.normalizeElement(content);
+
+ ContentLink actualContentLink = content.getCta();
+ assertNull(actualContentLink);
+ }
+
@Test
public void shouldReturnModelMap() {
HashMap modelMap = reaction.getModelHashMap();
diff --git a/sample/build.gradle b/sample/build.gradle
index 4b7cb448..1736332d 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -55,7 +55,7 @@ dependencies {
exclude group: 'com.android.support', module: 'support-annotations'
})
- // compile 'it.near.sdk:nearit:2.2.5'
+ // compile 'it.near.sdk:nearit:2.2.6'
compile project(':nearit')
compile 'com.android.support:appcompat-v7:26.1.0'