Case Study
NewsWave App Icon

NewsWave

Offline-First Bilingual News Platform built with Flutter, Supabase & Clean Architecture

NewsWave Bilingual EN/AR Live Switching & Reactive RTL Layout Demo

Overview

NewsWave is a production-grade, offline-first bilingual (English/Arabic) news platform built with Flutter. Designed for readers who move between languages and unstable network environments, the application combines breaking headlines, category-curated feeds, title-scoped search, and an in-app article reader into a cohesive experience that adapts its layout direction (LTR/RTL) and typography (Poppins/Cairo) instantaneously at runtime.

Architecturally, NewsWave enforces strict Clean Architecture and Dependency Inversion across its modular feature domains (splash, auth/onboarding flow, home, headlines, search, favorites, and profile) wired through a centralized GetIt service locator (sl). High-level repositories depend exclusively on abstract contracts such as HomeRepositoryContract, NetworkInfo, and TranslationService, allowing networking, caching, and translation providers to evolve independently of presentation Cubits.

Because NewsAPI's /v2/top-headlines endpoint primarily serves English-language articles for the US region while /v2/everything supports native Arabic queries, HomeRepository implements Dual-Strategy Arabic News Fetching: when Arabic is active, category feeds query /v2/everything using curated Arabic boolean expressions from _getArabicQueryForCategory, then pass results through the localization/translation pipeline. Articles already containing two or more Arabic fields (arabicCount >= 2) are skipped, while non-Arabic fields (title, cleanDescription, and cleanContent) are translated in parallel via Future.wait across batches of chunkSize = 3 (using \n<<<SEP>>>\n as the batch separator) and cached persistently.

Beyond news consumption, NewsWave features a complete user lifecycle powered by Supabase Auth, PostgreSQL, and Storage. Users can sign in, register with E.164 phone normalization, recover passwords end-to-end via the Android io.newswave://reset-password deep-link intent-filter—where supabase_flutter processes the recovery session and emits AuthChangeEvent.passwordRecovery for AuthListenerCubit to route to updatePasswordRoute—or browse immediately in Guest Mode. Authenticated users complete a 3-step personalization onboarding wizard whose preferences and avatar are dual-persisted via AuthRepositoryImpl to Supabase and local storage for zero-latency cold starts.

RoleLead Developer
PlatformiOS & Android
Tech Stack
FlutterDartBLoC / CubitHive (Binary & JSON Cache)Supabase (Auth · PostgreSQL · Storage)Dio & NewsAPI RESTLibreTranslate & MyMemory APIWebView (webview_flutter) & Deep-Link Auth Recovery
Interactive Feature Showcase

NewsWave

Offline-First Bilingual News Platform built with Flutter, Supabase & Clean Architecture

Breaking News Carousel & Curated Home Feed

Custom-sliver home experience combining a floating SliverAppBar, a 0.88-viewport PageView breaking-news carousel with animated dot indicators and Hero transitions, category filter chips across 7 NewsCategory domains, and a paginated 'For You' recommended feed.

Technical Architecture

A high-level overview of how the system is structured to ensure scalability and maintainability.

Feature-First Clean Architecture & GetIt Dependency Injection

Organized into core infrastructure and self-contained feature modules (splash, auth/onboarding flow, home, headlines, search, favorites, and profile), all wired deterministically in lib/core/di/service_locator.dart.

Strict Dependency Inversion: HomeRepository depends on HomeRepositoryContract (implemented by Dio-backed HomeServices) rather than concrete HTTP clients.Centralized Dio factory configured via BaseOptions (connectTimeout: 15s, receiveTimeout: 15s, and Authorization: Bearer ${AppConstants.apiKey} inside BaseOptions.headers) with Dio's LogInterceptor registered exclusively inside an assert(...) block.Lifecycle-aware singleton and factory registrations in GetIt, including automatic HTTP client disposal for TranslationService on container reset.

Offline-First Locale-Namespaced Hive Caching Layer

Multi-box Hive persistence storing binary TypeAdapters alongside JSON-serialized feed pages and translated article maps.

Locale-namespaced cache keys (cached_headlines_<category>_<locale> and cached_recommended_p<page>_<locale>) preventing English and Arabic feeds from overwriting each other.Zero-network fallback in HomeRepository: when NetworkInfo.isConnected is false or a Dio request fails, cached pages are served immediately with fromCache: true.Binary Hive TypeAdapters registered for Article (typeId: 0), Source (typeId: 1), and ProfileModel (typeId: 2) for fast local hydration.

Pluggable Multi-Provider Article Translation Pipeline

Abstract TranslationService interface decoupled from ArticleTranslationRepository to translate non-Arabic article fields into Arabic on demand.

Concrete translation implementations in core/translation including LibreTranslationService and MyMemoryTranslationService.Translates non-Arabic article fields (title, cleanDescription, and cleanContent) concurrently via Future.wait, while batch translation methods use \n<<<SEP>>>\n as the batch separator.Concurrent chunked execution (chunkSize = 3 via Future.wait), skipping articles that already contain two or more Arabic fields (arabicCount >= 2) or strings with >30% Arabic script (U+0600–U+06FF), backed by persistent local caching.

Dependency-Free Multi-Probe Connectivity Engine

Custom HTTP reachability verifier (NetworkInfoImpl) and periodic ConnectivityCubit replacing platform-specific connectivity plugins.

Races 3 HTTP endpoints concurrently (NewsAPI baseUrl, Google generate_204, and Cloudflare cdn-cgi/trace) with a 4-second timeout.Completes true on the first reachable HTTP response (even 401/404 status codes prove network path reachability) and deduplicates concurrent in-flight checks via a shared _pending Future.ConnectivityCubit polls every 3 seconds and drives the animated top OfflineBanner without false positives on captive portals.

Reactive BLoC/Cubit State & Cross-Cubit Stream Coordination

Predictable unidirectional data flow across global app Cubits and screen-scoped feature Cubits.

HomeCubit, HeadlinesCubit, and SearchCubit subscribe directly to LocaleCubit and CategoryCubit streams, automatically re-fetching or re-translating data on language or category change.Custom PaginationMeta value object computing totalPages, boundary guards, and a sliding 5-page window rendered by PaginationBarWidget.FavoritesCubit dynamically checks active locale on loadFavorites() and re-translates bookmarked articles via ArticleTranslationRepository when viewed in Arabic.

Supabase Auth, Deep-Link Recovery & Dual-Write Profile Sync

Cloud identity, 3-step onboarding persistence, and avatar storage backed by Supabase and mirrored locally.

AuthCubit manages Authenticated, Guest, Unauthenticated, and PasswordRecovery states, persisting guest sessions in SharedPreferences.AndroidManifest.xml defines an intent-filter for io.newswave://reset-password; supabase_flutter processes the recovery session and emits AuthChangeEvent.passwordRecovery, which AuthListenerCubit handles (with a _navigatingToReset guard) to navigate to updatePasswordRoute.AuthRepositoryImpl (coordinating AuthRemoteDataSource and AuthLocalDataSource) uploads avatars picked via ImagePicker(source: ImageSource.gallery, imageQuality: 80) to the Supabase Storage avatars bucket at avatars/$userId/avatar_$timestamp.$ext and dual-writes profile data to Supabase PostgreSQL and local storage.

Engineering Insights

Key technical decisions and challenges encountered during the development process.

Key Decisions

Custom Multi-Probe HTTP Reachability Instead of connectivity_plus

Context

Standard platform plugins like connectivity_plus only report whether Wi-Fi or cellular radio interfaces are enabled—not whether actual internet traffic can pass through captive portals or ISP outages.

Approach

Engineered a dependency-free NetworkInfoImpl using Dio that races three geographically distributed endpoints (NewsAPI, Google generate_204, and Cloudflare trace) in parallel. Any HTTP status code resolves the Completer immediately as online, while concurrent callers share a single in-flight Future—backed by unit tests with a fake HttpClientAdapter.

Dual-Strategy Arabic News Fetching & Parallel Translation Pipeline

Context

NewsAPI's /v2/top-headlines endpoint for US categories primarily returns English content, so relying solely on translating top-headlines responses produces unnatural Arabic feeds and quickly exhausts free translation API rate limits.

Approach

Implemented Dual-Strategy Arabic News Fetching in HomeRepository—switching Arabic category feeds to /v2/everything with curated Arabic boolean queries from _getArabicQueryForCategory—before passing results through ArticleTranslationRepository. Articles with arabicCount >= 2 are skipped outright, while remaining non-Arabic fields (title, cleanDescription, and cleanContent) are translated in parallel via Future.wait across batches of chunkSize = 3 (with \n<<<SEP>>>\n batch separator support) and cached persistently.

Locale-Namespaced Hive Keys & Translation Cache Invalidation

Context

Caching headlines under a single key per category caused stale English articles to appear after switching to Arabic (or vice versa) when offline or before a refresh completed.

Approach

Namespaced every Hive feed key by both category/page and active language code (e.g., cached_headlines_technology_ar vs cached_headlines_technology_en) and wired LocaleCubit.toggleLocale() to invoke ArticleTranslationRepository.clearCache(), purging transient tr_ keys while preserving raw feed caches.

Dual-Write Profile Persistence via AuthRepositoryImpl

Context

Awaiting a remote Supabase query on every app launch to check whether a user has completed onboarding introduces unnecessary splash latency and fails when launching offline.

Approach

Implemented a local-first read / dual-write strategy in AuthRepositoryImpl across AuthLocalDataSource and AuthRemoteDataSource: cached profile data is hydrated instantaneously on startup to route SplashView, while background refresh, profile updates, and avatar uploads (picked via ImagePicker with imageQuality: 80 and uploaded to avatars/$userId/avatar_$timestamp.$ext) synchronize with Supabase's profiles table and local cache.

Technical Challenges

Cross-Language Favorites Consistency When Switching Locales

The Challenge

Users often bookmark an article while browsing in English and later open their Saved Articles screen after switching the app to Arabic, expecting their saved reading list to match the active language.

The Solution

Stored the canonical Article model in the Hive favoritesBox keyed by a deterministic 50-character uniqueId, and enhanced FavoritesCubit.loadFavorites(locale) to pipe saved articles through ArticleTranslationRepository.translateArticles() whenever the active locale is Arabic. Saved articles remain permanently accessible offline and automatically render in Arabic or English to match the user's current locale.

Preventing Hero Tag Collisions Across Multi-Surface Article Lists

The Challenge

The same breaking news article can appear simultaneously in the Home PageView carousel, the 'For You' recommended sliver list, and Search results—causing Flutter's Hero controller to throw duplicate tag exceptions during navigation.

The Solution

Constructed context-scoped Hero tags (article-image-<uniqueId>-carousel, article-image-<uniqueId>-recommended, article-image-<uniqueId>-search) passed via ArticleDetailArgs into ArticleDetailView, paired with a custom flightShuttleBuilder that smoothly lerps BorderRadius from 24px to 0px. This achieved zero Hero tag collisions across overlapping feeds and 60fps shared-element transitions into the article reader.

Cold-Start vs. Warm-State Deep Link Password Recovery

The Challenge

Tapping the io.newswave://reset-password link from an email client can either cold-start the app (while SplashView's 3.6-second animation is running) or resume an already-mounted Navigator.

The Solution

Configured the io.newswave://reset-password intent-filter in AndroidManifest.xml so supabase_flutter recovers the auth session and emits AuthChangeEvent.passwordRecovery, while AuthListenerCubit listens with a _navigatingToReset guard to push updatePasswordRoute onto the global navigatorKey once the frame mounts. This guarantees reliable end-to-end password reset navigation regardless of whether the app was terminated or running in the background.

Coordinating Reactive Refreshes Across Category and Locale Streams

The Challenge

Changing the language in the AppDrawer or Profile screen must simultaneously update the RTL/LTR layout, switch fonts between Poppins and Cairo, and re-fetch/translate active feeds in HomeCubit, HeadlinesCubit, and SearchCubit.

The Solution

Injected LocaleCubit and CategoryCubit streams into feature Cubits via GetIt, cancelling StreamSubscriptions cleanly in close() and triggering force-refreshed, locale-scoped queries with shimmer skeleton states. This delivers seamless, flicker-free transitions between English and Arabic across every active screen in the navigation stack.

Screens Gallery

A comprehensive look at the user interface and interactions.

Download NewsWave

Install the latest Android build directly to your device. All builds are optimized for performance and security.

Recommended Build

NewsWave — ARM64 (arm64-v8a)

Recommended for most modern Android phones (64-bit ARM).

Versionv1.0.0
Build1
DateMarch 1, 2026
Artifact Pending
Release Notes

Android 6.0+ (API 23+). Unknown sources must be enabled.

Other Architectures

NewsWave — ARM32 (armeabi-v7a)

armeabi-v7a

For older 32-bit Android devices.

Pending

NewsWave — x86_64

x86_64

For Android emulators, Chromebooks, and x86_64 devices.

Pending

Ready to dive deeper?

Explore the source code or see how this architecture translates to other projects in my portfolio.

Up Next

FinDash