Skip to content

Native apps (iOS & Android)

Diadem can be wrapped as a native Android and iOS app using Capacitor. The native app bundles the Diadem client and runs it in a system webview as a thin client: every request is sent to a remote Diadem instance.

Two flavours are possible from the same codebase:

  • Generic app: on first launch the user is asked for the URL of the Diadem instance to connect to (the instance gate). They can change it later under Profile → Instance. This is meant for public builds that are hosted on app stores and the likes.
  • Branded app: an instance URL is baked in at build time, so the gate and the instance switcher are hidden and the app always connects to that one instance. This is meant for when you want to build native Diadem yourself.

The map still renders with MapLibre GL JS inside the webview. (Native MapLibre rendering is not used.)

  • Everything from the normal installation (Node 22+, pnpm)
  • JDK 21 and the Android SDK (Capacitor 7 builds against Java 21)
  • A connected Android device with USB debugging, or an emulator
  • iOS only: a Mac with Xcode, CocoaPods, and an Apple Developer account for App Store/TestFlight uploads

On macOS, make sure the full Xcode install is selected, not only the Command Line Tools:

Terminal window
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
xcodebuild -version
pod --version

A dev shell with the full Android toolchain (Node, JDK 21, Android SDK, Gradle) is provided in flake.nix. Run native build commands inside it:

Terminal window
nix develop

On other systems, install JDK 21 + the Android SDK yourself and set ANDROID_HOME/JAVA_HOME.

pnpm run build:native -> static client in build/
pnpm run build:native:android -> build client and sync android/
pnpm run build:native:ios -> build client and sync ios/

After syncing, build the native project with Gradle, Xcode, or Capacitor.

Build a debug APK and run it on a connected device.

  1. Confirm the device is connected and enter the nix shell (if on NixOS):

    Terminal window
    adb devices
    nix develop
  2. Build the client and sync the Android platform:

    Terminal window
    pnpm run build:native:android
  3. Build, install, and launch the debug APK:

    Terminal window
    npx cap run android

    Or build the APK manually and install it:

    Terminal window
    cd android && ./gradlew assembleDebug
    adb install -r app/build/outputs/apk/debug/app-debug.apk

After changing client code, re-run pnpm run build:native:android before rebuilding.

Build and run the app on an iOS simulator from a Mac with Xcode installed.

  1. Build the client and sync the iOS platform:

    Terminal window
    pnpm run build:native:ios
  2. Open the workspace in Xcode and run the App scheme on an iOS simulator:

    Terminal window
    npx cap open ios
  3. Or build from the CLI against an installed simulator runtime:

    Terminal window
    xcrun simctl list devices available
    xcodebuild -workspace ios/App/App.xcworkspace \
    -scheme App \
    -configuration Debug \
    -destination 'platform=iOS Simulator,name=iPhone 15 Pro' \
    build

After changing client code, re-run pnpm run build:native:ios before rebuilding.

Test iOS deep links in the booted simulator with:

Terminal window
xcrun simctl openurl booted "diadem://pokemon/<id>"
  • Deep links use the diadem:// scheme. Simulate one with:

    Terminal window
    adb shell am start -a android.intent.action.VIEW -d "diadem://pokemon/<id>"
  • Discord/OAuth login opens the system browser and returns to the app via diadem://auth. This works against any Diadem instance, the required bearer auth support is built into the Diadem server.

Play requires a signed App Bundle (.aab).

  1. Create a keystore (once) and keep it safe:

    Terminal window
    keytool -genkey -v -keystore diadem.keystore -alias diadem \
    -keyalg RSA -keysize 2048 -validity 10000
  2. Tell Gradle how to sign by creating android/keystore.properties (do not commit it):

    storeFile=/absolute/path/to/diadem.keystore
    storePassword=...
    keyAlias=diadem
    keyPassword=...

    and wiring it into android/app/build.gradle (signingConfigs + buildTypes.release.signingConfig) per the Capacitor signing docs.

  3. Build the release bundle:

    Terminal window
    pnpm run build:native:android
    cd android && ./gradlew bundleRelease
    # -> android/app/build/outputs/bundle/release/app-release.aab

Upload the .aab to the Play Console.

On a Mac:

Terminal window
pnpm run build:native:ios
npx cap open ios # opens Xcode

Set the signing team and bundle id in Xcode, then Archive and upload to App Store Connect. The committed project registers the diadem:// URL scheme for OAuth/deep links and includes the location usage description required by iOS.

Branded builds and embedding an instance URL

Section titled “Branded builds and embedding an instance URL”

To ship an app that always connects to one instance (hiding the gate and the Profile → Instance switcher), set VITE_DIADEM_INSTANCE at build time:

Terminal window
VITE_DIADEM_INSTANCE=https://map.example.com pnpm run build:native:android
cd android && ./gradlew assembleDebug # or bundleRelease

Or put it in a .env file (gitignored) so every build picks it up:

.env
VITE_DIADEM_INSTANCE=https://map.example.com

When set:

  • the first-run instance gate is skipped, the app connects straight to that URL,
  • the Instance section in the profile menu is hidden,
  • the URL cannot be changed at runtime.

Leave VITE_DIADEM_INSTANCE unset to build the generic app with the instance gate.

App id, name, icon and splash live in the native projects:

  • App id / name: capacitor.config.ts (appId, appName). The default id is ee.malt.diadem, change it before publishing your own app.
  • Icons / splash: replace the generated assets under android/app/src/main/res/ (and ios/App/App/Assets.xcassets/ on iOS), e.g. with @capacitor/assets.
  • Deep-link scheme: the diadem:// intent filter is in android/app/src/main/AndroidManifest.xml; the iOS URL scheme is in ios/App/App/Info.plist.