Skip to content

Repository files navigation

RuView Native — Kotlin/Compose port of RuView Lite

A native Android rewrite of the Expo/React Native ruview-lite app, same real WiFi RSSI presence-sensing logic, no simulation. This exists to get one concrete thing the RN version couldn't give reliably: sensing that survives the app being backgrounded, via a proper foreground Service.

Why native, specifically

react-native-wifi-reborn was already calling the exact same OS API this app calls — WifiManager.getConnectionInfo().getRssi(). Going native does not unlock deeper RSSI access or CSI-level data; that ceiling (per-subcarrier phase/amplitude) is an Android OS restriction that applies identically to native and RN apps alike, and still requires rooted monitor-mode firmware or dedicated hardware (ESP32-S3) to cross.

What native does buy:

  • A real foreground Service with a persistent notification, so OEM background-execution limits (the throttling the RN README called out) don't silently stop the sensing loop.
  • No Metro/dev-client build step, direct Gradle/Android Studio tooling.
  • Slightly less overhead per tick (no JS bridge) — irrelevant at 1Hz, but free.

Architecture

MainActivity.kt                 permission flow, starts+binds SensingService,
                                 hosts Compose UI
SensingService.kt                foreground Service: polls WifiManager every
                                 1s, owns SensorState, exposes StateFlows
                                 (reading, history, baselineMean, ssid,
                                 connectionError), shows status notification
engine/WifiSensingEngine.kt      pure Kotlin port of the RN engine — same
                                 constants (60-sample history, 15-sample
                                 baseline, 6-sample window, 2.5x sensitivity,
                                 4dB mean-shift), same math, no Android deps
ui/MainViewModel.kt              collects the bound service's StateFlows into
                                 one UiState for Compose to read
ui/RuViewApp.kt                  root composable: header + 3-tab bar
ui/screens/*.kt                  Dashboard / Signal / Settings, ported 1:1
                                 from the RN screens, same dark palette
ui/theme/Theme.kt                same colors as the RN app's StyleSheets

Data flow: SensingService is the single source of truth (it keeps running whether or not an Activity is bound to it). MainActivity binds to it after permission is granted, MainViewModel.bind() starts collecting its flows, and Compose screens read one UiState — no screen touches the service directly.

Design choice: polling vs. event-driven

SensingService polls WifiManager.connectionInfo.rssi every second, same cadence as the RN app — it does not use ConnectivityManager.NetworkCallback.onCapabilitiesChanged() for event-driven signal updates, even though that API exists and would in theory avoid unnecessary polling. Callback cadence and transportInfo availability for WiFi vary enough across OEMs and API levels that plain polling is the more predictable choice for a first native port. Worth revisiting once this is running on real target devices.

Fixed vs. the RN version

The RN SignalScreen.js labeled history[0].rssi (the oldest sample in the rolling 60-sample window) as "Baseline mean" — after the window filled past 60 seconds that number drifted and was no longer the actual calibrated baseline. This port exposes the real SensorState.baselineMean via its own StateFlow and displays that instead.

Building it

This project has no committed Gradle wrapper jar (binary file, not something to hand-write). Two ways to build:

Android Studio (recommended) — Open the RuViewNative/ folder directly; Studio detects the Gradle project, regenerates the wrapper, and syncs dependencies automatically. Run on a device or emulator with Play Services (needed for real WiFi radio state — most emulators fake a static AP).

Command line — requires a system-installed Gradle matching gradle/wrapper/gradle-wrapper.properties (8.7):

gradle wrapper          # generates gradlew + the wrapper jar once
./gradlew installDebug  # builds and installs on a connected device

On first launch, grant the location permission when prompted — same reason as the RN app: Android ties WiFi signal-strength access to it, and the app doesn't read or store actual location.

Known limitations (same honest scoping as before)

  • Android only (same OS restriction, not a native-vs-RN difference).
  • Requires an active WiFi connection.
  • Detects that something changed, not what — no person count, no location within the room, no vitals, no pose.
  • Sensitivity is a blunt threshold, not a trained model.
  • Baseline doesn't auto-invalidate on a network switch — only the manual "Recalibrate" button resets it. Worth adding a WifiManager SSID-change check if you hit this in practice.

Next step up from here

Same as before: real presence + vitals + pose needs actual CSI hardware — an ESP32-S3 node running CSI-capture firmware, feeding a signal-processing/ML pipeline this app would subscribe to over MQTT/WebSocket. That's a separate build, not something either the RN or this native app can get to on phone WiFi hardware alone.

About

Native Kotlin/Jetpack Compose port of RuView Lite — real WiFi RSSI presence sensing on Android

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages