Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Weather Activities

Weather Activities is a native Android take-home project that lets a user search for a city and rank four activities for that location over the next 7 days using Open-Meteo forecast data.

The ranked activities are:

  • Skiing
  • Surfing
  • Outdoor sightseeing
  • Indoor sightseeing

Platform And Tooling

  • Android, Kotlin, Jetpack Compose, Material 3
  • MVVM with Clean Architecture-style package separation
  • Coroutines and StateFlow for async work and state
  • Koin for dependency injection and ViewModel creation
  • JUnit and kotlinx-coroutines-test for unit tests
  • Open-Meteo Geocoding API and Forecast API

The project intentionally avoids Retrofit, Hilt, and larger networking stacks. For this scope, HttpURLConnection behind a small WeatherHttpClient abstraction keeps the networking layer mockable while still allowing HTTP error handling to be tested locally.

Architecture

The code is organized around separate UI, presentation, domain, and data concerns:

  • ui/: Compose screens and small reusable components.
  • presentation/: ViewModels, explicit UI state, and user-facing error mapping.
  • domain/model/: app-level models such as City, DailyForecast, and ActivityRecommendation.
  • domain/usecase/: search, ranking orchestration, and deterministic recommendation logic.
  • domain/repository/: repository contract used by the domain layer.
  • data/api/: mockable Open-Meteo API abstraction, injectable HTTP client, and remote implementation.
  • data/dto/: API transport models.
  • data/mapper/: DTO-to-domain mapping.
  • data/repository/: repository implementation that wraps API calls in Result.
  • di/: Koin module wiring repositories, use cases, and ViewModels.

Presentation state uses:

  • Idle
  • Loading
  • Loaded
  • Empty
  • Error

This makes UI behavior explicit and keeps the ViewModels straightforward to test.

Both ViewModels cancel prior in-flight work before starting a new search or forecast load. They also verify that a completed response still matches the latest query or selected city before mutating state, so a slow stale response cannot overwrite newer user intent.

How To Build And Run

Open the project in Android Studio and run the app configuration on an emulator or device.

From the command line:

./gradlew assembleDebug

On Windows PowerShell:

.\gradlew.bat assembleDebug

If your local Java trust store cannot validate Maven/Google certificates, this environment was verified with:

$env:GRADLE_OPTS='-Djavax.net.ssl.trustStoreType=Windows-ROOT'; .\gradlew.bat assembleDebug

How To Run Tests

./gradlew testDebugUnitTest

On Windows PowerShell:

.\gradlew.bat testDebugUnitTest

Verified locally with:

$env:GRADLE_OPTS='-Djavax.net.ssl.trustStoreType=Windows-ROOT'; .\gradlew.bat testDebugUnitTest

Testing Strategy

The included unit tests cover the most important behavior:

  • ActivityRecommenderTest: deterministic ranking scenarios for snowy/cold, mild/dry, and stormy/wet forecasts.
  • CitySearchViewModelTest: query validation, loaded state, error state, and stale search cancellation.
  • ActivityRankingViewModelTest: selected-city handling, loaded/error/empty states, and stale forecast cancellation.
  • WeatherMapperTest: partial-array handling and null-value filtering.
  • OpenMeteoRemoteDataSourceTest: Open-Meteo error reason parsing, HTTP fallback messages, and null numeric parsing.
  • OpenMeteoWeatherRepositoryTest: city and forecast DTO mapping plus API failure wrapping with a fake data source.

The domain logic has no Android dependency, the repository depends on a mockable WeatherApiDataSource, and the remote data source depends on a mockable WeatherHttpClient, so the important paths are tested without live network calls.

API Usage Notes

City search uses:

https://geocoding-api.open-meteo.com/v1/search

Forecast loading uses:

https://api.open-meteo.com/v1/forecast

Requested daily fields:

  • weather_code
  • temperature_2m_max
  • temperature_2m_min
  • precipitation_probability_max
  • precipitation_sum
  • snowfall_sum
  • wind_speed_10m_max

The app requests forecast_days=7 and timezone=auto.

For non-2xx responses, the app attempts to parse Open-Meteo error JSON and surfaces the reason field when present. If no reason is available, it falls back to Open-Meteo request failed with HTTP <status>.

Forecast JSON preserves null numeric values in DTOs. The mapper filters out incomplete daily rows instead of converting unknown values to 0, because zero is a meaningful weather value and would distort recommendation scores. The domain forecast model remains non-null and simple.

Activity Recommendation Logic

The scoring is deterministic and lives in ActivityRecommender.

High-level assumptions:

  • Skiing benefits from cold average temperatures and snowfall. Very warm temperatures reduce the score.
  • Surfing benefits from warmer temperatures and moderate wind. This is a simplified weather-only proxy because Open-Meteo Forecast API weather data does not include wave height or surf break quality.
  • Outdoor sightseeing benefits from mild temperatures, low precipitation probability, and lower wind.
  • Indoor sightseeing benefits from rain, snow, strong wind, severe weather codes, or uncomfortable temperatures.

Scores are clamped to 0..100 and sorted descending. Ties are stable by activity name, which keeps the output deterministic.

Assumptions

  • City search should start only after at least two characters.
  • Geocoding results are shown as returned by Open-Meteo, limited by the API request count.
  • The forecast API can return partial arrays, so mapping uses the shortest available daily array length.
  • Forecast days with any null required weather field are skipped. If all days are incomplete, the ranking screen shows an empty forecast state.
  • The app does not persist the selected city or forecast between launches.
  • The UI keeps search and ranking in separate modes: search results use the available screen space, then collapse to the selected city when rankings are shown.

Trade-Offs And Omissions

  • No offline cache.
  • No pull-to-refresh.
  • No UI tests or snapshot tests.
  • No location permissions or current-location search.
  • Retry actions are simple buttons for search and forecast failures; there is no exponential backoff or automatic retry.
  • Networking uses HttpURLConnection to avoid extra dependencies. In a larger production app, Retrofit/Ktor plus structured serialization would be reasonable.

Production-Readiness Notes

Before shipping this to production, I would add:

  • Structured JSON serialization with stronger schema handling.
  • Observability for network failures and recommendation outcomes.
  • Offline cache with freshness metadata.
  • UI tests for the main user flow.
  • Better accessibility review and larger-screen layout refinements.
  • More nuanced activity scoring, especially real surf/ocean data.
  • Dependency update policy and CI running unit tests and assemble on every PR.
  • More advanced cancellation/debouncing around search input if product requirements shift toward live-as-you-type search.

Cross-Platform Delivery Notes

This implementation is Android-only, as requested. The domain model and recommendation rules are intentionally platform-independent Kotlin, which would make it possible to share or port the core scoring behavior to iOS later. The UI, ViewModels, and data-source implementation are Android-specific.

AI Usage Disclosure

AI assistance was used to implement this codebase, tests, and README. Verification was performed with local Gradle unit tests and debug build commands; AI output was not treated as a substitute for compilation or tests.

About

Android weather app that ranks activities using Open-Meteo forecasts. Built with Kotlin, Jetpack Compose, MVVM, Clean Architecture, Coroutines, and Koin.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages