QuranApp Repository Structure: MVVM Architecture and Android Implementation Guide

The alfaazplus/quranapp repository is structured around a clean MVVM architecture that separates Jetpack Compose UI components from business logic through ViewModel state management, Domain layer use-cases, and Data layer persistence using DataStore and SQLite.

The alfaazplus/quranapp repository delivers an ad-free, privacy-focused Qur'an reader for Android built entirely in Kotlin. Understanding the repository structure requires mapping how the Presentation, ViewModel, Domain, and Data layers interact to handle everything from UI rendering to offline tafsir downloads. This guide walks through each architectural component with direct source links to the master branch.

Architecture Breakdown

The codebase organizes functionality into five distinct layers, each with specific responsibilities and entry points:

Presentation Layer

ViewModel Layer

Domain Layer

Data Layer

Network Layer

Build Configuration

Component Interaction Flow

The architecture enforces unidirectional data flow from UI to Data and back. Here is how a typical tafsir download operation traverses the layers:

  1. Initialization: TafsirViewModel initializes in [TafsirViewModel.kt](https://github.com/alfaazplus/quranapp/blob/master/app/src/main/java/com/quranapp/android/viewmodels/TafsirViewModel.kt) by reading the saved tafsir key from DataStoreManager, calling TafsirDownloadManager.initialize(context), and loading cached keys from QuranTafsirDBHelper.

  2. State Observation: The UI layer collects StateFlow<TafsirUiState> from the ViewModel. When observeDownloadStates() detects progress updates from the download manager, it maps them to TafsirDownloadState objects and emits new states, triggering automatic UI recomposition.

  3. User Action: When a user triggers a download, the UI calls viewModel.onEvent(TafsirEvent.DownloadTafsir(key)), which delegates to TafsirDownloadManager.startDownload().

  4. Background Processing: The download manager verifies network connectivity via NetworkStateReceiver, then launches a WorkManager task. Progress updates flow back to the ViewModel through callback observers.

  5. Persistence: Upon completion, QuranTafsirDBHelper writes the tafsir data to SQLite, and the ViewModel updates downloadedTafsirKeys in the UI state, immediately reflecting the new available resource in the interface.

Code Examples

Observing ViewModel State in Compose

The UI layer consumes state through Kotlin Coroutines collectAsState(), ensuring the interface reacts to data changes without manual callback wiring:

@Composable
fun TafsirScreen(viewModel: TafsirViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsState()

    when {
        uiState.isLoading -> CircularProgressIndicator()
        uiState.error != null -> Text(uiState.error?.message ?: "Error")
        else -> TafsirList(
            state = uiState,
            onEvent = viewModel::onEvent
        )
    }
}

Initiating a Download from UI

User interactions are encapsulated as sealed class events, maintaining type-safe communication between UI and ViewModel:

Button(
    onClick = { 
        viewModel.onEvent(TafsirEvent.DownloadTafsir(tafsirKey)) 
    }
) {
    Text("Download Tafsir")
}

Reading Saved Preferences via DataStore

The repository uses Jetpack DataStore instead of SharedPreferences for asynchronous, type-safe storage operations:

object SPReader {
    private val Context.dataStore by preferencesDataStore(name = "quranapp_prefs")
    private val SAVED_TAFSIR_KEY = stringPreferencesKey("saved_tafsir_key")

    suspend fun getSavedTafsirKey(context: Context): String? =
        context.dataStore.data
            .map { it[SAVED_TAFSIR_KEY] }
            .firstOrNull()
}

Key Files Reference

Navigate the repository efficiently using these direct links to critical implementation files:

Summary

  • MVVM Architecture: The repository strictly separates Presentation, ViewModel, Domain, and Data layers to ensure testability and maintainability.
  • State Management: UI states flow through StateFlow objects in ViewModels, consumed by Jetpack Compose screens for reactive updates.
  • Offline-First Design: Tafsir content persists in SQLite via QuranTafsirDBHelper, with DataStore managing user preferences asynchronously.
  • Modular UI: Custom widgets like PeaceRadioGroup reside in the widgets package, while navigation logic lives in the frags package.
  • Dependency Versioning: The gradle/libs.versions.toml file centralizes library versions across the multi-module project including the peacedesign UI library.

Frequently Asked Questions

What architecture pattern does QuranApp use?

The repository implements the Model-View-ViewModel (MVVM) pattern with unidirectional data flow. Activities and Fragments host Jetpack Compose UI elements that observe immutable state objects from ViewModels, while business logic remains isolated in domain managers and data repositories.

How are UI components organized in the repository?

UI components follow a hierarchical structure: custom Android Views and Compose wrappers reside in app/src/main/java/com/quranapp/android/widgets/, while screen-level containers and navigation logic are located in the frags directory. Shared theming components live in the separate peacedesign module.

Where is dependency injection handled?

The project does not use a DI framework like Hilt or Dagger. Instead, it employs manual singleton initialization through static initialize(context) methods, as seen in [TafsirViewModel.kt](https://github.com/alfaazplus/quranapp/blob/master/app/src/main/java/com/quranapp/android/viewmodels/TafsirViewModel.kt) where TafsirDownloadManager is initialized with an application context.

How does the app manage offline tafsir data?

Offline storage combines SQLite for content persistence and DataStore for metadata. When a user downloads a tafsir, TafsirDownloadManager streams the data to QuranTafsirDBHelper, which writes to a local SQLite database. The ViewModel then queries this database via loadDownloadedTafsirKeys() to display available offline content without network calls.

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 →