Habit Builder

repository·main·Indexed 43 days ago

https://github.com/ofalvai/habitbuilder

A minimal, Compose-only Android application for tracking habits and visualizing progress through streaks and calendar heatmaps. Features include local on-device database persistence, data import/export, and a customizable habit list layout.

Tokens
2.6K
Snippets
7
Records
12
Agent score
55%

What's inside Habit Builder

  1. Overview of Habit Builder features

    main

    Habit Builder is a minimal habit tracking Android application. Key features include:

    • Habit Tracking: Monitor new habits and track progress.
    • Statistics:
      • Per-habit: Completion rate, weekly/overall counts, and streaks.
      • Overall: Calendar heatmap, top habits, and top days for habits.
    • Customization: Reorderable and customizable habit list layouts.
    • Data Management: Local on-device database persistence, with support for exporting, importing, and archiving old items.
  2. Useful Gradle tasks for development

    main

    The project includes several custom Gradle tasks to assist with development, analysis, and reporting:

    • analyzeReleaseBundle: Runs the Spotify Ruler plugin to measure app size and library contributions.
    • licenseeRelease: Performs a license check and generates a report at app/build/reports/licensee/release/artifacts.json. This file should be copied to assets/licenses.json to populate the app's Licenses screen.
    • assembleRelease -Phabittracker.enableComposeCompilerReports=true: Generates Compose compiler metrics, which are written to $module/build/compose_metrics.
    • buildHealth: Runs dependency analysis to detect unused dependencies and incorrect module structures.
    # Run Spotify Ruler
    ./gradlew analyzeReleaseBundle
    
    # Run license check
    ./gradlew licenseeRelease
    
    # Generate Compose compiler metrics
    ./gradlew assembleRelease -Phabittracker.enableComposeCompilerReports=true
    
    # Run dependency analysis
    ./gradlew buildHealth
  3. Use the Telemetry interface for event tracking

    main

    The Telemetry interface provides a unified way to track application events, log non-fatal errors, and leave breadcrumbs for debugging. It abstracts the underlying crash reporting implementation (such as Bugsnag) and respects the crashReportingEnabled setting from AppPreferences.

    Methods

    • initialize(): Sets up the telemetry system. If crash reporting is disabled in AppPreferences, this method does nothing.
    • logNonFatal(e: Throwable): Logs a non-fatal error. It always logs the error to Logcat at ERROR priority, but only notifies the crash reporting service (e.g., Bugsnag) if crashReportingEnabled is true.
    • leaveBreadcrumb(message: String, metadata: Map<String, Any>, type: BreadcrumbType): Adds a breadcrumb to the session to help reconstruct the state leading up to an error. Breadcrumbs are only sent to the crash reporting service if crashReportingEnabled is true.

    When calling leaveBreadcrumb, use one of the following BreadcrumbType values to categorize the event:

    • Navigation: For tracking screen transitions.
    • State: For tracking changes in application state.
    • UserAction: For tracking specific user interactions.
    // Example usage of Telemetry
    telemetry.initialize()
    
    try {
        // ... some operation
    } catch (e: Throwable) {
        telemetry.logNonFatal(e)
    }
    
    telemetry.leaveBreadcrumb(
        message = "User clicked login button",
        metadata = mapOf("button_id" to "login_btn"),
        type = Telemetry.BreadcrumbType.UserAction
    )
  4. Use the AndroidLibraryConventionPlugin for Android library modules

    main

    The AndroidLibraryConventionPlugin is a custom Gradle convention plugin designed to standardize the configuration of Android library modules within the project. When applied to a module, it automatically performs the following setup:

    1. Applies core plugins:
      • com.android.library (Android Library plugin)
      • org.jetbrains.kotlin.android (Kotlin Android plugin)
      • org.gradle.android.cache-fix
    2. Configures Android SDKs: Sets compileSdk and minSdk using project-wide constants.
    3. Configures JVM targets: Sets JVM targets for both compileOptions and kotlinOptions using project-wide shared helpers.
    4. Configures Kotlin compiler arguments: Adds -opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi to the freeCompilerArgs.
    5. Sets up Toolchains: Configures the JVM toolchain.
    6. Adds Desugaring: Automatically adds the coreLibraryDesugaring dependency using the gradle.android.desugar library from the libs version catalog.
    // In a module's build.gradle.kts
    plugins {
        id("AndroidLibraryConventionPlugin")
    }
  5. Use StreamOpener to access system streams and files

    main

    The StreamOpener interface provides an abstraction over Android's Uri and ContentResolver. This allows you to open input and output streams for files or system resources using standard Java URI objects, making it possible to use these operations within ViewModels or other non-Android-dependent layers without direct dependencies on Android classes.

    To use it, call openInputStream(uri) or openOutputStream(uri) with a java.net.URI.

    // Example usage of the StreamOpener interface
    val inputStream = streamOpener.openInputStream(java.net.URI("content://com.example.provider/file.txt"))
    val outputStream = streamOpener.openOutputStream(java.net.URI("content://com.example.provider/new_file.txt"))
  6. Manage user onboarding flow with OnboardingManager

    main

    The OnboardingManager class tracks the user's progress through the onboarding process. It uses AppPreferences to persist completion flags for specific milestones. The current onboarding state is exposed via a MutableStateFlow<OnboardingState?>. If the flow is complete, the state is null.

    To advance the onboarding state, call the corresponding milestone methods. Each method updates the persistent preference and emits a new state to the state flow.

    // Example of observing and advancing onboarding state
    OnboardingManager.state.collect { onboardingState ->
        if (onboardingState != null) {
            // Show onboarding UI for step: onboardingState.step
        } else {
            // Onboarding is complete
        }
    }
    
    // When a milestone is reached, trigger the update
    OnboardingManager.firstHabitCreated()
  7. Apply the AndroidLibraryComposeConventionPlugin

    main

    The AndroidLibraryComposeConventionPlugin is a Gradle convention plugin designed to automate the configuration of Jetpack Compose within Android Library modules. When applied to a project, it performs the following actions:

    1. Enables Compose: Sets buildFeatures.compose = true in the LibraryExtension.
    2. Configures Compiler Version: Automatically sets the composeOptions.kotlinCompilerExtensionVersion using the compose-compiler version defined in your Version Catalog (libs).
    3. Configures Kotlin Compiler Args: Appends Compose metrics parameters (via buildComposeMetricsParameters()) to the kotlinOptions.freeCompilerArgs.
    4. Injects Dependencies: Automatically adds the following dependencies using the libs Version Catalog:
      • implementation(platform(libs.findLibrary("compose.bom")))
      • api(libs.findLibrary("compose.material3"))
      • implementation(libs.findLibrary("compose.ui.ui"))
      • implementation(libs.findLibrary("compose.animation"))
      • implementation(libs.findLibrary("compose.ui.toolingpreview"))
      • debugImplementation(libs.findLibrary("compose.ui.tooling"))
      • implementation(libs.findLibrary("showkase.annotation"))

    Note: This plugin requires a Version Catalog named libs to be present in the project to resolve the necessary versions and libraries.

    // In your Android library's build.gradle.kts
    plugins {
        id("habitbuilder.android.library.compose") // Note: The exact ID depends on how the plugin is registered in the build-logic plugin registry
    }
  8. OnboardingManager milestone methods

    main

    Use these methods to mark specific onboarding milestones as completed. Calling these methods updates the underlying AppPreferences and refreshes the state flow.

    • firstHabitCreated(): Marks that the user has created their first habit.
    • firstActionCompleted(): Marks that the user has completed their first action.
    • habitDetailsOpened(): Marks that the user has opened habit details.
    • insightsOpened(): Marks that the user has opened the insights screen.