Skip to content

feat: initial build — weather app with widget, WorkManager, and settings UI#1

Merged
wassupluke merged 9 commits into
mainfrom
feature/initial-build
Mar 25, 2026
Merged

feat: initial build — weather app with widget, WorkManager, and settings UI#1
wassupluke merged 9 commits into
mainfrom
feature/initial-build

Conversation

@wassupluke
Copy link
Copy Markdown
Owner

@wassupluke wassupluke commented Mar 24, 2026

Summary

  • Full Kotlin + Compose Android weather app backed by Open-Meteo (no API key)
  • Home screen Glance widget showing current temperature (stored as °C, displayed in user-selected unit)
  • Settings screen: GPS location toggle, °F/°C selector, update interval picker
  • WorkManager periodic background fetch with CONNECTED network constraint
  • DataStore for settings and weather cache; no Room, no Hilt

Architecture

WorkManager ──→ WeatherRepository ──→ Open-Meteo API
                                  ↓
                             DataStore (cache)
                                  ↓
                   SettingsViewModel / WeatherWidget

Two separate Retrofit instances for weather (api.open-meteo.com) and geocoding (geocoding-api.open-meteo.com).

Test Plan

  • ./gradlew :app:testDebugUnitTest — all unit tests pass (Robolectric, no emulator needed)
  • Build: ./gradlew assembleDebug succeeds
  • Install on device/emulator — settings screen appears on launch
  • Enable "Use device location" toggle → permission dialog → grant → location saved
  • Add widget from home screen → shows --° until first fetch
  • WorkManager fetch completes → widget updates to show temperature
  • Tap widget → opens settings screen

Summary by Sourcery

Introduce an initial Android SimpleWeather app with a Compose-based settings UI, a Glance home screen temperature widget, and background weather fetching via WorkManager backed by Open-Meteo APIs and DataStore.

New Features:

  • Add Compose-based settings screen for location selection, temperature units, and background update interval configuration.
  • Add Glance home screen widget that displays the latest temperature and opens the app on tap.
  • Add background weather fetch pipeline using WorkManager to periodically update cached weather data and refresh the widget.
  • Add WeatherRepository and DataStore-backed persistence for settings, location, and cached temperature using Open-Meteo weather and geocoding APIs.
  • Add dynamic Material3 theme setup and basic app resources including manifest, strings, and themes.

Enhancements:

  • Set up Gradle build configuration, dependency versions catalog, and Android app module wiring for Compose, Glance, WorkManager, DataStore, Retrofit, and testing.
  • Provide basic project documentation outlining the app overview and future feature wishlist.

Tests:

  • Add unit tests for WeatherRepository, WeatherDataStore, Open-Meteo API models, and SettingsViewModel using Robolectric, coroutines test utilities, and MockK.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 24, 2026

Reviewer's Guide

Initial implementation of the SimpleWeather Android app: a Compose-based settings UI backed by DataStore and a WeatherRepository that talks to Open-Meteo via Retrofit, a Glance home-screen widget that reads cached temperature, and a WorkManager-powered periodic worker scheduled via a WorkScheduler helper, plus the full Gradle/manifest/theme wiring and Robolectric unit tests.

Sequence diagram for enabling device location and saving it

sequenceDiagram
    actor User
    participant MainActivity
    participant SettingsScreen
    participant SettingsViewModel
    participant FusedLocationClient
    participant DataStore
    participant WorkScheduler

    User->>MainActivity: Launch app
    MainActivity->>SettingsScreen: setContent SimpleWeatherTheme SettingsScreen
    SettingsScreen->>SettingsViewModel: collect uiState

    User->>SettingsScreen: Toggle useDeviceLocation ON
    SettingsScreen->>SettingsScreen: Request ACCESS_COARSE_LOCATION
    User-->>SettingsScreen: Grant permission
    SettingsScreen->>SettingsViewModel: setUseDeviceLocation(true)

    SettingsScreen->>MainActivity: onLocationPermissionGranted()
    MainActivity->>FusedLocationClient: getLastLocation()
    FusedLocationClient-->>MainActivity: Location(lat, lon)
    MainActivity->>SettingsViewModel: saveDeviceLocation(lat, lon)

    SettingsViewModel->>DataStore: edit set LOCATION_LAT, LOCATION_LON, LOCATION_DISPLAY_NAME
    SettingsViewModel->>DataStore: read UPDATE_INTERVAL_MINUTES
    SettingsViewModel->>WorkScheduler: schedule(context, intervalMinutes)
Loading

Sequence diagram for background fetch updating widget

sequenceDiagram
    participant WorkManager
    participant WeatherFetchWorker
    participant DataStore
    participant WeatherRepository
    participant OpenMeteoAPI
    participant WeatherWidget

    WorkManager->>WeatherFetchWorker: doWork()
    WeatherFetchWorker->>DataStore: read LOCATION_LAT, LOCATION_LON
    DataStore-->>WeatherFetchWorker: lat, lon

    WeatherFetchWorker->>WeatherRepository: fetchAndCacheWeather(lat, lon)
    WeatherRepository->>OpenMeteoAPI: getCurrentWeather(latitude, longitude, current, temperature_unit)
    OpenMeteoAPI-->>WeatherRepository: WeatherResponse
    WeatherRepository->>DataStore: edit LAST_TEMP_CELSIUS, LAST_UPDATED_EPOCH

    WeatherFetchWorker->>WeatherWidget: updateAll(context)
    WeatherWidget->>DataStore: read LAST_TEMP_CELSIUS, TEMP_UNIT
    DataStore-->>WeatherWidget: tempCelsius, unit
    WeatherWidget->>WeatherWidget: compute displayTemp
Loading

Class diagram for core SimpleWeather types and data layer

classDiagram
    class MainActivity {
        +onCreate(savedInstanceState: Bundle): void
        +fetchAndSaveDeviceLocation(): void
        -viewModel: SettingsViewModel
    }

    class SimpleWeatherTheme {
        +SimpleWeatherTheme(content: () -> Unit): void
    }

    class SettingsUiState {
        +useDeviceLocation: Boolean
        +locationDisplayName: String
        +tempUnit: String
        +updateIntervalMinutes: Int
    }

    class SettingsViewModel {
        -context: Context
        -repository: WeatherRepository
        -dispatcher: CoroutineDispatcher
        +uiState: StateFlow~SettingsUiState~
        +setTempUnit(unit: String): void
        +setUpdateInterval(minutes: Int): void
        +setUseDeviceLocation(use: Boolean): void
        +resolveAndSaveLocation(query: String): void
        +saveDeviceLocation(lat: Float, lon: Float): void
    }

    class WeatherRepository {
        -context: Context
        -weatherService: OpenMeteoService
        -geocodingService: OpenMeteoService
        +fetchAndCacheWeather(lat: Float, lon: Float): void
        +geocodeLocation(query: String): GeocodingResult
    }

    class RepositoryGeocodingResult {
        +lat: Float
        +lon: Float
        +displayName: String
    }

    class WeatherDataStore {
        +USE_DEVICE_LOCATION: PreferencesKey~Boolean~
        +LOCATION_LAT: PreferencesKey~Float~
        +LOCATION_LON: PreferencesKey~Float~
        +LOCATION_DISPLAY_NAME: PreferencesKey~String~
        +TEMP_UNIT: PreferencesKey~String~
        +UPDATE_INTERVAL_MINUTES: PreferencesKey~Int~
        +LAST_TEMP_CELSIUS: PreferencesKey~Float~
        +LAST_UPDATED_EPOCH: PreferencesKey~Long~
    }

    class WeatherFetchWorker {
        -applicationContext: Context
        +doWork(): Result
    }

    class WorkScheduler {
        +schedule(context: Context, intervalMinutes: Int): void
        +cancel(context: Context): void
    }

    class WeatherWidget {
        +provideGlance(context: Context, id: GlanceId): void
        -celsiusToFahrenheit(celsius: Float): Int
    }

    class WeatherWidgetReceiver {
        +glanceAppWidget: GlanceAppWidget
    }

    class NetworkModule {
        +weatherService: OpenMeteoService
        +geocodingService: OpenMeteoService
    }

    class OpenMeteoService {
        +getCurrentWeather(latitude: Float, longitude: Float, current: String, temperatureUnit: String): WeatherResponse
        +searchLocation(query: String, count: Int, language: String, format: String): GeocodingResponse
    }

    class WeatherResponse {
        +current: CurrentWeather
    }

    class CurrentWeather {
        +time: String
        +temperatureCelsius: Float
    }

    class GeocodingResponse {
        +results: List~ApiGeocodingResult~
    }

    class ApiGeocodingResult {
        +name: String
        +latitude: Double
        +longitude: Double
        +country: String
        +state: String
    }

    MainActivity --> SettingsViewModel
    MainActivity ..> SimpleWeatherTheme
    SettingsViewModel --> SettingsUiState
    SettingsViewModel --> WeatherRepository
    SettingsViewModel --> WeatherDataStore
    SettingsViewModel ..> WorkScheduler

    WeatherRepository ..> OpenMeteoService
    WeatherRepository --> RepositoryGeocodingResult
    WeatherRepository --> WeatherDataStore

    WeatherFetchWorker ..> WeatherRepository
    WeatherFetchWorker ..> NetworkModule
    WeatherFetchWorker --> WeatherDataStore

    WorkScheduler ..> WeatherFetchWorker

    WeatherWidget --> WeatherDataStore
    WeatherWidgetReceiver --> WeatherWidget

    NetworkModule --> OpenMeteoService
    OpenMeteoService --> WeatherResponse
    OpenMeteoService --> GeocodingResponse
    WeatherResponse --> CurrentWeather
    GeocodingResponse --> ApiGeocodingResult
Loading

File-Level Changes

Change Details Files
Introduce app module configuration and shared version catalog for a Kotlin + Compose + WorkManager + Glance Android application.
  • Configure Android application Gradle module with Compose, Kotlin, WorkManager, Glance, Retrofit, DataStore, and testing dependencies.
  • Add top-level Gradle build, settings, wrapper scripts, and Gradle properties using a centralized libs.versions.toml version catalog.
  • Document project overview and wishlist items in the README.
app/build.gradle.kts
build.gradle.kts
settings.gradle.kts
gradle/libs.versions.toml
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat
gradle.properties
README.md
Implement persistent settings and cached weather storage via DataStore and a WeatherRepository over Open-Meteo APIs.
  • Define preference-backed WeatherDataStore keys and extension DataStore instance on Context.
  • Implement WeatherRepository to fetch current temperature from Open-Meteo, cache it in DataStore, and perform geocoding searches returning a simplified result model.
  • Add Kotlinx Serialization models and Retrofit service interface for Open-Meteo weather and geocoding endpoints, and a NetworkModule that builds two Retrofit clients with shared OkHttp and JSON configuration.
app/src/main/java/com/example/simpleweather/data/WeatherDataStore.kt
app/src/main/java/com/example/simpleweather/data/WeatherRepository.kt
app/src/main/java/com/example/simpleweather/data/api/WeatherApiModels.kt
app/src/main/java/com/example/simpleweather/data/api/OpenMeteoService.kt
app/src/main/java/com/example/simpleweather/data/api/NetworkModule.kt
Create a Compose-based settings UI and ViewModel that read/write DataStore and control background work and location behavior.
  • Add SettingsUiState and SettingsViewModel that expose settings via StateFlow, write updates to DataStore, perform geocoding, and schedule or cancel WorkManager jobs through WorkScheduler.
  • Implement SettingsScreen composable with sections for device/manual location, temperature unit segmented control, and radio-button update interval picker, including runtime location permission handling.
  • Wire MainActivity to host the SettingsScreen with SimpleWeatherTheme, construct WeatherRepository and SettingsViewModel manually, trigger initial work scheduling, and integrate fused-location retrieval when permission is granted.
app/src/main/java/com/example/simpleweather/ui/settings/SettingsViewModel.kt
app/src/main/java/com/example/simpleweather/ui/settings/SettingsScreen.kt
app/src/main/java/com/example/simpleweather/ui/MainActivity.kt
app/src/main/java/com/example/simpleweather/ui/theme/SimpleWeatherTheme.kt
Add a Glance-based home screen widget that displays cached temperature and opens the app, plus a periodic worker and scheduler for background updates.
  • Implement WeatherWidget GlanceAppWidget that reads cached temperature and unit from DataStore, converts Celsius to Fahrenheit as needed, and renders a tappable temperature text that launches MainActivity.
  • Add WeatherWidgetReceiver and widget provider XML metadata, and register the receiver in the manifest.
  • Introduce WeatherFetchWorker CoroutineWorker that reads stored coordinates, calls WeatherRepository.fetchAndCacheWeather, and triggers widget updates via Glance.
  • Provide a WorkScheduler helper to manage unique periodic weather-fetch work with a CONNECTED network constraint.
  • Declare required permissions and main activity/receiver setup in AndroidManifest, and basic app resources (theme, strings, widget info).
app/src/main/java/com/example/simpleweather/widget/WeatherWidget.kt
app/src/main/java/com/example/simpleweather/widget/WeatherWidgetReceiver.kt
app/src/main/java/com/example/simpleweather/worker/WeatherFetchWorker.kt
app/src/main/java/com/example/simpleweather/worker/WorkScheduler.kt
app/src/main/AndroidManifest.xml
app/src/main/res/xml/weather_widget_info.xml
app/src/main/res/values/strings.xml
app/src/main/res/values/themes.xml
Add Robolectric and unit tests for DataStore integration, repository behavior, API models, and settings ViewModel logic.
  • Test WeatherRepository weather fetch caching and geocoding result mapping using mock OpenMeteoService instances.
  • Verify WeatherDataStore preference defaults and read/write behavior via DataStore flows.
  • Validate OpenMeteoService JSON deserialization for weather and geocoding responses using kotlinx.serialization.
  • Test SettingsViewModel interactions with DataStore for updating temperature unit and update interval using a TestDispatcher and collecting uiState.
app/src/test/java/com/example/simpleweather/data/WeatherRepositoryTest.kt
app/src/test/java/com/example/simpleweather/data/WeatherDataStoreTest.kt
app/src/test/java/com/example/simpleweather/data/api/OpenMeteoServiceTest.kt
app/src/test/java/com/example/simpleweather/ui/settings/SettingsViewModelTest.kt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The useDeviceLocation toggle only updates a boolean in DataStore and doesn’t affect the scheduled WorkManager job, so background fetches will continue using the last lat/lon even when the user turns location off; consider cancelling or pausing the periodic work when location is disabled or cleared.
  • resolveAndSaveLocation writes a new lat/lon but doesn’t schedule WeatherFetchWorker, so manual location entry won’t start background updates until the app is restarted or the interval is changed; you may want to schedule (or reschedule) work immediately after a successful geocode.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `useDeviceLocation` toggle only updates a boolean in DataStore and doesn’t affect the scheduled `WorkManager` job, so background fetches will continue using the last lat/lon even when the user turns location off; consider cancelling or pausing the periodic work when location is disabled or cleared.
- `resolveAndSaveLocation` writes a new lat/lon but doesn’t schedule `WeatherFetchWorker`, so manual location entry won’t start background updates until the app is restarted or the interval is changed; you may want to schedule (or reschedule) work immediately after a successful geocode.

## Individual Comments

### Comment 1
<location path="app/src/main/java/com/example/simpleweather/ui/settings/SettingsScreen.kt" line_range="51-52" />
<code_context>
+                modifier = Modifier.fillMaxWidth()
+            ) {
+                Text("Use device location", modifier = Modifier.weight(1f))
+                Switch(
+                    checked = uiState.useDeviceLocation,
+                    onCheckedChange = { use ->
+                        viewModel.setUseDeviceLocation(use)
</code_context>
<issue_to_address>
**issue:** Handle the case where location permission is denied so the toggle doesn’t get out of sync with reality.

If the user enables "Use device location" but then denies the system permission dialog, `useDeviceLocation` remains `true` in DataStore even though the app still lacks permission. That desynchronizes the toggle from actual capability. Update the `locationPermissionLauncher` callback to revert the switch and preference when `granted` is `false`, or only call `setUseDeviceLocation(true)` after a successful permission result instead of doing it optimistically.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@wassupluke
Copy link
Copy Markdown
Owner Author

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • WeatherRepository is constructed in both MainActivity and WeatherFetchWorker with nearly identical wiring; consider extracting a small provider/factory so the construction is centralized and easier to change.
  • WorkScheduler.schedule accepts any intervalMinutes but WorkManager enforces a 15‑minute minimum; it would be safer to validate or clamp the value in schedule() rather than rely on callers to only pass valid values.
  • Temperature units ("C"/"F") and default values like 60 minutes are hard-coded in multiple places; defining them as shared constants (e.g., in WeatherDataStore or a Config object) would reduce duplication and the risk of inconsistency.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- WeatherRepository is constructed in both MainActivity and WeatherFetchWorker with nearly identical wiring; consider extracting a small provider/factory so the construction is centralized and easier to change.
- WorkScheduler.schedule accepts any intervalMinutes but WorkManager enforces a 15‑minute minimum; it would be safer to validate or clamp the value in schedule() rather than rely on callers to only pass valid values.
- Temperature units ("C"/"F") and default values like 60 minutes are hard-coded in multiple places; defining them as shared constants (e.g., in WeatherDataStore or a Config object) would reduce duplication and the risk of inconsistency.

## Individual Comments

### Comment 1
<location path="app/src/main/java/com/example/simpleweather/ui/MainActivity.kt" line_range="65-67" />
<code_context>
+        }
+    }
+
+    /** Called from SettingsScreen when location permission is granted */
+    @SuppressLint("MissingPermission")
+    fun fetchAndSaveDeviceLocation() {
+        val fusedClient = LocationServices.getFusedLocationProviderClient(this)
+        fusedClient.lastLocation.addOnSuccessListener { location ->
</code_context>
<issue_to_address>
**🚨 issue (security):** Consider explicitly checking location permission instead of relying only on the suppress lint annotation.

Because this method is only protected by `@SuppressLint("MissingPermission")`, it assumes `ACCESS_COARSE_LOCATION` is always granted when called. If it’s reused, called from another path, or the permission is revoked, it may hit a `SecurityException` at runtime. Keep the suppress annotation if needed, but also check the permission with `ContextCompat.checkSelfPermission` (or defensively catch `SecurityException`) before calling `lastLocation` to avoid crashes when the permission state changes.
</issue_to_address>

### Comment 2
<location path="app/src/main/java/com/example/simpleweather/ui/settings/SettingsViewModel.kt" line_range="44-47" />
<code_context>
+        }
+    }
+
+    fun setUpdateInterval(minutes: Int) {
+        viewModelScope.launch(dispatcher) {
+            context.dataStore.edit { it[WeatherDataStore.UPDATE_INTERVAL_MINUTES] = minutes }
+            val prefs = context.dataStore.data.first()
+            val lat = prefs[WeatherDataStore.LOCATION_LAT]
+            val lon = prefs[WeatherDataStore.LOCATION_LON]
</code_context>
<issue_to_address>
**suggestion:** Avoid re-reading DataStore and duplicating scheduling logic in each setter.

In `setUpdateInterval` you do an `edit` and then call `dataStore.data.first()` just to immediately read lat/lon for scheduling. The same `dataStore.data.first()` + `WorkScheduler.schedule` pattern appears in `resolveAndSaveLocation` and `saveDeviceLocation`. Extract a suspend helper (e.g. `private suspend fun scheduleIfLocationPresent()`) that reads lat/lon and schedules work, and call it from these methods to avoid duplicate reads and scattered scheduling logic.

Suggested implementation:

```
    fun setTempUnit(unit: String) {
        viewModelScope.launch(dispatcher) {
            context.dataStore.edit { it[WeatherDataStore.TEMP_UNIT] = unit }
        }
    }

    private suspend fun scheduleIfLocationPresent() {
        val prefs = context.dataStore.data.first()
        val lat = prefs[WeatherDataStore.LOCATION_LAT]
        val lon = prefs[WeatherDataStore.LOCATION_LON]
        val intervalMinutes = prefs[WeatherDataStore.UPDATE_INTERVAL_MINUTES] ?: 60
        if (lat != null && lon != null) {
            WorkScheduler.schedule(context, intervalMinutes)
        }
    }

    fun setUpdateInterval(minutes: Int) {
        viewModelScope.launch(dispatcher) {
            context.dataStore.edit { it[WeatherDataStore.UPDATE_INTERVAL_MINUTES] = minutes }
            scheduleIfLocationPresent()
        }
    }

```

To fully implement your suggestion and avoid duplicated scheduling logic:
1. In `resolveAndSaveLocation`, remove the inline `dataStore.data.first()` + `WorkScheduler.schedule(...)` block and replace it with a call to `scheduleIfLocationPresent()`, invoked from within the same coroutine scope where you perform the `dataStore.edit` calls.
2. In `saveDeviceLocation`, likewise remove the duplicated `dataStore.data.first()` + `WorkScheduler.schedule(...)` logic and replace it with `scheduleIfLocationPresent()`, again ensuring it's called after the relevant `dataStore.edit` completes and still within a coroutine.
3. If either of those methods currently passes an explicit interval value to `WorkScheduler.schedule`, rely instead on the stored `UPDATE_INTERVAL_MINUTES` preference (now read inside `scheduleIfLocationPresent`), or adjust `scheduleIfLocationPresent` to accept an explicit interval parameter if your scheduling semantics require it.
</issue_to_address>

### Comment 3
<location path="app/src/main/java/com/example/simpleweather/data/WeatherRepository.kt" line_range="34-35" />
<code_context>
+            format = "json"
+        )
+        val first = response.results?.firstOrNull() ?: return null
+        val displayName = listOfNotNull(first.name, first.state).joinToString(", ")
+        return GeocodingResult(
+            lat = first.latitude.toFloat(),
</code_context>
<issue_to_address>
**suggestion:** Improve geocoding display name fallback to avoid empty or overly terse labels.

Since `displayName` only uses `name` and `state`, it can end up empty if both are missing, and it also omits `country`, which can help disambiguate locations. Consider including `country` in the composition and/or as a fallback, e.g. `listOfNotNull(first.name, first.state ?: first.country)`, so labels are never empty and are more informative.

```suggestion
        val first = response.results?.firstOrNull() ?: return null
        val displayName = listOfNotNull(
            first.name,
            first.state ?: first.country,
            first.country
        ).joinToString(", ").ifBlank { first.country ?: query }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread app/src/main/java/com/wassupluke/simpleweather/ui/MainActivity.kt
Comment thread app/src/main/java/com/example/simpleweather/ui/settings/SettingsViewModel.kt Outdated
Comment thread app/src/main/java/com/example/simpleweather/data/WeatherRepository.kt Outdated
Kotlin 2.0.21, Compose BOM 2024.12.01, Glance 1.1.1, WorkManager 2.10,
DataStore 1.1, Retrofit 2.11 + kotlinx.serialization. Min SDK 26 / Target 35.
Includes GitHub Actions CI check and release workflows, lint rules.
- WeatherDataStore: preference keys for location, temp unit, widget color, cache
- Open-Meteo API models, Retrofit service, and dual-instance NetworkModule
  (separate weather + geocoding base URLs, no default params per Retrofit constraint)
- WeatherRepository: fetchAndCacheWeather(), geocodeLocation(), reverseGeocodeLocation()
  (reverse geocoding uses Android Geocoder to resolve lat/lon to city, state)
- WeatherFetchWorker: CoroutineWorker that fetches and caches weather, then
  triggers WeatherWidget.updateAll(); returns Result.failure() if no location set
- WorkScheduler: enqueueUniquePeriodicWork with UPDATE policy, requires CONNECTED
  network; schedule() guarded to only run when lat/lon are present in DataStore
Widget reads temp, unit, and text color via collectAsState() on the DataStore
flow inside provideContent, so any setting change recomposes immediately without
waiting for the next scheduled fetch.
- SettingsViewModel: StateFlow<SettingsUiState> backed by DataStore; all
  setting changes that affect the widget call WeatherWidget.updateAll()
- SettingsScreen: location toggle (full-row tappable), manual location entry,
  F/C segmented button, interval dropdown, widget text color with live preview;
  stateless SettingsScreenContent composable enables Compose Previews
- MainActivity: FusedLocationProviderClient for device location; reverse
  geocodes lat/lon to city/state display name; WorkManager init on launch
- Shared SetButton composable for consistent trailing-icon button styling
- String resources follow action_/title_/label_/hint_/error_ naming convention
All tests use RobolectricTestRunner + ApplicationProvider. DataStore tests use
@before clearDataStore() to prevent cross-test state pollution. ViewModel tests
inject StandardTestDispatcher and use backgroundScope + advanceUntilIdle()
to activate WhileSubscribed(5000) and avoid Thread.sleep().
@wassupluke wassupluke force-pushed the feature/initial-build branch from dc7d15a to 31b3004 Compare March 25, 2026 03:32
@wassupluke wassupluke merged commit bc30735 into main Mar 25, 2026
3 checks passed
@wassupluke wassupluke deleted the feature/initial-build branch March 25, 2026 03:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant