Giter Site home page Giter Site logo

unio-kt's Introduction

unio-kt

Introduction

Ordinary ViewModels might be implemented like this. But those implementation are not clear, for example which are states and which are dependencies and so on.

class CounterViewModel : ViewModel() {
    val count: Flow<String>
        get() = _count.map { it.toString() }

    val isCountDownEnabled: Flow<Boolean>
        get() = _isCountDownEnabled

    private val _count = MutableStateFlow(0)
    private val _isCountDownEnabled = MutableStateFlow(false)

    fun countUp() {
        _count.value = _count.value + 1
        _isCountDownEnabled.value = _count.value > 0
    }

    fun countDown() {
        _count.value = _count.value - 1
        _isCountDownEnabled.value = _count.value > 0
    }
}

About Unio

Unio is kProperty based Unidirectional Input / Output framework that works with Flow.

Unio.Input

The rule of Input is having MutableSharedFlow properties that are defined internal (or public) scope.

class CounterUnioInput : Unio.Input {
    val countUp = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
    val countDown = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
}

Properties of Input are defined internal (or public) scope. But these can only access InputProxy#getLambda via kProperty if Input is wrapped with InputProxy.

val input: InputProxy<CounterUnioInput>

input.getLambda(CounterUnioInput::countUp).invoke()  // accesses `MutableSharedFlow#tryEmit`

Unio.Output

The rule of Output is having MutableStateFlow properties that are defined internal (or public) scope.

class CounterUnioOutput(
    val count: Flow<String>,
    val isCountDownEnabled: MutableStateFlow<Boolean>,
) : Unio.Output

Properties of Output are defined internal (or public) scope. But these can only access Flow (or StateFlow) via kProperty if Output is wrapped with OutputProxy.

val output: OutputProxy<CounterUnioOutput>

output.getFlow(CounterUnioOutput::count)
    .onEach { Log.d("UNIO_DEBUG", it.toString()) })

output.getFlow(CounterUnioOutput::isCountDownEnabled)
    .onEach { Log.d("UNIO_DEBUG", it.toString()) })

If a property is MutableStateFlow, be able to access value via kProperty.

output.getValue(CounterUnioOutput::isCountDownEnabled)

If a property is defined as Computed, be able to access computed value.

class Output: Unio.Output {
    val isEnabled: Computed<Bool>
}

var _isEnabled = false
let output = OutputProxy(Output(Computed<Boolean> { _isEnabled }))

output.getComputed(CounterUnioOutput::isEnabled) // false
_isEnabled = true
output.getComputed(CounterUnioOutput::isEnabled) // true

Unio.State

The rule of State is having inner states of Unio.

class State: Unio.State {
    val count = MutableStateFlow(0)
    val isCountDownEnabled = MutableStateFlow(false)
}

Unio.Extra

The rule of Extra is having other dependencies of Unio.

class Extra(val githubApi: GitHubhAPI): Unio.Extra

Unio

The rule of Unio is generating Unio.Output from Dependency<Input, State, Extra>. It generates Unio.Output to call OutputFactory#create. It is called once when Unio is initialized.

class CounterUnio(
    input: CounterUnioInput,
    state: State,
    extra: Extra,
    viewModelScope: CoroutineScope,
) : Unio<
        CounterUnioInput,
        CounterUnioOutput,
        CounterUnio.Extra,
        CounterUnio.State
        >(
    input = input,
    extra = extra,
    state = state,
    outputFactory = CounterUnio,
    viewModelScope = viewModelScope
)

Connect sequences and generate Unio.Output in OutputFactory#create to use below properties and methods.

  • Dependency#state
  • Dependency#extra
  • Dependency#getFlow ... Returns a flow that is property of Unio.Input.
  • viewModelScope ... It might be ViewModel lifecycle.

Here is a exmaple of implementation.

companion object : OutputFactory<
        CounterUnioInput,
        CounterUnioOutput,
        Extra,
        State
        > {
    override fun create(
        dependency: Dependency<CounterUnioInput, Extra, State>,
        viewModelScope: CoroutineScope
    ): CounterUnioOutput {
        val state = dependency.state
        val extra = dependency.extra

        listOf(
            dependency.getFlow(CounterUnioInput::countUp).map { 1 },
            dependency.getFlow(CounterUnioInput::countDown).map { -1 }
        )
            .merge()
            .map { state.count.value + it }
            .onStart { emit(extra.startValue) }
            .onEach {
                state.count.emit(it)
                state.isCountDownEnabled.emit(it > 0)
            }
            .launchIn(viewModelScope)

        return CounterUnioOutput(
            count = state.count.map { it.toString() },
            isCountDownEnabled = state.isCountDownEnabled,
        )
    }
}

UnioFactory

The rule of UnioFactory is generating Unio.

class CounterUnioFactoryImpl : UnioFactory<CounterUnioInput, CounterUnioOutput> {
    override fun create(
        viewModelScope: CoroutineScope,
        onCleared: Flow<Unit>,
    ) = CounterUnio(
        input = CounterUnioInput(),
        state = CounterUnio.State(),
        extra = CounterUnio.Extra(5),
        viewModelScope = viewModelScope
    )
}

UnioViewModel

UnioViewModel represents AAC ViewModel. It has val input: InputProxy<Input> and val output: OutputProxy<Output>. It automatically generates val input: InputProxy<Input> and val output: OutputProxy<Output> from instances of Unio.Input, Unio.State, Unio.Extra and UnioFactory.

Be able to define a subclass of UnioViewModel like this.

class CounterViewModel : UnioViewModel<CounterUnioInput, CounterUnioOutput>(CounterUnioFactoryImpl())

Usage

This is example usage in an Activity.

class MainActivity : AppCompatActivity() {

    private val viewModel: CounterViewModel by viewModels()
    private var scope: CoroutineScope? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.count_up_button).setOnClickListener {
            viewModel.input.getLambda(CounterUnioInput::countUp).invoke()
        }

        val countDownButton = findViewById<Button>(R.id.count_down_button)
        countDownButton.setOnClickListener {
            viewModel.input.getLambda(CounterUnioInput::countDown).invoke()
        }

        scope = CoroutineScope(SupervisorJob() + Dispatchers.Main).also { scope ->
            viewModel.output
                .getFlow(CounterUnioOutput::isCountDownEnabled)
                .onEach {
                    countDownButton.isEnabled = it
                }
                .launchIn(scope)

            val textView = findViewById<TextView>(R.id.textView)
            viewModel.output
                .getFlow(CounterUnioOutput::count)
                .onEach {
                    textView.text = it
                }
                .launchIn(scope)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        scope?.cancel()
        scope = null
    }
}

Dagger Hilt Compatible

You can use unio-kt with Dagger Hilt.

@HiltViewModel
class CounterViewModel @Inject constructor(
    @CounterUnioFactory unioFactory: UnioFactory<CounterUnioInput, CounterUnioOutput>,
) : UnioViewModel<CounterUnioInput, CounterUnioOutput>(unioFactory)
class CounterUnio @AssistedInject constructor(
    input: CounterUnioInput,
    state: State,
    @Assisted viewModelScope: CoroutineScope,
    @Assisted onCleared: Flow<Unit>,
) : Unio<...>(...) { ... }
@AssistedFactory
interface CounterUnioFactory: UnioFactory<CounterUnioInput, CounterUnioOutput> {
    override fun create(
        @Assisted viewModelScope: CoroutineScope,
        @Assisted onCleared: Flow<Unit>,
    ): CounterUnio
}
@Module
@InstallIn(ViewModelComponent::class)
interface ViewModelBindModule {
    @Binds
    fun bindCounterUnioFactory(
        unioFactory: CounterUnioFactory,
    ): UnioFactory<CounterUnioInput, CounterUnioOutput>
}

@Module
@InstallIn(ViewModelComponent::class)
object ViewModelProvideModule {
    @Provides
    fun provideCounterUnioInput() = CounterUnioInput()

    @Provides
    fun provideCounterUnioState() = CounterUnio.State()
}

Setup

Gradle

Add maven { url 'https://jitpack.io' } to settings.gradle.

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

In your app build.gradle:

implementation 'com.github.marty-suzuki:unio-kt:TAG'

When import unio-kt, Hyphone (marty-suzuki) is wrong, Underscore (marty_suzuki) is correct. (import com.github.marty_suzuki.unio.*)

Related

License

unio-kt is released under the Apache License 2.0.

unio-kt's People

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

unio-kt's Issues

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.