Mobile apps: React Native, iOS and Android
Guidely runs in mobile apps as well as on the web. The same dashboard, guides, targeting rules, analytics, checklists, surveys and resource center work across all of them; each mobile SDK renders guides with native UI.
| SDK | Install | Source |
|---|---|---|
| React Native / Expo | npm install @guidelytech/react-native @react-native-async-storage/async-storage | packages/react-native |
| iOS (Swift, UIKit + SwiftUI, iOS 15+) | Swift Package Manager: https://github.com/parmarg/Guidely (product Guidely), or CocoaPods pod 'GuidelyTech' | mobile/ios |
| Android (Kotlin, Views + Compose, API 23+) | com.guidelytech:guidely-android (+ guidely-compose) | mobile/android |
Every SDK needs the environment public key (Settings → Environments) and the URL of your Guidely deployment. The dashboard's install panel (Settings → Environments, or the project setup wizard) shows ready-to-copy snippets for each SDK with your key filled in.
How mobile differs from the web
| Concept | Web | Mobile |
|---|---|---|
| Which guides | Guides with Web checked under Trigger → Platforms | Guides with iOS and/or Android checked |
| Where you are | URL (/deals/42) | Screen name you report with screen("Deals"), matched as the path /Deals |
| Elements | CSS selectors, ids, data-* | Guidely ids you attach to views (GuidelyTarget, .guidelyTarget, Modifier.guidelyTarget) |
| Rendering | Shadow DOM overlay | Native views above your app |
Platforms
Every guide has a Platforms setting (Trigger panel in the editor). Guides created before mobile support existed are web-only, so installing a mobile SDK never surprises you with web content. A guide can target several platforms at once, for example a cross-platform announcement or a tour of a feature that looks the same everywhere. The SDK config endpoint filters by platform, so apps only download guides meant for them.
Screens
Call screen(name) whenever the visible screen changes (the React Native SDK does this for you with React Navigation). Screen views behave like web page views:
- **URL patterns** (*Page restrictions*), **URL matches** triggers, multi-page steps (`step.url`)
and URL visited checklist tasks match the screen path
/<ScreenName>./Dealsmatches the screenDeals;/Deals/*matchesDeals/Detail. - Page load triggers fire on every screen view.
- Banners and hotspots restricted to screens close when the user moves to a screen they don't match.
- A
page_viewevent is recorded withproperties.screen.
Use stable, human-readable screen names (Home, Deals, Deals/Detail) — they are what authors type into the dashboard.
Guidely ids (targets)
Tooltips, spotlights and hotspots point at an element. On mobile, tag the view with a Guidely id and use the Guidely id selector kind in the editor (it is first in the selector list):
// React Native
<GuidelyTarget id="new-deal-button"><Button title="New deal" onPress={create} /></GuidelyTarget>
// SwiftUI / UIKit
Button("New deal", action: create).guidelyTarget("new-deal-button")
newDealButton.guidelyId = "new-deal-button"
// Compose / Views
Button(onClick = ::create, modifier = Modifier.guidelyTarget("new-deal-button")) { Text("New deal") }
newDealButton.guidelyTarget("new-deal-button")
On the web, the same id is the data-guidely-id attribute (<button data-guidely-id="new-deal-button">), and the element picker prefers it, so one guide can point at the same control in every app. Mobile SDKs also accept id selectors and CSS selectors that name a single id (#new-deal-button, [data-guidely-id='new-deal-button']). The editor warns when a mobile guide has a step without a mobile-compatible selector.
Taps on tagged views are observed (never intercepted), so Element clicked triggers, advance when the user clicks an element steps and Click checklist tasks work with Guidely ids too. Leaving the element field empty on a click-to-advance step uses the step's own target.
Targeting rules for apps
The rule builder has an App group:
| Rule | Value |
|---|---|
| Platform | web, ios or android (React Native reports ios/android) |
| Mobile screen | Current screen name; * wildcards |
| App version | Compared numerically, so 2.10 is newer than 2.9 |
Device type (mobile / tablet), operating system, locale, user and account traits, events, audiences, guide history and rollouts work exactly as on the web.
What renders how
| Step type | Mobile |
|---|---|
| Modal | Centered card, optional dimmed backdrop |
| Tooltip / popover | Card with an arrow next to the target; flips to fit the screen |
| Spotlight | Dimmed screen with a cut-out around the target (the target stays tappable) |
| Hotspot | Pulsing dot on the target; tapping it opens the card |
| Banner | Full-width strip at the top or bottom safe area |
| Slideout | Card docked to the bottom |
| Custom HTML | Shown as a plain modal (the editor warns about this) |
Step bodies support paragraphs, headings, bold/italic/underline, links, lists, inline code and images (imageUrl). Checklists show a floating launcher (hide it with showChecklistLauncher: false and open checklists from your own UI); surveys appear as a bottom card; the resource center opens as a sheet from openResourceCenter() (no floating launcher unless showResourceCenterLauncher is on).
Scout messenger
Scout, the AI support messenger (scout.md), runs in mobile apps too. In Scout → Settings → Show it on, tick iOS and/or Android (and Web for your site — any combination). Apps on a ticked platform show a chat bubble (above the checklist launcher when both are on the same side) that opens a native sheet with Home, Messages and Help tabs: Scout's replies stream in, cite your articles and offer "Show me" buttons that start a guide; "Talk to a person" hands the conversation to the team Inbox, and team replies appear in the thread (the SDK polls while it is open and shows an unread badge otherwise). When the messenger is on, openResourceCenter() opens its Help tab.
| React Native | iOS | Android | |
|---|---|---|---|
| Open | guidely.openMessenger("help") | Guidely.shared.openMessenger(.help) | Guidely.openMessenger(MessengerTab.HELP) |
| New message | guidely.showNewMessage("…") | Guidely.shared.showNewMessage("…") | Guidely.showNewMessage("…") |
| Hide the bubble | showMessengerLauncher={false} | showMessengerLauncher: false | showMessengerLauncher = false |
| Events | messenger:open, conversation:message | .messengerOpen, .conversationMessage | MessengerOpen, ConversationMessage |
Conversations belong to the app install that started them; reset() on logout forgets them.
Allowed origins
Allowed origins protect browser installs only. Native and React Native apps send no Origin header and are identified by the public key alone (public keys are not secrets: they only unlock published configuration and event ingestion for one environment).
Links and deep links
Button and checklist URLs that start with http(s):// open in the system browser. Relative paths (/settings/billing) and custom schemes (northwind://deals/42) go to your onNavigate handler, so you can route them inside the app. In React Native the handler sees every URL first and returns true when it handled one; on iOS and Android it receives the relative and custom-scheme URLs (without a handler, custom schemes are opened by the OS).
React Native
npm install @guidelytech/react-native @react-native-async-storage/async-storage
cd ios && pod install
Works with Expo (SDK 50+, including Expo Go — the SDK has no native code) and bare React Native 0.72+.
// App.tsx
import AsyncStorage from "@react-native-async-storage/async-storage";
import { NavigationContainer, useNavigationContainerRef } from "@react-navigation/native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { GuidelyProvider, guidelyScreenLayout, useGuidelyNavigation } from "@guidelytech/react-native";
export default function App() {
const navigationRef = useNavigationContainerRef();
return (
<GuidelyProvider
projectId="pk_prod_xxx"
apiUrl="https://guidelytech.com"
storage={AsyncStorage}
insets={useSafeAreaInsets()}
user={currentUser ? { id: currentUser.id, traits: { plan: currentUser.plan } } : null}
onNavigate={(url) => openInAppRoute(url)}
>
<Navigation navigationRef={navigationRef} />
</GuidelyProvider>
);
}
function Navigation({ navigationRef }) {
// Reports every route change as a Guidely screen view (route name = screen name).
const guidely = useGuidelyNavigation(navigationRef);
return (
<NavigationContainer ref={navigationRef} {...guidely}>
{/* Scopes targets to their screen: inactive tabs stay mounted and would otherwise "match". */}
<Tab.Navigator screenLayout={guidelyScreenLayout}>{/* … */}</Tab.Navigator>
</NavigationContainer>
);
}
Put GuidelyProvider at the root, outside your navigators, so guides render above every screen. Pass storage — without it, identity and guide history only last for the current app session.
| API | Purpose |
|---|---|
useGuidely() | The client: identify, track, screen, startGuide, stopGuide, dismissGuide, openChecklist, openResourceCenter, reset, setConsent, on… (a no-op stand-in until ready) |
useGuidelyNavigation(ref) | React Navigation bridge (onReady / onStateChange) |
useGuidelyScreen(name) | Report a screen from a component (other navigators, modals) |
<GuidelyTarget id> / useGuidelyTarget(id) | Make a view targetable; the hook returns props to spread on an existing view instead of wrapping it |
guidelyScreenLayout / <GuidelyScreenScope name> | Tell Guidely which screen targets are on (React Navigation screenLayout, or wrap a screen by hand) |
useGuidelyIdentify(id, traits, account) | Identify when the signed-in user changes |
useGuidelyReady(), useGuidelyEvent(name, cb), useChecklistProgress(key), useGuideState(key), useActiveGuide() | State and events |
Provider props: every client option (projectId, apiUrl, environment, storage, debug, analytics, consent, appVersion, configRefreshMs, theme, onNavigate, onError, showChecklistLauncher, showResourceCenterLauncher) plus user, insets and launcherOffset (distance of the checklist launcher above the bottom inset, to clear tab bars; default 72).
GuidelyTarget wraps its children in a View. In a column layout that wrapper stretches to the full width, so a tooltip would point at the middle of the row: give it style={{ alignSelf: "flex-start" }} or use useGuidelyTarget. React Native's measureInWindow does not see views inside native-stack modals that are presented above the root view; tour those screens with modal steps or present them as regular screens.
Pass appVersion (for example from expo-application's nativeApplicationVersion) to use App version rules.
A complete example lives in mobile/react-native/example (Expo + React Navigation). With the dashboard running locally and the demo seed loaded:
cd mobile/react-native/example
npm run sdk # builds and installs the SDK from this repo
EXPO_PUBLIC_GUIDELY_API_URL=http://localhost:3000 npx expo run:ios
iOS
See mobile/ios/README.md for the full reference.
import Guidely
Guidely.configure(GuidelyOptions(projectId: "pk_prod_xxx", apiURL: URL(string: "https://guidelytech.com")!, environment: .production))
Guidely.shared.identify(user.id, traits: ["plan": user.plan])
Guidely.shared.track("deal_created", properties: ["value": 1200])
// SwiftUI
DealsView().guidelyScreen("Deals")
Button("New deal", action: create).guidelyTarget("new-deal-button")
// UIKit
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated); Guidely.shared.screen("Deals") }
newDealButton.guidelyId = "new-deal-button"
Guides render in a pass-through overlay window above your app, so they work with SwiftUI and UIKit alike and never block touches outside guide UI. Example app: mobile/ios/Example.
Android
See mobile/android/README.md for the full reference.
class App : Application() {
override fun onCreate() {
super.onCreate()
Guidely.init(this, GuidelyOptions(projectId = "pk_prod_xxx", apiUrl = "https://guidelytech.com", environment = GuidelyEnvironment.PRODUCTION))
}
}
Guidely.identify(user.id, traits = mapOf("plan" to user.plan))
Guidely.track("deal_created", mapOf("value" to 1200))
// Compose (com.guidelytech:guidely-compose)
GuidelyScreen("Deals")
Button(onClick = ::create, modifier = Modifier.guidelyTarget("new-deal-button")) { Text("New deal") }
// Views
Guidely.screen("Deals") // in onResume()
newDealButton.guidelyTarget("new-deal-button")
Guides render in an overlay attached to the current Activity. Example app: mobile/android/example.
Authoring guides for mobile
- Open the guide, go to Trigger → Platforms and tick iOS and/or Android.
- For anchored steps, set the target to Guidely id and enter the id your developers attached.
- Restrict the guide to screens with *Page restrictions* (`/Home`) or an **App → Mobile screen**
rule, and use App → Platform when copy differs between iOS and Android.
- Publish to the environment your app's key belongs to. Apps pick up changes on their next config
refresh (every 5 minutes and when the app returns to the foreground).
The browser element picker and in-app preview are web-only for now; test mobile guides by publishing to the development environment and opening the app (debug: true logs every trigger decision).
Consistency across SDKs
All SDKs implement the contract in mobile/SPEC.md. Targeting, URL/screen patterns, version comparison, rollout bucketing and frequency rules are checked against shared test vectors (mobile/conformance/vectors.json) generated from the TypeScript engine, so a user lands in the same rollout bucket and sees the same guides on web, iOS and Android. After an intentional behaviour change, regenerate them with UPDATE_CONFORMANCE=1 pnpm --filter @guidelytech/sdk-core test and update the native SDKs until their test suites pass.
Troubleshooting
| Symptom | Check |
|---|---|
| No guides at all | debug: true and look for config loaded … N guides. N = 0 means no published guide has your platform ticked, or the key belongs to another environment. |
| Guide never starts | Its screen restriction must match the name you pass to screen() (case-sensitive, matched as /<Name>). Frequency once guides do not reappear after being seen — reset() gives you a fresh anonymous user. |
| Tooltip shows as a modal or is skipped | The target id was not found on screen within the step's wait time. Make sure the view is tagged with the same Guidely id and is visible. |
| Tooltip points at the wrong place (React Native) | The GuidelyTarget wrapper is stretching; see the note above. |
| Tooltip points at a control on another tab (React Native) | Add screenLayout={guidelyScreenLayout} to the navigator (or GuidelyScreenScope). |
| Android emulator cannot reach a local dashboard | Use http://10.0.2.2:<port> and allow cleartext for it in a debug network security config. |