Ivy FRP is a Functional Reactive Programming framework for declarative-style programming for Android

Overview

License: GPL v3 PRs welcome!

Ivy FRP

Ivy FRP is a Functional Reactive Programming framework for declarative-style programming for Android. 🚀

Minimalistic and light-weight implementation of the Ivy FRP Architecture.

Recommendation: Use it alongside Jetpack Compose.

Demo

Imaginary Weather app ☁️ ☀️

TL;DR: You'll find the code of the entire weather app below. If you're already sold to use Ivy FRP => skip to Installation.

Data (boring)

data class Temperature(
    val value: Float,
    val unit: TemperatureUnit
)

enum class TemperatureUnit {
    CELSIUS, FAHRENHEIT
}

data class UserPreference(
    val temperatureUnit: TemperatureUnit
)

IO (boring)

data class WeatherResponse(
    val temperature: Temperature
)

interface WeatherService {
    @GET("/api/weather")
    suspend fun getWeather(): WeatherResponse
}

interface UserPreferenceService {
    @GET("/preference/temp-unit")
    suspend fun getTempUnit(): UserPreference

    @POST("/preference/temp-unit")
    suspend fun updateTempUnit(
        @Query("unit") temperatureUnit: TemperatureUnit
    )
}

Actions

ConvertTempAct.kt

class ConvertTempAct @Inject constructor() : FPAction() {

    override suspend fun Input.compose(): suspend () -> Temperature =
        this asParamTo ::convertValue then { convertedValue ->
            Temperature(convertedValue, toUnit)
        }

    private fun convertValue(input: Input): Float = with(input.temperature) {
        if (unit == input.toUnit) value else {
            when (input.toUnit) {
                TemperatureUnit.CELSIUS -> fahrenheitToCelsius(value)
                TemperatureUnit.FAHRENHEIT -> celsiusToFahrenheit(value)
            }
        }
    }

    //X°F = (Y°C × 9/5) + 32
    private fun celsiusToFahrenheit(celsius: Float): Float = (celsius * 9 / 5) + 32

    //X°C = (Y°F - 32) / (9/5)
    private fun fahrenheitToCelsius(fahrenheit: Float): Float = (fahrenheit - 32) / (9 / 5)

    data class Input(
        val temperature: Temperature,
        val toUnit: TemperatureUnit
    )
}

CurrentTempAct.kt

class CurrentTempAct @Inject constructor(
    private val weatherService: WeatherService,
    private val convertTempAct: ConvertTempAct
) : FPAction>() {
    override suspend fun TemperatureUnit.compose(): suspend () -> Res<String, Temperature> = tryOp(
        operation = weatherService::getWeather
    ) mapSuccess { response ->
        ConvertTempAct.Input(
            temperature = response.temperature,
            toUnit = this //TemperatureUnit
        )
    } mapSuccess convertTempAct mapError {
        "Failed to fetch weather: ${it.message}"
    }
}

UserPreferencesAct.kt

class UserPreferenceAct @Inject constructor(
    private val userPreferenceService: UserPreferenceService
) : FPAction>() {
    override suspend fun Unit.compose(): suspend () -> Res<String, UserPreference> = tryOp(
        operation = userPreferenceService::getTempUnit
    ) mapError {
        "Failed to fetch user's preference: ${it.message}"
    }
}

UpdateUserPreferencesAct.kt

class UpdateUserPreferenceAct @Inject constructor(
    private val userPreferenceService: UserPreferenceService
) : FPAction>() {
    override suspend fun TemperatureUnit.compose(): suspend () -> Res<String, Unit> = tryOp(
        operation = this asParamTo userPreferenceService::updateTempUnit
    ) mapError {
        "Failed to update user preference: ${it.message}"
    }
}

ViewModel

WeatherState.kt

sealed class WeatherState {
    object Loading : WeatherState()

    data class Error(val errReason: String) : WeatherState()

    data class Success(
        val tempUnit: TemperatureUnit,
        val temp: Float
    ) : WeatherState()
}

WeatherEvent.kt

sealed class WeatherEvent {
    object LoadWeather : WeatherEvent()

    data class UpdateTempUnit(val unit: TemperatureUnit) : WeatherEvent()
}

WeatherViewModel.kt

@HiltViewModel
class WeatherViewModel @Inject constructor(
    private val userPreferenceAct: UserPreferenceAct,
    private val updateUserPreferenceAct: UpdateUserPreferenceAct,
    private val currentTempAct: CurrentTempAct,
) : FRPViewModel() {
    override val _state: MutableStateFlow<WeatherState> = MutableStateFlow(WeatherState.Loading)

    override suspend fun handleEvent(event: WeatherEvent): suspend () -> WeatherState =
        when (event) {
            is WeatherEvent.LoadWeather -> loadWeather()
            is WeatherEvent.UpdateTempUnit -> updateTempUnit(event)
        }

    private suspend fun loadWeather(): suspend () -> WeatherState = suspend {
        updateState { WeatherState.Loading }
        Unit
    } then userPreferenceAct mapSuccess {
        it.temperatureUnit //loadTempFor() expects TemperatureUnit
    } then ::loadTempFor

    private suspend fun updateTempUnit(
        event: WeatherEvent.UpdateTempUnit
    ): suspend () -> WeatherState = suspend {
        updateState { WeatherState.Loading }
        event.unit
    } then updateUserPreferenceAct mapSuccess {
        event.unit
    } then ::loadTempFor

    private suspend fun loadTempFor(tempRes: Res<String, TemperatureUnit>) =
        tempRes.lambda() thenIfSuccess currentTempAct thenInvokeAfter {
            when (it) {
                is Res.Ok -> WeatherState.Success(
                    temp = it.data.value,
                    tempUnit = it.data.unit
                )
                is Res.Err -> WeatherState.Error(errReason = it.error)
            }
        }
}

UI

WeatherScreen.kt

data class WeatherScreen(
    val title: String
) : Screen

@Composable
fun BoxWithConstraintsScope.WeatherScreen(screen: WeatherScreen) {
    FRP<WeatherState, WeatherEvent, WeatherViewModel>(
        initialEvent = WeatherEvent.LoadWeather
    ) { state, onEvent ->
        UI(title = screen.title, state = state, onEvent = onEvent)
    }
}

@Composable
private fun UI(
    title: String,
    state: WeatherState,

    onEvent: (WeatherEvent) -> Unit
) {
    //UI goes here.....
    //When Celsius is clicked:
    // onEvent(WeatherEvent.UpdateTempUnit(TemperatureUnit.CELSIUS))
}

Find the full sample here.

Key Features

  • Function composition using then. (supporting suspend functions, `Action & FPAction)

  • FPAction: declaritive-style "use-case" which can be composed.

  • FRPViewModel: functional-reactive ViewModel implementation, see Ivy FRP Architecture .

  • @Composable FRP(){ UI() }: functional-reactive UI implementation in Jetpack Compose.

  • Res.Ok / Res.Err result type: monadic result type supporting success and error composition.

    • thenIfSuccess: calls the next function only on success (OK)
    • mapSuccess: maps only the success type if the result is OK
    • mapError: maps only the error type if the result is Err
  • (optional) NavigationRoot + Navigation: navigation component for Jetpack Compose

    • ⚠️ to use it with proper back handling you must override onBackPressed()

if you use Navigation:

//required only for "NavigationRoot" and "Navigation"
override fun onBackPressed() {
    if (!navigation.onBackPressed()) {
        super.onBackPressed()
    }
}

Installation

Gradle KTS

1. Add maven(url = "https://jitpack.io") to repositories.

settings.gradle.kts (at the top)

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven(url = "https://jitpack.io")
    }
}

//...

build.gradle.kts (buildSrc) (right below "plugins")

plugins {
    `kotlin-dsl`
}

repositories {
    google()
    mavenCentral()
    maven(url = "https://jitpack.io")
}

//...

2. Add com.github.ILIYANGERMANOV:ivy-frp:0:0:0 dependency.

< -- latest version

build.gradle.kts (app)

implementation("com.githubILIYANGERMANOV:ivy-frp:0.0.0")

Replace 0.0.0 with the latest version from Jitpack.

Gradle

1. Add maven(url = "https://jitpack.io") to repositories.

2. Add com.github.ILIYANGERMANOV:ivy-frp:0:0:0 dependency.

< -- latest version

build.gradle.kts (app)

implementation 'com.githubILIYANGERMANOV:ivy-frp:0.0.0'

Docs

🚧 WIP... 🚧

For samples and usage in a real-world scenario please refer to the Ivy Wallet's GitHub repo.

You might also like...
An extension of EditText with pin style written in Kotlin
An extension of EditText with pin style written in Kotlin

pin-edittext An extension of EditText with pin style Usage Include PinCodeEditText in your layout XML com.oakkub.android.PinEditText android:layo

A kotlin library of extension functions that add smalltalk style methods to objects.

KtTalk A kotlin library of extension functions that add smalltalk style methods to objects. Motivation Smalltalk is a pure OO language in which everyt

An example for who are all going to start learning Kotlin programming language to develop Android application.

Kotlin Example Here is an example for who are all going to start learning Kotlin programming language to develop Android application. First check this

Android utilities for easier and faster Kotlin programming.
Android utilities for easier and faster Kotlin programming.

Android utilities for easier and faster Kotlin programming. Download Gradle compile 'com.costular:kotlin-utils:0.1' How to use It depends on utilities

A template that utilizes both Scala and Kotlin because why not (And also because I endorse programming hell)

Fabric-Scala-Kotlin-template A template that utilizes both Scala and Kotlin because why not (And also because I endorse programming hell) I don't care

A pair of applications provide a direct means of integrating with one another via application programming interfaces (APIs)

What is a native integration? It's when a pair of applications provide a direct means of integrating with one another via application programming interfaces (APIs). Once integrated, data can flow between the apps and become more readily available to your employees.

Lambda-snake.kt - Snake Game Implementation for Web using Kotlin programming language compiled for Javascript
Lambda-snake.kt - Snake Game Implementation for Web using Kotlin programming language compiled for Javascript

Projeto da disciplina de Linguagem de Programação Funcional 2021.1 (jan/2022) 📄

A thought experiment on architecture, object-oriented programming, and composability.

Journal3 There's barely anything special about the features that Journal3 is offering, it's literally yet another journaling application. What is spec

Android MVVM framework write in kotlin, develop Android has never been so fun.

KBinding 中文版 Android MVVM framework write in kotlin, base on anko, simple but powerful. It depends on my another project AutoAdapter(A library for sim

Releases(0.9.5)
Owner
null
Clean Code and Reactive Programming PlayGround for Bangkit 2021

Clean Code and Reactive Programming PlayGround for Bangkit 2021 Hello! This repo contains the IntelliJ project that I use to present my talk, "Clean A

raditya gumay 3 May 16, 2021
Kotlin Multiplatform Mobile + Mobile Declarative UI Framework (Jetpack Compose and SwiftUI)

Kotlin Multiplatform Mobile + Mobile Declarative UI Framework (Jetpack Compose and SwiftUI)

Kotchaphan Muangsan 3 Nov 15, 2022
Concurrency-programming - Homework for the course of Concurrency Programming, ITMO CT, Autumn 2020

Homework for the course of Concurrency Programming, ITMO CT, Autumn 2020 Выполни

Grigoriy Khlytin 2 Jan 23, 2022
{ } Declarative Kotlin DSL for choreographing Android transitions

Transition X Kotlin DSL for choreographing Android Transitions TransitionManager makes it easy to animate simple changes to layout without needing to

Arunkumar 520 Dec 16, 2022
A declarative, Kotlin-idiomatic API for writing dynamic command line applications.

A declarative, Kotlin-idiomatic API for writing dynamic command line applications.

Varabyte 349 Jan 9, 2023
fusion4j - declarative rendering language for the JVM based on Neos.Fusion

fusion4j - declarative rendering language for the JVM based on Neos.Fusion Supports the Neos Fusion syntax/semantic as described in the official Neos

sandstorm 2 May 3, 2022
Functional Constructs for Databinding + Kotlin + RxJava

ObservableFlow Pt 1/3 Pt 2/3: Stepper Indicator Pt 3/3: SugarPreferences Functional Kotlin constructs like map(), filter() and 12 more functions built

Rakshak R.Hegde 26 Oct 3, 2022
Functional Kotlin & Arrow based library for generating and verifying JWTs and JWSs

kJWT Functional Kotlin & Arrow based library for generating and verifying JWTs and JWSs. JWS JWT The following Algorithms are supported: HS256 HS384 H

Peter vR 31 Dec 25, 2022
A Kotlin library for reactive and boilerplate-free SharedPreferences in Android

KPreferences A Kotlin library for reactive and boilerplate-free Shared Preferences in Android. With KPreferences you can use Kotlin's marvelous delega

Mohamad Amin Mohamadi 19 Dec 16, 2020
A Kotlin Android library for content provider queries with reactive streams and coroutines.

Pickpocket An Android library for content provider queries with reactive streams and coroutines. Calendar Contacts SMS MMS Files/Media Call Log Bookma

Chris Basinger 27 Nov 14, 2022