Türkçe | English
A high-performance Flutter application that monitors real-time prices for 1500+ coins via the Binance API.
The initial snapshot is fetched using a REST API, followed by real-time price updates via WebSockets. All JSON parsing operations are performed in background isolates, ensuring the UI thread is never blocked.
.env (Env) ──► DioConfig ──► Dio
│
DependencyContainer (GetIt)
┌────────────┴────────────┐
▼ ▼
DioService WebSocketService<MiniTicker>
│ │
│ REST /ticker/24hr │ WSS !miniTicker@arr
│ (JsonStreamParser │ (WebSocketIsolateParser
│ in isolate) │ in isolate)
└──────────┬──────────────┘
▼
MarketManager
┌──── (throttle 1s) ─────┐
│ _tickerMap (snapshot) │
│ + real-time merge │
└─────────┬──────────────┘
▼
Stream<MarketState>
┌─────────┴──────────┐
▼ ▼
HomeViewModel CoinDetailViewModel
(Provider) (Provider)
│ │
┌────────┴─────┐ ▼
▼ ▼ CoinDetailView
HomeView SmartCoinRow ├─ PriceSection
(search) (per-coin stream) ├─ DetailGrid
└─ DetailCard
lib/
├── main.dart # App entry point
├── core/
├── feature/
│ ├── json_parser/
│ │ ├── json_stream_parser.dart # REST response isolate parser
│ │ └── websocket_isolate_parser.dart # WebSocket isolate parser
│ ├── managers/
│ │ ├── market_manager.dart # REST + WebSocket data orchestrator
│ │ └── market_state.dart # Immutable state object (Equatable)
│ ├── models/
│ │ ├── coin_ticker.dart # 24h ticker model (21 fields)
│ │ └── mini_ticker.dart # WebSocket mini ticker model
│ └── services/
│ ├── network/
│ │ ├── dio_config.dart # Dio instance configuration
│ │ └── dio_service.dart # HTTP client (streaming support)
│ └── web_socket/
│ └── web_socket_service.dart # WebSocket lifecycle management
└── views/
request<T>()— Standard JSON-parsed HTTP requests.requestStreaming<T>()— Fetches large JSON responses asResponseType.plainand parses them in chunks usingJsonStreamParserin a background isolate. Loads the 1500+ coin snapshot without blocking the main thread.
- Generic design — compatible with any model type.
- Constructor injection —
parserandmanuellyRetryparameters injected via constructor. - Isolate parsing — With
useIsolate: true, messages are routed toWebSocketIsolateParser. - Exponential backoff — Reconnects with increasing delays (1s, 4s, 9s... up to 60s) upon disconnection.
- Heartbeat — If no data is received for 5 seconds, the connection is considered dead and a reconnect is initiated.
- Manual retry mode — When
manuellyRetry: true, automatic reconnection is disabled and must be triggered by the user. - Status stream — Emits
connecting → connected → disconnected → reconnectingstates.
- Long-lived background Dart isolate.
- Character-by-character JSON scanning for arrays — parses items one by one without loading the entire array into memory.
- Configurable chunk size (default: 100 items/batch).
- Communicates with the main thread using the
SendPort/ReceivePorthandshake pattern.
- Data flow orchestrator — Merges REST snapshots with real-time WebSocket updates.
- Throttle mechanism — Collects WebSocket updates in a
_pendingUpdatesmap and merges them into_tickerMapin batches every 1 second. marketStream— BroadcastsStream<MarketState>.MarketStatecontains both the full ticker list and the symbols that changed in the last batch.getCoinStream(symbol)— Filtered stream for a specific coin. Uses.distinct()to prevent redundant emissions.socketStatusStream— Exposes WebSocket connection status to the UI.copyWithMiniTicker()— RecalculatespriceChangeandpriceChangePercentwhile merging mini ticker data into the main ticker to prevent data inconsistency.
HomeViewStateenum:loading → loaded → disconnected → error- Listens to
marketStream, callsnotifyListeners()only on snapshot emission or state changes. - Price updates are handled directly by
SmartCoinRow— they do not trigger the ViewModel. - Monitors the socket status stream to transition to the
disconnectedstate if the connection drops. - Filtering via
updateSearchText()(default:"TRY").
- Per-widget stream subscription — Listens only to updates for its specific coin via
MarketManager.getCoinStream(symbol). - Operates independently of the parent ViewModel. Only the relevant row rebuilds.
- Tick-based color comparison (green/red).
- Uses a separate
Selector<CoinDetailViewModel, String?>for each card. - Rebuilds only the specific field (highPrice, lowPrice, volume, etc.) that has changed out of the 7 available fields.
- A singleton wrapper built on GetIt.
read<T>()— Retrieves the registered dependency.readOrCreate<T>()— Registers as a lazy singleton if not already registered and returns it. Used for generic services at runtime.
- Clean facade:
DependencyInstances.service.dioService,.manager.marketManager - Decouples the project from a direct dependency on GetIt.
| Technique | Impact |
|---|---|
| Isolate JSON parsing | REST and WebSocket messages are parsed in a background thread, keeping the UI thread free |
| Char-by-char JSON scanning | Item-by-item processing for large arrays without memory overhead |
| 1s throttle | Batches hundreds of WebSocket updates per second into a single update |
| Per-widget stream | SmartCoinRow only rebuilds when its specific coin changes |
| Selector + shouldRebuild | Lists rebuild only when length changes, not on price fluctuations |
distinct() on streams |
Prevents emitting the same data multiple times using Equatable |
resizeToAvoidBottomInset: false |
Prevents list rebuilds when the keyboard is opened |
| Package | Usage |
|---|---|
provider |
State management (ChangeNotifier + Selector) |
dio |
REST API HTTP client |
web_socket_channel |
WebSocket connections |
get_it |
Service locator / DI container |
json_annotation + json_serializable |
JSON model code generation |
envied + envied_generator |
Obfuscated .env variable access |
equatable |
Value equality for models and states |
logging |
Structured logging |
mockito |
Test mocking |
very_good_analysis |
Lint rules |
# 1. Install dependencies
flutter pub get
# 2. Create the .env file
# Add Binance API URLs to assets/env/.env:
# BINANCE_TICKER_24H_URL=[https://api.binance.com/api/v3](https://api.binance.com/api/v3)
# BINANCE_PRICE_SOCKET_URL=wss://[stream.binance.com:9443/ws](https://stream.binance.com:9443/ws)
# 3. Run code generation
dart run build_runner build --delete-conflicting-outputs
# 4. Start the application
flutter run
# Run all tests
flutter test
# Run specific test files
flutter test test/web_socket_service_test.dart
flutter test test/market_manager_test.dart
flutter test test/websocket_isolate_parser_test.dart
flutter test test/dio_service_test.dart
flutter test test/json_stream_parser_test.dart
| Test File | Scope |
|---|---|
web_socket_service_test.dart |
Connection, message parsing, reconnect, heartbeat, manual retry |
market_manager_test.dart |
Snapshot loading, WebSocket merge, throttling, getCoinStream |
websocket_isolate_parser_test.dart |
Single object, array, large batches, error scenarios |
dio_service_test.dart |
HTTP requests, streaming response |
json_stream_parser_test.dart |
Incremental JSON parsing |