A bug tracking Android app built with Jetpack Compose. Capture bugs with screenshots and descriptions, upload them to Google Sheets (organized by daily tabs), with images hosted on ImgBB. Designed for teams that want lightweight, zero-setup bug tracking without the overhead of Jira or Linear.
Full quality video
Bugit/
├── :app → Entry point, DI root, navigation host, intent handling
├── :core:common → Utilities, date formatters, extensions
├── :core:data → Repository interfaces & implementations, connectors
├── :core:database → Room database, DAOs, entities, database DI module
├── :core:domain → Use cases (orchestration logic between repositories)
├── :core:model → Domain models, form config definitions
├── :core:network → Retrofit clients for ImgBB API, DTOs
├── :core:ui → Design system: theme, typography, shared Compose components
├── :feature:home → Home dashboard screen
├── :feature:report → Bug submission screen with dynamic form
└── :toolbox:googlesheets → Google Sheets API integration (service account auth, append/tab ops)
Why multi-module? Build times scale with module count, not project size. Changing a feature module only recompiles that module and :app. Feature modules never depend on each other, preventing spaghetti coupling.
┌─────────────────────────────────────────────────────┐
│ Presentation (:feature:*, :app, :core:ui) │ ← depends on Domain
├─────────────────────────────────────────────────────┤
│ Domain (:core:domain) │ ← depends on Data
├─────────────────────────────────────────────────────┤
│ Data (:core:data, :core:network, │ ← lowest layer
│ :core:database, :toolbox:*) │
└─────────────────────────────────────────────────────┘
The problem: Bug reports need different fields for different teams. Hardcoding a form means every new field (Priority, Labels, Assignee) requires code changes, rebuilds, and redeployment.
The solution: Forms render from a FormConfig — a serializable list of field definitions. Adding a field means adding an entry to the config. No UI code changes needed.
@Serializable
sealed interface FormFieldConfig {
val id: String
val label: String
val required: Boolean
val order: Int
data class Text(...) // Single-line and multi-line text
data class ImagePicker(...) // Single and multi-image selection
data class Dropdown(...) // Priority, category, etc.
data class ChipInput(...) // Labels/tags with suggestions
data class DatePicker(...) // Due dates
}The Compose renderer uses an exhaustive when block — adding a new field type is a compile error until a corresponding UI component handles it. This makes the system both flexible and safe.
Evolution path:
- Phase 1: Hardcoded default config
- Phase 2: JSON stored in DataStore (models are already
@Serializable) - Phase 3: Remote config endpoint for per-team customization
The data layer (:core:data) owns models, interfaces, and implementations. The domain layer (:core:domain) owns use cases that orchestrate them.
// :core:data — models, interfaces, and implementations
interface ImageRepository { ... } // ImgBBImageRepository
interface BugConnector { ... } // GoogleSheetsBugConnector (Hilt @IntoSet)
interface BugReportRepository { ... } // DefaultBugReportRepository (wraps Set<BugConnector>)
// :core:domain — orchestration use case
class SubmitBugReportUseCase(imageRepository, bugReportRepository)Why? Today BugIt uploads images to ImgBB and writes rows to Google Sheets. Tomorrow it could push to Jira, Notion, or Linear. Adding a connector means implementing BugConnector and adding a Hilt @IntoSet binding — all existing connectors continue to receive submissions automatically.
User fills form → taps Submit
→ ViewModel.submit()
→ SubmitBugReportUseCase(formData, imageUris)
→ Read image bytes from URIs (ContentResolver)
→ ImageRepository.uploadImage(bytes) → ImgBB URL per image
→ BugReportRepository.submitBug(formData, imageUrls)
→ Fans out to all BugConnectors:
→ GoogleSheetsBugConnector: get/create daily tab → append row
→ (Future: JiraBugConnector, NotionBugConnector, etc.)
→ Return Result<SubmissionResult>
→ Update UI: Success / Error
| Layer | Choice | Why |
|---|---|---|
| UI | Jetpack Compose + Material 3 | Declarative, less boilerplate, first-class animation support |
| DI | Hilt + KSP | Compile-time safety, first-class ViewModel injection |
| Serialization | Kotlinx Serialization | Kotlin-native, no reflection, multiplatform-ready, KSP codegen |
| Networking | Retrofit + OkHttp | Industry standard, multipart upload support for ImgBB |
| Persistence | Room | Type-safe SQLite wrapper, Flow-based reactive queries |
| Sheets | Google Sheets Java API | Official library, handles service account auth |
| Images | Coil | Kotlin-first, Compose-native image loading |
| Navigation | Compose Navigation | Official Jetpack solution, type-safe routes |
| Typography | Inter (Google Fonts) | Clean, professional dev-tool aesthetic |
| Architecture | Layered (Data → Domain → Presentation) + MVVM | Clean separation, use cases orchestrate, features consume |
- Android Studio Ladybug or later
- JDK 11+
- Android SDK 36 (install via Android Studio SDK Manager)
git clone https://github.com/<your-username>/Bugit.git
cd BugitBugIt uploads bug report screenshots to ImgBB for free image hosting.
- Go to https://api.imgbb.com/
- Sign up or log in
- Your API key is displayed on the page — copy it
BugIt writes bug reports to Google Sheets using a service account.
- Go to Google Cloud Console
- Create a new project (or select an existing one)
- Enable the Google Sheets API:
- Navigate to APIs & Services > Library
- Search for "Google Sheets API"
- Click Enable
- Create a service account:
- Go to APIs & Services > Credentials
- Click Create Credentials > Service Account
- Name it (e.g.
bugit-sheets) and click Done
- Generate a JSON key:
- Click the newly created service account
- Go to the Keys tab
- Click Add Key > Create new key > JSON
- A
.jsonfile will download — this is yourservice_account.json
Alternatively, if you have the gcloud CLI installed:
# Create project and enable Sheets API
gcloud projects create bugit-app --name="BugIt"
gcloud config set project bugit-app
gcloud services enable sheets.googleapis.com
# Create service account and download key
gcloud iam service-accounts create bugit-sheets --display-name="BugIt Sheets"
gcloud iam service-accounts keys create core/network/src/main/res/raw/service_account.json \
--iam-account=bugit-sheets@bugit-app.iam.gserviceaccount.com- Go to Google Sheets and create a new blank spreadsheet
- Click Share (top-right corner)
- Add the service account email as Editor:
The exact email is in your
bugit-sheets@<your-project-id>.iam.gserviceaccount.comservice_account.jsonunderclient_email. - Copy the Spreadsheet ID from the URL:
https://docs.google.com/spreadsheets/d/<SPREADSHEET_ID>/edit
Add your secrets to local.properties in the project root (this file is gitignored):
IMGBB_API_KEY=your_imgbb_api_key
SPREADSHEET_ID=your_google_spreadsheet_idCopy the downloaded service_account.json to:
core/network/src/main/res/raw/service_account.json
This file is gitignored. The app reads it at runtime via R.raw.service_account.
./gradlew assembleDebugOr open the project in Android Studio and run on a device/emulator (API 24+).
Indigo-Teal professional palette with full dark mode support. Dynamic colors on Android 12+, custom fallback below.
Home — Command center with hero banner, quick actions, and recent reports list. Extended FAB collapses to icon-only on scroll.
Report — Focused form with horizontal image attachment strip, dynamically rendered fields, and a fixed bottom submit bar with animated state transitions (idle → loading → success → error).
The :core:ui module provides 13 reusable components: top app bar, text fields, text areas, dropdowns, priority selectors, chip inputs, image strips, thumbnails, image source sheet, loading buttons, empty states, and section headers. All follow Material 3 guidelines.
TBD
