### TopicsRepository Usage Examples Source: https://context7.com/android/nowinandroid/llms.txt Demonstrates how to collect all topics and a single topic by ID from the TopicsRepository using Kotlin Flows. ```kotlin // Collect all topics topicsRepository.getTopics() .collect { topics -> println(topics.map { it.name }) } // Output: [Accessibility, Android Auto, Android TV, Compose, ...] // Collect a single topic by id topicsRepository.getTopic(id = "compose") .collect { topic -> println("${topic.name}: ${topic.shortDescription}") } ``` -------------------------------- ### Build App Variant Source: https://github.com/android/nowinandroid/blob/main/AGENTS.md Use this command to assemble a specific product flavor and build type of the application. For example, to build the demo debug version. ```bash ./gradlew assembleDemoDebug ``` -------------------------------- ### Example Test Class Naming and Structure Source: https://github.com/android/nowinandroid/wiki/Testing-strategy-and-how-to-test Demonstrates the recommended naming convention for test classes and the structure for a test method using the given-when-then pattern, adapted for test naming. ```kotlin /** * UI tests for [BookmarksScreen] composable. */ class BookmarksScreenTest { @Test fun feed_hasNoBookmarks_showsEmptyState() { ... } } ``` -------------------------------- ### User Data Repository Interface and Usage Source: https://context7.com/android/nowinandroid/llms.txt Defines the contract for reading and writing user preferences. Examples show how to observe user data and update preferences like followed topics, bookmarks, and theme settings. ```kotlin interface UserDataRepository { val userData: Flow suspend fun setFollowedTopicIds(followedTopicIds: Set) suspend fun setTopicIdFollowed(followedTopicId: String, followed: Boolean) suspend fun setNewsResourceBookmarked(newsResourceId: String, bookmarked: Boolean) suspend fun setNewsResourceViewed(newsResourceId: String, viewed: Boolean) suspend fun setThemeBrand(themeBrand: ThemeBrand) suspend fun setDarkThemeConfig(darkThemeConfig: DarkThemeConfig) suspend fun setDynamicColorPreference(useDynamicColor: Boolean) suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) } ``` ```kotlin // Read user preferences userDataRepository.userData .collect { userData -> println("Following topics: ${userData.followedTopics}") println("Bookmarks: ${userData.bookmarkedNewsResources}") } ``` ```kotlin // Write — follow a topic viewModelScope.launch { userDataRepository.setTopicIdFollowed("compose", followed = true) } ``` ```kotlin // Write — bookmark a news resource viewModelScope.launch { userDataRepository.setNewsResourceBookmarked(newsResourceId = "nr_42", bookmarked = true) } ``` ```kotlin // Write — update theme viewModelScope.launch { userDataRepository.setThemeBrand(ThemeBrand.ANDROID) userDataRepository.setDarkThemeConfig(DarkThemeConfig.DARK) userDataRepository.setDynamicColorPreference(useDynamicColor = false) } ``` -------------------------------- ### Run Instrumented Tests on Gradle-Managed Devices Source: https://github.com/android/nowinandroid/blob/main/AGENT.md Executes instrumented tests on virtual devices managed by Gradle. Examples include pixel6api31aospDebugAndroidTest, pixel4api30aospatdDebugAndroidTest, and pixelcapi30aospatdDebugAndroidTest. ```bash ./gradlew pixel6api31aospDebugAndroidTest ``` -------------------------------- ### TopicsRepository Interface Source: https://context7.com/android/nowinandroid/llms.txt Defines the contract for accessing and retrieving topic information. Provides methods to get all topics or a single topic by its ID. ```kotlin interface TopicsRepository : Syncable { fun getTopics(): Flow> fun getTopic(id: String): Flow } ``` -------------------------------- ### ViewModel Usage of NewsRepository Source: https://context7.com/android/nowinandroid/llms.txt Example of how a ViewModel can use the NewsRepository to fetch and expose a filtered stream of news resources using Kotlin Flows and StateFlow. ```kotlin // Usage in a ViewModel or use case class MyViewModel @Inject constructor( private val newsRepository: NewsRepository, ) : ViewModel() { // Stream all news for topics "android" and "compose" val filteredNews: StateFlow> = newsRepository .getNewsResources( query = NewsResourceQuery(filterTopicIds = setOf("android", "compose")), ) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) } ``` -------------------------------- ### Set Feed State to Loading Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md The ForYouViewModel calls a use case to get news resources. While waiting for data, the feed state is set to Loading. Search for usages of this state to find the relevant code. ```kotlin NewsFeedUiState.Loading ``` -------------------------------- ### Retrofit Network API Declaration Source: https://context7.com/android/nowinandroid/llms.txt Declares the Retrofit API endpoints for interacting with the backend. Uses Kotlin serialization for JSON and specifies HTTP GET requests with query parameters. ```kotlin private interface RetrofitNiaNetworkApi { @GET("topics") suspend fun getTopics(@Query("id") ids: List?): NetworkResponse> @GET("newsresources") suspend fun getNewsResources(@Query("id") ids: List?): NetworkResponse> @GET("changelists/topics") suspend fun getTopicChangeList(@Query("after") after: Int?): List @GET("changelists/newsresources") suspend fun getNewsResourcesChangeList(@Query("after") after: Int?): List } ``` -------------------------------- ### Gradle Commands for Build and Testing Source: https://context7.com/android/nowinandroid/llms.txt Common Gradle commands for building specific variants, running tests, recording and verifying screenshots, and analyzing Compose compiler metrics. ```bash # Build the demo debug variant (uses local JSON assets – no backend needed) ./gradlew assembleDemoDebug ``` ```bash # Run all unit tests against demoDebug ./gradlew testDemoDebug ``` ```bash # Run instrumented tests against demoDebug on a connected device/emulator ./gradlew connectedDemoDebugAndroidTest ``` ```bash # Record new baseline screenshots (run on Linux/CI for consistency) ./gradlew recordRoborazziDemoDebug ``` ```bash # Verify screenshots match recorded baselines ./gradlew verifyRoborazziDemoDebug ``` ```bash # Analyze Compose compiler stability (outputs to build/compose-reports) ./gradlew assembleRelease -PenableComposeCompilerMetrics=true -PenableComposeCompilerReports=true ``` ```bash # Build the production release variant (requires backend URL set in BuildConfig) ./gradlew assembleProdRelease ``` -------------------------------- ### Run Single Local Test Source: https://github.com/android/nowinandroid/blob/main/AGENTS.md Execute a specific local unit test class. Replace {variant} and "com.example.myapp.MyTestClass" with your target variant and test class. ```bash ./gradlew demoDebugTest --tests "com.example.myapp.MyTestClass" ``` -------------------------------- ### Generate Compose Compiler Metrics and Reports Source: https://github.com/android/nowinandroid/blob/main/README.md Run this Gradle command to generate Compose compiler metrics and reports for analysis. The reports are saved in build/compose-reports and metrics in build/compose-metrics. ```bash ./gradlew assembleRelease -PenableComposeCompilerMetrics=true -PenableComposeCompilerReports=true ``` -------------------------------- ### Build App Variant Source: https://github.com/android/nowinandroid/blob/main/AGENT.md Use this command to assemble a specific build variant of the app. Replace {Variant} with the desired flavor and build type (e.g., DemoDebug). ```bash ./gradlew assemble{Variant} ``` -------------------------------- ### Initialize Sync Job with WorkManager Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md Enqueues a WorkManager job to synchronize all repositories on app startup. This is the initial step for data fetching. ```kotlin Sync.initialize ``` -------------------------------- ### Run Local Tests Source: https://github.com/android/nowinandroid/blob/main/AGENTS.md Execute local unit tests for a specified variant. Replace {variant} with the desired build variant, e.g., demoDebug. ```bash ./gradlew demoDebugTest ``` -------------------------------- ### NewsRepository Interface Source: https://context7.com/android/nowinandroid/llms.txt Defines the contract for accessing and synchronizing news resources. Implements an offline-first strategy, reading from Room and syncing from the network. ```kotlin // core/data — interface interface NewsRepository : Syncable { fun getNewsResources( query: NewsResourceQuery = NewsResourceQuery(), ): Flow> } ``` -------------------------------- ### Run Local Unit Tests Source: https://github.com/android/nowinandroid/blob/main/AGENT.md Executes local unit tests for a specified variant. Replace {variant} with the target build variant (e.g., demoDebug). ```bash ./gradlew {variant}Test ``` -------------------------------- ### Lint Module Dependency Graph Source: https://github.com/android/nowinandroid/blob/main/lint/README.md Visualizes the module dependency graph for the lint module using Mermaid syntax. This helps understand the module's connections within the project. ```mermaid graph TB :lint[lint]:::unknown classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000; classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000; classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000; classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000; classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000; classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000; ``` -------------------------------- ### Run Single Local Test Source: https://github.com/android/nowinandroid/blob/main/AGENT.md Executes a specific local unit test. Replace {variant} with the target build variant and provide the fully qualified name of the test class. ```bash ./gradlew {variant}Test --tests "com.example.myapp.MyTestClass" ``` -------------------------------- ### ForYouViewModel for News Feed and Onboarding State Source: https://context7.com/android/nowinandroid/llms.txt Manages the state for the news feed and onboarding UI. It uses various repositories and use cases to fetch and process data. Requires Hilt for dependency injection. ```kotlin @HiltViewModel class ForYouViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, syncManager: SyncManager, private val userDataRepository: UserDataRepository, userNewsResourceRepository: UserNewsResourceRepository, getFollowableTopics: GetFollowableTopicsUseCase, private val analyticsHelper: AnalyticsHelper, ) : ViewModel() { // Sync spinner val isSyncing: StateFlow = syncManager.isSyncing .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false) // News feed (Loading → Success) val feedState: StateFlow = userNewsResourceRepository .observeAllForFollowedTopics() .map(NewsFeedUiState::Success) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), NewsFeedUiState.Loading) // Onboarding (shown until at least one topic is followed) val onboardingUiState: StateFlow = combine( userDataRepository.userData.map { !it.shouldHideOnboarding }, getFollowableTopics(), ) { shouldShow, topics -> if (shouldShow) OnboardingUiState.Shown(topics) else OnboardingUiState.NotShown }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), OnboardingUiState.Loading) fun updateTopicSelection(topicId: String, isChecked: Boolean) { viewModelScope.launch { userDataRepository.setTopicIdFollowed(topicId, isChecked) } } fun updateNewsResourceSaved(newsResourceId: String, isChecked: Boolean) { viewModelScope.launch { userDataRepository.setNewsResourceBookmarked(newsResourceId, isChecked) } } fun dismissOnboarding() { viewModelScope.launch { userDataRepository.setShouldHideOnboarding(true) } } } // Sealed state for the news feed sealed interface NewsFeedUiState { data object Loading : NewsFeedUiState data class Success(val feed: List) : NewsFeedUiState } ``` ```kotlin // Sealed state for the news feed sealed interface NewsFeedUiState { data object Loading : NewsFeedUiState data class Success(val feed: List) : NewsFeedUiState } ``` -------------------------------- ### GetFollowableTopicsUseCase for Topic Management Source: https://context7.com/android/nowinandroid/llms.txt Combines TopicsRepository and UserDataRepository to provide a flow of topics with their follow state. Allows sorting topics by name. ```kotlin class GetFollowableTopicsUseCase @Inject constructor( private val topicsRepository: TopicsRepository, private val userDataRepository: UserDataRepository, ) { operator fun invoke(sortBy: TopicSortField = NONE): Flow> = combine(userDataRepository.userData, topicsRepository.getTopics()) { userData, topics -> topics.map { FollowableTopic(topic = it, isFollowed = it.id in userData.followedTopics) }.let { if (sortBy == NAME) it.sortedBy { t -> t.topic.name } else it } } } ``` ```kotlin // Usage in InterestsViewModel: val uiState: StateFlow = combine( selectedTopicId, getFollowableTopics(sortBy = TopicSortField.NAME), InterestsUiState::Interests, ).stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), InterestsUiState.Loading) ``` -------------------------------- ### Fix Linting and Formatting Source: https://github.com/android/nowinandroid/blob/main/AGENT.md Applies code formatting and linting rules defined in the project. Run this command to ensure code style consistency. ```bash ./gradlew spotlessApply ``` -------------------------------- ### FollowableTopic Data Model Source: https://context7.com/android/nowinandroid/llms.txt A wrapper for the Topic model that includes a boolean indicating whether the user is currently following the topic. ```kotlin data class FollowableTopic( val topic: Topic, val isFollowed: Boolean, ) ``` -------------------------------- ### Synchronize Data with Remote Source Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md The OfflineFirstNewsRepository initiates synchronization with the remote data source. This method is key for updating local data. ```kotlin OfflineFirstNewsRepository.syncWith ``` -------------------------------- ### Retrofit Network Data Source Implementation Source: https://context7.com/android/nowinandroid/llms.txt Production implementation of NiaNetworkDataSource using Retrofit. It configures Retrofit with a base URL, lazy OkHttpClient initialization, and kotlinx.serialization for JSON. ```kotlin @Singleton internal class RetrofitNiaNetwork @Inject constructor( networkJson: Json, okhttpCallFactory: dagger.Lazy, ) : NiaNetworkDataSource { private val networkApi = Retrofit.Builder() .baseUrl(BuildConfig.BACKEND_URL) .callFactory { okhttpCallFactory.get().newCall(it) } // lazy init – avoids main-thread OkHttp .addConverterFactory(networkJson.asConverterFactory("application/json".toMediaType())) .build() .create(RetrofitNiaNetworkApi::class.java) override suspend fun getNewsResources(ids: List?): List = networkApi.getNewsResources(ids = ids).data } ``` -------------------------------- ### NiaPreferencesDataSource for User Preferences Source: https://context7.com/android/nowinandroid/llms.txt Wraps a DataStore to manage user preferences, providing reactive streams for user data and methods to update followed topics and bookmarked news resources. ```kotlin class NiaPreferencesDataSource @Inject constructor( private val userPreferences: DataStore, ) { // Reactive stream of UserData, always up-to-date val userData: Flow = userPreferences.data.map { UserData( bookmarkedNewsResources = prefs.bookmarkedNewsResourceIdsMap.keys, followedTopics = prefs.followedTopicIdsMap.keys, themeBrand = /* map proto enum */ ..., darkThemeConfig = /* map proto enum */ ..., useDynamicColor = prefs.useDynamicColor, shouldHideOnboarding = prefs.shouldHideOnboarding, ) } suspend fun setTopicIdFollowed(topicId: String, followed: Boolean) { userPreferences.updateData { prefs -> prefs.copy { if (followed) followedTopicIds.put(topicId, true) else followedTopicIds.remove(topicId) updateShouldHideOnboardingIfNecessary() // auto-reset if no topics followed } } } suspend fun setNewsResourceBookmarked(newsResourceId: String, bookmarked: Boolean) { userPreferences.updateData { prefs -> prefs.copy { if (bookmarked) bookmarkedNewsResourceIds.put(newsResourceId, true) else bookmarkedNewsResourceIds.remove(newsResourceId) } } } // Change-list version tracking for incremental sync suspend fun getChangeListVersions(): ChangeListVersions = ... suspend fun updateChangeListVersion(update: ChangeListVersions.() -> ChangeListVersions) { ... } } ``` -------------------------------- ### UserNewsResource Data Model Source: https://context7.com/android/nowinandroid/llms.txt A decorated version of NewsResource that combines content with user-specific state like bookmarking and viewing status. ```kotlin data class UserNewsResource( val newsResource: NewsResource, val userData: UserData, // carries bookmarked/viewed sets ) { val isSaved: Boolean get() = id in userData.bookmarkedNewsResources val hasBeenViewed: Boolean get() = id in userData.viewedNewsResources } ``` -------------------------------- ### UserDataRepository Source: https://context7.com/android/nowinandroid/llms.txt Provides methods for reading and writing user preferences, such as followed topics, bookmarked news, and theme settings. ```APIDOC ## UserDataRepository ### Description Interface for managing user-specific data and preferences. ### Methods - `userData`: Flow - A stream of the current user data. - `setFollowedTopicIds(followedTopicIds: Set)`: Suspend function to set all followed topic IDs. - `setTopicIdFollowed(followedTopicId: String, followed: Boolean)`: Suspend function to set the followed status for a specific topic. - `setNewsResourceBookmarked(newsResourceId: String, bookmarked: Boolean)`: Suspend function to set the bookmarked status for a news resource. - `setNewsResourceViewed(newsResourceId: String, viewed: Boolean)`: Suspend function to set the viewed status for a news resource. - `setThemeBrand(themeBrand: ThemeBrand)`: Suspend function to set the app's theme brand. - `setDarkThemeConfig(darkThemeConfig: DarkThemeConfig)`: Suspend function to set the dark theme configuration. - `setDynamicColorPreference(useDynamicColor: Boolean)`: Suspend function to set the preference for dynamic color. - `setShouldHideOnboarding(shouldHideOnboarding: Boolean)`: Suspend function to set whether the onboarding should be hidden. ``` -------------------------------- ### Combine News Resources with User Data Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md GetUserNewsResourcesUseCase combines news resources with user data to emit a list of UserNewsResources. This prepares the data for the UI. ```kotlin GetUserNewsResourcesUseCase.invoke ``` -------------------------------- ### Combine News and User Data Streams Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md The `GetUserNewsResourcesUseCase` combines streams from `NewsRepository` and `UserDataRepository` to produce a stream of `UserNewsResource`s, useful for displaying bookmarked news. ```kotlin GetUserNewsResourcesUseCase ``` -------------------------------- ### Read Topics List Flow Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md Obtain a list of topics by subscribing to the `TopicsRepository::getTopics` flow. This stream emits `List` and updates automatically when the list changes. ```kotlin TopicsRepository::getTopics ``` -------------------------------- ### AnalyticsHelper: Structured Event Logging Source: https://context7.com/android/nowinandroid/llms.txt Logs structured analytics events. Supports standard screen views and custom events with parameters. Different implementations exist for production (Firebase) and testing (NoOp/Stub). ```kotlin // core/analytics interface AnalyticsHelper { fun logEvent(event: AnalyticsEvent) } data class AnalyticsEvent( val type: String, val extras: List = emptyList(), ) { data class Param(val key: String, val value: String) object Types { const val SCREEN_VIEW = "screen_view" } object ParamKeys { const val SCREEN_NAME = "screen_name" } } // Log a standard screen view analyticsHelper.logEvent( AnalyticsEvent( type = AnalyticsEvent.Types.SCREEN_VIEW, extras = listOf(AnalyticsEvent.Param(AnalyticsEvent.ParamKeys.SCREEN_NAME, "ForYou")), ) ) // Log a custom event (e.g., opening a deep link) analyticsHelper.logEvent( AnalyticsEvent( type = "news_deep_link_opened", extras = listOf(AnalyticsEvent.Param(key = "linked_news_resource_id", value = newsResourceId)), ) ) // prod flavor: FirebaseAnalyticsHelper logs to Firebase // demo flavor: NoOpAnalyticsHelper / StubAnalyticsHelper (for tests) ``` -------------------------------- ### DefaultSearchContentsRepository for SQLite FTS Source: https://context7.com/android/nowinandroid/llms.txt Implements full-text search using SQLite FTS4 with wildcard matching. It combines results from news resource and topic searches. ```kotlin override fun searchContents(searchQuery: String): Flow { val newsResourceIds = newsResourceFtsDao.searchAllNewsResources("*$searchQuery*") val topicIds = topicFtsDao.searchAllTopics("*$searchQuery*") return combine( newsResourceIds.mapLatest { it.toSet() }.distinctUntilChanged() .flatMapLatest { newsResourceDao.getNewsResources(useFilterNewsIds = true, filterNewsIds = it) }, topicIds.mapLatest { it.toSet() }.distinctUntilChanged() .flatMapLatest(topicDao::getTopicEntities), ) { news, topics -> SearchResult(topics = topics.map { it.asExternalModel() }, newsResources = news.map { it.asExternalModel() }) } } ``` -------------------------------- ### Run Local Screenshot Tests Source: https://github.com/android/nowinandroid/blob/main/AGENT.md Executes local screenshot tests using Roborazzi for the demoDebug variant. This helps verify UI consistency across changes. ```bash ./gradlew verifyRoborazziDemoDebug ``` -------------------------------- ### Module Dependency Graph Visualization Source: https://github.com/android/nowinandroid/blob/main/app/README.md Visualizes the dependency relationships between different module types. This Mermaid code block renders the graph. ```mermaid graph TB application[application]:::android-application feature[feature]:::android-feature library[library]:::android-library jvm[jvm]:::jvm-library application -.-> feature library --> jvm ``` -------------------------------- ### newsFeed Compose Extension for LazyStaggeredGridScope Source: https://context7.com/android/nowinandroid/llms.txt Renders a vertical staggered grid of news resource cards. Handles loading states and user interactions like bookmarking and topic clicks. Requires a Context for launching custom chrome tabs. ```kotlin // core/ui — NewsFeed.kt fun LazyStaggeredGridScope.newsFeed( feedState: NewsFeedUiState, onNewsResourcesCheckedChanged: (String, Boolean) -> Unit, onNewsResourceViewed: (String) -> Unit, onTopicClick: (String) -> Unit, onExpandedCardClick: () -> Unit = {}, ) { when (feedState) { NewsFeedUiState.Loading -> Unit // show shimmer / nothing is NewsFeedUiState.Success -> items(feedState.feed, key = { it.id }) { resource -> NewsResourceCardExpanded( userNewsResource = resource, isBookmarked = resource.isSaved, hasBeenViewed = resource.hasBeenViewed, onClick = { analyticsHelper.logNewsResourceOpened(resource.id) launchCustomChromeTab(context, Uri.parse(resource.url), backgroundColor) onNewsResourceViewed(resource.id) }, onToggleBookmark = { onNewsResourcesCheckedChanged(resource.id, !resource.isSaved) }, onTopicClick = onTopicClick, modifier = Modifier.padding(horizontal = 8.dp).animateItem(), ) } } } // Use in a screen composable: LazyVerticalStaggeredGrid(columns = StaggeredGridCells.Adaptive(300.dp)) { newsFeed( feedState = viewModel.feedState.collectAsStateWithLifecycle().value, onNewsResourcesCheckedChanged = viewModel::updateNewsResourceSaved, onNewsResourceViewed = { viewModel.setNewsResourceViewed(it, true) }, onTopicClick = { navigator.navigate(TopicNavKey(it)) }, ) } ``` -------------------------------- ### Network Data Source Interface Source: https://context7.com/android/nowinandroid/llms.txt Defines the interface for fetching data from the network. It specifies methods for retrieving topics and news resources, with optional ID filtering. ```kotlin interface NiaNetworkDataSource { suspend fun getTopics(ids: List? = null): List suspend fun getNewsResources(ids: List? = null): List suspend fun getTopicChangeList(after: Int? = null): List suspend fun getNewsResourceChangeList(after: Int? = null): List } ``` -------------------------------- ### Module Dependency Graph Legend Source: https://github.com/android/nowinandroid/blob/main/app/README.md Defines the visual styles for different module types in the dependency graph. Used for Mermaid syntax highlighting. ```mermaid classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000; classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000; classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000; classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000; classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000; classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000; ``` -------------------------------- ### RetrofitNiaNetwork Source: https://context7.com/android/nowinandroid/llms.txt Production implementation of NiaNetworkDataSource using Retrofit for network requests. ```APIDOC ## RetrofitNiaNetwork ### Description Retrofit implementation of the `NiaNetworkDataSource` interface. It makes network calls to the backend API. ### Methods - `getNewsResources(ids: List? = null)`: Fetches news resources from the network, optionally filtered by IDs. Returns `List`. ### Endpoint Details (Internal Retrofit API) - `GET /newsresources`: Retrieves news resources. Accepts an optional `id` query parameter for filtering. ``` -------------------------------- ### Obtain User Data Stream Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md Retrieves a stream of UserData objects from a local data source backed by Proto DataStore. This is part of the data fetching process. ```kotlin NiaPreferencesDataSource.userData ``` -------------------------------- ### UserData Data Model Source: https://context7.com/android/nowinandroid/llms.txt Stores persisted user preferences, including bookmarked and viewed news resources, followed topics, and theme configurations. Stored in Proto DataStore. ```kotlin data class UserData( val bookmarkedNewsResources: Set, val viewedNewsResources: Set, val followedTopics: Set, val themeBrand: ThemeBrand, // DEFAULT | ANDROID val darkThemeConfig: DarkThemeConfig, // FOLLOW_SYSTEM | LIGHT | DARK val useDynamicColor: Boolean, val shouldHideOnboarding: Boolean, ) ``` -------------------------------- ### Topic Data Model Source: https://context7.com/android/nowinandroid/llms.txt Represents a content category or tag used for organizing news resources. Includes descriptive information and an image URL. ```kotlin data class Topic( val id: String, val name: String, val shortDescription: String, val longDescription: String, val url: String, val imageUrl: String, ) ``` -------------------------------- ### Result and Flow.asResult() for State Management Source: https://context7.com/android/nowinandroid/llms.txt Defines a sealed interface for Result states (Loading, Success, Error) and a Flow extension to wrap cold Flows with these states. Useful for managing asynchronous data loading in UI. ```kotlin sealed interface Result { data class Success(val data: T) : Result data class Error(val exception: Throwable) : Result data object Loading : Result } fun Flow.asResult(): Flow> = map> { Result.Success(it) } .onStart { emit(Result.Loading) } .catch { emit(Result.Error(it)) } ``` ```kotlin val uiState: StateFlow>> = topicsRepository.getTopics() .asResult() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), Result.Loading) ``` ```kotlin when (val state = uiState.collectAsStateWithLifecycle().value) { is Result.Loading -> CircularProgressIndicator() is Result.Success -> TopicList(state.data) is Result.Error -> ErrorScreen(state.exception.message) } ``` -------------------------------- ### NiaButton: Styled Buttons Source: https://context7.com/android/nowinandroid/llms.txt Provides styled buttons including filled, outlined, and text-only variants. Supports optional leading icons for filled buttons. ```kotlin // core/designsystem — Button.kt // Filled button with optional leading icon NiaButton( onClick = { /* save action */ }, text = { Text("Save") }, leadingIcon = { Icon(NiaIcons.BookmarkBorder, contentDescription = null) }, ) // Outlined button NiaOutlinedButton( onClick = { /* dismiss */ }, enabled = canDismiss, text = { Text("Done") }, ) // Text-only button NiaTextButton( onClick = { navController.popBackStack() }, text = { Text("Cancel") }, ) ``` -------------------------------- ### Transform Database Model to Public Model Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md OfflineFirstNewsRepository acts as an intermediate operator, transforming internal database models (PopulatedNewsResource) to the public NewsResource model used by other layers. ```kotlin OfflineFirstNewsRepository.getNewsResources ``` -------------------------------- ### Navigator: Programmatic Navigation Source: https://context7.com/android/nowinandroid/llms.txt Handles programmatic navigation using Jetpack Navigation 3 and a custom NavigationState. Supports navigating to any key, clearing sub-stacks, and switching top-level destinations. ```kotlin // core/navigation — Navigator.kt class Navigator(val state: NavigationState) { // Navigate to any key; handles top-level re-selection (clears sub-stack) fun navigate(key: NavKey) { when (key) { state.currentTopLevelKey -> clearSubStack() // re-tap tab → scroll to top in state.topLevelKeys -> goToTopLevel(key) // switch top-level destination else -> goToKey(key) // push detail destination } } fun goBack() { /* pops sub-stack or top-level stack */ } } // Feature modules define NavKey data classes in their :api module: // feature/topic/api @Serializable data class TopicNavKey(val topicId: String) : NavKey // Navigate from Interests → Topic detail: navigator.navigate(TopicNavKey(topicId = "compose")) ``` -------------------------------- ### changeListSync — Generic Sync Utility Source: https://context7.com/android/nowinandroid/llms.txt A generic utility function for change-list synchronization. It handles version reading, fetching changes, updating versions, deleting models, and updating models. Reused for topics sync. ```kotlin suspend fun Synchronizer.changeListSync( versionReader: (ChangeListVersions) -> Int, changeListFetcher: suspend (Int) -> List, versionUpdater: ChangeListVersions.(Int) -> ChangeListVersions, modelDeleter: suspend (List) -> Unit, modelUpdater: suspend (List) -> Unit, ): Boolean // returns true on success, false on error ``` ```kotlin synchronizer.changeListSync( versionReader = ChangeListVersions::topicVersion, changeListFetcher = { network.getTopicChangeList(after = it) }, versionUpdater = { copy(topicVersion = it) }, modelDeleter = topicDao::deleteTopics, modelUpdater = { ids -> val topics = network.getTopics(ids = ids) topicDao.upsertTopics(topics.map(NetworkTopic::asEntity)) }, ) ``` -------------------------------- ### Follow a Topic Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md Use the `UserDataRepository.toggleFollowedTopicId` suspend function to follow or unfollow a topic. Pass the topic ID and a boolean indicating the desired follow state. ```kotlin UserDataRepository.toggleFollowedTopicId(topicId = "1", followed = true) ``` -------------------------------- ### NewsResource Data Model Source: https://context7.com/android/nowinandroid/llms.txt Represents a single content unit (article, video, link) in the Now in Android feed. Includes basic information and associated topics. ```kotlin data class NewsResource( val id: String, val title: String, val content: String, val url: String, val headerImageUrl: String?, val publishDate: Instant, // kotlinx-datetime val type: String, // "Video", "Article", etc. val topics: List, // associated topic tags ) ``` -------------------------------- ### NiaTheme: Material 3 App Theme Source: https://context7.com/android/nowinandroid/llms.txt Configures the app's theme, supporting dynamic color, Android green palette, default schemes, and dark/light modes. Wrap any composable preview or screen with this theme. ```kotlin import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext // core/designsystem — Theme.kt @Composable fun NiaTheme( darkTheme: Boolean = isSystemInDarkTheme(), androidTheme: Boolean = false, // use Android green palette disableDynamicTheming: Boolean = true, // opt out of Material You dynamic color content: @Composable () -> Unit, ) { val colorScheme = when { androidTheme -> if (darkTheme) DarkAndroidColorScheme else LightAndroidColorScheme !disableDynamicTheming && supportsDynamicTheming() -> if (darkTheme) dynamicDarkColorScheme(LocalContext.current) else dynamicLightColorScheme(LocalContext.current) else -> if (darkTheme) DarkDefaultColorScheme else LightDefaultColorScheme } MaterialTheme(colorScheme = colorScheme, typography = NiaTypography, content = content) } // Wrap any composable preview or screen: NiaTheme(androidTheme = true, darkTheme = false) { NiaButton(onClick = {}, text = { Text("Follow") }) } // Check dynamic color support at runtime fun supportsDynamicTheming(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ``` -------------------------------- ### Emit News Resources from DAO Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md When data changes in the NewsResourceDao, it emits into a Flow. This is how the data layer signals updates to consumers. ```kotlin NewsResourceDao.getNewsResources ``` -------------------------------- ### NiaNetworkDataSource Source: https://context7.com/android/nowinandroid/llms.txt Interface for fetching data from the network layer. ```APIDOC ## NiaNetworkDataSource ### Description Interface defining network data fetching operations. ### Methods - `getTopics(ids: List? = null)`: Suspend function to retrieve topics, optionally filtered by IDs. - `getNewsResources(ids: List? = null)`: Suspend function to retrieve news resources, optionally filtered by IDs. - `getTopicChangeList(after: Int? = null)`: Suspend function to get a list of topic changes after a specific point. - `getNewsResourcesChangeList(after: Int? = null)`: Suspend function to get a list of news resource changes after a specific point. ``` -------------------------------- ### Execute Sync Job with Worker Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md The SyncWorker's doWork function is executed by WorkManager to initiate data synchronization. This triggers the OfflineFirstNewsRepository. ```kotlin SyncWorker.doWork ``` -------------------------------- ### Make API Request with Retrofit Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md RetrofitNiaNetwork uses Retrofit to execute the API request to the remote server. This is the network layer's responsibility for fetching data. ```kotlin RetrofitNiaNetwork.getNewsResources ``` -------------------------------- ### Dependency Graph Legend Source: https://github.com/android/nowinandroid/blob/main/lint/README.md Provides a legend for interpreting the module dependency graph. It defines the visual representation for different module types. ```mermaid graph TB application[application]:::android-application feature[feature]:::android-feature library[library]:::android-library jvm[jvm]:::jvm-library application -.-> feature library --> jvm classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000; classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000; classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000; classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000; classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000; classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000; ``` -------------------------------- ### GetSearchContentsUseCase for Full-Text Search Source: https://context7.com/android/nowinandroid/llms.txt Combines Room FTS results with user data to return UserSearchResult, enabling full-text search functionality. Filters search queries shorter than 2 characters. ```kotlin class GetSearchContentsUseCase @Inject constructor( private val searchContentsRepository: SearchContentsRepository, private val userDataRepository: UserDataRepository, ) { operator fun invoke(searchQuery: String): Flow = searchContentsRepository.searchContents(searchQuery) .combine(userDataRepository.userData) { result, userData -> UserSearchResult( topics = result.topics.map { FollowableTopic(it, it.id in userData.followedTopics) }, newsResources = result.newsResources.map { UserNewsResource(it, userData) }, ) } } ``` ```kotlin // Usage in SearchViewModel val searchResultUiState: StateFlow = searchQuery .flatMapLatest { query -> if (query.trim().length < 2) flowOf(SearchResultUiState.EmptyQuery) else getSearchContentsUseCase(query).map { SearchResultUiState.Success(it.topics, it.newsResources) } } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), SearchResultUiState.Loading) ``` -------------------------------- ### Update Feed State to Success Source: https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md When the ForYouViewModel receives saveable news resources, it updates the feed state to Success. The ForYouScreen then uses this state to render the UI. ```kotlin NewsFeedUiState.Success ``` -------------------------------- ### NiaDispatchers for Coroutine Dispatcher Injection Source: https://context7.com/android/nowinandroid/llms.txt Custom qualifier and enum for injecting specific CoroutineDispatchers (Default, IO) using Hilt. Useful for managing background threads in repositories and other classes. ```kotlin // core/common — NiaDispatchers.kt @Qualifier @Retention(RUNTIME) annotation class Dispatcher(val niaDispatcher: NiaDispatchers) enum class NiaDispatchers { Default, IO } // Inject a specific dispatcher in any class: class MyRepository @Inject constructor( @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, ) { suspend fun heavyIoWork() = withContext(ioDispatcher) { /* ... */ } } // In tests, replace with TestDispatcherRule / UnconfinedTestDispatcher via Hilt test modules. ``` -------------------------------- ### NewsResourceDao — News Resource CRUD Operations Source: https://context7.com/android/nowinandroid/llms.txt Data Access Object for managing NewsResource entities in the Room database. Supports reactive queries emitting on DB changes, upserting, and deleting news resources. ```kotlin @Dao interface NewsResourceDao { // Reactive query – emits on every DB change, filtered by optional topic/id sets @Transaction @Query(""" SELECT * FROM news_resources WHERE CASE WHEN :useFilterNewsIds THEN id IN (:filterNewsIds) ELSE 1 END AND CASE WHEN :useFilterTopicIds THEN id IN (SELECT news_resource_id FROM news_resources_topics WHERE topic_id IN (:filterTopicIds)) ELSE 1 END ORDER BY publish_date DESC """) fun getNewsResources( useFilterTopicIds: Boolean = false, filterTopicIds: Set = emptySet(), useFilterNewsIds: Boolean = false, filterNewsIds: Set = emptySet(), ): Flow> @Upsert suspend fun upsertNewsResources(newsResourceEntities: List) @Query("DELETE FROM news_resources WHERE id in (:ids)") suspend fun deleteNewsResources(ids: List) } ``` -------------------------------- ### NewsResourceQuery Data Class Source: https://context7.com/android/nowinandroid/llms.txt A query object used to filter news resources by topic IDs or specific news IDs. ```kotlin data class NewsResourceQuery( val filterTopicIds: Set? = null, // null = no filter val filterNewsIds: Set? = null, ) ``` -------------------------------- ### OfflineFirstNewsRepository.syncWith — Change-list Delta Sync Source: https://context7.com/android/nowinandroid/llms.txt Fetches changed or deleted IDs from the server for delta synchronization. Upserts into Room and fires notifications for new items relevant to followed topics. Fetches in batches of 40 to limit payload size. ```kotlin override suspend fun syncWith(synchronizer: Synchronizer): Boolean = synchronizer.changeListSync( versionReader = ChangeListVersions::newsResourceVersion, changeListFetcher = { currentVersion -> network.getNewsResourceChangeList(after = currentVersion) }, versionUpdater = { latestVersion -> copy(newsResourceVersion = latestVersion) }, modelDeleter = newsResourceDao::deleteNewsResources, modelUpdater = { // Fetch in batches of 40 to limit payload size changedIds.chunked(SYNC_BATCH_SIZE).forEach { val networkItems = network.getNewsResources(ids = chunkedIds) topicDao.insertOrIgnoreTopics(networkItems.map(NetworkNewsResource::topicEntityShells).flatten()) newsResourceDao.upsertNewsResources(networkItems.map(NetworkNewsResource::asEntity)) newsResourceDao.insertOrIgnoreTopicCrossRefEntities( networkItems.map(NetworkNewsResource::topicCrossReferences).flatten() ) } // Post system notifications for newly added items on followed topics notifier.postNewsNotifications(addedNewsResources) }, ) ```