🔒
GitLab App GitLab API Swagger

BankingApp

Modular Clean Architecture Starter

Base project Android modern untuk membangun aplikasi perbankan dengan arsitektur modular, Jetpack Compose, MVVM + Clean Architecture, dan Hilt

Dokumentasi Teknis
Version 2.0 - 2025

Daftar Isi

Rilis v2 (2.0)

Ringkasan perubahan utama dari dd6473d3cdc6cc:

Panduan Migrasi Singkat dari v1

1. Fitur Utama

2. Teknologi dan Prinsip

3. Arsitektur: MVVM + Clean

Lapisan Utama

📱 Presentation (Feature Module)

UI Compose, ViewModel, pemetaan domain → UI, event satu-kali (one-off), i18n via StringProvider

🎯 Domain (core-domain + domain di tiap feature)

UseCase, Entity/Model domain, kontrak Repository, Result dan DomainFailure

💾 Data (di feature dan/atau core)

Implementasi Repository (network/local/security), mapper DTO ↔ domain

🔧 Platform/Library

core-ui (LoadingManager, StringProvider), lib-designsystem (tema/komponen), lib-navigation (Navigator/Destination)

Aliran Data

Contoh submit form:

UI → ViewModel → UseCase (domain) → Repository → 
Result dikembalikan → ViewModel map ke state/error → UI render

4. Struktur Proyek

├── app ├── core │ ├── core-domain │ ├── core-ui │ ├── core-network │ ├── core-database │ ├── core-security │ ├── core-analytics │ └── core-testing ├── lib │ ├── lib-designsystem │ ├── lib-navigation │ ├── lib-storage │ └── lib-notification └── feature-* ├── feature-auth ├── feature-dashboard ├── feature-transfer ├── feature-gold ├── feature-loan ├── feature-payment ├── feature-profile ├── feature-support └── feature-notification

Struktur dalam Satu Feature (Auth)

co.id.why.feature.auth ├── domain/ (UseCase, entity, failure, kontrak Repository) ├── data/ (RepositoryImpl, DTO, mapper) ├── login/ (UI Compose + ViewModel + mapper UI) └── navigation/ (route, destination, graph)

5. Komponen Penting

5.1 Result dan DomainFailure

Result adalah sealed class untuk keberhasilan/kegagalan:

sealed class Result<out T> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(
        val message: String = "",
        val code: Int? = null,
        val failure: DomainFailure? = null
    ) : Result<Nothing>()
}

DomainFailure adalah marker interface untuk kegagalan domain yang bertipe kuat.

5.2 Validasi dan Use Case

LoginUseCase menaruh aturan domain:

class LoginUseCase(
    private val repo: AuthRepository,
    dispatcher: CoroutineDispatcher
) : UseCase<LoginUseCase.Param, User>(dispatcher) {

    data class Param(val username: String, val password: String)

    override suspend fun execute(param: Param): Result<User> {
        val username = param.username.trim()
        val password = param.password

        if (username.isBlank()) 
            return Result.Error(failure = LoginFailure.UsernameEmpty)
        if (password.isBlank()) 
            return Result.Error(failure = LoginFailure.PasswordEmpty)

        val usernameValid = username.length >= 3
        val pwdValid = password.length >= 6
        
        if (!usernameValid) 
            return Result.Error(failure = LoginFailure.UsernameTooShort)
        if (!pwdValid) 
            return Result.Error(failure = LoginFailure.PasswordTooShort)

        return repo.login(username, password)
    }
}
Keuntungan: Domain bebas dari string UI/i18n; ViewModel yang memetakan kegagalan ke pesan UI.

5.3 ViewModel dan Event Satu-Kali

LoginViewModel mengelola FormState + FormErrorState:

private val _events = MutableSharedFlow<LoginEvent>(replay = 1)
val events: SharedFlow<LoginEvent> = _events

// on success: 
_events.emit(LoginEvent.LoginSuccess(user))

5.4 i18n: StringProvider

Abstraksi untuk mengambil string resource di ViewModel:

interface StringProvider { 
    fun get(id: Int): String 
}

class AndroidStringProvider @Inject constructor(
    @ApplicationContext ctx: Context
) : StringProvider {
    override fun get(id: Int) = ctx.getString(id)
}

5.5 Loading Global

LoadingManager menggunakan reference counter:

fun show() { /* ++counter → isLoading true */ }
fun hide() { /* --counter → isLoading false jika 0 */ }

5.6 Navigasi, Splash, dan Sesi

5.7 Design System

Warna brand (BrandRed, dsb), tema Material3, komponen seperti ShimmerPlaceholder. Komponen UI diselaraskan dengan MaterialTheme untuk konsistensi.

5.8 Komponen Baru v2

  • BaseViewModel: pola loading, error mapping (Failure → UI), Global Snackbar.
  • RetryManager: util untuk mengulang operasi gagal (network) dengan backoff ringan.
  • Session Expiration Handler: observer global yang mengarahkan ke Login saat token kadaluarsa.
  • Screen Result Passing: pola aman untuk mengembalikan hasil antar layar/fitur.
@HiltViewModel
class ExampleViewModel @Inject constructor(
    private val useCase: DoSomethingUseCase,
    private val retry: RetryManager,
) : BaseViewModel() {
    fun submit() = launchWithLoading {
        retry.run { emitSnackbarOnError = true }
            .execute { useCase() }
            .onSuccess { /* update state */ }
            .onFailure { /* optional handling */ }
    }
}

6. Praktik Baik di UI

7. Contoh Alur Login

  1. LoginScreen memanggil vm.login() pada IME Done / klik tombol
  2. LoginViewModel:
    • show() LoadingManager
    • loginUseCase(Param) → Result
    • Success → emit(LoginEvent.LoginSuccess)
    • Error → map LoginFailure ke usernameError/passwordError atau generalError
    • hide() LoadingManager
  3. UI mengamati events dan melakukan navigasi

8. Menjalankan Proyek

  1. Buka dengan Android Studio (Hedgehog+ direkomendasikan)
  2. Sync Gradle, pastikan JDK yang sesuai
  3. Jalankan app pada emulator/perangkat
  4. Modul yang diperlukan akan ter-build otomatis
💡 Catatan Splash Screen: Android 12+ memakai AndroidX SplashScreen API; Android 11 ke bawah menggunakan emulasi via core-splashscreen.

9. Konvensi Kode

Konvensi Penamaan

Komponen Format Contoh
UseCase VerbNounUseCase LoginUseCase
Repository Interface XxxRepository AuthRepository
Repository Impl XxxRepositoryImpl AuthRepositoryImpl
ViewModel XxxViewModel LoginViewModel
State XxxScreenState / XxxFormState LoginFormState
Failure FeatureFailure.SpecificCase LoginFailure.UsernameEmpty

Dependency Injection (Hilt)

@Module
@InstallIn(SingletonComponent::class)
interface AuthDataModule {
    @Binds
    fun bindAuthRepository(impl: AuthRepositoryImpl): AuthRepository
}

Error Handling & Mapping

when (val res = loginUseCase(param)) {
    is Result.Success -> 
        _events.emit(LoginEvent.LoginSuccess(res.data))
    is Result.Error -> when (res.failure) {
        LoginFailure.UsernameEmpty -> 
            _state.update { 
                it.copy(usernameError = sp.get(R.string.error_username_empty)) 
            }
        LoginFailure.PasswordTooShort -> 
            _state.update { 
                it.copy(passwordError = sp.get(R.string.error_password_short)) 
            }
        else -> 
            _state.update { 
                it.copy(generalError = sp.get(R.string.error_generic)) 
            }
    }
}

10. Panduan Testing

Unit Test UseCase

class LoginUseCaseTest {
    private val repo = FakeAuthRepository()
    private val useCase = LoginUseCase(repo, StandardTestDispatcher())

    @Test 
    fun blank_username_returns_failure() = runTest {
        val res = useCase(LoginUseCase.Param(" ", "123456"))
        assertTrue(res is Result.Error && 
                   res.failure == LoginFailure.UsernameEmpty)
    }
}

Unit Test ViewModel

Perintah Testing

# Jalankan semua unit test
./gradlew testDebugUnitTest

# Jalankan lint
./gradlew lint

Cara Membuat Feature Baru

  1. Buat modul baru feature-xyz
  2. Buat paket: domain, data, ui/screen, navigation
  3. Definisikan Failure, UseCase, dan kontrak Repository di domain
  4. Implementasikan RepositoryImpl + mapper di data
  5. Buat XyzScreen + XyzViewModel di presentation
  6. Tambah route di navigation dan daftarkan di AppNavHost
  7. Tambah dependency modul di build.gradle.kts
  8. Tulis minimal 1 unit test UseCase dan 1 unit test ViewModel

Template ViewModel

@HiltViewModel
class XyzViewModel @Inject constructor(
    private val useCase: XyzUseCase,
    private val sp: StringProvider,
    private val loading: LoadingManager,
) : ViewModel() {
    // StateFlow, events, intent handlers
}

Build Configuration

Build Variants & Environment

android {
  buildTypes {
    debug { 
        buildConfigField("String", "BASE_URL", '"https://dev.api"') 
    }
    release { 
        buildConfigField("String", "BASE_URL", '"https://prod.api"') 
    }
  }
}

Secrets Management

⚠️ Penting: Jangan commit kunci/credential. Simpan di local.properties atau gradle.properties (lokal).
val API_KEY: String by project
buildTypes {
  debug { 
    buildConfigField("String", "API_KEY", '"' + API_KEY + '"') 
  }
}

Quality & Accessibility

Logging & Analytics

Aksesibilitas & i18n

Definition of Done

Minimal Requirements

PR Checklist

11. Troubleshooting & FAQ

Gradle Sync Gagal (JDK Mismatch)

Gunakan JDK bawaan Android Studio:

Settings → Build, Execution, Deployment → Gradle → Gradle JDK

Hilt Error "Cannot be Provided"

Pastikan:

  • @Binds module benar
  • @Inject constructor benar
  • Module @InstallIn komponen yang tepat

Compose Compiler Mismatch

Sesuaikan versi Kotlin/Compose dengan libs.versions.toml atau konfigurasi versi centralized.

Build Lambat

  • Aktifkan Gradle build cache
  • Aktifkan parallel build
  • Jalankan ./gradlew --scan untuk diagnosa

Event Navigasi Dobel

Gunakan SharedFlow untuk one-off event (replay = 1) dan konsumsi di LaunchedEffect.

Peta Dependensi Modul

app ├─ depends on: lib-navigation, core-ui, core-domain, lib-designsystem └─ hosts: NavHost root, Splash, App-level Hilt entries feature-*/ ├─ depends on: core-domain, core-ui, lib-designsystem, lib-navigation └─ optional: core-network, core-database, core-security core-domain ← digunakan oleh semua fitur core-ui ← digunakan oleh app + fitur (LoadingManager, StringProvider) lib-designsystem ← dipakai di semua modul UI lib-navigation ← dipakai oleh app + fitur
Aturan: Fitur tidak mengetahui fitur lain secara langsung; komunikasi melalui kontrak domain atau jalur navigasi.

Pedoman Navigasi

Contoh Pemakaian

navController.navigate(Routes.DASHBOARD) {
    popUpTo(Routes.LOGIN) { inclusive = true }
}

Tambahan v2

Ringkasan Teknologi

Kategori Teknologi Penggunaan
Language Kotlin Bahasa utama development
UI Framework Jetpack Compose Modern declarative UI
Architecture MVVM + Clean Separation of concerns
DI Hilt Dependency injection
Async Coroutines + Flow Asynchronous operations
Image Loading Coil Efficient image loading
Design Material 3 Design system
Database Room Caching & akses offline ringan
Push Notification Firebase Cloud Messaging Notifikasi & tindakan

Best Practices Summary

✅ DO

  • Gunakan sealed class untuk state dan result
  • Pisahkan domain logic dari Android framework
  • Implementasikan proper error handling dengan typed failures
  • Tulis unit test untuk UseCase dan ViewModel
  • Gunakan StringProvider untuk i18n di ViewModel
  • Implementasikan one-off events dengan SharedFlow
  • Gunakan collectAsStateWithLifecycle untuk StateFlow

❌ DON'T

  • Jangan hardcode string di domain/data layer
  • Jangan akses Context langsung di ViewModel
  • Jangan gunakan LiveData untuk one-off events
  • Jangan buat dependency antar feature modules
  • Jangan commit secrets ke repository
  • Jangan skip unit testing
  • Jangan gunakan magic numbers

Workflow Development

1. Membuat Feature Baru

  1. Buat module feature-xyz
  2. Setup struktur: domain, data, ui, navigation
  3. Definisikan contracts (UseCase, Repository, Failure)
  4. Implementasi domain logic
  5. Implementasi data layer
  6. Buat UI dengan Compose
  7. Setup ViewModel dengan proper state management
  8. Tambahkan route ke navigation graph
  9. Tulis unit tests
  10. Update documentation

2. Code Review Checklist

Glossary

Term Definisi
UseCase Single business logic operation di domain layer
Repository Abstraction untuk data source (network, database, etc)
Entity Pure domain model tanpa framework dependency
DTO Data Transfer Object - model untuk transport layer
Mapper Function untuk konversi antar layer (DTO ↔ Entity)
DomainFailure Typed error representation di domain layer
StringProvider Abstraction untuk string resources di ViewModel
LoadingManager Global loading state dengan reference counting
One-off Event Event yang hanya terjadi sekali (navigasi, toast, dll)

Resources & Links

Documentation

Best Practices

Version History

Version Date Changes
2.0 2025-11-26 Registrasi + OTP, Notifikasi & FCM, Deep Linking & Actions, Room caching, BaseViewModel + RetryManager, Global Snackbar & error sheet, session expiration, screen result passing, update dokumentasi.
1.0 2025 Initial release dengan core features dan modular architecture

Terima Kasih

Dokumentasi ini dibuat untuk membantu developer memahami dan menggunakan
BankingApp dengan lebih efektif.

BankingApp - Modular Clean Architecture Starter
Version 2.0 - 2025