FreshLoop is a local-first Expo 57 / React Native pantry application for Android-first classroom demonstration. It keeps inventory in on-device SQLite, distinguishes Use By safety wording from Best Before quality wording, ranks items with an explanation, offers a small local Rescue recipe catalogue, and records only user-confirmed outcomes.
The complete app and bundled ingredient/recipe content are available in English, Simplified Chinese, and Traditional Chinese. Change language in Settings → Language; the choice applies immediately, survives restart, and overrides the device language. User-entered names/notes and Open Food Facts product names remain exactly as supplied, while search matches all three bundled catalog languages.
- Windows 10/11 with hardware virtualization enabled, or Ubuntu 24.04 under WSL 2 with a Windows Android emulator.
- About 15 GB of free space for the Android SDK, native caches, and an API 36 emulator image.
- Git is optional when using the submitted source archive.
- No account, backend, API key, or
.envfile is required.
FreshLoop resolves and injects Node 24.14.0, npm 11.3.0, JDK 21, Android API/Build Tools 36, NDK 27.1.12297006, and CMake 3.22.1 without changing the global PATH, JAVA_HOME, or ANDROID_HOME. Android Studio is optional; the setup command can install Google's command-line tools and emulator in the user profile.
From the repository root, these are the only commands required:
freshloop.cmd doctor
freshloop.cmd setup
freshloop.cmd androiddoctor reports REUSE, INSTALL, or BLOCKED for each tool and supports --json. setup reuses compatible installations first; before every missing download it shows the pinned version, official source, checksum policy, disk use, destination, and permissions, then asks for confirmation. Use --dry-run for a no-change preview or --yes only when unattended acceptance is intentional.
android installs JavaScript dependencies when missing, generates the Android project through Expo CNG, builds the x86_64 debug APK, starts or reuses an AVD, installs and cold-starts FreshLoop, establishes the ADB/Metro bridge, and then keeps Metro open. After the success message, a running terminal is expected: Ctrl+C stops Metro but leaves the emulator open for fast incremental builds.
For a bundled four-ABI release APK:
freshloop.cmd release
freshloop.cmd release --installThe first form only builds mobile/android/app/build/outputs/apk/release/app-release.apk; --install also starts/reuses the emulator, installs the APK, and cold-starts the app without Metro. A first native build can spend several minutes downloading/compiling Gradle and CMake inputs. The runner allows 20 minutes for debug and 30 minutes for release, prints a phase diagnostic after 60 seconds without output, and writes details under .freshloop/logs/. Short checkouts use .g/ for the Gradle cache. If a Windows checkout would push React Native native headers past Ninja's 260-character limit, the runner automatically uses the same-volume, checkout-isolated X:\.fl-gradle\<id> fallback. Both forms avoid C:/E: native-library copies; moving the repository to C:\src\FreshLoop or E:\src\FreshLoop remains recommended.
Once a compatible Node is available, the equivalent commands from mobile/ are:
npm run android
npm run android:rebuild
npm run apk:release
npm run android:releaseDo not call mobile/android/gradlew.bat from mobile/; Gradle would use the wrong project root. The FreshLoop release entry always runs the wrapper with mobile/android/ as its working directory.
Keep the active checkout in the WSL ext4 filesystem, not /mnt/c or /mnt/e:
git clone git@github.com:DrEden33773/FreshLoop.git ~/FreshLoop
cd ~/FreshLoop
./freshloop doctor
./freshloop setup
./freshloop androidAfter the ext4 clone has the expected HEAD, origin, and a clean worktree, use it as the only active development copy. Keep the previous Windows checkout as an undeleted logical read-only backup; do not develop directly under /mnt/c or /mnt/e.
The wrapper loads nvm in non-interactive shells, reuses Node 24.14.0 and JDK 21 when present, and installs only the Linux build SDK under ~/.local/share/freshloop/android-sdk. It never launches a Linux emulator or talks to a Linux ADB server. Instead it starts/reuses the Windows AVD through WSL interop, builds on ext4, stages the APK in Windows Temp, installs through Windows adb.exe, adds adb reverse tcp:8081 tcp:8081, starts Metro on WSL 127.0.0.1:8081, and cold-starts FreshLoop. Ctrl+C removes the reverse mapping and stops Metro while retaining the Windows emulator.
The bridge expects WSL 2 mirrored networking. It does not edit %UserProfile%\.wslconfig or disable Windows Firewall. A suitable host configuration is:
[wsl2]
networkingMode=mirrored
dnsTunneling=true
firewall=trueChanging .wslconfig, Windows virtualization, firewall rules, license acceptance, UAC prompts, and required reboots remain explicit user-controlled operations. If a firewall rule is ever required, restrict it to Metro TCP 8081 rather than disabling the firewall. ./freshloop release builds all release ABIs in WSL; add --install to stage and install through Windows ADB without Metro.
./mac_env is the supported contributor workflow for building the Android app on Apple Silicon and Intel Macs. Hosted macos-15 and macos-15-intel jobs verify its command contracts, discovery order, dry-run behavior and CPU selection. Those jobs are not evidence that an AVD, physical device, APK installation or cold start succeeded on a real contributor Mac; Android remains the only platform with full emulator/device verification (see Verified platform boundary).
From the repository root:
./mac_env doctor
./mac_env setup
./mac_env android
./mac_env releasemac_env mirrors the freshloop command surface (doctor [--json], setup [--dry-run|--yes], android [--rebuild], release [--install], plus --java-home/--android-sdk/--avd/--device overrides) and reuses only the platform-independent runner/setup helpers. SDK discovery is deterministic: explicit --android-sdk, FRESHLOOP_ANDROID_SDK, ANDROID_HOME, ANDROID_SDK_ROOT, ~/Library/Android/sdk, then the FreshLoop-managed directory. It automatically selects arm64-v8a on Apple Silicon and x86_64 on Intel.
Android command-line tools are pinned separately to Google's official revision 15859902 Mac ARM and Intel archives with repository-recorded SHA-256 values; the Linux archive is never reused on macOS. Every missing SDK/JDK action is disclosed by source, checksum policy, destination, disk use and permissions before confirmation. JDK 21 can be installed through an existing Homebrew (brew install openjdk@21), but Homebrew itself is never auto-installed.
The hosted ARM lane may build ./mac_env release without an emulator and upload APK metadata. ./mac_env android and ./mac_env release --install require an actual local AVD/device and remain explicitly unverified on real Mac hardware until a contributor records that evidence.
Equivalent scripts are available from mobile/: npm run mac:doctor, npm run mac:setup, npm run mac:android, npm run mac:release.
Run the local verification gates from mobile/:
npm run typecheck
npm run lint
npm run test:ci
npm run export:android
npm run export:iosToolchain contract tests run from the repository root:
node --test tools/freshloop/__tests__/*.test.cjsA clean-extraction acceptance is not complete until the native command has also auto-started an emulator, built and installed the APK, brought com.comp7506d.freshloop/.MainActivity to the foreground, and reached the Metro phase. JavaScript exports alone do not validate the README native path.
The Android critical journeys are in mobile/.maestro/ and can be run against an installed APK with maestro test .maestro. The current integration branch passes 58 Jest suites / 162 tests plus Android/iOS production exports. Pull requests run only deterministic mobile/export and platform-contract gates; native APK builds run on main only when mobile or toolchain paths change, and manual dispatches run only the requested native lane. The hosted three-language emulator lane is manual because a missing hosted emulator can otherwise spend minutes polling ADB after a successful APK build. Full Android surface acceptance remains in F1-F4. iOS remains export-only; no iOS device or simulator result is claimed.
For the first launch, choose Import Demo Data. To restore the deterministic fixture later:
- Open the Settings tab from any top-level destination.
- Scroll to Demo data.
- Tap Reset Demo Data, then Confirm reset.
This replaces local items, outcomes, cached product data, and settings. The complete 3-4 minute route and offline fallback are in docs/demo-script.md.
Expo Router screens
-> feature services and view models
-> deterministic domain rules (dates, priority, recipes, outcomes, insights)
-> repository transactions
-> SQLite (authoritative local state)
Optional adapters
-> Open Food Facts read-only product-name enrichment
-> Android/iOS local notification scheduling
Important source areas:
mobile/app/: Expo Router routes and navigation shell.mobile/src/domain/: pure date, priority, lifecycle, recipe, and insight rules.mobile/src/data/: SQLite migrations and transactional repositories.mobile/src/features/: onboarding, Pantry, Today, Rescue, Insights, scanner, outcomes, and Settings.mobile/src/services/: optional Open Food Facts and local notification adapters.
- Pantry data is stored locally in SQLite. FreshLoop has no login, cloud sync, analytics SDK, advertising SDK, or server backend.
- Manual entry, Today, Rescue, outcomes, and Insights work without network access after installation.
- Barcode lookup is optional. When used online, the scanned barcode is sent to the public Open Food Facts API to suggest a product name. The user must confirm the physical package; FreshLoop never imports expiry dates or makes a safety decision from Open Food Facts.
- Reminder settings use OS-local notifications and can be denied or disabled without blocking the pantry.
- Reminder payloads record the selected locale. On startup or language change, managed reminders created in another language are replaced without changing the SQLite schema.
- Open Food Facts data is crowdsourced and licensed under ODbL; see THIRD_PARTY_NOTICES.md for dependency, font, asset, and data-source notices.
- Android: release APK built through Expo CNG/Gradle, installed with
adb, cold-started, exercised offline, and passed the critical automated journeys twice on an API 36 emulator. - macOS Android tooling: command contracts are verified on hosted ARM and Intel runners; the ARM lane builds without an emulator. No real Mac AVD, install, cold-start or physical-device success is claimed.
- iOS: production JavaScript export passed. No device, simulator, camera, or notification QA was performed on iOS.
doctorreports an unsupported Node such as 24.1.0: runfreshloop.cmd setup; the managed portable Node is user-local and does not replace the global installation.JAVA_HOMEand PATH report different JDKs: FreshLoop selects JDK 21 for its child processes and prints the conflict. It does not persistently change either value.npm cireportsEPERMfordotslash.exe: stop a verified orphaned Metro or React Native DevTools process that still owns the file, then retry. Do not delete the lockfile.- Gradle cannot find the SDK: run
doctor, thensetup; the runner injects its resolved SDK into the child process without requiring a global environment variable. - No AVD or multiple AVDs: run setup to create
FreshLoop_API_36, or choose an existing one with--avd <name>. - Device is offline or unauthorized: restart the emulator or accept the physical-device RSA prompt, then rerun; the runner will not install to an ambiguous target.
- Port 8081 is occupied: stop the unrelated process. A healthy existing Metro server is reused automatically.
- Build appears idle: read the current
.freshloop/logs/file. The runner emits a 60-second phase diagnostic and enforces the documented hard timeout. - CMake warns about long object paths: move the active checkout to
C:\src\FreshLoop,E:\src\FreshLoop, or~/FreshLoopin WSL. - A debug APK says the JavaScript bundle is missing: start Metro with
npx expo start, or install the bundled release APK instead. - Barcode lookup is unavailable: continue with manual entry. Offline or denied-camera states preserve the current form.
- A demo state is inconsistent: use the Settings reset procedure above; it is transactional and does not depend on the network.
- Android release-candidate evidence:
qa/evidence/T16-android-qa/ - Documentation evidence:
qa/evidence/T17-documentation/ - Final report:
docs/FreshLoop-Project-Report.pdf - Android native-tab/safe-area evidence:
qa/evidence/T21-navigation/ - Refreshed 60fps video/submission evidence:
qa/evidence/T22-video-refresh/ - Windows one-command toolchain evidence:
qa/evidence/T19-windows-toolchain/ - WSL/Windows emulator bridge evidence:
qa/evidence/T20-wsl-android/ - Three-language localization evidence:
qa/evidence/T23-i18n/ - macOS Android toolchain evidence:
qa/evidence/T24-macos-toolchain/
Generated APKs, source archives, final rendered videos, and the supplied course brief are intentionally excluded from Git history. Build distributable copies with the documented release scripts and publish them through the approved course delivery channel or a suitable release store.
See CONTRIBUTING.md for the branch, test, pull-request, and scope rules used by the project.
FreshLoop project code and project-authored documentation are available under the MIT License. Third-party code, fonts, data sources, course materials, and platform assets remain under their respective terms as recorded in THIRD_PARTY_NOTICES.md.