This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Treetracker Android is a field data collection app for the Greenstand open source project. The app captures tree growth data in the field, establishing employment for people in extreme poverty through tree planting. Data flows through a pipeline to a verification service.
Required: Request the treetracker.keys.properties file from the #android_chat Slack channel before building. This file contains AWS S3 credentials, API client IDs, and secrets for all build variants.
Five build variants exist, each with different API endpoints and configurations:
- dev: Development variant with relaxed tree data accuracy requirements. Use this for local development.
- debug: Standard debug build with test environment settings and blur detection enabled.
- beta: Testing variant pointing to test environment. Used for internal releases to testers.
- prerelease: Pre-production build using production environment.
- release: Production build with minification enabled and production API endpoints.
# Build dev variant (recommended for development)
./gradlew assembleDev
# Build other variants
./gradlew assembleDebug
./gradlew assembleBeta
./gradlew assemblePrerelease
./gradlew assembleRelease# Run all unit tests
./gradlew test
# Run tests for specific variant
./gradlew testDevUnitTest
./gradlew testDebugUnitTest
# Run instrumented tests on connected device
./gradlew connectedDebugAndroidTest# Run all checks (includes ktlint, spotless, detekt)
./gradlew check
# Run ktlint only
./gradlew ktlintCheck
# Format code with ktlint
./gradlew ktlintFormat
# Run detekt static analysis
./gradlew detekt
# Apply spotless formatting
./gradlew spotlessApply# Setup fastlane
bundle install --path vendor/bundle
fastlane install_plugins
firebase loginThe app follows MVVM (Model-View-ViewModel) architecture:
- View: Jetpack Compose UI with reusable components in
view/package - ViewModel: Per-screen ViewModels manage UI state and business logic
- Model: Room database + Retrofit API + Repositories as single source of truth
Koin is used for dependency injection with three main modules in di/:
- AppModule.kt: ViewModels and application-level dependencies
- RoomModule.kt: Database and DAOs
- NetworkModule.kt: Retrofit, OkHttp, AWS S3 SDK
- Language: Kotlin
- UI: Jetpack Compose
- DI: Koin
- Networking: Retrofit + OkHttp
- Storage: AWS S3 (images), Room (local database)
- Async: Kotlin Coroutines
- Camera: CameraX
- Testing: JUnit, MockK, Robolectric, Turbine
- Analytics: Firebase Analytics + Crashlytics
Managed by CaptureSetupNavigationController. Dynamic flow defined by the user's organization:
- Dashboard → UserSelect (user selection or creation)
- Organization-specific setup screens (varies by org)
- TreeCapture (final destination)
When setup completes, step counter, session tracker, and GPS updates are started before entering tree capture.
Managed by CaptureFlowNavigationController. Dynamic flow configured per organization that can include:
- TreeCapture: Main capture screen
- TreeHeightScreen: Height measurement (if enabled)
- TreeImageReview: Review captured photos
- SessionNote: Add notes to capture session
- TreeDBH: Diameter at breast height (if enabled)
- MessagesUserSelect → IndividualMessageList → Chat/Announcement/Survey
Background sync is handled by TreeSyncWorker (a CoroutineWorker) that runs SyncDataUseCase. The sync process executes in this order:
- Sync Messages:
MessagesRepo.syncMessages() - Upload Device Config:
DeviceConfigUploader.upload() - Upload User Data:
PlanterUploader.upload()(includes user images) - Upload Session Data:
SessionUploader.upload() - Upload Trees:
TreeUploader.uploadLegacyTrees()andTreeUploader.uploadTrees() - Upload Location Data:
UploadLocationDataUseCase.execute()
All uploaders follow this pattern:
- Fetch data from Room database
- Upload images to AWS S3 using
UploadImageUseCase(if applicable) - Update local data with image URLs
- Create JSON bundle with upload data
- Upload bundle to AWS S3 via
ObjectStorageClient - Update local data with bundle ID and mark as uploaded
- Delete local image files (if applicable)
Main packages under org.greenstand.android.TreeTracker:
activities/: Android activitiesanalytics/: Analytics and exception loggingapi/: Retrofit API interfaces and object storage clientsapplication/: Application class and initializationbackground/: Background workers (sync, notifications)camera/: Camera capture and image review screenscapture/: Tree capture screens and logicdashboard/: Main dashboarddatabase/: Room entities, DAOs, migrationsdi/: Koin dependency injection modulesmessages/: Messaging features (chat, announcements, surveys)models/: Data models, repositories, business logicnavigation/: Navigation controllers for app flowssignup/: User registrationuserselect/: User selection screenusecases/: Use case classes for business operationsview/: Reusable Compose UI components
Tests are located in app/src/test/ with structure mirroring main source. Key test utilities:
- MainCoroutineRule.kt: Custom rule for testing coroutines
- FakeFileGenerator.kt: Generate test files for upload testing
- Unit tests use MockK for mocking, Turbine for Flow testing
Run tests for a single class:
./gradlew test --tests "org.greenstand.android.TreeTracker.capture.TreeCaptureViewModelTest"The app has several main screen flows, which are controlled by CaptureSetupNavigationController and CaptureFlowNavigationController.
1. Onboarding and Signup:
- The app starts with the
SplashScreen. - From the splash screen, the user might be taken to the
Dashboard,Languageselection, or theSignupFlow. - The
SignupFlowis used for new user registration.
2. Capture Setup Flow:
- This flow is managed by
CaptureSetupNavigationController. - It starts from the
Dashboardand navigates toUserSelect. - If a new user is created, it follows a dynamic path defined by the user's organization to set up the capture process.
- This flow ends by navigating to the
TreeCapturescreen.
3. Tree Capture Flow:
- This flow is managed by
CaptureFlowNavigationController. - It starts on the
TreeCapturescreen. - The flow for capturing tree data is dynamic and defined by the user's organization. It can include screens like
TreeHeightScreen,TreeImageReview, andSessionNote. - After the capture is complete, the tree data is saved, and the user is returned to the
TreeCapturescreen to capture another tree. - The user can navigate back to the
Dashboardfrom this flow.
4. Messaging Flow:
- This flow allows users to communicate with each other.
- It starts from
MessagesUserSelect, then goes toIndividualMessageList. - From the message list, the user can navigate to a
Chatscreen, anAnnouncementscreen, or aSurveyscreen.
The data upload process is managed by the TreeSyncWorker, a CoroutineWorker that runs in the background. The worker triggers the SyncDataUseCase, which orchestrates the entire upload process.
The upload process is as follows:
- Sync Messages:
MessagesRepo.syncMessages()is called to sync messages. - Upload Device Config:
DeviceConfigUploader.upload()is called to upload the device configuration. - Upload User Data:
PlanterUploader.upload()is called to upload user data, including user images. - Upload Session Data:
SessionUploader.upload()is called to upload session data. - Upload Trees:
TreeUploader.uploadLegacyTrees()andTreeUploader.uploadTrees()are called to upload legacy and new trees, respectively. - Upload Location Data:
UploadLocationDataUseCase.execute()is called to upload location data.
General Upload Pattern:
The uploaders (TreeUploader, PlanterUploader, SessionUploader) follow a similar pattern:
- Fetch Data: Get the data to be uploaded from the local Room database.
- Upload Images (if applicable): Upload images to AWS S3 using the
UploadImageUseCaseand get the image URL. - Update Local Data: Update the local data with the image URL.
- Create JSON Bundle: Create a JSON bundle containing the data to be uploaded.
- Upload Bundle: Upload the JSON bundle to AWS S3 using the
ObjectStorageClient. - Update Local Data: Update the local data with the bundle ID and mark it as uploaded.
- Delete Local Images (if applicable): Delete the local image files that have been uploaded.