Skip to content

Architecture

The app follows clean architecture with three inner layers wired together via GetIt:

Layer Path What lives here
Domain lib/domain/ Pure-Dart entities, abstract repository contracts, use cases, service interfaces. No Flutter imports.
Data lib/data/ Datasources (HTTP via DioApiProvider, local via Hive / SharedPreferences), repository implementations, DTO ↔ entity mappers.
Presentation lib/presentation/ BLoC/Cubit classes, pages, widgets. Each feature folder in pages/ has a mirrored entry in bloc/.
Core lib/core/ Shared constants, enums, error types, networking base classes, utilities.

Dependencies point one way: presentation → domain ← data. Domain knows nothing about the layers around it.

  • dartz Either for repository return types — success/failure without exceptions.
  • Use cases are thin: one public call() method, delegating to a repository.
  • Models are hand-written fromJson / toEntity() classes, not generated. Only routes (AppRouter) and Hive adapters use codegen.

lib/injection_container.dart bootstraps GetIt; init() is called from main(). Every datasource → repository → use case → bloc registration lives in this one file. When you introduce a feature, add its registrations here. test/injection_container_test.dart fails if the graph can’t resolve.

AppRouter in lib/core/router/app_router.dart defines all routes with AutoRoute. After any route change, regenerate app_router.gr.dart:

Terminal window
dart run build_runner build --delete-conflicting-outputs

Deep links are resolved by resolveDeepLink (lib/core/utils/deep_link_path.dart) and dispatched from lib/domain/services/deeplink_services.dart.

lib/core/constant/const.dart holds app-wide ValueNotifier singletons — current_user, internet_available, country, bottom_bar_index and friends. These are accessed directly, not through DI or BLoCs. It’s a deliberate trade-off: simple, but any test touching them needs tearDown(resetGlobals).

The video player surface has three layers:

  1. ottstack_player — provides OSPlayerController / OSPlayerView and the OSMediaModel entry type.
  2. kvideo — the native layer underneath: a Pigeon bridge to ExoPlayer/Media3 (Android) and AVPlayer (iOS).
  3. lib/presentation/widgets/player/ — app-level widgets (player_media_builder.dart, player_controls.dart, player_gesture_handler.dart, trailer_controls.dart, the cast/ and airplay/ subtrees) composing the above.

A single OSPlayerController is constructed in main.dart and provided app-wide, so the content-page trailer and the full-screen player share one controller.

Both ottstack_player and kvideo are absolute path dependencies (see Environment setup).

Hive is the offline store: downloads (CacheContentEntity) and the notification queue (FirebaseNotification). Register adapters in main() before opening boxes.