This repo is an Android app (Kotlin, MVVM, Clean-ish layering) built with Gradle Kotlin DSL.
- Cursor rules: none found (
.cursor/rules/and.cursorrulesdo not exist). - Copilot rules: none found (
.github/copilot-instructions.mddoes not exist).
- Modules:
:app,:core:common,:core:design-system(seesettings.gradle). - Tooling: Gradle wrapper (
./gradlew), JDK 17 (CI uses Temurin 17; app compiles with Java 17). - Kotlin style:
kotlin.code.style=officialingradle.properties.
Build reads secrets from local.properties in app/build.gradle.kts.
- Required keys in
local.properties:DEV_BASE_URL="..."PROD_BASE_URL="..."KAKAO_NATIVE_APP_KEY=...NAVER_MAPS_CLIENT_ID=...
- Firebase file is gitignored:
app/google-services.json. If these are missing, Gradle may crash during configuration (properties are read as non-nullString). Never commit secrets:local.properties,app/google-services.json, keystores (*.jks/*.keystore), service account JSON,.env.
All commands should be run from repo root.
- Full build (CI-style):
./gradlew build
- Debug APK:
./gradlew :app:assembleDebug
Android Lint is available; app module has lint { abortOnError = false }.
- Lint app Debug:
./gradlew :app:lintDebugCommon report locations:
app/build/reports/lint-results-debug.htmlapp/build/reports/lint-results-debug.xml
- Run all unit tests:
./gradlew test
- App unit tests (Debug):
./gradlew :app:testDebugUnitTest
Run a single unit test class/method (JUnit4):
- Class:
./gradlew :app:testDebugUnitTest --tests "com.eatssu.android.SomeTest"
- Method:
./gradlew :app:testDebugUnitTest --tests "com.eatssu.android.SomeTest.someMethod"
- Run connected tests (Debug):
./gradlew :app:connectedDebugAndroidTest
Run a single instrumentation test:
- Class:
./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.eatssu.android.ExampleInstrumentedTest
- Method:
./gradlew :app:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.eatssu.android.ExampleInstrumentedTest#useAppContext
- Workflow:
.github/workflows/android.ymlruns./gradlew build. - CI generates:
local.propertiesfrom GitHub Secretsapp/google-services.jsonfrom base64 secret
Follow existing patterns first. Repo has minimal automated formatting/lint beyond Android/IDE defaults.
- Kotlin official style (Android Studio: "Kotlin style guide").
- Indent: 4 spaces; keep lines readable; prefer trailing commas in multiline params/args.
- Avoid reformat churn: change only what you touch.
- No wildcard imports.
- Order: standard Kotlin/Java, then Android/AndroidX, then project (
com.eatssu...), then third-party. - Keep imports minimal; prefer explicit imports over fully-qualified usage in code.
- Packages:
lowercase(already:com.eatssu.android.*,com.eatssu.common.*). - Classes/objects:
PascalCase. - Functions/vars:
camelCase. - Constants:
UPPER_SNAKE_CASE(const val). - Android components:
*Activity,*Fragment,*ViewModel,*Adapter,*ViewHolder.
- Domain:
*UseCasefor usecases (com.eatssu.android.domain.usecase.*).
- Network/DTO:
*Request,*Responsefor DTOs; keep mapping out of UI layer.
- Prefer immutable types (
val, read-onlyList/Map) unless mutation is necessary. - Avoid
!!in new code; use safe calls, early returns, or explicit error states. - UI state uses
UiState<T>fromcore/common:Init,Loading,Success(data),Error.
- ViewModels typically expose:
private val _uiState = MutableStateFlow<UiState<...>>(UiState.Init)val uiState: StateFlow<UiState<...>> = _uiState.asStateFlow()
- One-off UI actions use
UiEventviaMutableSharedFlow+asSharedFlow().
- Use
viewModelScopefor UI-driven work; keep long work off main. - Prefer
Dispatchers.IOfor network/disk. - If doing parallel work, use
async/awaitAll()likeMenuViewModel.loadMenus(). - Keep Flow collection lifecycle-aware (e.g.,
lifecycleScope.launch { ...collect... }).
- Network layer wraps calls with
ApiResult(app/src/main/java/com/eatssu/android/data/model/ApiResult.kt). - Prefer returning
ApiResult/domain results instead of throwing. - Handle failures explicitly:
ApiResult.Failure(responseCode, message)for server errorsApiResult.NetworkError(IOException)for connectivityApiResult.UnknownError(Throwable)for unexpected
- Global behaviors:
TokenStateManageremits token state;Appforwards toTokenEventBus.ApiResultCalltriggersNetworkErrorEventBusforIOException.
- Use
Timber(Timber.d/e/w) for logs; avoidprintln. - Never log secrets (tokens, API keys, URLs with credentials).
- Hilt is used (
@HiltAndroidApp,@HiltViewModel,@Inject). - Prefer constructor injection; keep modules under
app/src/main/java/com/eatssu/android/di. - Use qualifiers (e.g.,
@ApplicationContext) when required.
- Use Version Catalog (
gradle/libs.versions.toml) vialibs.*aliases. - Prefer adding dependencies to the smallest module that needs them.
- Keep build config/secrets in
local.properties(never hardcode keys in code).
- Project overview:
README.md. - Project overview:
README.md; troubleshooting:.github/TROUBLESHOOTING.md. - Team conventions (external):