Skip to content

Repository files navigation

Smart Feed (Android Architecture Showcase)

Android CI License Decompose Kotlin

Smart Feed is a showcase Android application demonstrating modern, production-grade architectural patterns. It features a modular, offline-first article feed, dynamic filtering/sorting, and an on-device recommendation engine powered by text embeddings.

A reference project demonstrating scalable Android architecture. The core focus is on building predictable systems using Feature-Driven Vertical Slices with strict modular boundaries (API / Local / Impl). It utilizes Decompose for lifecycle-aware navigation, while architectural constraints and code quality are automatically enforced via Konsist, Detekt 2, and Spotless.

🎥 Demo

Demo


🏗 Architecture & Design Highlights

The project is structured under Clean Architecture guidelines with Feature-Driven Vertical Slice decomposition and Component-Driven UI navigation:

  1. Vertical Feature Slices: Each feature owns its full stack — domain contracts (:api), Room entities and DAOs (:local), and all UI and infrastructure implementations (:impl). This eliminates the "horizontal monolith" anti-pattern.
    app
     └─ feed:impl
         ├─ feed:api
         ├─ feed:local
         ├─ recommendation:api
         └─ core contracts
    
    api is the stable feature contract. local owns the Room schema without circular dependencies. impl contains UI, Store, repositories, and Android-specific integrations.
  2. 3-Module Feature Structure: The :local module is a deliberate architectural solution to prevent circular Gradle dependencies caused by Room's @Database entity registration requirement. See Architecture Documentation.
  3. Decompose Navigation: Pure Kotlin component tree controlling lifecycle, state preservation, and back-stack handling — navigation and state ownership are decoupled from Android UI implementations. See ADR 0001.
  4. Executable Architecture Guards (Konsist): A dedicated :architecture-tests JVM module enforces module boundary rules on every CI build — preventing domain leakage, platform imports in API modules, and naming violations.
  5. Consolidated Gradle Build-Logic: Modern composite build-logic eliminating old buildSrc. Convention plugins handle per-module Detekt profiles, Spotless formatting, and toolchain configuration.
  6. Compose-Only UI Runtime: The production UI path is now fully Jetpack Compose while retaining Decompose as the navigation/state backbone. The earlier XML-to-Compose island phase remains documented as the migration path and benchmark baseline. See ADR 0002 and Performance Results.

For a complete breakdown, see the Architecture Documentation.


🛠 Tech Stack

Layer Technologies
Language Kotlin 2.3.21, JVM 17 target
Build Android Gradle Plugin 9.2.1, Gradle 9.4.1, KSP 2.3.9, composite build-logic
Navigation Decompose 3.3.0 with Compose rendering (extensions-compose)
State MVIKotlin 4.2.0 for complex Feed components; simple coordinators remain Decompose components
Database Room 2.7.1 with float-array embedding converters, per-feature entity/DAO modules (:local), Paging 3 (PagingData, GetPagedContentUseCase) owned by :feature:feed:impl
Background WorkManager with Hilt worker scheduling
DI Dagger Hilt 2.60 (assisted factories, interface binds, per-feature Hilt modules)
Images Coil 3 for Compose UI plus a pure Kotlin ImageLoader contract (:core:image:api) for shared/background image work
Static Lint Detekt 2.0.0-alpha.5 (layered profiles), Spotless 6.25.0 / Ktlint
Arch Testing Konsist 0.17.3 — executable architecture guards
Networking Retrofit + OkHttp, Ktor local mock server for dev flavour

🧠 On-Device Recommendation Engine

Smart Feed includes a fully local recommendation pipeline based on article text embeddings. No reading behavior leaves the device.

User reads article
      ↓
engagementWeight = 0.5×readPercentage + 0.5×normalizedTime
      ↓
Weighted moving average updates user interest vector
(old_vec×visitCount + article_vec×weight) / (visitCount+1)
      ↓
Article embeddings (FloatArray, unit-norm) loaded from Room
Already-read articles fully excluded
      ↓
top-K candidates (highest cosine similarity to user vector)
cold-K candidates (opposite vector → diversity / serendipity)
      ↓
MMR diversification: λ×sim(candidate,profile) − (1−λ)×max_sim_to_selected
      ↓
Ranked recommendations persisted to Room → displayed in feed

The algorithm lives entirely in :feature:recommendation:implisolated, independently testable, and offline-first. Room schema is owned by :feature:recommendation:local. Public contracts (use cases, repository interfaces) live in :feature:recommendation:api.

For full details, see Recommendation Engine.


📁 Project Structure

smart-feed/
├── app/                    # Composition root (Hilt, AppBootstrapper, MainActivity)
├── architecture-tests/     # Konsist architecture enforcement tests (pure JVM, no Android)
├── build-logic/            # Convention plugins: toolchain, Detekt, Spotless, feature config
├── config/detekt/          # Layered Detekt rule profiles (domain, data, ui, test, common)
├── core/
│   ├── common/             # Pure Kotlin shared utilities: coroutine helpers, embedding math,
│   │                       #   time converters (formerly :core:core → renamed)
│   ├── analytics/
│   │   ├── api/            #   AnalyticsService interface (pure Kotlin)
│   │   └── impl/           #   Analytics implementation
│   ├── connectivity/       # ConnectivityRepository (network state, modern observer-based)
│   ├── content/api/        # Shared content value objects used across features
│   ├── core-database/      # RoomDatabase orchestrator, cross-feature schema migrations
│   ├── core-networks/      # Retrofit/Ktor config, prod & dev network data sources
│   ├── coroutines/         # Coroutine Dispatchers DI module
│   ├── image/api/          # Pure Kotlin ImageLoader contract (KMP-portable)
│   └── lifecycle/          # AppLifecycleObserver
├── docs/
│   ├── adr/                # Architectural Decision Records
│   └── plans/              # Implementation and refactoring plans
└── feature/
    ├── feed/               # Article feed — full vertical slice
    │   ├── api/            #   Component contracts, ContentItem domain model, repository API
    │   ├── local/          #   ContentEntity, ContentDao (feed-owned Room storage)
    │   └── impl/           #   Compose UI, Hilt modules, repository impls,
    │                       #   Paging 3 (ContentPagingRepository, GetPagedContentUseCase)
    ├── recommendation/     # Recommendation engine — full vertical slice
    │   ├── api/            #   Recommendation contracts, models (Recommendation, Recommender,
    │   │                   #   RecommendationRepository, RecommendForUserUseCase, RecommendForArticleUseCase)
    │   ├── local/          #   Room schema: ArticleEmbedding, ArticleEmbeddingDao,
    │   │                   #   ContentInteractionStats, ContentInteractionStatsDao,
    │   │                   #   UserRecommendationEntity, ContentRecommendationEntity, RecommendationDao
    │   └── impl/           #   RecommenderImpl (cosine similarity + MMR + cold-picks),
    │                       #   EmbeddingIndex, RecommendationRepositoryImpl, Hilt modules
    └── userprofile/        # User identity — vertical slice
        ├── api/            #   UserProfile model, UserProfileRepository contract
        ├── impl/           #   Repository impl, Hilt module
        └── local/          #   User profile Room storage

📅 Modernization Roadmap

Phase Description Status
0 Stash WIP (create-post-wip), isolate polish steps ✅ Done
1 Architecture docs, ADRs, diagrams ✅ Done
2 Build logic consolidation (buildSrcbuild-logic), AGP 9.2.1, Kotlin 2.3.21, KSP 2.3.9, Detekt 2 ✅ Done
3 Konsist architecture enforcement module (:architecture-tests) ✅ Done
4 MainActivity decoupling — AppStartupCoordinator, SystemBarsController, Compose app shell ✅ Done
5 AndroidX Paging dependency inversion — extracted to :core:core-paging, then co-located into :feature:feed:impl (sole consumer) ✅ Done
6 Feature API/Impl split — :feature:feed:api and :feature:feed:impl ✅ Done
7 Core Layer Modularization — 3-module feature slices (api/local/impl), :core:core:core:common, eliminated core-domain / core-data / core-paging monoliths, build noise cleanup Done
8 MVIKotlin stores for Feed List, Recommendations, Article, and Article Recommendations with reducer/component tests ✅ Done
9 Parallel Compose ArticleCard track with XML parity, performance comparison, and Baseline Profiles ✅ Done
10 Full Jetpack Compose migration of remaining screens and navigation ✅ Done
11 Image Prefetching and migration to Coil for Compose 📝 Planned

📦 Local Development

  1. Clone the repository:
    git clone https://github.com/nikkiw/smart-feed.git
    cd smart-feed
  2. Run the full quality gate locally:
    # Auto-format code
    ./gradlew spotlessApply
    # Static analysis (layered Detekt profiles)
    ./gradlew detekt
    # Architecture consistency tests (Konsist)
    ./gradlew :architecture-tests:test
    # All unit tests
    ./gradlew test
    # Assemble developer debug build
    ./gradlew assembleDevDebug
  3. Full CI verification matrix in one command:
    ./gradlew spotlessCheck detekt :architecture-tests:test test

📄 License

Licensed under the Apache License 2.0. See LICENSE for details.


🙋‍♂️ Author

Made by Nikolay Vlasov – Android Architect.

About

Android architecture showcase: Decompose, MVIKotlin, vertical feature slices, Room/Paging and on-device recommendations.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

Contributors

Languages