Last updated: 2025-05-31 Version: 1.0
- This is a recommended project structure, but be flexible and adapt to existing project structures.
- Do not enforce these patterns if the project already follows a different organization.
- Focus on maintaining consistency while applying Jetpack Compose best practices.
- Adapt to existing project architecture while maintaining clean code principles.
- Follow Material Design 3 guidelines and components.
- Implement clean architecture with domain, data, and presentation layers.
- Use Kotlin coroutines and Flow for async operations.
- Use Hilt for dependency injection.
- Follow unidirectional data flow with
ViewModeland UI state. - Use Jetpack Navigation-Compose for screen management.
- Apply proper state hoisting and composition techniques.
app/
src/
main/
java/com/yourpackage/
data/
repository/
datasource/
models/
domain/
usecases/
models/
repository/
presentation/
screens/
components/
theme/
viewmodels/
di/
utils/
res/
values/
drawable/
mipmap/
test/
androidTest/
- Use
rememberandderivedStateOfproperly. - Optimize recomposition scopes.
- Respect modifier order (e.g.,
paddingbeforebackground). - Use consistent and descriptive function names.
- Add preview annotations for reusable composables.
- Use
MutableStateorStateFlowto manage state. - Handle loading and error UI states clearly.
- Use
MaterialThemefor theming. - Follow accessibility practices.
- Use Compose animation APIs when relevant.
- Write unit tests for
ViewModels and use cases. - Use Compose UI testing with assertions on nodes.
- Provide fake repositories or test doubles for isolation.
- Keep good test coverage on business logic.
- Use proper coroutine dispatchers in tests (
TestDispatcher).
- Avoid unnecessary recompositions using
key,remember, etc. - Use
LazyColumnandLazyRowfor lists. - Load images efficiently (e.g., Coil, Glide).
- Structure state to avoid full recomposition on minor changes.
- Respect lifecycle-aware components (e.g.,
LaunchedEffect). - Be aware of memory usage.
- Offload work to background threads when needed.
-
Use
@Bindsfor interface-to-implementation bindings (more efficient). -
Use
@Providesfor:- Third-party types (e.g.,
OkHttpClient,Retrofit) - Classes requiring special construction logic.
- Third-party types (e.g.,
-
Annotate with
@Singletonor appropriate scope. -
Prefer
@Injectconstructors when possible.
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Preferences>@Provides
@Singleton
fun provideAppDatabase(context: Context): AppDatabase
@Provides
fun providePartnerDao(db: AppDatabase): PartnerDao@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient
@Provides
@Singleton
fun provideRetrofit(): Retrofit
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService@Binds
@Singleton
abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
@Binds
@Singleton
abstract fun bindPartnerRepository(impl: PartnerRepositoryImpl): PartnerRepositoryRecommended location for the di/ folder:
com/yourpackage/
di/
✅ This keeps DI concerns at the same level as data, domain, and presentation.
Just update imports after moving — there are no functional side effects.