Home About Skills Experience Projects Certifications GitHub ↗ Hire me
← Back to Portfolio
EGY DEAD icon
Personal Project · Entertainment · Personal Project · Feb 2025 – Aug 2026

EGY DEAD

Overview

EGY DEAD is a Flutter movie and TV-series discovery app for Android and iOS, built on the TMDB API. It lets viewers browse curated rails (now playing, popular, top rated, upcoming, on-the-air, airing today), open a full details page for any title — cast, reviews, ratings, genres, runtime, seasons, trailer — and search across movies, series and people from one field. Features a dark-only Arabic/English UI, offline detection, and a Firebase Remote Config-driven "new version available" prompt.

TMDBPowered by TMDB discovery API
SoloDesigned and built independently
BLoCState management with Dio-powered search
Core Capabilities

What the app does

🎬
Movie Discovery Rails

Now Playing carousel plus Popular, Top Rated and Upcoming horizontal rails, each with a paginated 'see more' screen that loads further TMDB pages on scroll.

📺
TV & Series Browsing

A parallel TV module with On The Air, Airing Today, Popular and Top Rated sections, plus per-series season listings.

ℹ️
Rich Title Details

Details screens pull cast, user reviews, rating, runtime, genres, release date, recommendations and similar titles in a single append_to_response call.

🔍
Debounced Multi-Search

Searches TMDB's /search/multi endpoint across movies, series and people, with a 400ms debounce and switchMap so in-flight requests for stale keystrokes are dropped.

▶️
Trailer Playback

Extracts the trailer key from the TMDB videos payload and opens it externally via url_launcher, hiding the button when a title has no trailer.

📡
Connectivity Gate

An InternetCubit listens to connectivity_plus and holds the app on an offline screen with a snackbar until a connection is available.

💀
Skeleton Loading States

Every rail and details section renders a Skeletonizer placeholder while loading and a dedicated error widget on failure, instead of a bare spinner.

🔄
Remote Update Prompt

Firebase Remote Config publishes the latest version string; the app compares it against its own package version and offers an update dialog.

🖼️
Cached Imagery

A shared CachedImage wrapper handles TMDB poster/backdrop/avatar loading with cached_network_image, spinkit placeholders and fallback art.

⚙️
Settings & Support

In-app policy link, support screen, and version info read from package_info_plus.

Architecture

How it's built

The app follows Clean Architecture, split by feature rather than by layer: movies/, tv/ and search/ each contain their own data (models, remote data source, repository implementation), domain (entities, abstract repository, use cases) and presentation (bloc, screens, widgets) folders, with everything cross-cutting living under core/ and the shell under layout/.

State management is BLoC — flutter_bloc with plain Bloc/Cubit, not Provider or Hydrated storage. Each feature has a coarse-grained bloc (MoviesBloc, TvsBloc, MoviesDetailsBloc, TvsDetailsBloc, SearchBloc) holding a single Equatable state object with per-section RequestState enums updated through copyWith, so one screen can show four rails in four independent loading states. Dependency injection is get_it, and errors are modelled functionally with dartz — data sources throw ServerException, repositories convert it to Either, and blocs fold it into loaded or error states.

Widget dispatches an event to its feature Bloc→Bloc invokes the matching use case from get_it→Use case calls the abstract repository contract→Repository delegates to the Dio-backed remote data source→Data source hits the TMDB endpoint→JSON parsed into models; non-200 throws ServerException→Repository wraps the result as Either<Failure, T>→Bloc folds the Either and emits a new state→UI renders loaded state, a skeleton placeholder, or an error widget
Walkthrough

See it in action

Splash Screen
Splash Screen
Movies
Movies
Upcoming Movies
Upcoming Movies
Movie Details
Movie Details
Tvs
Tvs
Top Rated Tvs
Top Rated Tvs
Problems Solved

Real engineering challenges

Connectivity
Offline gate misreporting valid connections

The InternetCubit originally emitted "connected" only when connectivity_plus reported mobile or Wi-Fi, and treated everything else as lost. Because the whole app is gated behind that state, any other connection type fell through to the offline branch. The fix inverted the condition to test for ConnectivityResult.none and treat every other result as connected.

Pagination
A failed 'load more' shouldn't wipe the list

The see-more screens page through TMDB by firing a fetch-more event when the scroll controller reaches the bottom. Initially a failure on page N reused the same error state as the initial load, so one bad request replaced an already-populated list with a full-screen error. A distinct fetchMoreError value was added to the state, so loaded items stay on screen and the error is surfaced in place instead.

State scoping
Tab switches recreating feature blocs

The TV screen created its own BlocProvider inside the route, which meant the bloc was rebuilt every time users moved through the bottom navigation. The provider was lifted up to where the movies and TV screens are constructed once with their initial events dispatched, so tab switching no longer tears down and refetches feature state.

Bug fix
Carousel taps that did nothing

Tapping a poster in the now-playing carousel silently did nothing. The tap handler referenced the navigation function without actually calling it, so the route was never pushed. Adding the missing parentheses restored navigation to the details screen.

Technologies Employed

Built with

Flutterflutter_blocget_itDiodartzequatableTMDB APIClean ArchitectureFirebase Remote Configconnectivity_pluscached_network_imageskeletonizercarousel_sliderflutter_dotenvurl_launcherShorebird
Screenshot preview