Quick Start
Install the Stay22 Mobile SDK, initialize it, and schedule your first accommodation notification.
This guide takes you from an empty project to a working integration: the SDK installed, the user opted in, and a trip handed to Stay22. You need your partner ID (aid) — the same one you use for Allez links.
Install the SDK
Add the Stay22 Maven repository in settings.gradle.kts:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://raw.githubusercontent.com/Stay22/stay22-android-sdk/main/maven") }
}
}Then add the dependency:
dependencies {
implementation("com.stay22:sdk:1.3.0")
}In Xcode, go to File > Add Package Dependencies and enter:
https://github.com/Stay22/stay22-ios-sdkOr add it to Package.swift:
dependencies: [
.package(url: "https://github.com/Stay22/stay22-ios-sdk.git", from: "1.2.0")
]Add the Stay22SDK product to your app target.
Add the package to pubspec.yaml, pinned to a release tag:
dependencies:
stay22_flutter:
git:
url: https://github.com/Stay22/stay22-flutter-sdk.git
ref: "1.3.0"Then run flutter pub get.
The plugin bundles the iOS framework, so there is no iOS equivalent of this step. Android resolves the native SDK from Stay22's Maven repository, which your app declares once in its top-level Android build file. Depending on when your project was created that file is either Kotlin DSL or Groovy, so use the one you have.
android/build.gradle.kts
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://raw.githubusercontent.com/Stay22/stay22-android-sdk/main/maven") }
}
}android/build.gradle
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://raw.githubusercontent.com/Stay22/stay22-android-sdk/main/maven' }
}
}Without either one, the Android build fails with Could not find com.stay22:sdk.
The plugin supports Android and iOS. Importing it on web or desktop is safe; calling into it throws, so guard shared code with Stay22.isSupportedPlatform.
Install from GitHub. The package is not on npm.
npm install github:Stay22/stay22-capacitor-sdk#0.1.2
npx cap syncPin the tag you want from the Capacitor releases.
The plugin bundles the iOS framework. Android resolves com.stay22:sdk from Stay22's Maven repository, which your app declares once. The wrapped Android SDK needs API 26. A default Capacitor 6 app is API 22, so set minSdkVersion to 26 in variables.gradle.
If repositories live in android/build.gradle:
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://raw.githubusercontent.com/Stay22/stay22-android-sdk/main/maven' }
}
}If they live in settings.gradle (dependencyResolutionManagement) instead, add the same URL there. Templates that set FAIL_ON_PROJECT_REPOS will reject the allprojects block.
The plugin supports Android and iOS. There is no web or desktop implementation.
Check the Android releases, iOS releases, Flutter releases and Capacitor releases for the latest version. The Android and iOS repos also offer manual distribution (a raw .aar / .xcframework) — see their READMEs.
Initialize at app launch
Initialize once, as early as possible, with your partner ID. Set Stay22.isEnabled before initializing if you gate Stay22 offers behind your own consent flow — nothing is scheduled while it's false.
Call from Application.onCreate():
import android.app.Application
import com.stay22.sdk.Stay22
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Stay22.isEnabled = userHasOptedInToStay22Offers
Stay22.initialize(application = this, aid = "your-partner-id")
}
}Call early in your app launch:
import Stay22SDK
Stay22.isEnabled = userHasOptedInToStay22Offers
Stay22.initialize(aid: "your-partner-id")Declare the partner ID natively, so the SDK starts during app launch rather than after Dart runs.
ios/Runner/Info.plist:
<key>Stay22PartnerAID</key>
<string>your-partner-id</string>android/app/src/main/AndroidManifest.xml, inside <application>:
<meta-data
android:name="com.stay22.sdk.PartnerAID"
android:value="your-partner-id" />This is the recommended setup on both platforms, for a different reason on each. On iOS it is the only arrangement that reliably opens the booking page for a notification tapped from a cold start — Dart runs after iOS has already decided who receives that tap. On Android it is what lets the notification permission prompt find a live screen to appear on.
Declaring the ID natively starts the SDK before any Dart runs, which has one consequence worth planning for: on a first launch the startup request goes out before a Dart await Stay22.setEnabled(false) can land. Later launches honour the stored value.
So pick the arrangement that matches your consent model:
- Stay22 may run by default. Declare the ID natively, and set the gate from Dart whenever the user changes it. You keep the cold-start notification tap.
- Stay22 must not be contacted before the user consents. Skip the native declaration and initialize from Dart once they have. On iOS this gives up the cold-start tap.
import 'package:stay22_flutter/stay22_flutter.dart';
await Stay22.setEnabled(userHasOptedInToStay22Offers);
// Only when you are not declaring the ID natively.
await Stay22.initialize(aid: 'your-partner-id');Every call returns a Future and reports failures as a PlatformException. Calls that need a live SDK fail with the not_initialized error code if they run before initialization finishes.
Declare the partner ID natively, so the SDK starts while Capacitor sets up its bridge, before any of your JavaScript runs.
ios/App/App/Info.plist:
<key>Stay22PartnerAID</key>
<string>your-partner-id</string>android/app/src/main/AndroidManifest.xml, inside <application>:
<meta-data
android:name="com.stay22.sdk.PartnerAID"
android:value="your-partner-id" />This is the recommended setup on both platforms. On iOS it is the only arrangement that reliably opens the booking page for a notification tapped from a cold start. On Android, Stay22.initialize({ aid }) from JavaScript can miss the first Activity resume, so the permission prompt may find no live screen.
Declaring the ID natively starts the SDK before JavaScript runs, which has one consequence worth planning for: on a first launch the startup request goes out before await Stay22.setEnabled({ enabled: false }) can land. Later launches honour the stored value. The enabled flag defaults to true until you set it.
So pick the arrangement that matches your consent model:
- Stay22 may run by default. Declare the ID natively, and set the gate from JavaScript whenever the user changes it. You keep the cold-start notification tap.
- Stay22 must not be contacted before the user consents. Skip the native declaration and initialize from JavaScript once they have. On iOS this gives up the cold-start tap.
import { Stay22 } from '@stay22/capacitor';
await Stay22.setEnabled({ enabled: userHasOptedInToStay22Offers });
// Only when you are not declaring the ID natively.
await Stay22.initialize({ aid: 'your-partner-id' });Calls that need a live SDK reject with not_initialized if they run before initialization finishes.
Ask for notification permission
The SDK delivers standard local notifications, so the user has to grant notification permission. Request it at a moment that makes sense in your UX — ideally right after the user opts in to travel offers:
Stay22.requestNotificationPermission()Handles the Android 13+ runtime permission; on older versions it's a no-op. Check the current state anytime with Stay22.hasNotificationPermission().
Task {
await Stay22.requestNotificationPermission()
}Prompts for local notification permission. Check the current state anytime with await Stay22.hasNotificationPermission().
final granted = await Stay22.requestNotificationPermission();Prompts where the system allows one, and returns the user's answer. On Android 12 and below, once the user has already denied, or when the notification channel is off, it returns the current state without prompting. Check the current state anytime with await Stay22.hasNotificationPermission().
const { value: granted } = await Stay22.requestNotificationPermission();Shows the system prompt and reports what the user chose. Resolves with the current state when no prompt can be shown. Check the current state anytime with await Stay22.hasNotificationPermission().
Tell the SDK about the trip
This is the heart of the integration. Whenever your app learns about a trip — a booked flight, a searched destination, saved travel dates — pass it along. The SDK takes it from there:
import com.stay22.sdk.TravelContext
Stay22.setTravelContext(
TravelContext(
address = "Paris, France",
checkinDate = "2026-03-20",
checkoutDate = "2026-03-25"
)
)Stay22.setTravelContext(
TravelContext(
address: "Paris, France",
checkinDate: "2026-03-20",
checkoutDate: "2026-03-25"
)
)await Stay22.setTravelContext(
TravelContext(
address: 'Paris, France',
checkinDate: '2026-03-20',
checkoutDate: '2026-03-25',
),
);Invalid input throws an ArgumentError before anything reaches the platform channel.
await Stay22.setTravelContext({
address: 'Paris, France',
checkinDate: '2026-03-20',
checkoutDate: '2026-03-25',
});Every field is optional — a destination alone is enough to start. Dates use YYYY-MM-DD. You can also pass latitude/longitude for precision, or hotelName when the user searched a specific property.
If the trip is cancelled or the user clears their search, call Stay22.clearTravelContext() to drop the context and any pending notification.
See it work
In a normal run the notification arrives after the user leaves your app, on Stay22's schedule — which is the right behavior in production but slow for a first test. Flip on test mode to get the notification after backgrounding the app.
Platform setup notes
The SDK's manifest merges in everything it needs (INTERNET, POST_NOTIFICATIONS, the activity that handles notification taps, and the queries entries it uses to check for installed booking apps) — no required manifest changes. Optionally, give the notification your own status-bar icon:
<application>
<meta-data
android:name="com.stay22.sdk.notification_small_icon"
android:resource="@drawable/ic_stat_notification" />
</application>Add these entries to your Info.plist so the SDK can route the offer to a booking app the user already has installed. iOS returns false for any scheme you leave out, so list all five:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>airbnb</string>
<string>booking</string>
<string>expda</string>
<string>hotelsapp</string>
<string>agoda</string>
</array>Android needs nothing: the plugin merges the SDK's manifest into your app.
For iOS, add the same entries to ios/Runner/Info.plist so the SDK can route the offer to a booking app the user already has installed. iOS returns false for any scheme you leave out, so list all five:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>airbnb</string>
<string>booking</string>
<string>expda</string>
<string>hotelsapp</string>
<string>agoda</string>
</array>If your app assigns UNUserNotificationCenter.delegate itself, forward the notification callbacks from AppDelegate.swift — see the package README. Most apps do not need this.
Android needs nothing extra in the manifest: the plugin merges the SDK's entries into your app. Set minSdkVersion to 26 if you have not already.
For iOS, add the same URL-scheme entries to ios/App/App/Info.plist so the SDK can route the offer to a booking app the user already has installed. iOS returns false for any scheme you leave out, so list all five:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>airbnb</string>
<string>booking</string>
<string>expda</string>
<string>hotelsapp</string>
<string>agoda</string>
</array>On iOS, Capacitor gives local notifications a single handler slot. This plugin takes that slot and passes anything Stay22 did not schedule to the plugin that holds it, so @capacitor/local-notifications keeps working alongside Stay22 in either load order. If another plugin claims the slot, Stay22 takes it back the next time the app comes to the foreground. Stay22.isNotificationHandlerInstalled() reports whether Stay22 currently holds it.
If you set handleApplicationNotifications: false in capacitor.config.ts to run your own delegate, this plugin's slot never receives anything. Forward taps and foreground presentations from your own delegate instead — see the package README. Most apps do not need this.
Next steps
- Notifications & Events — customize the notification copy and observe scheduling decisions
- Testing & Troubleshooting — verify the full flow on a device
- The GitHub READMEs (Android, iOS, Flutter, Capacitor) carry the full API reference and a step-by-step integration guide you can hand to an AI coding agent