Install the SDK
Four paths depending on your stack. In every case you install AppwinCore plus the product modules you want: Core carries the device identity, the session and the network client, and the products are only consumers of it.
| Product | iOS (SPM) | Android (Maven) | React Native | Flutter |
|---|---|---|---|---|
| Core | AppwinCore product | io.appwin:appwin-core | @appwin/react-native | appwin_core |
| Support | AppwinSupport product | io.appwin:appwin-support | same | appwin_support |
| Community | AppwinCommunity product | io.appwin:appwin-community | same | appwin_community |
| Notifications | AppwinNotifications product | io.appwin:appwin-notifications | same | through appwin_support |
Core is installed everywhere except on React Native, where the single package already exposes all four products and there is nothing to add.
On iOS the four are products of a single package: you add one URL, then tick
what you import. That is how firebase-ios-sdk works.
Modules of the same platform ship together and share a version: do not let them drift, cross combinations are not tested.
| Minimum version | |
|---|---|
| iOS | 16.0, Xcode 16 (the packages are Swift 6) |
| Android | 7.0 (API 24), JDK 17 |
| React Native | 0.73 |
| Flutter | 3.3, Dart 3.9.2 |
Adding the dependencies
Install AppwinCore, then the product modules you want. Same rule in Swift,
Kotlin and Dart. React Native is the one exception: a single package exposes the
four products there, so there is nothing to break apart.
Technically, every product module already declares Core and makes it reachable
from your code: you could leave it out. Declare it anyway. You call
AppwinCore.configure in your own code, so it is a direct dependency of your
app, not an implementation detail of a product. You see its version in your
build, and nothing breaks the day a product changes how it exposes it.
This is the Firebase model: firebase_core is installed explicitly next to
firebase_auth, for the same reason.
Swift Package Manager
From Xcode: File → Add Package Dependencies, paste the URL below, then tick the products you want in the list it shows.
https://github.com/appwin-dev/appwin-ios
Or declaratively, in your Package.swift:
dependencies: [
.package(url: "https://github.com/appwin-dev/appwin-ios.git", from: "0.1.0"),
],
targets: [
.target(name: "MyApp", dependencies: [
.product(name: "AppwinCore", package: "appwin-ios"),
// + the product you want: AppwinSupport, AppwinCommunity, AppwinNotifications
.product(name: "AppwinSupport", package: "appwin-ios"),
]),
]
One URL for the four products: you have neither four dependencies to add nor four versions to keep aligned.
The product already links AppwinCore, so import AppwinCore would compile
without the first line. It is there because you call configure: a transitive
import would break if a product ever stopped depending on Core.
SPM only: it is Apple's dependency manager, built into Xcode, and it asks for
neither a separate configuration file nor a manual install step. No Podfile to
touch.
iOS 16 minimum, and Xcode 16: the packages are declared in Swift 6. The SDK is built on SwiftUI and async/await, and a lower target does not compile.
Initialising
One call, at launch, before any product is used. It is idempotent: calling it again breaks nothing, and achieves nothing either.
In your AppDelegate or when the scene starts:
import AppwinCore
AppwinCore.configure(projectAppId: "your-app-id")
configure is synchronous: it prepares the device identity and the network
client immediately, then opens the session in the background. Your app keeps
starting meanwhile.
If a call absolutely needs an open session:
try await AppwinCore.bootstrapSession()
4. Initialise each product
configure prepares the foundation and checks nothing. Each product then has
its own initialize(), which asks the server whether it may open. Call it
before showing that product's entry point, and gate your own UI on the answer:
the SDK cannot hide your tab or your button, it does not own your navigation.
let support = await AppwinSupport.initialize()
print("Appwin Support: \(support)")
It answers rather than throwing, because "not entitled" is a normal outcome of a normal launch. The three products share one round trip and the verdict is cached on disk, so being offline falls back to the last known answer instead of closing a product you pay for.
The full table of results, and what to do with each, is in the quickstart. :::
Which environment it talks to
By default the SDK talks to https://api.appwin.io. There is nothing to
configure: your App ID is enough to route requests to your project.
The baseUrl parameter of configure exists for the cases where we give you
another entry point, a staging environment for instance. Otherwise, leave it
empty.
Checking it works
try await AppwinCore.bootstrapSession()
print(AppwinCore.deviceId ?? "configure was not called")
A non-null device identifier and a bootstrap that does not throw: the foundation is in place, you can wire a product.
It does not work
| Symptom | Likely cause |
|---|---|
| "not configured" error on the first call | configure not called, or called after a product was used |
| Intermittent 401s at launch | Two configure calls with different App IDs in the same app |
| 403 on every call | Product not enabled on the App ID - enable it from the dashboard |
| "Coming soon" screen | The product is switched off on the studio's side |
The user stays anonymous after identify | Bootstrap not replayed: use the product's login |
| iOS build fails | Deployment target below iOS 16, or Xcode older than 16 |
| Android build fails | minSdk below 24, or a JDK older than 17 |
| Native module not found on React Native | Build not rerun after installing, or app launched in Expo Go |
Next
- Quickstart - the whole path, up to a screen on display
- Identity - anonymous, signed in, shared across products
- Community - showing the feed