JustWords2 is a commercial-grade, multi-module Android application designed for interactive vocabulary acquisition and language learning. Built entirely in Kotlin and Jetpack Compose, the project strictly adheres to Clean Architecture principles, Modular Design, and the Model-View-Intent (MVI) architectural pattern.
The application is engineered with an Offline-First mindset, ensuring uninterrupted user experience regardless of network availability, backed by an asynchronous background synchronization engine and a dedicated Ktor REST API.
(Note: While the default vocabulary datasets are configured for English-Polish learning, the underlying domain model is language-agnostic).
| Authentication Flow | Learning & Analytics |
|---|---|
![]() |
![]() |
- Strict Multi-Module Clean Architecture: The codebase is decoupled into feature modules (
:auth,:word,:shop,:user) and shared foundation modules (:core). Each feature is further divided intodomain,data, andpresentationlayers. Domain modules are pure JVM libraries (org.jetbrains.kotlin.jvm) with zero Android framework dependencies, guaranteeing business logic isolation and testability. - Scalable Build System (Gradle Convention Plugins): Build logic is centralized in a dedicated
build-logiccomposite build. Custom convention plugins (e.g.,AndroidFeatureUiConventionPlugin,AndroidRoomConventionPlugin,JvmKtorConventionPlugin) alongside a Gradle Version Catalog (libs.versions.toml) and Type-Safe Project Accessors eliminate build script boilerplate across all 17 modules. - Offline-First & Background Synchronization: User progress and learning history are persisted locally first via Room (
OfflineFirstWordHistoryRepository). If a network call fails, failed mutations are queued in a dedicated pending sync table (HistoryPendingSyncEntity) and scheduled for background execution via AndroidWorkManager(SyncHistoryWorkerScheduler) with exponential backoff and network constraints. - Stateless JWT Authentication & Auto-Refresh: Network communication is powered by the Ktor CIO engine. The
HttpClientFactoryimplements an automated Bearer token interceptor that transparently intercepts401 Unauthorizedresponses, hits the/accessTokenrefresh endpoint, and updates session credentials without disrupting active user flows. - Security by Design (Encrypted Storage): Sensitive session payloads (
AuthInfocontaining JWT access and refresh tokens) are serialized and persisted exclusively within hardware-backedEncryptedSharedPreferencesutilizingAES256_SIV(key encryption) andAES256_GCM(value encryption) schemes. - Unidirectional Data Flow (MVI) & Custom Design System: The presentation layer is driven by Jetpack Compose and Material 3, utilizing a reusable
:core:presentation:designsystemmodule. ViewModels expose immutable UI states and process sealedActionintents, while one-off side effects (navigation, toasts) are dispatched via KotlinChanneland collected safely within the lifecycle using a customObserveAsEventscomposable. - Type-Safe Functional Error Handling: Exceptions are caught at the infrastructure boundary (
safeCall) and mapped to a strongly-typed, domain-specificResult<D, E: Error>monad (DataError.Network,DataError.Local), preventing raw exceptions from leaking into the domain or UI layers.
JustWords2
├── app/ # Application entry point, DI root, and Navigation Graph
├── build-logic/ # Custom Gradle Convention Plugins for multi-module setup
├── core/
│ ├── domain/ # Pure JVM shared domain models, repositories, and Result monad
│ ├── data/ # Ktor HTTP client factory, EncryptedSessionStorage, Offline-first repos
│ ├── database/ # Room database, DAOs, Entities, and BSON ObjectId generation
│ └── presentation/
│ ├── designsystem/ # Reusable Compose UI components, Typography, and Theme
│ └── ui/ # UI utilities (UiText, ObserveAsEvents, Error mappers)
├── auth/ # Registration, Login, and UserDataValidator (:data, :domain, :presentation)
├── shop/ # Remote vocabulary pack browsing and downloading (:data, :domain, :presentation)
├── word/ # Core learning loop, flashcards, and WorkManager sync (:data, :domain, :presentation)
└── user/ # User profile, streaks, and Canvas-based statistical graphs (:data, :domain, :presentation)
- Language: Kotlin 1.9+ (Coroutines, Flow, Kotlinx Serialization)
- UI Framework: Jetpack Compose, Material 3, Custom Canvas Graphics, Splash Screen API
- Dependency Injection: Koin (with ViewModel and WorkManager integration)
- Networking: Ktor Client (CIO Engine, Content Negotiation, Auth Bearer Plugin, Logging)
- Local Persistence: Room Database (SQLite), Jetpack DataStore (Preferences), AndroidX Security Crypto (
EncryptedSharedPreferences) - Background Processing: AndroidX WorkManager (CoroutineWorker)
- Build System: Gradle Kotlin DSL, Version Catalogs, Convention Plugins, KSP (Kotlin Symbol Processing)
- Logging: Timber
- Android Studio Iguana (or newer)
- JDK 11 or higher (configured via Gradle toolchains/compileOptions)
- Local instance of the JustWords2 Backend API running for network features
-
Clone the client repository:
git clone https://github.com/Wilie12/JustWords2.git
-
Set up the Backend API: The application compiles and launches out of the box. However, to perform authentication, fetch vocabulary sets from the Shop, or synchronize learning history, you must run the dedicated Ktor backend server:
- Clone and follow the setup instructions in the ktor-justwords2 API Repository.
-
Configure the Local Base URL: Open
build-logic/convention/src/main/java/com/willaapps/convention/BuildTypes.ktand update theBASE_URLbuild config field inconfigureDebugBuildType()to match your local machine's IP address (or10.0.2.2if running on the Android Emulator):private fun BuildType.configureDebugBuildType() { buildConfigField("String", "BASE_URL", "\"http://YOUR_LOCAL_IP:8080\"") }
-
Build and Run: Execute the build using the repository-bound Gradle wrapper or run the
appconfiguration directly from Android Studio:./gradlew assembleDebug


