// Case Study · 03 · Flutter App

Near Chat

Off-Grid. Encrypted. Forever.

TypeFlutter App
StackFlutter · BLE · Wi-Fi Direct · AES-256-GCM
Status● Live · Play Store Pending
Year2026
StudioFerryPot · Jayy
0ms
Internet required
AES
256-bit GCM encryption
<1s
Node discovery time
P2P
No server. Ever.

// The Problem

What happens when
the internet goes down?

The Reality
Every messenger needs a server. Until now.

WhatsApp, Telegram, Signal — all require internet to function. In a disaster, a remote area, or a network outage, communication collapses completely. Emergency responders, trekkers, event coordinators — all face this gap. There was no solution that worked truly off-grid.

The Mission
Build a messenger that works without internet, SIM, or servers.

Near Chat uses Bluetooth Low Energy and Wi-Fi Direct to create a mesh network between phones in range. Messages hop device-to-device. Every byte is AES-256-GCM encrypted with a fresh per-message nonce. No cell towers. No routers. No servers. Just phones talking directly — and securely.

// Architecture

How the mesh
actually works.

The entire network is built on Google's Nearby Connections API in P2P_CLUSTER mode. Room discovery uses a SHA-256 hashed Room Code over BLE — peers that don't know the code can't even see the endpoint. Once discovered, a lexicographic tie-breaker resolves simultaneous connection requests and Wi-Fi Direct handles the high-bandwidth socket.

📱
Device A
Derives roomHash via SHA-256, broadcasts endpoint via BLE
BLE Discovery
🔐
Handshake
Tie-breaker resolves initiator. PBKDF2 key derived locally. AES-256-GCM negotiated.
Wi-Fi Direct
📱
Device B
Receives encrypted payload, decrypts with local key, renders message

// Features

Built for the edge.

📡
P2P_CLUSTER Mesh

Google Nearby Connections in cluster mode. BLE for discovery, Wi-Fi Direct for payloads. Extends mesh range with every new node in range.

🔐
AES-256-GCM + PBKDF2

Every message encrypted end-to-end. Keys derived via PBKDF2 with HMAC-SHA256 at 100,000 iterations from the Room Code. Zero key exchange over the air.

Sub-second Discovery

BLE scanning finds nearby Near Chat devices in under 1 second. SHA-256 hashed room codes prevent passive discovery of active rooms.

📷
QR Instapair

Skip manual code entry. Display your Room QR or scan a peer's using the integrated camera scanner. Session formed in under a second.

🎙️
Offline Voice Notes

Record, encrypt (AES-256), and transmit M4A voice notes over Wi-Fi Direct. Custom waveform player with playback progress and duration tracker.

Delivery Receipts

WhatsApp-style double-tick system. Grey clock → single tick (sent) → double tick (delivered). Silent receipt frames auto-sent on message decryption.

💬
Typing Indicators

Real-time typing status broadcast across the mesh. 2-second debounce to minimize traffic, 5-second auto-clear timeout on inactivity.

📦
Offline Queue Sync

Messages queued locally when peers disconnect. Auto-flushes in order on reconnection. Mesh-wide pending queue for zero-peer states.

📳
Haptics & Tones

Custom WAV beep generator for sent/received tones. Haptic feedback on message delivery, reactions, and button taps via HapticFeedback API.

// Tech Stack

Deep tech,
real implementation.

This wasn't a tutorial project. Every component required reading Android and Flutter low-level documentation, understanding the hardware BLE/Wi-Fi layer, and resolving platform-specific quirks on MIUI/Android 12.

Flutter (Dart) nearby_connections 4.3.0 AES-256-GCM (encrypt 5.0.3) PBKDF2 HMAC-SHA256 Riverpod 2.6.1 BLE (Bluetooth Low Energy) Wi-Fi Direct (P2P) flutter_foreground_task mobile_scanner 6.0.11 qr_flutter 4.1.0 record 6.2.1 audioplayers 6.6.0 shared_preferences Permission Handler pointycastle 3.9.1

// Engineering Decisions

Why each choice
was made.

01 / Flutter over native Android
Cross-platform from day one

Near Chat could have been native Kotlin. But the Flutter Nearby Connections plugin wraps Google's Nearby API cleanly, and Dart's async/await model is perfect for event-driven mesh networking. An iOS port becomes possible later without a full rewrite.

02 / AES-256-GCM over CBC
Authenticated encryption is non-negotiable

CBC mode has no authentication — a man-in-the-middle on the local BLE radio can flip bits without detection. GCM provides both confidentiality and integrity in one primitive, and forces a unique nonce per message so identical plaintexts produce different ciphertexts.

03 / PBKDF2 key derivation
A 4-char code padded with zeros is not encryption

Early builds padded the Room Code to 32 bytes with zeros — effectively a 4-character password with zero hashing cost. PBKDF2 with 100,000 iterations and a static salt makes brute-force enumeration of short codes computationally expensive.

04 / SHA-256 hashed BLE endpoint names
Room codes should not be visible to passive scanners

The original design broadcast userName|roomCode in plaintext over BLE. Anyone with a BLE scanner could read active Room Codes. Broadcasting SHA256(roomCode + serviceId) instead means only devices that already know the code can recognize the match.

05 / Riverpod for state management
Network state is complex — providers help

The mesh state — nodes discovered, connections active, messages in-flight, typing statuses, offline queues — is highly reactive. Riverpod's stream providers map naturally to the event stream from the Nearby Connections API without setState spaghetti.

// Lessons learned

What the hardest
project teaches you.

✓ What worked

Flutter Nearby Connections handled the heavy BLE/Wi-Fi Direct complexity. The abstraction let us focus on the mesh routing logic rather than raw hardware quirks.

! What was hard

Android permission hell — BLE scanning requires FINE_LOCATION, BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE — all changed in Android 12/13. MIUI adds a layer of unpredictability on top.

✓ What worked

Proper cryptography from day one. Designing PBKDF2 + AES-GCM into the protocol early meant we never had to retrofit security, which would have required a full architecture rethink.

! What was hard

STATUS_ENDPOINT_IO_ERROR (8012) — when two devices call requestConnection() simultaneously, Google Play Services throws a race condition error. Lexicographic tie-breaker logic solved it completely.

✓ The outcome

Near Chat proves the most. Shipping it demonstrates low-level networking knowledge, security engineering, and the ability to build complex distributed systems — not just CRUD apps.

! What I'd add next

Message hop routing and multi-hop relaying. Right now each device connects directly. A proper relay protocol would let messages hop through intermediate nodes, extending the real mesh range.

Communicate
anywhere.

No internet · No SIM · No servers · Just phones

Visit Near Chat site ← Back to Studio
← Earlier project BudgetBee Expense tracker · Firebase · Excel export ← Previous MemoDate Private journal · Google Drive sync