GSYGithubAppCompose Clean Architecture Folder Structure and Module Organization

GSYGithubAppCompose implements Clean Architecture through a modular Gradle project structure separating concerns into Presentation (feature/*), Domain (data), and Infrastructure (core/*) layers with strict unidirectional dependencies.

This open-source GitHub client application demonstrates how to scale Android projects using Jetpack Compose by enforcing clear boundaries between UI, business logic, and data access. The Clean Architecture folder structure ensures that feature modules remain independent while sharing common infrastructure through the core layer.

The Three-Layer Architectural Division

The repository organizes code into three logical layers mapped to distinct Gradle modules. This separation prevents tight coupling and enables independent testing of business logic without Android framework dependencies.

Presentation Layer: Feature Modules

The feature/ directory contains 14 independent modules handling UI rendering and user interaction. Each feature module owns its own screens, ViewModels, and UI state definitions.

feature/
├── welcome/
├── login/
├── home/
├── dynamic/
├── trending/
├── profile/
├── search/
├── detail/
├── code/
├── issue/
├── push/
├── list/
├── notification/
└── info/

Each feature follows the same internal structure containing *Screen.kt composables, *ViewModel.kt classes extending BaseViewModel, and *UiState.kt data classes. For example, feature/issue/src/main/java/com/shuyu/gsygithubappcompose/feature/issue/IssueScreen.kt implements the issue tracking UI while delegating business logic to the domain layer.

Domain Layer: Business Logic Coordination

The data/ module serves as the domain layer, coordinating business rules and data mapping between presentation and infrastructure. Located at the project root, this module contains repository implementations that abstract data sources.

Key contents include:

  • data/repository/ - Repository implementations like IssueRepository.kt that expose clean APIs to ViewModels
  • data/mapper/ - Data transformation logic converting network entities to domain models
  • data/repository/vm/BaseViewModel.kt - Abstract ViewModel providing common state management across all features

This layer depends only on core/ modules for low-level data access, maintaining the dependency rule that inner layers know nothing about outer layers.

Infrastructure Layer: Core Services

The core/ directory houses five infrastructure modules providing low-level technical capabilities:

core/
├── network/          # Retrofit/Apollo services, API models (User.kt, Repository.kt)

├── database/         # Room entities, DAOs (UserEntity.kt, RepositoryDao.kt)

├── common/           # DataStore preferences, utilities (UserPreferencesDataStore.kt)

└── ui/               # Reusable Compose components, theme resources, Navigation.kt

The core/network/ module defines GitHubApiService.kt for remote data access, while core/database/ handles local persistence. The core/ui/ module contains shared navigation logic in Navigation.kt and reusable widgets used across multiple features.

Module Dependency Rules

GSYGithubAppCompose enforces strict dependency constraints preventing circular references between layers:

app          → feature/*, core/ui, data
feature/*    → data, core/ui, core/common
data         → core/network, core/database, core/common
core/ui      → core/common
core/network → independent
core/database→ independent
core/common  → independent

As shown in the graph, feature/* modules never communicate directly with core/network or core/database. All data requests flow through the data/ module's repositories, ensuring that network implementation details remain isolated from UI code.

Data Flow Implementation

The architecture implements unidirectional data flow using Kotlin Flow and StateFlow:

  1. Presentation - Compose screens in feature/* emit UI events (clicks, scrolls) and observe StateFlow from ViewModels
  2. Domain - ViewModels (e.g., IssueViewModel) receive events, apply business rules, and call repository methods defined in data/repository/
  3. Infrastructure - Repositories fetch from core/network/ (Retrofit/Apollo) or core/database/ (Room) based on data requirements
  4. Response - Results flow back through repositories → ViewModels → UI state updates, triggering recompositions in Compose screens

This chain follows the pattern: Screen → ViewModel → Repository → Network/Database.

Adding a New Feature Module: Practical Example

Creating a new feature/settings module requires conforming to the established Clean Architecture conventions. Below is the minimal implementation skeleton.

The screen composable resides in feature/settings/src/main/java/com/shuyu/gsygithubappcompose/feature/settings/SettingsScreen.kt:

package com.shuyu.gsygithubappcompose.feature.settings

import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.hilt.navigation.compose.hiltViewModel

@Composable
fun SettingsScreen(
    viewModel: SettingsViewModel = hiltViewModel()
) {
    Text(text = "App Settings")
}

The ViewModel extends BaseViewModel and receives dependencies through constructor injection in SettingsViewModel.kt:

package com.shuyu.gsygithubappcompose.feature.settings

import com.shuyu.gsygithubappcompose.data.repository.vm.BaseViewModel
import com.shuyu.gsygithubappcompose.core.common.datastore.UserPreferencesDataStore
import com.shuyu.gsygithubappcompose.core.common.util.StringResourceProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class SettingsViewModel @Inject constructor(
    preferencesDataStore: UserPreferencesDataStore,
    stringResourceProvider: StringResourceProvider,
) : BaseViewModel<SettingsUiState>(
    initialUiState = SettingsUiState(),
    preferencesDataStore = preferencesDataStore,
    stringResourceProvider = stringResourceProvider,
    commonStateUpdater = { state, _, _, _, _, _, _, _ -> state }
) {
    // Settings-specific logic (toggle theme, clear cache)
}

The UI state definition in SettingsUiState.kt implements BaseUiState:

package com.shuyu.gsygithubappcompose.feature.settings

import com.shuyu.gsygithubappcompose.data.repository.vm.BaseUiState

data class SettingsUiState(
    val isDarkTheme: Boolean = false,
    override val isPageLoading: Boolean = false,
    override val isRefreshing: Boolean = false,
    override val isLoadingMore: Boolean = false,
    override val error: String? = null,
    override val currentPage: Int = 1,
    override val hasMore: Boolean = false,
    override val loadMoreError: Boolean = false
) : BaseUiState

Integration steps:

  1. Create feature/settings/build.gradle.kts depending on :data and :core:ui modules
  2. Register the navigation route in core/ui/src/main/java/com/shuyu/gsygithubappcompose/core/ui/Navigation.kt
  3. Hilt automatically injects SettingsViewModel following the existing DI pattern

Key Source Files and Their Roles

Path Role
app/src/main/java/com/shuyu/gsygithubappcompose/App.kt Application class, Hilt entry point
core/ui/Navigation.kt Central Compose navigation host
core/network/GitHubApiService.kt Retrofit API definitions
core/database/RepositoryDao.kt Room data access objects
core/common/datastore/UserPreferencesDataStore.kt Preference storage using DataStore
data/repository/IssueRepository.kt Business logic coordination
data/mapper/DataMappers.kt Entity-to-model transformations
data/repository/vm/BaseViewModel.kt Shared ViewModel functionality

Summary

  • GSYGithubAppCompose uses a modular Clean Architecture with three distinct layers: Presentation (feature/*), Domain (data), and Infrastructure (core/*)
  • Strict dependency rules prevent feature modules from accessing network or database implementations directly, enforcing repository patterns
  • Unidirectional data flow moves from UI events through ViewModels to repositories, returning via StateFlow updates
  • Hilt dependency injection wires components across module boundaries while maintaining testability
  • Base classes (BaseViewModel, BaseUiState) standardize state management across all 14 feature modules

Frequently Asked Questions

How does the Clean Architecture folder structure improve testability in GSYGithubAppCompose?

The modular separation allows unit testing of ViewModels in data/ without Android framework dependencies, while repository implementations can be tested against mocked core/network services. Since feature/* modules depend only on abstractions in data/, UI tests can use fake repositories without hitting actual APIs.

Can feature modules communicate directly with core/database or core/network?

No. According to the dependency rules defined in the project documentation, feature/* modules may only depend on data, core/ui, and core/common. All data access must flow through repository interfaces in the data/ module, ensuring that database schema changes or network endpoint updates don't require modifications to UI code.

What is the purpose of the BaseViewModel class in the data module?

BaseViewModel (located at data/repository/vm/BaseViewModel.kt) provides common functionality for all feature ViewModels, including state initialization, preference management via UserPreferencesDataStore, and standardized loading state handling. Feature-specific ViewModels extend this class to inherit pagination, error handling, and lifecycle-aware state management.

How do I add a new screen to the existing navigation structure?

New screens must be registered in core/ui/src/main/java/com/shuyu/gsygithubappcompose/core/ui/Navigation.kt, which serves as the central navigation host. The feature module containing your screen should depend on :core:ui, and you must use hiltViewModel() to obtain ViewModel instances to ensure proper dependency injection across module boundaries.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →