nowinandroid Documentation

A fully functional Android app built entirely with Kotlin and Jetpack Compose

21,933 stars Kotlin Kotlin 1 file ยท ~20,960 tokens #android#jetpack-compose#kotlin#sample
RAW Doc

Technical Documentation: android/nowinandroid

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`README.md`)

Now in Android App

Learn how this app was designed and built in the design case study, architecture learning journey and modularization learning journey.

This is the repository for the Now in Android
app. It is a work in progress ๐Ÿšง.

Now in Android is a fully functional Android app built entirely with Kotlin and Jetpack Compose. It
follows Android design and development best practices and is intended to be a useful reference
for developers. As a running app, it's intended to help developers keep up-to-date with the world
of Android development by providing regular news updates.

The app is currently in development. The prodRelease variant is available on the Play Store.

Features

Now in Android displays content from the
Now in Android series. Users can browse for
links to recent videos, articles and other content. Users can also follow topics they are interested
in, and be notified when new content is published which matches interests they are following.

Screenshots

Development Environment

Now in Android uses the Gradle build system and can be imported directly into Android Studio (make sure you are using the latest stable version available here).

Change the run configuration to app.

The demoDebug and demoRelease build variants can be built and run (the prod variants use a backend server which is not currently publicly available).

Once you're up and running, you can refer to the learning journeys below to get a better
understanding of which libraries and tools are being used, the reasoning behind the approaches to
UI, testing, architecture and more, and how all of these different pieces of the project fit
together to create a complete app.

Architecture

The Now in Android app follows the
official architecture guidance
and is described in detail in the
architecture learning journey.

Modularization

The Now in Android app has been fully modularized and you can find the detailed guidance and
description of the modularization strategy used in
modularization learning journey.

Build

The app contains the usual debug and release build variants.

In addition, the benchmark variant of app is used to test startup performance and generate a
baseline profile (see below for more information).

app-nia-catalog is a standalone app that displays the list of components that are stylized for
Now in Android.

The app also uses
product flavors to
control where content for the app should be loaded from.

The demo flavor uses static local data to allow immediate building and exploring of the UI.

The prod flavor makes real network calls to a backend server, providing up-to-date content. At
this time, there is not a public backend available.

For normal development use the demoDebug variant. For UI performance testing use the
demoRelease variant.

Testing

To facilitate testing of components, Now in Android uses dependency injection with
Hilt.

Most data layer components are defined as interfaces.
Then, concrete implementations (with various dependencies) are bound to provide those interfaces to
other components in the app.
In tests, Now in Android notably does not use any mocking libraries.
Instead, the production implementations can be replaced with test doubles using Hilt's testing APIs
(or via manual constructor injection for ViewModel tests).

These test doubles implement the same interface as the production implementations and generally
provide a simplified (but still realistic) implementation with additional testing hooks.
This results in less brittle tests that may exercise more production code, instead of just verifying
specific calls against mocks.

Examples:

  • In instrumentation tests, a temporary folder is used to store the user's preferences, which is
    wiped after each test.
    This allows using the real DataStore and exercising all related code, instead of mocking the
    flow of data updates.

  • There are Test implementations of each repository, which implement the normal, full repository
    interface and also provide test-only hooks.
    ViewModel tests use these Test repositories, and thus can use the test-only hooks to
    manipulate the state of the Test repository and verify the resulting behavior, instead of
    checking that specific repository methods were called.

To run the tests execute the following gradle tasks:

  • testDemoDebug run all local tests against the demoDebug variant. Screenshot tests will fail
    (see below for explanation). To avoid this, run recordRoborazziDemoDebug prior to running unit tests.
  • connectedDemoDebugAndroidTest run all instrumented tests against the demoDebug variant.

[!NOTE]
You should not run ./gradlew test or ./gradlew connectedAndroidTest as this will execute
tests against all build variants which is both unnecessary and will result in failures as only the
demoDebug variant is supported. No other variants have any tests (although this might change in future).

Screenshot tests

A screenshot test takes a screenshot of a screen or a UI component within the app, and compares it
with a previously recorded screenshot which is known to be rendered correctly.

For example, Now in Android has screenshot tests
to verify that the navigation is displayed correctly on different screen sizes
(known correct screenshots).

Now In Android uses Roborazzi to run screenshot tests
of certain screens and UI components. When working with screenshot tests the following gradle tasks are useful:

  • verifyRoborazziDemoDebug run all screenshot tests, verifying the screenshots against the known
    correct screenshots.
  • recordRoborazziDemoDebug record new "known correct" screenshots. Use this command when you have
    made changes to the UI and manually verified that they are rendered correctly. Screenshots will be
    stored in modulename/src/test/screenshots.
  • compareRoborazziDemoDebug create comparison images between failed tests and the known correct
    images. These can also be found in modulename/src/test/screenshots.

[!NOTE]
Note on failing screenshot tests
The known correct screenshots stored in this repository are recorded on CI using Linux. Other
platforms may (and probably will) generate slightly different images, making the screenshot tests fail.
When working on a non-Linux platform, a workaround to this is to run recordRoborazziDemoDebug on the
main branch before starting work. After making changes, verifyRoborazziDemoDebug will identify only
legitimate changes.

For more information about screenshot testing
check out this talk.

UI

The app was designed using Material 3 guidelines. Learn more about the design process and
obtain the design files in the Now in Android Material 3 Case Study (design assets also available as a PDF).

The Screens and UI elements are built entirely using Jetpack Compose.

The app has two themes:

  • Dynamic color - uses colors based on the user's current color theme (if supported)
  • Default theme - uses predefined colors when dynamic color is not supported

Each theme also supports dark mode.

The app uses adaptive layouts to
support different screen sizes.

Find out more about the UI architecture here.

Performance

Benchmarks

Find all tests written using Macrobenchmark
in the benchmarks module. This module also contains the test to generate the Baseline profile.

Baseline profiles

The baseline profile for this app is located at app/src/main/baseline-prof.txt.
It contains rules that enable AOT compilation of the critical user path taken during app launch.
For more information on baseline profiles, read this document.

[!NOTE]
The baseline profile needs to be re-generated for release builds that touch code which changes app startup.

To generate the baseline profile, select the benchmark build variant and run the
BaselineProfileGenerator benchmark test on an AOSP Android Emulator.
Then copy the resulting baseline profile from the emulator to app/src/main/baseline-prof.txt.

Compose compiler metrics

Run the following command to get and analyze compose compiler metrics:

bash
./gradlew assembleRelease -PenableComposeCompilerMetrics=true -PenableComposeCompilerReports=true

The reports files will be added to build/compose-reports. The metrics files will also be
added to build/compose-metrics.

For more information on Compose compiler metrics, see this blog post.

License

Now in Android is distributed under the terms of the Apache License (Version 2.0). See the
license for more information.


Overview (`app-nia-catalog/README.md`)

`:app-nia-catalog`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:analytics[analytics]:::android-library
    :core:designsystem[designsystem]:::android-library
    :core:model[model]:::jvm-library
    :core:ui[ui]:::android-library
  end
  :app-nia-catalog[app-nia-catalog]:::android-application

  :app-nia-catalog -.-> :core:designsystem
  :app-nia-catalog -.-> :core:ui
  :core:ui --> :core:analytics
  :core:ui --> :core:designsystem
  :core:ui --> :core:model

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;

๐Ÿ“‹ Graph legend

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;

Overview (`app/README.md`)

`:app`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`benchmarks/README.md`)

`:benchmarks`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`build-logic/README.md`)

Convention Plugins

The build-logic folder defines project-specific convention plugins, used to keep a single
source of truth for common module configurations.

This approach is heavily based on
https://developer.squareup.com/blog/herding-elephants/
and
https://github.com/jjohannes/idiomatic-gradle.

By setting up convention plugins in build-logic, we can avoid duplicated build script setup,
messy subproject configurations, without the pitfalls of the buildSrc directory.

build-logic is an included build, as configured in the root
settings.gradle.kts.

Inside build-logic is a convention module, which defines a set of plugins that all normal
modules can use to configure themselves.

build-logic also includes a set of Kotlin files used to share logic between plugins themselves,
which is most useful for configuring Android components (libraries vs applications) with shared
code.

These plugins are additive and composable, and try to only accomplish a single responsibility.
Modules can then pick and choose the configurations they need.
If there is one-off logic for a module without shared code, it's preferable to define that directly
in the module's build.gradle, as opposed to creating a convention plugin with module-specific
setup.

Current list of convention plugins:


Overview (`lint/README.md`)

`:lint`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
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;

๐Ÿ“‹ Graph legend

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;

Overview (`ui-test-hilt-manifest/README.md`)

`:ui-test-hilt-manifest`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  :ui-test-hilt-manifest[ui-test-hilt-manifest]:::android-library

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/analytics/README.md`)

`:core:analytics`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:analytics[analytics]:::android-library
  end

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/common/README.md`)

`:core:common`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:common[common]:::jvm-library
  end

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/data-test/README.md`)

`:core:data-test`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`core/data/README.md`)

`:core:data`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`core/database/README.md`)

`:core:database`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:database[database]:::android-library
    :core:model[model]:::jvm-library
  end

  :core:database --> :core:model

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/datastore-proto/README.md`)

`:core:datastore-proto`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:datastore-proto[datastore-proto]:::jvm-library
  end

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/datastore-test/README.md`)

`:core:datastore-test`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:common[common]:::jvm-library
    :core:datastore[datastore]:::android-library
    :core:datastore-proto[datastore-proto]:::jvm-library
    :core:datastore-test[datastore-test]:::android-library
    :core:model[model]:::jvm-library
  end

  :core:datastore -.-> :core:common
  :core:datastore --> :core:datastore-proto
  :core:datastore --> :core:model
  :core:datastore-test -.-> :core:common
  :core:datastore-test -.-> :core:datastore

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/datastore/README.md`)

`:core:datastore`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:common[common]:::jvm-library
    :core:datastore[datastore]:::android-library
    :core:datastore-proto[datastore-proto]:::jvm-library
    :core:model[model]:::jvm-library
  end

  :core:datastore -.-> :core:common
  :core:datastore --> :core:datastore-proto
  :core:datastore --> :core:model

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/designsystem/README.md`)

`:core:designsystem`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:designsystem[designsystem]:::android-library
  end

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/domain/README.md`)

`:core:domain`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`core/model/README.md`)

`:core:model`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:model[model]:::jvm-library
  end

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/navigation/README.md`)

`:core:navigation`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:navigation[navigation]:::android-library
  end

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/network/README.md`)

`:core:network`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:common[common]:::jvm-library
    :core:model[model]:::jvm-library
    :core:network[network]:::android-library
  end

  :core:network --> :core:common
  :core:network --> :core:model

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/notifications/README.md`)

`:core:notifications`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:common[common]:::jvm-library
    :core:model[model]:::jvm-library
    :core:notifications[notifications]:::android-library
  end

  :core:notifications -.-> :core:common
  :core:notifications --> :core:model

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/screenshot-testing/README.md`)

`:core:screenshot-testing`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:designsystem[designsystem]:::android-library
    :core:screenshot-testing[screenshot-testing]:::android-library
  end

  :core:screenshot-testing -.-> :core:designsystem

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;

๐Ÿ“‹ Graph legend

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;

Overview (`core/testing/README.md`)

`:core:testing`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`core/ui/README.md`)

`:core:ui`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :core
    direction TB
    :core:analytics[analytics]:::android-library
    :core:designsystem[designsystem]:::android-library
    :core:model[model]:::jvm-library
    :core:ui[ui]:::android-library
  end

  :core:ui --> :core:analytics
  :core:ui --> :core:designsystem
  :core:ui --> :core:model

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;

๐Ÿ“‹ Graph legend

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;

Overview (`sync/sync-test/README.md`)

`:sync:sync-test`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`sync/work/README.md`)

`:sync:work`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`feature/bookmarks/api/README.md`)

`:feature:bookmarks:api`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :feature
    direction TB
    subgraph :feature:bookmarks
      direction TB
      :feature:bookmarks:api[api]:::android-library
    end
  end
  subgraph :core
    direction TB
    :core:navigation[navigation]:::android-library
  end

  :feature:bookmarks:api --> :core:navigation

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;

๐Ÿ“‹ Graph legend

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;

Overview (`feature/bookmarks/impl/README.md`)

`:feature:bookmarks:impl`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`feature/foryou/api/README.md`)

`:feature:foryou:api`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :feature
    direction TB
    subgraph :feature:foryou
      direction TB
      :feature:foryou:api[api]:::android-library
    end
  end
  subgraph :core
    direction TB
    :core:navigation[navigation]:::android-library
  end

  :feature:foryou:api --> :core:navigation

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;

๐Ÿ“‹ Graph legend

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;

Overview (`feature/foryou/impl/README.md`)

`:feature:foryou:impl`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`feature/interests/api/README.md`)

`:feature:interests:api`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :feature
    direction TB
    subgraph :feature:interests
      direction TB
      :feature:interests:api[api]:::android-library
    end
  end
  subgraph :core
    direction TB
    :core:navigation[navigation]:::android-library
  end

  :feature:interests:api --> :core:navigation

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;

๐Ÿ“‹ Graph legend

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;

Overview (`feature/interests/impl/README.md`)

`:feature:interests:impl`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`feature/search/api/README.md`)

`:feature:search:api`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`feature/search/impl/README.md`)

`:feature:search:impl`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`feature/settings/impl/README.md`)

`:feature:settings:api`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

Overview (`feature/topic/api/README.md`)

`:feature:topic:api`

Module dependency graph

mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :feature
    direction TB
    subgraph :feature:topic
      direction TB
      :feature:topic:api[api]:::android-library
    end
  end
  subgraph :core
    direction TB
    :core:analytics[analytics]:::android-library
    :core:designsystem[designsystem]:::android-library
    :core:model[model]:::jvm-library
    :core:navigation[navigation]:::android-library
    :core:ui[ui]:::android-library
  end

  :core:ui --> :core:analytics
  :core:ui --> :core:designsystem
  :core:ui --> :core:model
  :feature:topic:api -.-> :core:designsystem
  :feature:topic:api --> :core:navigation
  :feature:topic:api -.-> :core:ui

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;

๐Ÿ“‹ Graph legend

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;

Overview (`feature/topic/impl/README.md`)

`:feature:topic:impl`

Module dependency graph

text
/* Detailed source-code truncated for AI context efficiency. */

๐Ÿ“‹ Graph legend

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;

ArchitectureLearningJourney (`docs/ArchitectureLearningJourney.md`)

Architecture Learning Journey

In this learning journey you will learn about the Now in Android app architecture: its layers, key classes and the interactions between them.

Goals and requirements

The goals for the app architecture are:

  • Follow the official architecture guidance as closely as possible.
  • Easy for developers to understand, nothing too experimental.
  • Support multiple developers working on the same codebase.
  • Facilitate local and instrumented tests, both on the developerโ€™s machine and using Continuous Integration (CI).
  • Minimize build times.

Architecture overview

The app architecture has three layers: a data layer, a domain layer and a UI layer.

[!NOTE]
The official Android architecture is different from other architectures, such as "Clean Architecture". Concepts from other architectures may not apply here, or be applied in different ways. More discussion here.

The architecture follows a reactive programming model with unidirectional data flow. With the data layer at the bottom, the key concepts are:

  • Higher layers react to changes in lower layers.
  • Events flow down.
  • Data flows up.

The data flow is achieved using streams, implemented using Kotlin Flows.

Example: Displaying news on the For You screen

When the app is first run it will attempt to load a list of news resources from a remote server (when the prod build flavor is selected, demo builds will use local data). Once loaded, these are shown to the user based on the interests they choose.

The following diagram shows the events which occur and how data flows from the relevant objects to achieve this.

Here's what's happening in each step. The easiest way to find the associated code is to load the project into Android Studio and search for the text in the Code column (handy shortcut: tap โ‡ง SHIFT twice).

Step Description **Code **
1 On app startup, a WorkManager job to sync all repositories is enqueued. Sync.initialize
2 The ForYouViewModel calls GetUserNewsResourcesUseCase to obtain a stream of news resources with their bookmarked/saved state. No items will be emitted into this stream until both the user and news repositories emit an item. While waiting, the feed state is set to Loading. Search for usages of NewsFeedUiState.Loading
3 The user data repository obtains a stream of UserData objects from a local data source backed by Proto DataStore. NiaPreferencesDataSource.userData
4 WorkManager executes the sync job which calls OfflineFirstNewsRepository to start synchronizing data with the remote data source. SyncWorker.doWork
5 OfflineFirstNewsRepository calls RetrofitNiaNetwork to execute the actual API request using Retrofit. OfflineFirstNewsRepository.syncWith
6 RetrofitNiaNetwork calls the REST API on the remote server. RetrofitNiaNetwork.getNewsResources
7 RetrofitNiaNetwork receives the network response from the remote server. RetrofitNiaNetwork.getNewsResources
8 OfflineFirstNewsRepository syncs the remote data with NewsResourceDao by inserting, updating or deleting data in a local Room database. OfflineFirstNewsRepository.syncWith
9 When data changes in NewsResourceDao it is emitted into the news resources data stream (which is a Flow). NewsResourceDao.getNewsResources
10 OfflineFirstNewsRepository acts as an intermediate operator on this stream, transforming the incoming PopulatedNewsResource (a database model, internal to the data layer) to the public NewsResource model which is consumed by other layers. OfflineFirstNewsRepository.getNewsResources
11 GetUserNewsResourcesUseCase combines the list of news resources with the user data to emit a list of UserNewsResources. GetUserNewsResourcesUseCase.invoke
12 When ForYouViewModel receives the saveable news resources it updates the feed state to Success.

ForYouScreen then uses the saveable news resources in the state to render the screen. | Search for instances of NewsFeedUiState.Success |

Data layer

The data layer is implemented as an offline-first source of app data and business logic. It is the source of truth for all data in the app.

Each repository has its own models. For example, the TopicsRepository has a Topic model and the NewsRepository has a NewsResource model.

Repositories are the public API for other layers, they provide the only way to access the app data. The repositories typically offer one or more methods for reading and writing data.

Reading data

Data is exposed as data streams. This means each client of the repository must be prepared to react to data changes. Data is not exposed as a snapshot (e.g. getModel) because there's no guarantee that it will still be valid by the time it is used.

Reads are performed from local storage as the source of truth, therefore errors are not expected when reading from Repository instances. However, errors may occur when trying to reconcile data in local storage with remote sources. For more on error reconciliation, check the data synchronization section below.

Example: Read a list of topics

A list of Topics can be obtained by subscribing to TopicsRepository::getTopics flow which emits List<Topic>.

Whenever the list of topics changes (for example, when a new topic is added), the updated List<Topic> is emitted into the stream.

Writing data

To write data, the repository provides suspend functions. It is up to the caller to ensure that their execution is suitably scoped.

Example: Follow a topic

Simply call UserDataRepository.toggleFollowedTopicId with the ID of the topic the user wishes to follow and followed=true to indicate that the topic should be followed (use false to unfollow a topic).

Data sources

A repository may depend on one or more data sources. For example, the OfflineFirstTopicsRepository depends on the following data sources:

Name Backed by Purpose
TopicsDao Room/SQLite Persistent relational data associated with Topics
NiaPreferencesDataSource Proto DataStore Persistent unstructured data associated with user preferences, specifically which Topics the user is interested in. This is defined and modeled in a .proto file, using the protobuf syntax.
NiaNetworkDataSource Remote API accessed using Retrofit Data for topics, provided through REST API endpoints as JSON.

Data synchronization

Repositories are responsible for reconciling data in local storage with remote sources. Once data is obtained from a remote data source it is immediately written to local storage. The updated data is emitted from local storage (Room) into the relevant data stream and received by any listening clients.

This approach ensures that the read and write concerns of the app are separate and do not interfere with each other.

In the case of errors during data synchronization, an exponential backoff strategy is employed. This is delegated to WorkManager via the SyncWorker, an implementation of the Synchronizer interface.

See the OfflineFirstNewsRepository.syncWith for an example of data synchronization.

Domain layer

The domain layer contains use cases. These are classes which have a single invocable method (operator fun invoke) containing business logic.

These use cases are used to simplify and remove duplicate logic from ViewModels. They typically combine and transform data from repositories.

For example, GetUserNewsResourcesUseCase combines a stream (implemented using Flow) of NewsResources from a NewsRepository with a stream of UserData objects from a UserDataRepository to create a stream of UserNewsResources. This stream is used by various ViewModels to display news resources on screen with their bookmarked state.

Notably, the domain layer in Now in Android does not (for now) contain any use cases for event handling. Events are handled by the UI layer calling methods on repositories directly.

UI Layer

The UI layer comprises:

The ViewModels receive streams of data from use cases and repositories, and transforms them into UI state. The UI elements reflect this state, and provide ways for the user to interact with the app. These interactions are passed as events to the ViewModel where they are processed.

Modeling UI state

UI state is modeled as a sealed hierarchy using interfaces and immutable data classes. State objects are only ever emitted through the transform of data streams. This approach ensures that:

  • the UI state always represents the underlying app data - the app data is the source-of-truth.
  • the UI elements handle all possible states.

Example: News feed on For You screen

The feed (a list) of news resources on the For You screen is modeled using NewsFeedUiState. This is a sealed interface which creates a hierarchy of two possible states:

  • Loading indicates that the data is loading
  • Success indicates that the data was loaded successfully. The Success state contains the list of news resources.

The feedState is passed to the ForYouScreen composable, which handles both of these states.

Transforming streams into UI state

ViewModels receive streams of data as cold flows from one or more use cases or repositories. These are combined together, or simply mapped, to produce a single flow of UI state. This single flow is then converted to a hot flow using stateIn. The conversion to a state flow enables UI elements to read the last known state from the flow.

Example: Displaying followed topics

The InterestsViewModel exposes uiState as a StateFlow. This hot flow is created by obtaining the cold flow of List<FollowableTopic> provided by GetFollowableTopicsUseCase. Each time a new list is emitted, it is converted into an InterestsUiState.Interests state which is exposed to the UI.

Processing user interactions

User actions are communicated from UI elements to ViewModels using regular method invocations. These methods are passed to the UI elements as lambda expressions.

Example: Following a topic

The InterestsScreen takes a lambda expression named followTopic which is supplied from InterestsViewModel.followTopic. Each time the user taps on a topic to follow this method is called. The ViewModel then processes this action by informing the user data repository.

Further reading

Guide to app architecture

Jetpack Compose


ModularizationLearningJourney (`docs/ModularizationLearningJourney.md`)

Modularization learning journey

In this learning journey you will learn about the modularization strategy used
to create modules in the Now in Android app. For the theory behind modularization, check out
the official guidance.

IMPORTANT: Every module has a dependency graph in its README (example for the app module) which can be useful for understanding the overall structure of the project.

Module types

mermaid
graph TB
  subgraph :core
    direction TB
    :core:data[data]:::android-library
    :core:database[database]:::android-library
    :core:model[model]:::jvm-library
    :core:network[network]:::android-library
    :core:ui[ui]:::android-library
  end
  subgraph :feature 
    direction TB
    :feature:topic[topic]:::android-feature
    :feature:foryou[foryou]:::android-feature
    :feature:interests[interests]:::android-feature
    :feature:foo[...]:::android-feature

  end
  :app[app]:::android-application

  :app -.-> :feature:foryou
  :app -.-> :feature:interests
  :app -.-> :feature:topic
  :core:data ---> :core:database
  :core:data ---> :core:network
  :core:database ---> :core:model
  :core:network ---> :core:model
  :core:ui ---> :core:model
  :feature:topic -.-> :core:data
  :feature:topic -.-> :core:ui

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 jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;

๐Ÿ“‹ Graph legend

mermaid
graph TB
  application:::android-application -. implementation .-> feature:::android-feature
  library:::android-library -- api --> jvm:::jvm-library

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 jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;

Top tip: A module graph (shown above) can be useful during modularization planning for
visualizing dependencies between modules.

The Now in Android app contains the following types of modules:

The `app` module

This contains app level and scaffolding classes that bind the rest of the codebase, such as
MainActivity, NiaApp and app-level controlled navigation. A good example of this is the navigation setup through NiaNavHost and the bottom navigation bar setup through TopLevelDestination. The app module depends on all feature modules and required core modules.

Feature modules

These are feature-specific modules that handle a single responsibility in the app. For example, the ForYou feature handles all content and UI state for the "ForYou" screen. Feature modules aren't Gradle modules themselves, they are split into two submodules:

  • api - contains navigation keys
  • impl - contains everything else

This approach allows features to navigate to other features by using the target feature's navigation keys. A feature's api and impl modules can be used by any app, including test or other flavoured apps. If a class is needed only by one feature module, it should remain within that module. If not, it should be placed into an appropriate core module.

A feature's api module should not depend on another feature's api or impl module. A feature's impl should only depend on another feature's api module. Both submodules should only depend on the core modules that they require.

Core modules

These are common library modules containing auxiliary code and specific dependencies that
need to be shared between other modules in the app. These modules can depend on other core
modules, but they shouldnโ€™t depend on feature nor app modules.

Miscellaneous modules

For example, sync, benchmark and test modules, as well as app-nia-catalog - a catalog app for displaying our design system quickly.

Examples

Name Responsibilities Key classes and good examples
app Brings everything together required for the app to function correctly. This includes UI scaffolding and navigation. NiaApp, MainActivity
App-level controlled navigation via NiaNavHost, NiaAppState, TopLevelDestination
feature:1:api,
feature:2:api
... Navigation keys and functions that other features can use to navigate to this feature.
For example: The :topic:api module exposes a Navigator.navigateToTopic function that the
:interests:impl module uses to navigate from the InterestsScreen to the TopicScreen when
a topic is clicked. TopicNavKey
feature:1:impl,
feature:2:impl
... Functionality associated with a specific feature or user journey. Typically contains UI components and ViewModels which read data from other modules.
Examples include:
text
[`feature:topic:impl`](https://github.com/android/nowinandroid/tree/main/feature/topic/impl) displays information about a topic on the TopicScreen.
  [`feature:foryou:impl`](https://github.com/android/nowinandroid/tree/main/feature/foryou/impl) which displays the user's news feed, and onboarding during first run, on the For You screen. | `TopicScreen`

TopicViewModel |
| core:data | Fetching app data from multiple sources, shared by different features. | TopicsRepository |
| core:designsystem | Design system which includes Core UI components (many of which are customized Material 3 components), app theme and icons. The design system can be viewed by running the app-nia-catalog run configuration. | NiaIcons NiaButton NiaTheme |
| core:ui | Composite UI components and resources used by feature modules, such as the news feed. Unlike the designsystem module, it is dependent on the data layer since it renders models, like news resources. | NewsFeed NewsResourceCardExpanded |
| core:common | Common classes shared between modules. | NiaDispatchers
Result |
| core:network | Making network requests and handling responses from a remote data source. | RetrofitNiaNetworkApi |
| core:testing | Testing dependencies, repositories and util classes. | NiaTestRunner
TestDispatcherRule |
| core:datastore | Storing persistent data using DataStore. | NiaPreferences
UserPreferencesSerializer |
| core:database | Local database storage using Room. | NiaDatabase
DatabaseMigrations
Dao classes |
| core:model | Model classes used throughout the app. | Topic
Episode
NewsResource |

Dependency graphs

Each module has its own README.md file containing a module graph (e.g. :app module graph).
When modules dependencies change, module graphs are automatically updated by the Build.yaml workflow.
You can also manually update the graphs by running the graphUpdate task.

Further considerations

Our modularization approach was defined taking into account the โ€œNow in Androidโ€ project roadmap, upcoming work and new features. Additionally, our aim this time around was to find the right balance between overmodularizing a relatively small app and using this opportunity to showcase a modularization pattern fit for a much larger codebase, closer to real world apps in production environments.

This approach was discussed with the Android community, and evolved taking their feedback into account. With modularization however, there isnโ€™t one right answer that makes all others wrong. Ultimately, there are many ways and approaches to modularizing an app and rarely does one approach fit all purposes, codebases and team preferences. This is why planning beforehand and taking into account all goals, problems youโ€™re trying to solve, future work and predicting potential stepping stones are all crucial steps for defining the best fit structure under your own, unique circumstances. Developers can benefit from a brainstorming session to draw out a graph of modules and dependencies to visualize and plan this better.

Our approach is such an example - we donโ€™t expect it to be an unchangeable structure applicable to all cases, and in fact, it could evolve and change in the future. Itโ€™s a general guideline we found to be the best fit for our project and offer it as one example you can further modify, expand and build on top of. One way of doing this would be to increase the granularity of the codebase even more. Granularity is the extent to which your codebase is composed of modules. If your data layer is small, itโ€™s fine to keep it in a single module. But once the number of repositories and data sources starts to grow, it might be worth considering splitting them into separate modules.

We are also always open to your constructive feedback - learning from the community and exchanging ideas is one of the key elements to improving our guidance.


--- METRICS ---