Skip to content

Give every feature an api and a presentation module - #88

Merged
AhmadKharfan merged 1 commit into
developfrom
feat/api-presentation-split
Jul 30, 2026
Merged

Give every feature an api and a presentation module#88
AhmadKharfan merged 1 commit into
developfrom
feat/api-presentation-split

Conversation

@AhmadKharfan

Copy link
Copy Markdown
Owner

Why

Adopt the module shape used by Novix and MENA-mobile: every feature is a pair of modules, a contract and an implementation, so a consumer depends on what a feature offers rather than on how it is built.

What changed

All seven features are now api + presentation:

feature/<name>/api           contract: @Composable entry points + routes
feature/<name>/presentation  implementation: screens, ViewModels, DI module
  • :feature:{onboarding,settings,projects,terminal,editor}:api are new; git and buildrun already had contracts.
  • Every former :feature:<name> module is now :feature:<name>:presentation.
  • Each contract carries the feature's Routes object and an XxxFeatureApi interface of @Composable entry points — WelcomeEntry, HubEntry, RootEntry, TerminalEntry, EditorEntry and so on.
  • Each presentation module holds an internal XxxFeatureApiImpl and binds its own contract in its own Koin module.
  • :app resolves contracts with koinInject() and no longer imports a single screen composable or nav-graph function from any feature.
  • Every api module has explicitApi() on and depends only on :domain plus Compose.

The one deliberate difference from the references

Novix and MENA each give a feature its own nested NavHost behind its entry point. This keeps a single app-level NavHost: ASL is one window with a deep cross-feature back stack (editor → git diff → editor) and saved-state results between screens, and per-feature nav hosts give up a unified back stack, handle predictive back per feature, and lose state when switching. The api shape is the same; the back stack stays whole.

Why the DI change had to come first

Both references bind every XxxApiImpl centrally in the app module — Novix in app/di/ApiModule.kt, MENA in composeApp/di/apiModule.kt. That leaves the app compile-dependent on every implementation and stops any impl from being internal, which defeats the split. Here each feature binds itself, so all seven XxxFeatureApiImpl classes are internal.

Behavior

No behavior change. Route strings, destination registration order, arguments and callbacks are unchanged; only who declares them moved.

Some callback names moved to present tense to satisfy the Compose naming rule on the new public surfaces (onGetStartedonStart, onPickedFolderConsumedonConsumePickedFolder, onCreatedonCreate, onFolderSelectedonSelectFolder, onConflictPathOpenedonConsumeConflictPath). The implementations map them back to the unchanged Route parameters, so no screen signature changed. These were fixed rather than baselined because they are declarations added here.

Tests

  • 723 tests passing, 0 failures.
  • KoinModuleGraphTest verifies all seven new contract bindings resolve.
  • verifyModuleBoundaries: 0 baseline entries, 0 violations. The contract check added earlier now guards seven api modules instead of two — no @Composable interface member may take default arguments, which is the crash this migration already shipped once.

Verification

./gradlew test detekt verifyModuleBoundaries :app:assembleDebug — BUILD SUCCESSFUL.

Verified on an emulator, exercising every new contract:

  • OnboardingFeatureApi / ProjectsFeatureApi — hub renders; create project → browse location → select folder returns /storage/emulated/0/Documents into Save location, so saved-state results still cross the contract
  • SettingsFeatureApi — Preferences renders, Git & GitHub opens
  • EditorFeatureApi — project opens, editor renders
  • TerminalFeatureApi — the Terminal tab renders a live session inside the editor's bottom panel, through the slot the editor exposes rather than a dependency
  • No AbstractMethodError, NoBeanDefFoundException or InstanceCreationException in logcat

@AhmadKharfan
AhmadKharfan merged commit 9c7e5e4 into develop Jul 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant