Yagni Launcher Architecture

September 7, 2026 · View on GitHub

This document describes how Yagni Launcher's Gradle modules are organized and how they map onto Clean Architecture. It focuses on stable module responsibilities and dependency rules rather than an inventory of classes, which changes far more often than the architecture itself.

This module structure follows The Clean Architecture

Clean Architecture


Table of Contents


Clean Architecture Layers

The codebase is split into five layers:

  1. Domain — Pure Kotlin entities, repository/framework interfaces, use cases, and grid algorithms. No Android SDK imports.
  2. Data — Persistence implementations: Room database, Proto DataStore preferences, and the repositories that combine them.
  3. Framework — Thin wrappers around Android system APIs (PackageManager, LauncherApps, WallpaperManager, AppWidgetManager, etc.), most of them implementing an interface declared in domain:framework.
  4. Presentation — Compose UI and ViewModels: the feature:* screens plus the ui and design-system component libraries.
  5. Android Entry Points — The outermost layer: app and service, the two modules Android itself talks to (the Application/Activity graph and background Service components). They wire every other module together and are the only modules allowed to depend on the whole graph at once.

Dependency Rule

Dependencies only point inward: Android Entry Points depend on Presentation, Framework, Data, and Domain; Presentation depends on Framework and Domain; Data depends on Domain; and Framework depends on Domain. Domain depends on nothing else in the project. No inner layer ever references an outer one.

Module Groups

Domain

Pure Kotlin modules with no Android dependency, so their logic is fully unit-testable in isolation:

ModuleResponsibility
domain:modelEntity and value-object definitions shared by every other layer.
domain:repositoryRepository interfaces describing the data operations the domain needs; implemented by data:repository.
domain:frameworkInterfaces abstracting Android system services; implemented by the framework:* modules.
domain:use-caseApplication business logic, composed from repositories and framework interfaces.
domain:gridGrid layout and collision-resolution algorithms used when moving or resizing items.
domain:commonCross-cutting abstractions such as coroutine dispatcher qualifiers.

Data

Concrete persistence implementations behind the domain:repository interfaces:

ModuleResponsibility
data:repositoryRepository implementations that combine data:room and data:datastore sources.
data:roomThe local SQLite database (grid items, installed apps, widgets, shortcuts, icon packs).
data:datastoreUser settings persistence via Proto DataStore.
data:datastore-protoThe .proto schema definitions consumed by data:datastore.

Framework

Each framework:* module wraps a single Android system API so the rest of the codebase never imports it directly (e.g. framework:launcher-apps, framework:package-manager, framework:wallpaper-manager, framework:widget-manager, framework:icon-pack-manager, framework:file-manager, framework:resources, framework:accessibility-manager, framework:notification-manager, framework:settings, framework:image-serializer, framework:user-manager, framework:jaro-winkler-similarity, framework:content-resolver).

Most of these modules implement an interface declared in domain:framework and are bound to it via Hilt, which lets the domain layer depend on the abstraction instead of the Android API. A few modules (for example framework:user-manager, framework:accessibility-manager, framework:notification-manager, framework:settings, and framework:image-serializer) wrap a system API directly without a domain:framework interface, since nothing in the domain layer currently needs to consume them through an abstraction.

Presentation

ModuleResponsibility
feature:* (home, action, pin, edit-application-info, edit-grid-item, settings:*)Feature-specific screens, ViewModels, and UI state.
design-systemGeneric, model-free Compose primitives.
uiShared, application-aware UI reused by multiple features.

Android Entry Points

The outermost layer. app and service are the only modules the Android OS talks to directly, so they're the two places allowed to reach across the whole module graph to wire things together:

ModuleResponsibility
appWires every module together: Hilt setup, Application class, and the root Activity/navigation graph.
serviceBackground Android services (accessibility, notification listener, icon pack updates).

Shared Infrastructure

ModuleResponsibility
commonApplication-wide Hilt bindings (icon key generation, coroutine dispatchers).
build-logicGradle convention plugins that standardize module build configuration.

design-system, ui, and feature:* Boundaries

These three module groups all sit in the Presentation layer, but each has a distinct responsibility:

  • design-system contains generic, reusable visual primitives — theming, icons, animations, dialogs, and other Compose building blocks that carry no knowledge of the launcher's domain models or features. It is model-free: it has no dependency on domain:model or any other domain module.
  • ui contains shared, application-aware UI: composites built from design-system primitives that are reused across multiple features (dialogs like SelectApplicationDialog, settings composables, etc.). This is where launcher-model-aware and framework-aware shared UI lives — it depends on design-system, on domain:model for the types it renders, and exposes the framework:* modules as API dependencies so features can reach framework implementations through a single dependency on :ui.
  • feature:* modules contain screens, ViewModels, and UI state specific to one feature. They depend on both design-system and ui (added automatically by the feature Gradle convention plugin) plus whatever domain:* modules that feature's use cases require.

Further Reading

Yagni Launcher uses its own Clean Architecture and module dependency rules, as described throughout this document. Now in Android only influenced selected tooling and Gradle module terminology — for example build-logic, design-system, and ui — and the link below does not mean that Yagni Launcher's architecture is based on that project.