feat(news): port the cu-12 / cu-13 news flow on top of NewsAPI#27
Merged
Conversation
Wires the News tab and the article detail screen end-to-end through
Retrofit + Moshi + OkHttp against NewsAPI.org, with Glide-rendered
images and a HorizontalPager carousel sitting above the LATEST
LazyColumn.
* domain/news: NewsArticle data class + NewsRepository interface,
nullable image and description because real-world NewsAPI rows
skip them on some sources.
* data/news: NewsApiService Retrofit binding + the Moshi DTOs,
NewsArticleMapper that drops rows missing required fields or
flagged with NewsAPI's "[Removed]" sentinel, and an in-memory
repository implementation that caches the last successful fetch
by article URL so the detail screen can look an article up
without a second round-trip. Room-backed offline-first lands in
the final entrega; the in-memory cache is the smallest thing
that satisfies the second-submission contract.
* di/NewsModule: provides Moshi, an OkHttp client whose interceptor
attaches `X-Api-Key`, the configured Retrofit + MoshiConverterFactory,
the NewsApiService and binds the repository.
* feature/news: sealed NewsUiState (Loading / Success / Error) + a
SourceFilter sealed type, a Hilt MVVM ViewModel that triggers the
first refresh on init and exposes a StateFlow, search + source
filtering applied in-memory so neither burns the 100 req/day quota.
* NewsScreen renders the header, the reactive search input, the
horizontally-scrollable source chips, the FEATURED carousel
(HorizontalPager over the top 5 articles with violet/grey dot
indicators), the LATEST LazyColumn with Glide thumbnails, and a
centred ErrorState card on failure.
* NewsDetailScreen renders the hero (Glide), title, published-at
metadata, optional description, a "Read full article" CTA that
fires `Intent.ACTION_VIEW` and a share chip on the header that
fires `Intent.ACTION_SEND` with the title + URL.
Routes through `Route.NewsDetail.build(id)` which URL-encodes the
article id; NewsAPI's only stable id is the article URL itself, and
the raw colons + slashes break Compose Navigation's path matching
without encoding.
16 unit tests cover the ViewModel state machine (initial Loading,
success → filtered, search by title and source case-insensitive,
filter composition with search, retry after error) and the mapper
contract (drops the row when title / url / publishedAt / source are
missing or "[Removed]", keeps it and drops the field for description
or image individually).
android.net.Uri.encode returns null under plain JVM unit tests where
the Android framework is stubbed, which broke the pre-existing
RouteTest.newsDetail_build_inserts_id_into_path_pattern assertion.
URLEncoder lives in java.net so it works in tests and on device alike;
the trailing replace("+", "%20") brings the output to RFC 3986 path
encoding (URLEncoder defaults to form encoding where space becomes +).
Extends the test to also lock the encoding behaviour against a real
article URL with colons and slashes, so the next change to the helper
fails fast if someone drops the encoding.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Architecture
domain/news:NewsArticle+NewsRepositoryinterface.data/news:NewsApiServiceRetrofit binding, Moshi DTOs,NewsArticleMapper(drops rows missing required fields or flagged with NewsAPI's[Removed]sentinel) and an in-memoryNewsRepositoryImplthat keys the cache by article URL so the detail screen finds articles without a second round-trip. Room-backed offline-first ships in the final entrega.di/NewsModule: provides Moshi, an OkHttp client whose interceptor attachesX-Api-Key, the configured Retrofit + MoshiConverterFactory and the NewsApiService; binds the repository.feature/news: sealedNewsUiState(Loading / Success / Error) +SourceFiltersealed type, Hilt MVVM ViewModel exposing aStateFlowwithcollectAsStateWithLifecyclein the UI.NewsScreen: header, reactive search, scrollable source chips (driven dynamically off the loaded articles' source set), FEATURED HorizontalPager carousel with violet/grey dot indicators, LATEST LazyColumn with Glide thumbnails, and a centred ErrorState card on failure.NewsDetailScreen: hero (Glide), title, published-at metadata, optional description, "READ FULL ARTICLE" CTA (Intent.ACTION_VIEW) and a header share chip (Intent.ACTION_SENDwith title + URL).Notes
Route.NewsDetail.build(id)URL-encodes the article id. NewsAPI's only stable id is the URL itself, and raw colons + slashes break Compose Navigation's path matching without encoding.INTERNETpermission is already declared (added in the assistant PR for the Gemini SDK); Retrofit reuses it.Test plan
Local setup
Add to
stack/local.properties(gitignored):Then
./gradlew installDebug.