ScopedState - Android Scoped State With Kotlin

Related tags

Kotlin ScopedState
Overview

Android Scoped State

CircleCI CircleCI Android

There is no need for complicated code - just define scopes and then add states between brackets :) 🤤 EZPZ right?

Android Android Android

Installation

Gradle

dependencies {
    ...
    implementation 'io.github.kotlinbyte:scoped-state:1.0.0'
}

template1.png

Scopedstate: How to use?

The concept of scopedstate focuses on scopes and states

As an example, there is a currency screen which has different features like auto-updating prices or manually updating, real-time data or auto-updating date and time.

So let's define scope for those features as follows

sealed class CurrencyScreenScope {
    object Initial : CurrencyScreenScope()
    object AutomatedPriceUpdates : CurrencyScreenScope()
    object ManualPriceUpdates : CurrencyScreenScope()
    object AutomatedDateAndTimeUpdates : CurrencyScreenScope()
}

Now that we have different scopes, yay! 😎
But now you might ask yourself, what is the point of having different scopes for my features, right!? The answer is that every feature has different states, like maybe it is in the loading state, it is in data state, ..., or maybe it has an error.
Next, let's define different states for each of our scopes as shown below

sealed class AutomatedPriceUpdateStates : StateWatcher.BaseState {
    object Initial : AutomatedPriceUpdateStates()
    object Loading : AutomatedPriceUpdateStates()
    data class Data(val currencies: List<Currency>): AutomatedPriceUpdateStates()
    object Error: AutomatedPriceUpdateStates()
}

sealed class ManualPriceUpdateStates : StateWatcher.BaseState {
    object Loading : ManualPriceUpdateStates()
    data class Data(val currencies: List<Currency>): ManualPriceUpdateStates()
    data class Error(val reason: String): ManualPriceUpdateStates()
}

sealed class AutomatedDateAndTimeUpdateState : StateWatcher.BaseState {
    data class HoursTicker(val hour:Int) : AutomatedDateAndTimeUpdateState()
    data class MinutesTicker(val minute:Int) : AutomatedDateAndTimeUpdateState()
    data class SecondsTicker(val seconds:Int) : AutomatedDateAndTimeUpdateState()

}

Now we have different states for each scope, great! 🥳
As we move forward, let's create a viewmodel that contains a MutableScopedStateFlow so we can emit cool stuff from it

class CurrencyScreenViewModel : ViewModel() {
    // By marking it as private, only viewmodel will be able to emit data through it
    private val _scopedState: MutableScopedStateFlow<CurrencyScreenScope> =
        MutableScopedStateFlow.create<CurrencyScreenScope, CurrencyScreenScope.Initial>()

    val state: ScopedStateFlow<ExampleScope> = _scopedState
}

A MutableScopedStateFlow can be constructed in a variety of ways, as shown below:

// When it is created, initialScope is emitted
val _scopedState: MutableScopedStateFlow<Scope> = MutableScopedStateFlow.create<Scope, Scope.InitialScope>()
    
// Result is same as previous method
val _scopedState: MutableScopedStateFlow<Scope> = MutableScopedStateFlow.create<Scope, Scope.InitialScope>(Scope.InitialScope::class.java)
    
// It emits initialScope with the state of ExampleState.Init when it is created
val _scopedState: MutableScopedStateFlow<Scope> = MutableScopedStateFlow.create<Scope, Scope.InitialScope>(ExampleState.Init)
    
// Result is same as previous method
val _scopedState: MutableScopedStateFlow<Scope> = MutableScopedStateFlow.create<Scope, Scope.InitialScope>(Scope.InitialScope::class.java, ExampleState.Init)

NOTE: When you're trying to create MutableScopedState, you need to specify a scope for initializing stateflow!
TIPS: The initial state can be specified as well, but it's optional

As with stateflow, MutableScopedStateFlow has emit() method too :

//emits scope
_scopedState.emit<Scope>()

//emits state
_scopedState.emit(state)

//emits scope and state
_scopedState.emit<Scope>(state)

Let's apply them to our use case

class CurrencyScreenViewModel(repo: CurrencyRepo) : ViewModel() {
    // By marking it as private, only viewmodel will be able to emit data through it
    private val _scopedState: MutableScopedStateFlow<CurrencyScreenScope> =
        MutableScopedStateFlow.create<CurrencyScreenScope, CurrencyScreenScope.Initial>()

    val scopedStateFlow: ScopedStateFlow<ExampleScope> = _scopedState
    
    fun fetchCurrencyListInInterval(){
        _scopedState.emit<CurrencyScreenScope.AutomatedPriceUpdates>(AutomatedPriceUpdateStates.Loading)
        repo.fetch().fold(
            error = {
                _scopedState.emit<CurrencyScreenScope.AutomatedPriceUpdates>(AutomatedPriceUpdateStates.Error)
            },
            data = { currencies ->
                _scopedState.emit<CurrencyScreenScope.AutomatedPriceUpdates>(AutomatedPriceUpdateStates.Data(currencies))
            }
        )
    }
}

EZ, that's done. You may have noticed that we are duplicating our scope each time! Personally, I don't like it 😂 😂
Therefore, for this usecase, our function is single-purpose and the scope of the emissions is the same. This is where the withScope() function comes into play:

_scopedState.withScope<Scope, BaseState> {

}

Here's our modified code:

class CurrencyScreenViewModel(repo: CurrencyRepo) : ViewModel() {
    // By marking it as private, only viewmodel will be able to emit data through it
    private val _scopedState: MutableScopedStateFlow<CurrencyScreenScope> =
        MutableScopedStateFlow.create<CurrencyScreenScope, CurrencyScreenScope.Initial>()

    val scopedStateFlow: ScopedStateFlow<ExampleScope> = _scopedState
    
    fun fetchCurrencyListInInterval() = _scopedState.withScope<CurrencyScreenScope.AutomatedPriceUpdates, AutomatedPriceUpdateStates> {
        emit(AutomatedPriceUpdateStates.Loading)
        repo.fetch().fold(
            error = {
                emit(AutomatedPriceUpdateStates.Error)
            },
            data = { currencies ->
                emit(AutomatedPriceUpdateStates.Data(currencies))
            }
        )
    }
}

We have finished our viewmodel, It's time to move on to the activity/fragment
In order to change the UI based on scope and state of an activity or fragment, we need to collect them from ScopedStateFlow. This can be accomplished with StateWatcher:

StateWatcher.watch(ScopedStateFlow){
    //stuff
}

Here is how we can apply that to our activity:

class CurrencyActivity : AppCompatActivity() {
    
    lateinit var viewModel: CurrencyScreenViewModel
    
    override fun onCreate(savedInstanceState: Bundle?) {
        // setup views + viewmodel
        setupStateWatcher()
    }
    
    fun setupStateWatcher(){
        StateWatcher.watch(viewModel.scopedStateFlow){
            attach(lifecycle) //important
            
            // This is where you can define your scopes
            
            scope<CurrencyScreenScope.AutomatedPriceUpdates, AutomatedPriceUpdateStates>{
            
                // This is where you can define your states
                
                state<AutomatedPriceUpdateStates.Loading> {
                    // show loading progresss
                }
                
                state<AutomatedPriceUpdateStates.Data>{ currencyList ->
                    // show currency list
                }
                
            }
        }
    }
    
}
You might also like...
This Project for how to use  MVVM , state flow, Retrofit, dagger hit, coroutine , use cases with Clean architecture.
This Project for how to use MVVM , state flow, Retrofit, dagger hit, coroutine , use cases with Clean architecture.

Clean-architecture This Project for how to use MVVM , state flow, Retrofit, dagger hit, coroutine , use cases with Clean architecture. Why i should us

Real life Kotlin Multiplatform project with an iOS application developed in Swift with SwiftUI, an Android application developed in Kotlin with Jetpack Compose and a backed in Kotlin hosted on AppEngine.

Conferences4Hall Real life Kotlin Multiplatform project with an iOS application developed in Swift with SwiftUI, an Android application developed in K

Run Kotlin/JS libraries in Kotlin/JVM and Kotlin/Native programs

Zipline This library streamlines using Kotlin/JS libraries from Kotlin/JVM and Kotlin/Native programs. It makes it possible to do continuous deploymen

A somewhat copy past of Jetbrain's code from the kotlin plugin repo to make it humanly possible to test Intellij IDEA kotlin plugins that work on kotlin

A somewhat copy past of Jetbrain's code from the kotlin plugin repo to make it humanly possible to test Intellij IDEA kotlin plugins that work on kotlin

Android + Kotlin + Github Actions + ktlint + Detekt + Gradle Kotlin DSL + buildSrc = ❤️

kotlin-android-template 🤖 A simple Github template that lets you create an Android/Kotlin project and be up and running in a few seconds. This templa

LifecycleMvp 1.2 0.0 Kotlin  is MVP architecture implementation with Android Architecture Components and Kotlin language features
LifecycleMvp 1.2 0.0 Kotlin is MVP architecture implementation with Android Architecture Components and Kotlin language features

MinSDK 14+ Download Gradle Add to project level build.gradle allprojects { repositories { ... maven { url 'https://jitpack.io' }

👋 A common toolkit (utils) ⚒️ built to help you further reduce Kotlin boilerplate code and improve development efficiency. Do you think 'kotlin-stdlib' or 'android-ktx' is not sweet enough? You need this! 🍭

Toolkit [ 🚧 Work in progress ⛏ 👷 🔧️ 🚧 ] Snapshot version: repositories { maven("https://s01.oss.sonatype.org/content/repositories/snapshots") }

An app architecture for Kotlin/Native on Android/iOS. Use Kotlin Multiplatform Mobile.
An app architecture for Kotlin/Native on Android/iOS. Use Kotlin Multiplatform Mobile.

An app architecture for Kotlin/Native on Android/iOS. Use Kotlin Multiplatform Mobile. 项目架构主要分为原生系统层、Android/iOS业务SDK层、KMM SDK层、KMM业务逻辑SDK层、iOS sdkfra

Opinionated Redux-like implementation backed by Kotlin Coroutines and Kotlin Multiplatform Mobile

CoRed CoRed is Redux-like implementation that maintains the benefits of Redux's core idea without the boilerplate. No more action types, action creato

Releases(beta1.0.1)
Owner
Ali Azizi
Android & flutter ❤️
Ali Azizi
Simple State Machines in Kotlin (KSSM)

Simple State Machines in Kotlin (KSSM) What is this? KSSM (reordered: Kotlin - Simple State Machines) provides an easy and simple DSL (Domain Specific

Milos Marinkovic 22 Dec 12, 2022
Kotlin coroutine capable Finite-State Machine (multiplatform)

Comachine Features Kotlin corutines. Event handlers can launch coroutines for collecting external events of performing side effects. Structured concur

Sergej Shafarenka 22 Dec 14, 2022
Stresscraft - State-of-art Minecraft stressing software written in Kotlin

StressCraft (W.I.P) State-of-art Minecraft stressing software written in Kotlin.

Cubxity 57 Dec 4, 2022
This repository contains the article describing my attempt to implement a simple state reducer based on Kotlin Flow and an example app that uses it.

This repository contains the article describing my attempt to implement a simple state reducer based on Kotlin Flow and an example app that uses it.

Maciej Sady 18 Dec 29, 2022
🔴 A non-deterministic finite-state machine for Android & JVM that won't let you down

HAL is a non-deterministic finite-state machine for Android & JVM built with Coroutines StateFlow and LiveData. Why non-deterministic? Because in a no

Adriel Café 73 Nov 28, 2022
💫 Small microservice to handle state changes of Kubernetes pods and post them to Instatus or Statuspages

?? Kanata Small microservice to handle state changes of Kubernetes pods and post them to Instatus or Statuspages ?? Why? I don't really want to implem

Noel 4 Mar 4, 2022
Basic app to use different type of observables StateFlow, Flow, SharedFlow, LiveData, State, Channel...

stateflow-flow-sharedflow-livedata Basic app to use different type of observables StateFlow, Flow, SharedFlow, LiveData, State, Channel... StateFlow,

Raheem 5 Dec 21, 2022
ConstraintSetChangesTest - Simple project showing Changes of ConstraintSet value as part of mutable state in JetpackCompose.

ConstraintSetChangesTest Simple project showing Changes of ConstraintSet value as part of mutable state in JetpackCompose. Version: implementation

Mateusz Perlak 1 Feb 13, 2022
Advanced State in Jetpack Compose Codelab

Advanced State in Jetpack Compose Codelab This folder contains the source code for the Advanced State in Jetpack Compose Codelab codelab. The project

Carlos Barrios 1 May 12, 2022
Simplify mutating "immutable" state models

Mutekt (Pronunciation: /mjuːˈteɪt/, 'k' is silent) "Simplify mutating "immutable" state models" Generates mutable models from immutable model definiti

Shreyas Patil 179 Nov 30, 2022