Skip to main content
GemAI follows Clean Architecture principles combined with the MVVM (Model-View-ViewModel) pattern, creating a scalable and maintainable Android application. This architecture separates concerns into distinct layers, making the codebase testable and easy to extend.

Clean Architecture layers

The application is organized into three primary layers, each with specific responsibilities:
Contains all UI-related code including Jetpack Compose screens, ViewModels, and UI state management.Location: presentation/Key components:
  • Composable UI screens (presentation/screen/)
  • ViewModels extending BaseViewModel (presentation/screen/*/viewmodel/)
  • UI state, events, and actions
  • Navigation graphs (navigation/graph/)
The core business logic layer containing use cases, repository interfaces, and domain models. This layer has no Android dependencies.Location: domain/Key components:
  • Use cases implementing BaseUseCase (domain/use_case/)
  • Repository interfaces (domain/repository/)
  • Domain models (domain/model/)
  • Business logic and validation
Manages data sources including Room database, DataStore, and external APIs. Implements repository interfaces from the domain layer.Location: data/Key components:
  • Repository implementations
  • Room database and DAOs (data/local/dao/)
  • Entity models (data/local/model/)
  • Mappers for data transformation (data/local/mapper/)

Project structure

Here’s the complete directory structure of GemAI:
com.sarath.gem/
├── core/
│   ├── ai/                    # AI model abstractions
│   │   ├── BaseAIModel.kt
│   │   ├── GemAIModel.kt     # Main chat AI model
│   │   ├── SystemAIModel.kt  # System-level AI tasks
│   │   └── ModelBuilder.kt
│   ├── base/                  # Base classes and interfaces
│   │   ├── BaseViewModel.kt
│   │   ├── BaseUseCase.kt
│   │   ├── BaseDao.kt
│   │   └── BaseMapper.kt
│   └── util/                  # Utility classes
├── data/
│   ├── local/
│   │   ├── dao/              # Room DAOs
│   │   ├── mapper/           # Entity-to-Domain mappers
│   │   ├── model/            # Room entities
│   │   └── AppDatabase.kt
│   └── remote/               # API models
├── di/                        # Hilt dependency injection modules
│   ├── AppModule.kt
│   ├── RepositoryModule.kt
│   ├── DispatcherModule.kt
│   └── CoroutineScopesModule.kt
├── domain/
│   ├── model/                # Domain models
│   ├── repository/           # Repository interfaces
│   └── use_case/
│       ├── chat/             # Chat-related use cases
│       └── api_key/          # API key use cases
├── navigation/               # Navigation setup
│   └── graph/
└── presentation/
    ├── screen/               # Composable screens
    │   ├── chat/
    │   └── onboarding/
    ├── theme/                # Material 3 theming
    └── util/                 # UI utilities

MVVM pattern implementation

GemAI uses the MVVM pattern with Unidirectional Data Flow (UDF) for predictable state management:
abstract class BaseViewModel<State : UIState, Event : UIEvent, Action : UIAction>() : ViewModel() {
    private val initialState: State by lazy { initialState() }
    abstract fun initialState(): State
    protected abstract fun onActionEvent(action: Action)

    private val _uiState = MutableStateFlow(initialState)
    val uiState: StateFlow<State> = _uiState.asStateFlow()
    
    private val _uiEventFlow = Channel<Event>(capacity = Channel.BUFFERED)
    val uiEvent = _uiEventFlow.receiveAsFlow()

    protected val currentState: State
        get() = uiState.value

    protected fun update(updatedState: State.() -> State) = _uiState.update(updatedState)
    
    protected fun sendOneTimeUIEvent(event: Event, delayMillis: Long? = null) {
        launch {
            delayMillis?.let { delay(it) }
            _uiEventFlow.send(event)
        }
    }

    fun onAction(action: Action) {
        onActionEvent(action)
    }
}
Unidirectional Data Flow ensures that:
  • State flows down from ViewModel to UI
  • Actions flow up from UI to ViewModel
  • Events are one-time occurrences (navigation, toasts, etc.)

Dependency injection with Hilt

GemAI uses Hilt for dependency injection, providing a clean way to manage dependencies:
@InstallIn(SingletonComponent::class)
@Module
object AppModule {
    @Provides
    @Singleton
    fun provideLogDao(database: AppDatabase): ConversationDao {
        return database.conversationDao()
    }

    @Provides
    @Singleton
    fun provideMessageDao(database: AppDatabase): MessageDao {
        return database.messageDao()
    }

    @Provides
    @Singleton
    fun providesAppDatabase(@ApplicationContext context: Context): AppDatabase = 
        AppDatabase.getInstance(context)
}

Dependency flow

The architecture enforces strict dependency rules:
Key principle: The domain layer has no dependencies on other layers. Presentation and data layers depend on domain, but never on each other directly.

Next steps

Core components

Learn about base classes, use cases, and AI models

Data layer

Explore Room database, repositories, and data flow

Extending GemAI

Add new features while maintaining architecture

Build docs developers (and LLMs) love