Skip to content

Commit b5befac

Browse files
Ai docs expanded
1 parent d457147 commit b5befac

11 files changed

Lines changed: 1382 additions & 304 deletions

docs/ai/deep-linking.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Deep Linking — Iterable Android SDK
2+
3+
Handle deep links and custom actions from push notifications, in-app messages, and embedded messages.
4+
5+
> **Prerequisites:** SDK must be initialized (see [integration-guide.md](integration-guide.md)).
6+
7+
---
8+
9+
## Business Context
10+
11+
Deep links connect Iterable campaigns (push, in-app, embedded) to specific screens in the app. The marketing team sets link URLs in Iterable templates. The developer's role is to:
12+
13+
1. Define how URLs map to app screens
14+
2. Register the URL handler with the SDK
15+
3. Configure which URL schemes the SDK is allowed to open
16+
17+
Custom actions (`action://` URLs) are an alternative to deep links — they trigger app-specific logic (dismiss UI, toggle features, fire analytics) without navigating to a URL.
18+
19+
---
20+
21+
## Decision Points
22+
23+
| Decision | Tier | Default | Notes |
24+
|----------|------|---------|-------|
25+
| Does the app use a custom URI scheme? | 🟠 Important | No (HTTPS only) | e.g., `myapp://`. Must be registered with `setAllowedProtocols`. Ask the developer. |
26+
| App Links (HTTPS verified) vs custom scheme | 🟡 Relevant | Custom scheme (simpler) | App Links require hosting `assetlinks.json` on the domain. Better for shared web/app URLs. |
27+
| URL-to-screen mapping | 🔴 Critical | N/A | How do URLs map to app screens? The developer must define this — it's app-specific. |
28+
| Implement `IterableCustomActionHandler`? | 🟡 Relevant | No | Only needed if Iterable campaigns use `action://` URLs. Ask the developer if marketing uses custom actions. |
29+
| Behavior based on action source (push vs in-app vs embedded) | 🟢 Optional | Same for all sources | `IterableActionContext.source` tells you where the link came from. Most apps don't differentiate. |
30+
31+
---
32+
33+
## Step 1: URL Handler
34+
35+
> **Agent note:** Ask the developer (🔴 Critical) what URL patterns the app uses and how they map to screens. Adapt the routing logic below.
36+
37+
```kotlin
38+
class AppUrlHandler(private val appContext: Context) : IterableUrlHandler {
39+
override fun handleIterableURL(uri: Uri, actionContext: IterableActionContext): Boolean {
40+
if (uri.scheme == "yourscheme" && uri.host == "detail") {
41+
val itemId = uri.pathSegments.firstOrNull()?.toIntOrNull() ?: return false
42+
appContext.startActivity(Intent(appContext, DetailActivity::class.java).apply {
43+
putExtra("item_id", itemId)
44+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
45+
})
46+
return true
47+
}
48+
// Return false for URLs you don't handle — the SDK will try to open them with the system
49+
return false
50+
}
51+
}
52+
```
53+
54+
Register in `IterableConfig.Builder`:
55+
```kotlin
56+
.setUrlHandler(AppUrlHandler(this))
57+
.setAllowedProtocols(arrayOf("yourscheme")) // 🟠 Only if using custom schemes
58+
```
59+
60+
---
61+
62+
## Step 2: AndroidManifest Intent Filters (optional)
63+
64+
> **Agent note:** Manifest intent filters are only needed if deep links arrive from OUTSIDE the Iterable SDK (e.g., from a browser, email, or other app). Links from Iterable push notifications, in-app messages, and embedded messages are routed through the `UrlHandler` registered in Step 1 — they do NOT need manifest intent filters.
65+
>
66+
> Adding intent filters for the same scheme to multiple activities will cause Android to show a disambiguation dialog. Only add them if the developer specifically needs system-level deep link handling.
67+
68+
For the system to route URLs to the app (outside of the SDK handler), declare intent filters:
69+
70+
### Custom scheme:
71+
```xml
72+
<activity android:name=".MainActivity" android:exported="true">
73+
<intent-filter>
74+
<action android:name="android.intent.action.VIEW" />
75+
<category android:name="android.intent.category.DEFAULT" />
76+
<category android:name="android.intent.category.BROWSABLE" />
77+
<data android:scheme="yourscheme" />
78+
</intent-filter>
79+
</activity>
80+
```
81+
82+
### App Links (HTTPS verified):
83+
```xml
84+
<activity android:name=".MainActivity" android:exported="true">
85+
<intent-filter android:autoVerify="true">
86+
<action android:name="android.intent.action.VIEW" />
87+
<category android:name="android.intent.category.DEFAULT" />
88+
<category android:name="android.intent.category.BROWSABLE" />
89+
<data android:scheme="https" android:host="yourdomain.com" />
90+
</intent-filter>
91+
</activity>
92+
```
93+
94+
> **Agent note:** App Links require hosting a `/.well-known/assetlinks.json` file on the domain. This is usually a web team or DevOps task — inform the developer if they choose this path.
95+
96+
---
97+
98+
## Step 3: Custom Action Handler (optional)
99+
100+
Only implement this if campaigns use `action://` URLs. The SDK routes any non-`openUrl` action type here.
101+
102+
```kotlin
103+
class AppCustomActionHandler : IterableCustomActionHandler {
104+
override fun handleIterableCustomAction(action: IterableAction, actionContext: IterableActionContext): Boolean {
105+
when (action.type) {
106+
"dismiss" -> { /* dismiss current screen */ }
107+
"showPromo" -> { /* navigate to promo screen */ }
108+
else -> return false
109+
}
110+
return true
111+
}
112+
}
113+
```
114+
115+
Register in `IterableConfig.Builder`:
116+
```kotlin
117+
.setCustomActionHandler(AppCustomActionHandler())
118+
```
119+
120+
> **Agent note:** Custom actions also apply to embedded message clicks. URLs starting with `action://` or `itbl://` in embedded messages are routed through this handler.
121+
122+
---
123+
124+
## Gotchas
125+
126+
- **Custom schemes are blocked by default.** The SDK only allows `https` URLs. You MUST add `.setAllowedProtocols(arrayOf("yourscheme"))` or custom scheme links are silently dropped with a debug log.
127+
- **Return `true` only when you fully handled the URL.** Returning `true` tells the SDK to stop processing. If you return `false`, the SDK tries to resolve the URL with `ACTION_VIEW` intent (system/browser).
128+
- **`handleIterableURL` receives links from all channels** — push opens, in-app button taps, embedded message clicks. Use `actionContext.source` if you need to differentiate.
129+
- **The SDK processes push open URLs through `IterableTrampolineActivity`.** This is an internal activity that handles the action and finishes. Don't interfere with it in your manifest.
130+
- **`handleEmbeddedClick` does NOT call `trackEmbeddedClick` automatically.** If you build custom embedded UI, you must call both `handleEmbeddedClick` (for routing) and `trackEmbeddedClick` (for analytics) yourself. The OOTB embedded views handle both.

docs/ai/embedded-messaging.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Embedded Messaging — Iterable Android SDK
2+
3+
Display persistent, native embedded messages within app screens using placements configured in Iterable.
4+
5+
> **Prerequisites:** SDK must be initialized with `setEnableEmbeddedMessaging(true)` (see [integration-guide.md](integration-guide.md)). Dependency: `iterableapi-ui` for OOTB views.
6+
7+
---
8+
9+
## Business Context
10+
11+
Embedded messages are persistent content blocks that live inside app screens — unlike in-app messages (which are overlays) or push (which is external). The marketing team creates campaigns and assigns them to **placements** in Iterable's dashboard. Each placement has an auto-generated numeric ID.
12+
13+
The developer's role is to:
14+
15+
1. Enable embedded messaging in the SDK config
16+
2. Get placement IDs from the marketing team (or Iterable dashboard)
17+
3. Register update listeners on screens that show embedded content
18+
4. Render the messages using OOTB views or custom UI
19+
20+
---
21+
22+
## Decision Points
23+
24+
| Decision | Tier | Default | Notes |
25+
|----------|------|---------|-------|
26+
| Enable embedded messaging | 🟠 Important | `false` | Only enable if the app uses embedded placements. Ask the developer. |
27+
| Placement IDs | 🔴 Critical | N/A | Auto-generated in Iterable dashboard. Must ask the developer — never assume a value. |
28+
| Which screens show embedded content | 🔴 Critical | N/A | App-specific. Ask the developer which screens should display embedded messages. |
29+
| OOTB view type (Banner, Card, Notification) vs custom UI | 🟡 Relevant | OOTB Card | Start with OOTB for speed. Go custom when the design system requires it. |
30+
| Which message to show when placement returns multiple | 🟡 Relevant | First in list | Confirm with the developer this is intentional. Marketing may expect priority-based selection. |
31+
| OOTB styling (colors, border, radius) | 🟢 Optional | SDK defaults | Customize via `IterableEmbeddedViewConfig` to match app theme. |
32+
33+
---
34+
35+
## Step 1: Enable in Config
36+
37+
```kotlin
38+
val config = IterableConfig.Builder()
39+
.setEnableEmbeddedMessaging(true) // 🟠 Only if using embedded messages
40+
.build()
41+
```
42+
43+
---
44+
45+
## Step 2: Register Update Listener
46+
47+
> **Agent note:** Replace `PLACEMENT_ID` with the actual placement ID from the developer (🔴 Critical — never assume a value).
48+
49+
```kotlin
50+
import com.iterable.iterableapi.IterableEmbeddedUpdateHandler
51+
52+
class YourFragment : Fragment(), IterableEmbeddedUpdateHandler {
53+
54+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55+
IterableApi.onSDKInitialized {
56+
IterableApi.getInstance().embeddedManager.addUpdateListener(this)
57+
activity?.runOnUiThread { displayEmbeddedMessages() }
58+
}
59+
}
60+
61+
override fun onMessagesUpdated() {
62+
activity?.runOnUiThread { displayEmbeddedMessages() }
63+
}
64+
65+
override fun onEmbeddedMessagingDisabled() {
66+
// Hide embedded UI — messaging was disabled server-side or due to auth issues
67+
}
68+
69+
private fun displayEmbeddedMessages() {
70+
val messages = IterableApi.getInstance().embeddedManager.getMessages(PLACEMENT_ID)
71+
if (messages.isNullOrEmpty()) {
72+
// Hide embedded UI container
73+
return
74+
}
75+
val message = messages.first()
76+
// Render using OOTB view or custom UI (see below)
77+
}
78+
79+
override fun onDestroyView() {
80+
try { IterableApi.getInstance().embeddedManager.removeUpdateListener(this) }
81+
catch (_: Exception) { }
82+
}
83+
}
84+
```
85+
86+
---
87+
88+
## Step 3a: OOTB Views
89+
90+
The SDK provides three view types: `BANNER`, `CARD`, and `NOTIFICATION`.
91+
92+
```kotlin
93+
// newInstance takes (viewType, message, config?) — NO context parameter
94+
val embeddedView = IterableEmbeddedView.newInstance(
95+
IterableEmbeddedViewType.CARD, // or BANNER, NOTIFICATION
96+
message,
97+
null // optional IterableEmbeddedViewConfig
98+
)
99+
yourContainer.addView(embeddedView)
100+
```
101+
102+
**Custom styling:**
103+
104+
> **Agent note:** `IterableEmbeddedViewConfig` is a data class with NO default values on its parameters. You must provide ALL 10 fields — use `null` for any you don't want to customize.
105+
106+
```kotlin
107+
val config = IterableEmbeddedViewConfig(
108+
backgroundColor = Color.WHITE,
109+
borderColor = Color.LTGRAY,
110+
borderWidth = 1,
111+
borderCornerRadius = 8f,
112+
primaryBtnBackgroundColor = Color.BLUE,
113+
primaryBtnTextColor = Color.WHITE,
114+
secondaryBtnBackgroundColor = null,
115+
secondaryBtnTextColor = null,
116+
titleTextColor = null,
117+
bodyTextColor = null
118+
)
119+
```
120+
121+
> **Agent note:** OOTB views handle both `handleEmbeddedClick` (navigation) and `trackEmbeddedClick` (analytics) automatically on button and default action taps.
122+
123+
---
124+
125+
## Step 3b: Custom UI
126+
127+
If the design requires a fully custom layout, use the message data model directly:
128+
129+
```kotlin
130+
val title = message.elements?.title
131+
val body = message.elements?.body
132+
val imageUrl = message.elements?.mediaUrl
133+
val buttons = message.elements?.buttons // List<EmbeddedMessageElementsButton>
134+
val defaultAction = message.elements?.defaultAction
135+
136+
// On button click — MUST call both:
137+
IterableApi.getInstance().embeddedManager.handleEmbeddedClick(message, button.id, clickedUrl)
138+
IterableApi.getInstance().trackEmbeddedClick(message, button.id, clickedUrl)
139+
```
140+
141+
> **Agent note:** `handleEmbeddedClick` does NOT call `trackEmbeddedClick` automatically. Custom UI must call both or clicks won't be tracked. The OOTB views handle this internally.
142+
143+
---
144+
145+
## Dashboard Setup
146+
147+
- [ ] **Embedded placement** must be created in Iterable (auto-generated ID)
148+
- [ ] **Message Type** for Embedded channel must exist before creating templates
149+
- [ ] **Campaign** must be active and targeting the user
150+
151+
---
152+
153+
## Gotchas
154+
155+
- **Accessing `embeddedManager` before SDK init throws RuntimeException.** Always wrap in `onSDKInitialized`.
156+
- **Placement IDs are auto-generated** by Iterable — never hardcode `1` or any assumed value. Ask the developer for the actual ID from the dashboard.
157+
- **`getMessages()` returns `List?` (nullable).** Always use `isNullOrEmpty()`.
158+
- **Messages sync on foreground switch.** After creating a campaign, background and foreground the app to trigger a sync.
159+
- **`onEmbeddedMessagingDisabled` fires when messaging is disabled** server-side or due to subscription/auth issues. Hide the embedded UI when this is called.
160+
- **Create a Message Type for the Embedded channel** in the Iterable dashboard before creating templates. Without it, campaigns can't be created.

0 commit comments

Comments
 (0)