Skip to Content

Installation

There are two ways to get Luno onto a device: install the published app from Google Play, or build it from source. Play is the right default for anything beyond development.

Install from Google Play

A Play install is exempt from both of the Android restrictions that make sideloading awkward: it never triggers Play Protect’s fraud-protection warning, and — more importantly — its SMS permissions are not hard-restricted, so they can be granted normally on Android 15+.

If you install from Play, you can skip straight to Pairing. The rest of this page is for building from source.

Build from source

Luno is built with Flutter. There are two build flavors, and choosing between them is the only decision you need to make before building.

Choose a flavor

FlavorRECEIVE_SMSInbound SMSInstalls clean from any source
fulldeclaredyesno — warns on internet-sideload
sendOnlyabsentnoyes

Pick full unless you have a specific reason not to. The warning it triggers is a Play Protect heuristic, not a defect, and installing over adb avoids it completely. sendOnly exists for cases where an APK must be distributed through a browser or messaging app and inbound capture is genuinely not needed.

“Installs clean” is a narrower promise than it sounds. On Android 15+, both SEND_SMS and RECEIVE_SMS are hard-restricted for any app not installed from an app store — so a sideloaded sendOnly build still cannot send until the user allows restricted settings. See Play Protect.

Build

Get the dependencies

flutter pub get

Build a flavored APK

flutter build apk --flavor full # → build/app/outputs/apk/full/release/app-full-release.apk

Install over adb

adb install -r build/app/outputs/apk/full/release/app-full-release.apk

adb install is not an “internet-sideloading source”, so Play Protect’s enhanced fraud protection does not apply to it.

Always pass --flavor, and pass it to the subcommand. Bare flutter build apk does not fail — it builds every variant and leaves a build/app/outputs/apk/release/app-release.apk that declares RECEIVE_SMS. That is a full build under a neutral name, and sideloading it trips Play Protect exactly as if you had asked for full. Name the flavor and ship the flavored artifact, so the file says what it is.

Note the placement: flutter build apk --flavor full, not flutter build --flavor full. The latter is rejected outright with “Could not find an option named —flavor”.

Other build targets

flutter run --flavor full # run on a connected device or emulator flutter build appbundle --flavor full # AAB, for Play Store upload flutter analyze # static analysis flutter test # Dart unit and widget tests

First launch

The app must be opened manually once. This is not a nicety — Android does not deliver BOOT_COMPLETED to an app that has never been launched or that has been force-stopped, so without a first launch the node would never auto-start after a reboot.

On that first launch you will be walked through:

  1. Permissions — phone state first, then SMS. See Permissions.
  2. Battery optimisation exemption — required for the foreground service to survive Doze. See OEM reliability.
  3. Pairingthe next step.

Testing SMS send and receive requires a real device or two emulator instances. An emulator can send SMS to another emulator’s port, but never to a real SIM or carrier.