A production-ready Flutter news application delivering breaking headlines, personalized feeds, and offline-first reading β fully bilingual (English / Arabic) with reactive RTL support, dependency-free network resilience, and an architecture built to survive real-world failure modes, not just demo conditions.
Features Β· Showcase Β· Screenshots Β· Architecture Β· Localization Β· Tech Stack Β· Getting Started Β· Project Structure Β· Roadmap
NewsWave is a full-featured, production-ready news reader built entirely in Flutter. It aggregates live news from NewsAPI.org, organizes articles by category, and keeps your reading experience fast β even when you're offline, thanks to intelligent Hive-backed caching and a custom, dependency-free connectivity layer.
Whether you're exploring as a guest, or signed in with a personalized profile, NewsWave adapts to you. Authenticated users get a tailored onboarding experience where they choose their country, hobbies, and preferred news categories β shaping a feed that's truly theirs.
Built to showcase senior-level Flutter engineering: strict Clean Architecture with enforced Dependency Inversion, reactive state management, secure authentication, offline-first data with locale-aware caching, fully localized UI (EN/AR) with RTL support, resilient third-party API integration with rate-limit handling, and a UI that users will actually want to come back to.
| Category | Features |
|---|---|
| π Authentication | Sign Up, Sign In, Forgot Password, Deep-Link Password Recovery, Guest Mode |
| π° News Feed | Breaking Headlines Carousel, "For You" paginated feed, 7 categories |
| π Search | Full-text search with 500ms debounce, infinite scroll, locale-aware results |
| π Localization | 100% localized (zero hardcoded strings), reactive RTL/LTR layout switching, locale-aware caching, translated Favorites & Search |
| πΎ Offline | Hive local cache for headlines & recommended articles, keyed per-locale to prevent cross-language cache pollution |
| π°οΈ Connectivity | Custom, dependency-free network polling with multi-endpoint probing β no connectivity_plus, no third-party plugin |
| π¨ Theming | Light / Dark mode with animated toggle, locale-aware typography (Poppins / Cairo) |
| π§ Onboarding | 3-step profile wizard (name, country/hobby, category preferences) |
| π Favorites | Save articles locally, dynamically re-translated to match the active locale |
| ποΈ Categories | General, Business, Technology, Sports, Health, Entertainment, Science |
| π Navigation | Custom page transitions: Fade, Slide, Shared-Axis |
| π€ Profile | Update display name, theme preference, language preference, sign out |
π To add your GIFs: Record with ScreenToGif or LICEcap, drop the files into
docs/assets/, then replace the placeholder links below with the relative paths or hosted URLs.

NewsWave uses Supabase Auth as the identity layer, with a clean AuthRepository abstraction so the data source can be swapped without touching the UI or state layers.
Auth flows supported:
- β Email + Password Sign Up (with optional phone number)
- β Email + Password Sign In
- β Forgot Password (email link)
- β
Deep-link Password Recovery β the app intercepts
AuthChangeEvent.passwordRecoveryat the root level and routes directly to the Update Password screen - β
Guest Mode β browse the app without an account; guest state is a first-class
AuthGueststate in the Cubit - β
Session persistence β
restoreSession()on cold launch; user stays signed in across restarts - β
Profile creation is handled server-side via a Supabase trigger, ensuring atomicity between the
auth.userstable and the publicprofilestable
AuthUserState
βββ AuthInitial
βββ AuthLoading
βββ AuthAuthenticated(user, profile) β may need onboarding
βββ AuthGuest
βββ AuthUnauthenticated
βββ AuthError(message)
The Home screen is split into two independent data streams:
1. Breaking Headlines Carousel
- English locale fetches
top-headlinesfrom NewsAPI; Arabic locale fetcheseverythingwith curated Arabic-language category queries, sincetop-headlineshas no reliable Arabic coverage - Displayed as a full-bleed horizontal carousel with glassmorphism overlays
- Page size: 10 articles, cached in Hive under a locale-namespaced key (e.g.
cached_headlines_business_ar) so switching languages never serves stale or mistranslated content
2. "For You" Recommended Feed
- Mirrors the same locale-aware endpoint switch as the carousel, filtered by the active category
- Rendered as a paginated list with a visible Page X of Y counter
- Page size: 5 articles β each page individually cached (
rec_page_N_<locale>) - Lazy-loads the next page when the user scrolls within 3 items of the bottom (
infiniteScrollThreshold)
Categories are managed globally via CategoryCubit and surfaced as animated FilterChip pills, with category labels pulled from the active AppLocalizations. Switching categories triggers a fresh paginated fetch while preserving cached pages already loaded. Both the headlines and recommended feed cubits subscribe to LocaleCubit's stream and force a fresh fetch on every language change β guaranteeing the feed reflects the correct locale's source data, never a translated echo of the previous language's cache.
All caching is handled through a dedicated NewsCacheManager β a focused class extracted specifically to keep cache orchestration out of the repository layer (see Architecture Overview), backed by Hive (LocalDatabaseHive) in a single box keyed by articles_box.
| Cache Key Pattern | Contents |
|---|---|
cached_headlines_<category>_<locale> |
Latest top-headlines articles, namespaced per category and language |
rec_page_N_<locale> |
Individual page N of recommended articles, namespaced per language |
tr_<md5(article_id)> |
Per-article translated title/description/content, cleared automatically on language switch |
favorite_articles |
User-saved articles (persisted indefinitely, re-translated on read) |
This locale-aware keying scheme was a deliberate architectural fix: in earlier iterations, switching the app language could surface cached English articles that were then machine-translated on top of stale data instead of fetching fresh, language-appropriate sources. Every cache key now carries the active locale, and every locale switch triggers a forced refetch β eliminating that entire class of bug.
Articles are serialized as @HiveType objects with generated adapters, ensuring blazing-fast reads and zero JSON overhead on the hot path. The Article model includes computed extensions (cleanContent, cleanDescription, formattedDate, shortAuthor) that strip NewsAPI's HTML fragments and truncation markers before any data hits the UI.
NewsWave is 100% localized into English and Arabic β zero hardcoded UI strings, anywhere β driven by a reactive LocaleCubit rather than a static, restart-required setting.
flutter_localizations+ ARB files β every UI string (navigation, dialogs, errors, validation messages, category labels, hobby labels, even fallback titles for untitled articles) is generated throughAppLocalizations. Hobby and category labels are modeled as exhaustiveenums with alocalize(AppLocalizations)method, so the compiler β not a runtime crash β catches any missing translation.LocaleCubitβ aCubit<Locale>that persists the chosen language to a dedicated Hivesettings_box, restores it on cold launch, and exposes a one-taptoggle()between English and Arabic. A defensive_initializedguard ensures the Hive box is always ready before the first write, even ifsetLocale()is called in a race withinit().- Reactive RTL/LTR layout β
Directionalityflips automatically with the locale; layout-sensitive widgets useEdgeInsetsDirectionaland locale-aware icon mirroring (e.g. back/forward chevrons) instead of hardcoded left/right values - Locale-aware typography β
AppTheme.of(locale: ..., brightness: ...)swaps the active font family (Poppins for English, Cairo for Arabic) at theThemeDatalevel - Locale-aware data fetching, everywhere β
HomeCubit,HeadlinesCubit,FavoritesCubit, andSearchCubitall subscribe toLocaleCubit's stream and react automatically when the language changes:- Headlines and the recommended feed force a fresh, correctly-sourced fetch (not a translated stale cache)
- Favorites are dynamically re-translated on read β an article saved while browsing in English is transparently localized when you revisit your saved list in Arabic
- Search results are locale-aware β the active language is passed as a query parameter to NewsAPI, and any in-progress search automatically re-runs when the language changes
- On-device article translation β articles fetched for the Arabic locale are passed through an
ArticleTranslationRepository, which translates and caches the title/description/content per article so repeat views don't re-hit the translation API, and purges its cache automatically on every locale switch to prevent unbounded growth - Language picker β a dedicated
LanguagePickerDialoglets the user choose their language explicitly from the Drawer or Profile Settings, in addition to the quicktoggle()shortcut
LocaleCubit
βββ init() β reads persisted locale from Hive on app start
βββ setLocale(_) β persists + clears translation cache + emits a new Locale
βββ toggle() β flips between Locale('en') and Locale('ar')
Search is built on a dedicated SearchCubit with these production-level details:
- Debouncing β 500ms
searchDebouncetimer; no API call fires until the user pauses typing - Locale-aware β subscribes to
LocaleCubit's stream and transparently re-runs the active query when the language changes, passing the current locale to NewsAPI as alanguageparameter SearchServiceβ a thin, plain-Dio service wired through the service locator (a@RestApi-annotatedSearchServicesRetrofitinterface also exists in the codebase as an alternative typed-client implementation, generated viabuild_runner)- Paginated results β page size 15, with a
paginationWindowSizeof 5 for the visible page controls - Max API results cap β hard limit of 100 results respects NewsAPI's free-tier constraints
- Infinite scroll β triggers at 3 items from the bottom of the current page
- Search scope β queries the
/v2/everythingendpoint withsearchIn: 'title'for higher-precision results
One of the most deliberate architectural decisions in NewsWave: no connectivity_plus, no platform connectivity plugin, no native channel β at all.
Most Flutter apps reach for a connectivity package that reports interface state (is Wi-Fi/cellular "on?") rather than actual internet reachability β meaning a phone connected to a Wi-Fi network with no real internet access (a common captive-portal scenario in airports, hotels, and cafΓ©s) gets falsely reported as "online."
NewsWave's NetworkInfo abstraction solves this properly:
- Multi-endpoint HTTP probing β
NetworkInfoImplraces a small Dio client against several independent endpoints simultaneously (the app's own API host, plus two well-known low-latency probes), resolvingtruethe instant any one of them responds β andfalseonly if all of them fail - Captive-portal aware β because the check is a real HTTP round-trip rather than a raw socket or DNS lookup, it correctly detects networks that appear connected but silently block traffic
- In-flight de-duplication β concurrent callers share a single pending check via a cached
Future, so a burst ofisConnectedcalls never fires redundant network requests ConnectivityCubitβ a thinCubit<bool>that performs an immediate check on construction, then pollsNetworkInfoon aTimer.periodicinterval, emitting only on actual state changes; the timer is cleanly cancelled inclose()- Fully unit-tested β
NetworkInfoImplships with a dedicated test suite covering first-probe-success, total-failure, partial-failure fallthrough, and concurrent-call de-duplication, using a fakeHttpClientAdapterβ no real network access required to run the suite
NetworkInfo (abstract)
βββ NetworkInfoImpl
βββ races N probe URLs via Dio, completes on first success
βββ in-flight check de-duplication (single shared Future)
βββ consumed by ConnectivityCubit via Timer.periodic polling
- Light & Dark themes fully defined via
AppTheme.light/AppTheme.darkusing Material 3ColorScheme - Brand palette: Primary
#1A73E8(Google Blue), Dark#0D47A1, Accent#FF6B35 - Typography: locale-aware font selection β Poppins for English, Cairo for Arabic β applied at the
ThemeDatalevel viaAppTheme.of(locale: ...) - Animated shimmer β custom
ShimmerBoxwithAnimationControllerfor skeleton loading states on both carousel cards and list rows - Theme persistence β
ThemeCubitsaves and restores the user's mode preference via Hive - Animated background β auth screens feature a subtle
_AnimatedBackgroundwidget usingSingleTickerProviderStateMixin - Custom page transitions β Fade (auth flows), Slide (list views), Shared-Axis (article detail)
- Optimized widget rebuilds β the root
MaterialAppis wrapped inBlocBuilder<ThemeCubit>/BlocBuilder<LocaleCubit>with explicitbuildWhenpredicates, so a theme or locale change rebuilds only what actually depends on it instead of tearing down the entire navigation tree - Isolated dev tooling β
DevicePreviewlives behind its own dedicatedmain_dev.dartentry point, completely absent from the productionmain.dartbuild target β multi-device preview during development with zero risk of shipping debug-only overhead
NewsWave follows a strict, feature-first Clean Architecture with a shared core module and enforced Dependency Inversion (DIP) at every repository boundary:
lib/
βββ core/ # Shared infrastructure
β βββ constants/ # AppConstants, NewsCategory enum
β βββ cubits/ # CategoryCubit (global)
β βββ cache/ # NewsCacheManager β extracted cache orchestration (SRP)
β βββ di/ # service_locator.dart (GetIt setup)
β βββ exceptions/ # NewsException sealed hierarchy + NewsErrorCode
β βββ locale/ # LocaleCubit, LanguagePickerDialog, LanguageOption
β βββ translation/ # TranslationService (interface) + LibreTranslate / MyMemory impls
β βββ network/ # NetworkInfo (interface), NetworkInfoImpl (HTTP-probe), ConnectivityCubit
β βββ browser/ # In-app WebView (InAppBrowserView)
β βββ repositories/ # HomeRepository β depends only on abstractions
β βββ models/ # Article, Source (Hive + JSON)
β βββ pagination/ # PaginationMeta, pagination bar widgets
β βββ router/ # AppRouter, AppRoutes
β βββ secrets/ # AppSecrets (reads .env)
β βββ services/ # LocalDatabaseHive (Hive init, box helpers)
β βββ supabase/ # AuthLocalDataSource, AuthRemoteDataSource, exceptions
β βββ theme/ # AppTheme, AppColors, ThemeCubit
β βββ helpers/ # EmptyState, ErrorState, ShimmerBox, category l10n extension
β βββ widgets/ # Shared: ArticleCard, SaveButton, ShareButton,
β # OfflineBanner, AppDrawer
β
βββ l10n/ # ARB files + generated AppLocalizations + context.l10n extension
β # + news_error_x.dart (NewsException β localized message)
β
βββ features/
βββ auth/ # AuthCubit, AuthRepository, Auth + Onboarding views
β βββ cubit/ # auth_cubit.dart Β· auth_listener_cubit.dart
β βββ data/ # auth_repository.dart (interface + impl)
β βββ views/ # SignIn, SignUp, ForgotPassword, UpdatePassword, Onboarding
βββ home/ # HomeView, HomeCubit (headlines + paginated "For You" feed)
β βββ domain/repositories/ # home_repository_contract.dart β the DIP boundary
β βββ cubit/ # home_cubit.dart (single cubit drives both data streams)
β βββ services/ # home_services.dart β implements the contract, Dio-backed
β βββ views/ # home_view.dart, article_detail_view.dart
βββ Headlines/ # HeadlinesCubit + HeadlinesView (full-screen, paginated list)
βββ search/ # SearchCubit (locale-aware), SearchService, SearchView
βββ favorites/ # FavoritesCubit (locale-aware, Hive-backed), FavoritesView
βββ profile/ # ProfileSettingsView (theme, language, account)
βββ splash/ # SplashView (session restore + routing logic)
Data flow per feature:
UI (View)
βββΊ Cubit (state logic)
βββΊ Repository (interface)
βββΊ Impl (Supabase / Dio / Hive)
Dependency Inversion, enforced β not just described:
HomeRepositorydepends on four abstractions, never a concrete class:HomeRepositoryContract(the remote data-source boundary, implemented byHomeServices),NewsCacheManager(cache orchestration, extracted from the repository to keep it focused on a single responsibility),NetworkInfo, andArticleTranslationRepository. Swapping the news provider, the cache backend, or the translation engine requires touching exactly one binding inservice_locator.dartβ nothing else in the codebase needs to know.TranslationServiceis an abstract interface with two interchangeable implementations shipped side by side βLibreTranslationServiceandMyMemoryTranslationServiceβ proving the abstraction isn't theoretical. The active implementation, plus itsdispose()callback for clean resource teardown, is selected in exactly one place.- Domain errors are modeled as a sealed
NewsExceptionhierarchy (OfflineNoCacheException,NetworkTimeoutException,ServerException, β¦) instead of raw strings β keeping user-facing copy entirely out of the data layer and resolved to localized text only at the presentation boundary via a singleresolveErrorMessage()helper.
The core layer has zero dependency on any feature. Features may only depend on core, never on each other. All cross-cutting singletons β Dio, NetworkInfo, NewsCacheManager, LocaleCubit, ThemeCubit, repositories, and services (each registered with an explicit dispose: callback where a resource needs closing) β are wired through a single setupServiceLocator() entry point using GetIt.
| Layer | Technology | Purpose |
|---|---|---|
| Framework | Flutter 3.x | Cross-platform UI |
| Language | Dart 3.x | Null-safe, strong-typed |
| State Management | flutter_bloc / Cubit |
Predictable reactive state, with buildWhen-optimized rebuilds |
| Dependency Injection | get_it |
Centralized service locator (setupServiceLocator()), with explicit dispose: callbacks for resource cleanup |
| Backend / Auth | Supabase | Auth, Postgres DB, Realtime |
| News Data | NewsAPI v2 | Headlines, everything, and search endpoints |
| Localization | flutter_localizations + ARB |
100% of UI strings, reactive locale switching, locale-aware caching |
| Translation | TranslationService interface β LibreTranslate (primary) / MyMemory (alternate impl) |
Chunked, field-merged article translation for the Arabic locale, Hive-cached |
| HTTP Client | Dio | Shared instance registered as a lazy singleton in the DI container; also powers the connectivity probe |
| Local Database | Hive | NoSQL key-value, locale-namespaced offline cache, theme & locale persistence |
| Environment | flutter_dotenv |
Secrets management via .env |
| Routing | onGenerateRoute |
Named routes with custom transitions |
| Fonts | Poppins (EN) / Cairo (AR) | Locale-aware brand typography |
| Dev Tools | DevicePreview | Isolated in a dedicated main_dev.dart entry point β zero footprint in production builds |
| Testing | flutter_test |
Unit tests for network probing logic with a fake HttpClientAdapter |
| Code Generation | build_runner |
Hive adapters, generated AppLocalizations |
- Flutter SDK
>=3.0.0 - Dart SDK
>=3.0.0 - A NewsAPI.org API key (free tier)
- A Supabase project with Auth enabled
git clone https://github.com/<your-username>/news_app.git
cd news_appCreate a .env file in the project root:
# .env
NEWS_API_KEY=your_newsapi_key_here
SUPABASE_URL=https://your-project-ref.supabase.co
SUPABASE_ANON_KEY=your_supabase_anon_key_here
β οΈ Never commit.envto version control. It is already listed in.gitignore.
In your Supabase dashboard:
- Enable Email Auth under Authentication β Providers.
- Create a
profilestable with at least these columns:
create table public.profiles (
id uuid references auth.users on delete cascade primary key,
first_name text,
last_name text,
country text,
hobby text,
categories text[],
is_onboarded boolean default false
);- Create a trigger to auto-insert a profile row on new user sign-up:
create or replace function public.handle_new_user()
returns trigger as $$
begin
insert into public.profiles (id)
values (new.id);
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();- Enable deep-link redirect for password recovery (add your app's redirect URL in Authentication β URL Configuration).
flutter pub getRequired for Hive TypeAdapters:
dart run build_runner build --delete-conflicting-outputsGenerated
AppLocalizationsclasses are produced automatically by the Flutter toolchain from the ARB files underlib/l10n/onflutter pub get/flutter runβ no extra command needed, as long asgenerate: trueis set underflutter:inpubspec.yaml.
flutter runTo run on a specific device:
flutter run -d <device_id>π‘ To preview the app with
DevicePreviewfor rapid multi-device testing, run the isolated dev entry point instead:flutter run -t lib/main_dev.dart. The production target (lib/main.dart) never importsDevicePreviewat all.
news_app/
βββ .env # β create this (see setup)
βββ pubspec.yaml
βββ lib/
β βββ main.dart # Production entry: Supabase, Hive, DI, BlocProviders, MaterialApp
β βββ main_dev.dart # Dev-only entry: wraps NewsWave() in DevicePreview, isolated from prod
β βββ core/
β β βββ constants/
β β β βββ app_constants.dart # API urls, pagination sizes, cache key prefixes, NewsCategory enum
β β β βββ app_images.dart # Asset path constants
β β βββ cubits/
β β β βββ category_cubit.dart # Global active-category state
β β βββ cache/
β β β βββ news_cache_manager.dart # Locale-aware cache key generation + read/write (extracted SRP)
β β βββ di/
β β β βββ service_locator.dart # GetIt setup β every singleton/factory + dispose callback lives here
β β βββ exceptions/
β β β βββ news_exceptions.dart # Sealed NewsException hierarchy + Dio β NewsException mapper
β β βββ locale/
β β β βββ locale_cubit.dart # LocaleCubit (persists to Hive `settings_box`, purges translation cache)
β β β βββ language_option.dart
β β β βββ language_picker_dialog.dart
β β βββ translation/
β β β βββ translation_service.dart # Abstract interface
β β β βββ libre_translation_service.dart # LibreTranslate implementation (primary)
β β β βββ my_memory_translation_service.dart # MyMemory implementation (alternate)
β β β βββ translation_exceptions.dart # Rate-limit / network / offline exception types
β β β βββ article_translation_repository.dart # Chunked, field-merged translate + Hive cache
β β βββ network/
β β β βββ network_info.dart # Abstract NetworkInfo + Dio multi-probe NetworkInfoImpl
β β β βββ connectivity_cubit.dart # Timer.periodic polling, no 3rd-party plugin
β β βββ browser/
β β β βββ view/in_app_browser_view.dart
β β β βββ widgets/ # error_page_widget.dart, nav_icon_widget.dart
β β βββ repositories/
β β β βββ home_repository.dart # Orchestrates 4 injected abstractions β zero concrete deps
β β βββ models/
β β β βββ article_model.dart # @HiveType Article + Source + extensions
β β β βββ article_model.g.dart # Generated Hive adapter
β β β βββ article_detail_args.dart
β β β βββ news_api_response.dart
β β βββ pagination/
β β β βββ helpers/nav_button.dart
β β β βββ widgets/ # pagination_bar_widget.dart, load_more_footer.dart
β β βββ router/
β β β βββ app_router.dart # onGenerateRoute + transition builders
β β β βββ app_routes.dart # Route name constants
β β βββ secrets/
β β β βββ app_secrets.dart # Reads .env values
β β βββ services/
β β β βββ local_database_hive.dart # Hive init, box helpers
β β βββ supabase/
β β β βββ auth_local_data_source.dart
β β β βββ auth_remote_data_source.dart
β β β βββ auth_exception.dart # Sealed AuthUserException hierarchy
β β βββ theme/
β β β βββ app_colors.dart # Brand color palette
β β β βββ app_theme.dart # Light + Dark ThemeData, locale-aware fonts
β β β βββ theme_cubit.dart # ThemeCubit (persists to Hive)
β β β βββ model/theme_model.dart
β β βββ helpers/
β β β βββ category_localization_x.dart # NewsCategory β localized label extension
β β β βββ empty_state.dart
β β β βββ error_state.dart
β β β βββ shimmer_box.dart
β β βββ widgets/
β β βββ drawer/
β β β βββ authenticated_header_widget.dart
β β β βββ guest_header_widget.dart
β β β βββ category_item.dart
β β β βββ drawer_item.dart
β β βββ article_card_widget.dart
β β βββ save_button_widget.dart
β β βββ share_button_widget.dart
β β βββ offline_banner.dart
β β βββ app_snack_bar.dart
β βββ l10n/
β β βββ app_en.arb # English source strings
β β βββ app_ar.arb # Arabic translations
β β βββ app_localizations_x.dart # `context.l10n` extension
β β βββ news_error_x.dart # NewsException β localized message extension
β β βββ gen_l10n/app_localizations.dart # Auto-generated by Flutter
β βββ features/
β βββ auth/
β β βββ cubit/
β β β βββ auth_cubit.dart # signIn/Up/Out, guest, password reset
β β β βββ auth_listener_cubit.dart
β β βββ data/
β β β βββ auth_repository.dart
β β β βββ auth_repository_impl.dart
β β βββ views/
β β βββ sign_in_view.dart
β β βββ sign_up_view.dart
β β βββ forgot_password_view.dart
β β βββ update_password_view.dart
β β βββ onboarding_view.dart # 3-page PageView wizard
β βββ home/
β β βββ domain/repositories/
β β β βββ home_repository_contract.dart # Abstraction HomeRepository depends on (DIP)
β β βββ cubit/home_cubit.dart # Drives both headlines + paginated "For You" feed
β β βββ services/home_services.dart # Implements the contract β Dio calls under the hood
β β βββ views/
β β βββ home_view.dart
β β βββ article_detail_view.dart
β βββ Headlines/
β β βββ cubit/headlines_cubit.dart
β β βββ widgets/ # glass_category_row.dart, glass_category_card.dart
β β βββ views/headlines_view.dart
β βββ search/
β β βββ cubit/search_cubit.dart # Locale-aware: subscribes to LocaleCubit, re-queries on change
β β βββ services/
β β β βββ search_services.dart # Plain-Dio SearchService (used by SearchCubit)
β β β βββ search_services_retrofit.dart # @RestApi alternative client
β β βββ views/search_view.dart
β βββ favorites/
β β βββ cubit/favorite_cubit.dart # Locale-aware: re-translates saved articles on language switch
β β βββ services/favorite_services.dart
β β βββ views/favorites_view.dart
β βββ profile/
β β βββ views/profile_settings_view.dart # Theme, language, account
β βββ splash/
β βββ view/splash_view.dart
βββ assets/
β βββ images/icons/ # App icon assets
βββ docs/
βββ screenshots/ # β add your screenshots here
βββ assets/ # β add your showcase GIFs here
Locale-aware cache architecture: NewsCacheManager was extracted from HomeRepository specifically to own cache-key generation and read/write logic in one place. Every key it produces is namespaced by both category and locale (e.g. cached_headlines_business_ar), and every HomeCubit/HeadlinesCubit locale-change handler forces a fresh fetch (forceRefresh: true) β together eliminating an entire category of "stale-language cache" bugs by construction.
Chunked, throttle-safe translation pipeline: Rather than firing one HTTP request per article field (which can mean 20β30+ simultaneous calls against a single feed), ArticleTranslationRepository first merges an article's title, description, and content into a single delimited request, then processes articles in small concurrent chunks (batches of 3) instead of firing the entire feed at once. Combined, these two optimizations cut outbound translation API calls by well over 80% compared to a naive per-field, per-article implementation β directly addressing the rate-limit constraints of free-tier translation services.
Resilient translation layer with typed exceptions: TranslationService is a true interface with interchangeable backends. The active implementation, LibreTranslationService, wraps every failure mode in a specific exception type β rate-limit (429) responses, network/offline failures, and unexpected non-200 responses are never silently swallowed or shown to the user as raw English error text. They're caught, classified, and resolved to a localized message via resolveErrorMessage() at the presentation layer.
Memory-safe resource lifecycle: Every http.Client and Dio-backed service that owns a connection pool exposes an explicit close(), and is registered in service_locator.dart with a matching dispose: callback so GetIt tears it down correctly. Every StreamSubscription created by a Cubit (locale changes, category changes, auth state changes) is cancelled in that Cubit's close() override β verified across FavoritesCubit, HomeCubit, HeadlinesCubit, SearchCubit, and AuthListenerCubit.
Optimized rebuild boundaries: The root NewsWave widget nests BlocBuilder<ThemeCubit> and BlocBuilder<LocaleCubit> with explicit buildWhen predicates (comparing languageCode rather than the full Locale object), so a theme toggle or language switch only rebuilds the MaterialApp subtree that actually needs new theme/locale data β not the entire navigator and route stack.
Pagination architecture: Each page of recommended articles is independently cached using a locale-namespaced key prefix (rec_page_N_<locale>). On cache hit, the UI renders instantly; on miss, a Dio call is made and the result is written to Hive before emission. This means pages the user has already seen are always available offline, in the correct language.
Auth deep-link handling: The navigatorKey is registered at the MaterialApp level and used inside main() to intercept AuthChangeEvent.passwordRecovery from Supabase's auth stream β routing the user to UpdatePasswordView regardless of which screen they're currently on.
Dio via dependency injection: A single Dio instance is built once inside service_locator.dart (base URL, auth header, conditional logging interceptor in non-release builds) and registered as a lazy singleton with GetIt. Every feature service (HomeServices, SearchService, the network probe, etc.) receives that same instance β or a purpose-built one with its own timeout policy β through constructor injection rather than instantiating its own client ad hoc.
Article model cleanliness: The ArticleExtension strips HTML tags, resolves HTML entities, and removes NewsAPI's [+N chars] truncation suffix from content β so the UI always receives clean, renderable text, in either language.
Based on the current codebase, these are the most impactful improvements that would push NewsWave even further past production-ready:
- Push Notifications β Supabase Realtime or FCM integration for breaking news alerts, with locale-aware topic subscriptions
- Social Auth β Google / Apple Sign-In via Supabase OAuth providers
- Native Share Sheet polish β refine in-app browser navigation guards and share result messaging
- Personalized Feed Algorithm β Weight recommended articles by onboarding category preferences
- Self-Hosted Translation Backend β point
LibreTranslationServiceat a self-hosted instance (Railway / Fly.io) instead of the public endpoint, with an automatic fallback to the alternateTranslationServiceimplementation if the primary is unreachable - Cache TTL & Eviction β time- and size-bound eviction for both the news cache and the translation cache to cap long-term on-device storage growth
- Broader Test Coverage β extend the existing
NetworkInfoImplunit-test suite to Cubit state tests (including locale-switch behavior) and widget golden tests for core components - CI/CD Pipeline β GitHub Actions for lint, test, and Flutter build on each PR
- Additional Locales β extend the existing ARB-based localization beyond English / Arabic
- Accessibility β Semantic labels, sufficient color contrast audit, dynamic text scaling
β
Connectivity Banner,Localization (i18n),Dependency Inversion on repositories, andTranslation Rate-Limitingβ all shipped: connectivity is detected via a dependency-free, multi-endpoint HTTP-probingNetworkInfo(zero third-party plugins), the app is 100% localized into English and Arabic with reactive RTL support and locale-aware caching, every repository depends on injected interfaces rather than concrete implementations, and article translation is chunked, field-merged, and protected by typed rate-limit exceptions.
Contributions are welcome! Please open an issue first to discuss what you'd like to change. For pull requests:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature-name - Commit your changes:
git commit -m 'feat: add your feature' - Push to the branch:
git push origin feature/your-feature-name - Open a Pull Request
Distributed under the MIT License. See LICENSE for more information.
Built with β€οΈ using Flutter
If this project helped you, please consider giving it a β β it means a lot!















