Granify Android SDK Inlines
Inlines
Overview
As of v1.7.0, the Granify Android SDK has introduced the initial infrastructure for inline campaigns. With the release of v2.0.0, the inline infrastructure has been completed and is now ready for full integration into your application(s).
Inline campaigns display via views embedded in your application’s user interface. Initially, these views are hidden, however, they will still exist within the layout and their height will be 0px. When activated, they are expanded with animation to reveal the campaign. They will usually contain a headline, a message, and potentially a Call To Action (CTA).
The process to display an inline campaign is as follows:
- When the activity is passed into the Granify Android SDK during the
Granify.trackPageView(...)
call, the SDK will search through the activity and identify all theGranifyInlineView
elements that are present on the page. - When a shopper matches to a campaign, our servers tell the SDK to display an inline campaign in a specific
GranifyInlineView
on the page for the user to see. - The SDK will search through the available inline views and select the
GranifyInlineView
that the campaign will display in. - The inline campaign initializes itself within the location and sets up its animator.
- The animator begins expanding the campaign and passes its current height during each animation frame to the
expandHandler
set on the location to allow for layout changes to be applied on every animation tick. - If the inline needs to be dismissed (by the user or otherwise), a collapse handler is used with a similar mechanism as the expand handler. If the user navigates to another page the inline is prepared for redisplay should the user return to the page the inline was initially displayed on.
Implementation
To enable the Granify SDK to display inline campaigns you must add at least one GranifyInlineView
to your app. When a campaign is selected for display, the Granify SDK will look for an appropriate GranifyInlineView
, load and insert the code for the campaign into the view, and display it.
Adding a GranifyInlineView
requires the following 3 steps, which will be elaborated on in the respective sections:
- Add the
GranifyInlineView
to your UI - Set the expand and collapse handlers on the
GranifyInlineView
throughGranifyInlineView.setExpandHandler(expandHandler: GranifyInlineViewExpander)
andGranifyInlineView.setCollapseHandler(collapseHandler: GranifyInlineViewCollapser)
- Set the label for the
GranifyInlineView
The following sections will go through these steps in greater detail.
Adding a GranifyInlineView to your UI
To support inline campaigns, you will need to add a GranifyInlineView
to every position on a page where it is desired for an inline to appear. The GranifyInlineView
class extends FrameLayout
and can be added to your user interface in the same way. In the most common case, a GranifyInlineView
will not initially be visible in your UI. If you are using the layout editor the easiest way to achieve this is by setting the layout_height
to 0.
The following are recommended constraints:
layout_height
of 0- Horizontal constraints set to the start and end of the parent layout
- The top of the
GranifyInlineView
is constrained to the bottom of the layout above - The bottom of the
GranifyInlineView
is constrained to the top of the layout below
An example of this layout in XML can be seen here for a product display page activity:
<ImageView
android:id="@+id/productPageImageView"
android:layout_width="290dp"
android:layout_height="305dp"
android:layout_marginTop="10dp"
android:contentDescription="@string/product_image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.495"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/relatedProductButton"
tools:layout_conversion_absoluteHeight="376dp"
tools:layout_conversion_absoluteWidth="393dp"
tools:srcCompat="@tools:sample/avatars" />
<com.granifyinc.granifysdk.campaigns.inlineView.GranifyInlineView
android:id="@+id/granifyInlineViewBelowProductImage"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="56dp"
app:layout_constraintBottom_toTopOf="@+id/colour_spinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/productPageImageView" />
<Spinner
android:id="@+id/colour_spinner"
android:layout_width="134dp"
android:layout_height="48dp"
android:contentDescription="@string/colour_text"
android:text="@string/colour_text"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/productPageAddToCartButton"
app:layout_constraintEnd_toEndOf="@+id/productPageAddToCartButton"
app:layout_constraintStart_toStartOf="@+id/productPageAddToCartButton"
app:layout_constraintTop_toBottomOf="@+id/granifyInlineViewBelowProductImage" />
Please note that the above are only recommendations. The only requirements for an inline to be displayed in a GranifyInlineView
are that they are initially 0px in height and can be expanded/collapsed via a handler. Your user interface may require different constraints to meet these conditions.
Additionally, Android ListView
layouts are not supported for inlines. This is due to the remeasuring of the ListView
and its items on every animation frame leading to poor performance of both the inline and your application. RecyclerView
can be used as an alternative to ListView
.
Setting GranifyInlineView Expand And Collapse Handlers
As mentioned in the previous section, a GranifyInlineView
will not initially be visible in your UI. As a result, Granify requires clients to pass in two handlers:
- An expand handler of type
GranifyInlineViewExpander
that will expand theGranifyInlineView
, allowing an inline campaign to appear. All fragments below theGranifyInlineView
must accommodate the expansion and must be updated accordingly. The expanded height of theGranifyInlineView
will be determined by Granify. - A collapse handler of type
GranifyInlineViewCollapser
that will collapse theGranifyInlineView
back to its initial state (height = 0px).
The following handlers must be passed in the onCreate()
method of the current activity:
- Expand handlers are passed through
GranifyInlineView.setExpandHandler(expandHandler: GranifyInlineViewExpander)
- Collapse handlers are passed through
GranifyInlineView.setCollapseHandler(collapseHandler: GranifyInlineViewCollapser)
. These handlers are called on every tick of animation while the inline is expanding/collapsing and are passed the parameterheight
by the animator representing the current height of the inline view. The Granify SDK handles animation of the inlines internally. Do NOT add any additional animation handling.
If these handlers are not set in the GranifyInlineView
, inlines cannot be displayed.
Here is an example in Kotlin of how the expand and collapse handlers can be written when using relative constraints (an example of which is shown above) for both the page layout and the elements around the GranifyInlineView
(recommended):
inlineView.setExpandHandler { granifyInlineView, height ->
val params = granifyInlineView.layoutParams
granifyInlineView.layoutParams = params.apply { this.height = height }
}
inlineView.setCollapseHandler { granifyInlineView ->
val params = granifyInlineView.layoutParams
granifyInlineView.layoutParams = params.apply { this.height = height }
}
The implementation of the handlers may be different depending on the complexity of your application.
Setting the Label
GranifyInlineViewLabel
identifies unique locations on a page and must not be duplicated within the same page.
The GranifyInlineViewLabel
has 3 fields that make the label unique to a location on a page:
name
: RequiredString
- Identifies the associated
GranifyInlineView
. This field must be unique per location on anActivity
. If anActivity
containing aGranifyInlineView
is reused for several pages only one unique label is required. The label must only be unique within a page, it can be used elsewhere within your application.
- Identifies the associated
productId
: OptionalString
- The ID of the associated product. Required when the
GranifyInlineView
is located on a product page, or on any page where theGranifyInlineView
is associated with a specific product (e.g. inlines associated with a cart product).
- The ID of the associated product. Required when the
sku
: OptionalString
- This field is required when the associated
GranifyInlineView
is associated with a specificproductID
/sku
combination. If asku
is specified, aproductId
must also be specified however aproductId
can be specified without asku
.
- This field is required when the associated
Multiple labels can exist on a single page and the name
field will be used to uniquely identify them when the productId
and sku
fields are the same. For example,
a product page for productId
“Pants” with sku
“Green” could have the following labels:
GranifyInlineView(name="aboveProductPhoto", productId="Pants", sku="Green")
GranifyInlineView(name="belowProductPhoto", productId="Pants", sku="Green")
GranifyInlineView(name="belowAddToCartButton", productId="Pants", sku="Green")
A GranifyInlineView
located on a product page must (at minimum) use the name
and productId
parameters. The sku
should also be provided where applicable.
On a cart page, a GranifyInlineView
not associated with any product (for example at the top of the page) would only require the name
field in the GranifyInlineViewLabel
. However, if the GranifyInlineView
was associated with a product in the cart, then the name
and productId
fields would be required. The sku
field would also be required if there are multiple SKUs for that product. If these labels are not properly created, the inline display will not work properly.
A list of all GranifyInlineViewLabel
in your application must be sent to your Granify Project Manager / Account Manager for us to create inline campaigns.
Do not change these label names without prior warning as this will break live campaigns.
Testing your GranifyInlineView
Because a GranifyInlineView
is embedded into your user interface they must be tested extensively to make sure that expanding and collapsing them does not interfere with any of your other layouts.
To specify which location to attach an inline to while testing, the method Granify.overrideGranifyInlineView(label: String?)
has been provided within the SDK.
This method should be used exclusively during testing and allows for specifying a list of GranifyInlineViewLabel
(in JSON) that will override the locations that Granify will use to display our inline campaigns. Also note that this list is ordered by priority and the first label within a page that matches in the list will be used for display.
If you would like to reset to the default behaviour of having Granify select the inline location, call Granify.overrideGranifyInlineView(label: String?)
method with the label
parameter set to null
.
The label parameter of this method is a string of JSON which correlates to the GranifyInlineViewLabel
fields in the format:
"[{\"name\": \"LABEL_NAME\", \"product_id\": \"PRODUCT_ID\", \"sku\": \"PRODUCT_SKU\"}]"
Note that the JSON string here has three catches that can make troubleshooting difficult:
- The
product_id
field in the JSON is all lowercase and contains an underscore while theproductId
field in theGranifyInlineViewLabel
is camel case and does not contain any special characters. - The JSON string is an array of labels rather than just a single label to allow for specifying multiple inline locations that can be attached to when testing.
- The
product_id
andsku
fields can be set to null to act as a wildcard match to anyGranifyInlineViewLabel
that matches thename
field. If just thesku
field is set tonull
then anyGranifyInlineViewLabels
that match both thename
andproductId
fields will be chosen for display.
Example Usage Scenario
Three new GranifyInlineView
locations were added to a product page layout and a developer would like to test them to ensure the constraints and expand/collapse handlers behave as expected.
The developer will go through the following steps:
- Double-check that the
GranifyInlineView
has its expand/collapse handlers and label set in theonCreate()
method of the activity it will display in. - Translate the
GranifyInlineViewLabel
that was set for each location into JSON. If the new labels were:GranifyInlineView(name="aboveProductPhoto", productId="Pants", sku="Green") GranifyInlineView(name="belowProductPhoto", productId="Pants", sku="Green") GranifyInlineView(name="belowAddToCartButton", productId="Pants", sku="Green")
If you are targeting a location on a specific product/SKU, the product information needs to be included. From the
GranifyInlineViewLabel
above, they would translate to the following JSON for testing:{"name": "aboveProductPhoto", "product_id": "Pants", "sku": "Green"} {"name": "belowProductPhoto", "product_id": "Pants", "sku": "Green"} {"name": "belowAddToCartButton", "product_id": "Pants", "sku": "Green"}
If the location is not product or SKU specific, the product details may be omitted from their respective fields. In this example, since both the
product_id
andsku
fields were set tonull
, Granify will automatically match anyGranifyInlineViewLabel
that shares the samename
field regardless of product.{"name": "aboveProductPhoto", "product_id": null, "sku": null} {"name": "belowProductPhoto", "product_id": null, "sku": null} {"name": "belowAddToCartButton", "product_id": null, "sku": null}
Note: The
sku
field can also be omitted independently of theproduct_id
field if testing aGranifyInlineViewLabel
for a specific product where the SKU does not matter. However,product_id
cannot be omitted if asku
is provided. - Add the JSON for each field into an array so that it will then appear as:
[ {"name": "aboveProductPhoto", "product_id": null, "sku": null}, {"name": "belowProductPhoto", "product_id": null, "sku": null}, {"name": "belowAddToCartButton", "product_id": null, "sku": null} ]
- Ensure that the JSON array can be translated into a string. Shown here is the array after escaping the quote characters and removing the newlines being assigned as the value to
overrideLabelString
as well as an unescaped string example for use within the method call in the next step.val overrideLabelString: String = "[{\"name\":\"aboveProductPhoto\",\"product_id\":null,\"sku\":null},{\"name\":\"belowProductPhoto\",\"product_id\":null,\"sku\":null},{\"name\":\"belowAddToCartButton\",\"product_id\":null,\"sku\": null}]" // If unescaped JSON is preferable then the following also works val overrideLabelString: String = """ [ {"name": "aboveProductPhoto", "product_id": null, "sku": null}, {"name": "belowProductPhoto", "product_id": null, "sku": null}, {"name": "belowAddToCartButton", "product_id": null, "sku": null} ] """
- Call the
Granify.overrideGranifyInlineView(label: String?)
method with the JSON string as thelabel
parameter. - Force match to an inline campaign (this will be provided by Granify to you during your installation) and navigate to the page to which the new inline locations have been added to.
- Observe as the campaign displays in the first label that was provided in the array, in this scenario this is a
GranifyInlineView
with thename
field set to “aboveProductPhoto”
We recommend having a text box in a debug menu that calls Granify.overrideGranifyInlineView(label: String?)
when the JSON object is input. Using this override makes any matched inline campaign appear in the GranifyInlineView
with one of the GranifyInlineViewLabel
that has been specified.
Inlines and Restricted States
Please note that inline campaigns are not affected by restricted states. They will not be hidden while in a restricted state.
See Granify.setRestrictionState(state: RestrictionState)
for more information on how and when restriction states are set.
Troubleshooting
A few common scenarios and their solutions are listed below:
- The inline view has been added to the layout and the expand/collapse/label have all been added to it in the
onCreate()
method but the campaign won’t display:- Check the LogCat output. Granify will log all the labels that it finds within an activity as well as the label it is searching for. This can aid in finding any typos or differences between labels.
- Ensure the correct
FragmentActivity
is being passed into the call toGranify.trackPageView(...)
and that the most recent call to it is for theFragmentActivity
that you are currently on. Having the correctFragmentActivity
is necessary for Granify to be able to find the right labels for a page and display an inline in them.
- The steps in the previous scenario were followed and the label was found and selected for display but the campaign still doesn’t appear:
- Double-check your constraints and expand/collapse handlers. The inline may be displaying, but it is covered by another element within the layout or the inline view is displaying outside the bounds of the screen.
- The inline is finally displaying, but my application takes a large hit to performance while it’s expanding/collapsing:
- Make sure that your expand/collapse handlers are very lightweight. There should be no logging or extraneous calculations within them because they are called on every animation tick. They should be responsible for nothing other than updating the layout based on the height of the
GranifyInlineView
as it expands/collapses.
- Make sure that your expand/collapse handlers are very lightweight. There should be no logging or extraneous calculations within them because they are called on every animation tick. They should be responsible for nothing other than updating the layout based on the height of the
- The inline is now displaying correctly and is performant, but I don’t quite like to style of it/I have an idea for one that would be even cooler:
- Talk to your Granify Project Manager / Account Manager about your concerns with the campaign and Granify will work with you to give you and your users the best experience that we can.