Giter Site home page Giter Site logo

weatherapp's Introduction

Weather App with Arrow + Compose Desktop

Based on the How to Build an MVI Clean Code Weather App tutorial by Philipp Lackner. The Weather domain model is heavily based on his original implementation.

This repository contains an implementation of a small weather forecast application using functional style, as described in Arrow's design section and the book Functional Ideas for the Curious Kotliner.

The application uses Open-Meteo to gather forecast data, following the original tutorial. GeoIP2 is used to map IPs to locations, since we don't use location services.

Compose Desktop

The application is implemented in Compose Multiplatform Desktop instead of Android. The main reason is being able to use experimental Kotlin features, which are only available in the JVM back-end. Furthermore, it makes it possible for everybody to check the application, even if they don't own an Android phone nor want to download a simulator.

State as sealed interface

The original tutorial uses a class with nullable fields to represent the different states of the application (loading, error, success).

data class WeatherState(
    val isLoading: Boolean, 
    val weatherInfo: WeatherInfo?, 
    val error: String?
)

Our implementation uses sealed interfaces instead. Each state gets its own type, making it impossible to represent invalid states,

sealed interface WeatherState {
    data object Loading : WeatherState
    data class Error(val error: String) : WeatherState
    data class Ok(val place: String?, val weatherInfo: WeatherInfo) : WeatherState
}

Context receivers

Our implementation doesn't use dependency injection framework, as opposed to most Android applications, which use Hilt. Instead, the dependencies are represented as context receivers,

context(WeatherRepository, LocationTracker)
class WeatherViewModel { /* implementation */ }

The actual injection of dependencies is performed manually in the entry point,

suspend fun <A> injectDependencies(
    block: context(WeatherRepository, LocationTracker) () -> A
): A = resourceScope {
    val weather: WeatherRepository = WeatherRepositoryImpl(autoCloseable { WeatherApi() })
    val location: LocationTracker = autoCloseable { LocationTrackerImpl() }
    block(weather, location)
}

Another advantage of this approach, apart from the speed gains at both compile and run time, is that resources are managed correctly using Arrow's resourceScope. This is often a convoluted task when using dependency injection frameworks -- when are instances actually created and disposed -- whereas here everything is explicit.

Lifecycle as CoroutineScope context

Jetpack Compose encourages to keep the activity state in a ViewModel. One of the main benefits of this approach is that ViewModels are lifecycle-aware. For example, if you launch a concurrent coroutine and the activity is then closed, the coroutine is automatically cancelled.

This ability comes in a great deal from the structured concurrency guarantees from Kotlin's coroutines. If you capture a CoroutineScope, you can launch new coroutines tied to the lifecycle of that scope. This is exactly what we do in our ViewModel,

context(/* other contexts */, CoroutineScope)
class WeatherViewModel {
    /* ... */
    
    fun loadWeatherInfo() {
        // 'launch' comes from the CoroutineScope
        launch(Dispatchers.IO) {
            /* ... */
        }
    }
}

In our case we want to tie the lifecycle of the ViewModel to that of the entire application. The CoroutineScope comes from the outermost call to SuspendApp.

Arrow DSLs

We've already mentioned that resourceScope is used to correctly manage resource acquisition and disposal. This is one of Arrow's DSLs, each of them providing additional features within a certain scope. The other one used heavily within this application are typed errors.

The implementation of LocationTracker showcases how the DSLs can be used and combined.

Tests with Turbine

One of the advantages of having a Flow as source of truth for our application is the availability of specialized testing libraries. In particular, Turbine allows us to specify how the flow should evolve over time.

For example, one of our tests simulates that our location tracking is failing by providing a LocationTracker instance that always returns null. In that case, we know that the expected turn of events is loading, and then error.

"errors when location is down" {
    // set up WeatherViewModel with a LocationTracker that always fails
    model.state.test {
        awaitItem().shouldBeInstanceOf<WeatherState.Loading>()
        model.loadWeatherInfo()
        awaitItem().shouldBeInstanceOf<WeatherState.Error>()
    }
}

Another tool in our tests is property-based testing, brought by Kotest. Shortly, property- based testing executes the same tests several times with arbitrary data, ensuring that more complex conditions and corner cases are covered. By using their reflective generators, starting with a random location and weather data is quite simple.

checkAll(
    Arb.bind<Location>(),
    Arb.list(Arb.bind<WeatherData>(), 24..48)
) { location, weatherData -> /* test */ }

weatherapp's People

Contributors

renovate[bot] avatar serras avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

weatherapp's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • Update dependency app.cash.turbine:turbine to v1.1.0

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

gradle
gradle.properties
settings.gradle.kts
build.gradle.kts
gradle/libs.versions.toml
  • org.jetbrains.kotlinx:kotlinx-datetime 0.4.1
  • org.jetbrains.kotlinx:kotlinx-serialization-json 1.6.0
  • io.ktor:ktor-client-core 2.3.5
  • io.ktor:ktor-client-cio 2.3.5
  • io.ktor:ktor-client-content-negotiation 2.3.5
  • io.ktor:ktor-serialization-kotlinx-json 2.3.5
  • io.arrow-kt:arrow-core 1.2.1
  • io.arrow-kt:arrow-fx-coroutines 1.2.1
  • io.arrow-kt:suspendapp 0.4.0
  • com.maxmind.geoip2:geoip2 4.1.0
  • io.gitlab.arturbosch.detekt:detekt-formatting 1.23.1
  • app.cash.turbine:turbine 1.0.0
  • io.kotest:kotest-assertions-core 5.7.2
  • io.kotest:kotest-property 5.7.2
  • io.kotest:kotest-runner-junit5 5.7.2
  • org.jetbrains.kotlin.jvm 1.9.10
  • org.jetbrains.kotlin.plugin.serialization 1.9.10
  • org.jetbrains.compose 1.5.3
  • io.gitlab.arturbosch.detekt 1.23.1
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.4

  • Check this box to trigger a request for Renovate to run again on this repository

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.