7.8 KiB
Contributing Guide
Thank you for your interest in contributing to TRMNL App! This document provides guidelines and instructions to help you contribute effectively.
Application Overview
This guide will help you get started with the TRMNL Android application development.
See technical details on the project
Project Structure
The app uses a modern Android architecture with the following components:
- UI: Jetpack Compose with Circuit UDF architecture
- Background Processing: WorkManager for scheduled image (re)loading
- Networking: Retrofit, OkHttp, Moshi, and EitherNet for API communication
- DI: Metro for dependency injection
- Data Storage: DataStore for preferences and token storage
Key Features/Screens
- Main TRMNL visualization in
TrmnlMirrorDisplayScreen - Settings management via
AppSettingsScreen - Image refresh log/history in
DisplayRefreshLogScreen - Background refresh scheduling with
TrmnlWorkScheduler&TrmnlImageRefreshWorker
Development Setup
- Clone the repository
- Open the project in Android Studio
- Sync Gradle files
- Connect an Android device or start an emulator and select it
- Click the Run button (green triangle) in the toolbar
Code Style and Formatting
We use ktlint for Kotlin code formatting. Before submitting any changes, run:
./gradlew formatKotlin
This will automatically format your Kotlin code according to project standards.
Pull Request Process
- Fork the repository and create your branch from
main - Make your changes
- Run
./gradlew formatKotlinto ensure code style compliance - Run tests with
./gradlew test - Submit a pull request to the
mainbranch
Testing
Before submitting your changes, please run:
./gradlew lintKotlin testDebugUnitTest
Building
To build a debug APK:
./gradlew assembleDebug
Build Types
The app uses standard Android build types (debug and release). There are no product flavors.
Build Types:
- Debug: Development builds with debug keystore and no code shrinking
- Release: Production builds with code shrinking, ProGuard, and production keystore
To build the release APK:
# Build the release variant
./gradlew assembleRelease
This command builds a single, signed APK. Local release builds still require a decoded production keystore file plus KEYSTORE_PASSWORD and KEY_ALIAS; CI provides those values through secrets.
Release Process
For instructions on creating new releases and managing versions across the project, see the Release Checklist.
Snapshot Builds
Automatic snapshot release builds are available in the release workflow artifacts as both APK and AAB files.
Issues
- For bug reports, include steps to reproduce, expected behavior, and actual behavior
- For feature requests, describe the feature and why it would be valuable
Additional Resources
Thank you for contributing to TRMNL App!
TRMNL App Image Loading Flow
Here is a generated sequence diagram illustrating the flow of image loading in the TRMNL application.
Click to expand the mermaid diagram
sequenceDiagram
participant WorkManager
participant TrmnlImageRefreshWorker
participant TrmnlDisplayRepository
participant MainActivity
participant TrmnlImageUpdateManager
participant TrmnlMirrorDisplayScreen
participant AsyncImage
Note over WorkManager: Scheduled or one-time work
WorkManager->>TrmnlImageRefreshWorker: Start work (periodic/one-time)
TrmnlImageRefreshWorker->>TrmnlDisplayRepository: Get display data
TrmnlDisplayRepository-->>TrmnlImageRefreshWorker: Return image URL & metadata
alt Success
TrmnlImageRefreshWorker-->>WorkManager: Return success with imageUrl in output data
alt Periodic Work
Note over TrmnlImageRefreshWorker: Workaround for periodic work observer issue
TrmnlImageRefreshWorker->>TrmnlImageUpdateManager: updateImage(imageUrl, refreshInterval)
end
else Failure
TrmnlImageRefreshWorker-->>WorkManager: Return failure with error message
end
WorkManager-->>MainActivity: Notify work completed via WorkInfo observer
alt Success (One-time work)
MainActivity->>TrmnlImageUpdateManager: updateImage(imageUrl)
else Failure
MainActivity->>TrmnlImageUpdateManager: updateImage("", errorMessage)
end
Note right of TrmnlImageUpdateManager: Notifies via imageUpdateFlow
TrmnlImageUpdateManager-->>TrmnlMirrorDisplayScreen: Emit new image metadata
alt Success
TrmnlMirrorDisplayScreen->>AsyncImage: Load image from URL
alt Image Loads Successfully
AsyncImage-->>TrmnlMirrorDisplayScreen: Image displayed
else Image Load Failure (HTTP 403 - Expired URL)
AsyncImage-->>TrmnlMirrorDisplayScreen: onError callback triggered
TrmnlMirrorDisplayScreen->>TrmnlMirrorDisplayScreen: eventSink(RefreshCurrentPlaylistItemRequested)
Note over TrmnlMirrorDisplayScreen: Trigger refresh for fresh URL
else Image Load Failure (Other errors)
AsyncImage-->>TrmnlMirrorDisplayScreen: onError callback triggered
TrmnlMirrorDisplayScreen->>TrmnlMirrorDisplayScreen: eventSink(ImageLoadingError)
Note over TrmnlMirrorDisplayScreen: Display error UI
end
else Failure
TrmnlMirrorDisplayScreen->>TrmnlMirrorDisplayScreen: Display error message
end
📖 Flow Explanation
-
WorkManager initiates the image refresh work (either periodic scheduled work or one-time work)
-
TrmnlImageRefreshWorker executes the work:
- Fetches display data from TrmnlDisplayRepository
- Adds success/failure log entry
- Returns result with image URL or error message
- For periodic work only: Directly calls
TrmnlImageUpdateManager.updateImage()as a workaround for potential WorkInfo observer issues with periodic work
-
MainActivity observes the work completion:
- Receives WorkInfo updates from WorkManager
- Calls
TrmnlImageUpdateManager.updateImage()with new image URL (for one-time work) or error message
-
TrmnlImageUpdateManager gets notified of new image:
- Receives updates from either MainActivity (one-time work) or directly from TrmnlImageRefreshWorker (periodic work)
- Updates the image metadata
- Emits the update through imageUpdateFlow
-
TrmnlMirrorDisplayScreen receives the update:
- Collects from imageUpdateFlow
- Updates state with new image URL or error
-
AsyncImage loads the image:
- On success: Displays the image
- On failure: Triggers onError callback
- HTTP 403 (expired URL): Automatically triggers a refresh to get a fresh URL
- Other errors: Shows error UI with error message
This data flow uses a combination of WorkManager for background processing, StateFlow for reactive updates, and Compose for UI rendering.