4.3 KiB
4.3 KiB
Mobile App — Social Proximity
Technology Choice: Flutter
We chose Flutter over React Native for this project because BLE scanning requires close hardware access. Flutter compiles to native ARM code and communicates with the platform via direct platform channels — no JavaScript bridge between the app logic and the BLE hardware.
| Flutter (chosen) | React Native | |
|---|---|---|
| Language | Dart | TypeScript |
| BLE access | Platform channels — native | Via JS bridge |
| Performance | Compiles to native | JS runtime overhead |
| BLE library | flutter_blue_plus |
react-native-ble-plx |
| Platforms | Android + iOS from one codebase | Same |
Dart is straightforward to learn if you know TypeScript: strongly typed, OOP, async/await, null safety built in.
BLE Library: flutter_blue_plus
flutter_blue_plus is the most actively maintained Flutter BLE library. It supports:
- Scanning for nearby BLE peripherals
- Advertising as a BLE peripheral (Android 5+, iOS limited)
- Reading/writing GATT characteristics
- Connection state management
Required permissions:
Android (AndroidManifest.xml):
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
iOS (Info.plist):
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Used to detect nearby people with shared interests.</string>
BLE Flow
App starts
└─► Check BLE permissions (request if missing)
└─► Generate ephemeral BLE token (UUID v4, per-session)
└─► Register token + interests with backend (POST /session)
User enables "Open to talk"
└─► Start BLE advertising (token in manufacturer data)
└─► Start BLE scanning for other tokens
└─► Open WebSocket to backend (/ws/{token})
Nearby token detected
└─► Send detected token to backend (POST /match)
└─► If match: WebSocket nudge received
└─► Display nudge card to user
User disables "Open to talk" or closes app
└─► Stop advertising + scanning
└─► Close WebSocket
└─► Session expires on backend (Redis TTL)
Project Structure
app/
├── lib/
│ ├── main.dart
│ ├── screens/
│ │ ├── onboarding_screen.dart ← Interest selection on first launch
│ │ ├── home_screen.dart ← "Open to talk" toggle + nudge display
│ │ └── settings_screen.dart ← Manage interests, reset session
│ ├── widgets/
│ │ ├── nudge_card.dart ← The nudge notification UI
│ │ ├── interest_chip.dart ← Selectable interest tag
│ │ └── open_toggle.dart ← Big friendly on/off toggle
│ ├── services/
│ │ ├── ble_service.dart ← BLE scan/advertise logic
│ │ ├── api_service.dart ← HTTP client (register, match)
│ │ └── ws_service.dart ← WebSocket client (nudge receiver)
│ └── models/
│ ├── interest.dart
│ ├── session.dart
│ └── nudge.dart
├── android/
├── ios/
└── pubspec.yaml
Screens
Onboarding (first launch)
- Choose interest categories (multi-select chips)
- Brief explanation of how the app works
- Consent acknowledgement
Home
- Large "Open to talk" toggle — the primary interaction
- When active: scanning indicator
- Nudge card appears when a match is found
- Shows shared interests (no name, no face, no location)
- "Say hello" is just a reminder — no in-app chat
Settings
- Manage interest categories
- Reset ephemeral identity
- Privacy information
State Management
For MVP simplicity: use Flutter's built-in Provider or Riverpod.
Avoid complex state management (no BLoC in MVP).
Running Locally
cd app
flutter pub get
flutter run # runs on connected device or emulator
flutter run -d android
flutter run -d ios
Prerequisites:
- Flutter SDK ≥ 3.x
- Android Studio (for Android emulator) or Xcode (for iOS simulator)
- Physical device recommended for BLE testing — emulators do not support BLE scanning