/kotlin-maze

:steam_locomotive: A simple way to implement applications using observable streams

Primary LanguageKotlinApache License 2.0Apache-2.0


kotlin-maze

Download Build Status

🚂 A simple way to implement applications using observable streams


██╗  ██╗ ██████╗ ████████╗██╗     ██╗███╗   ██╗      ███╗   ███╗ █████╗ ███████╗███████╗
██║ ██╔╝██╔═══██╗╚══██╔══╝██║     ██║████╗  ██║      ████╗ ████║██╔══██╗╚══███╔╝██╔════╝
█████╔╝ ██║   ██║   ██║   ██║     ██║██╔██╗ ██║█████╗██╔████╔██║███████║  ███╔╝ █████╗  
██╔═██╗ ██║   ██║   ██║   ██║     ██║██║╚██╗██║╚════╝██║╚██╔╝██║██╔══██║ ███╔╝  ██╔══╝  
██║  ██╗╚██████╔╝   ██║   ███████╗██║██║ ╚████║      ██║ ╚═╝ ██║██║  ██║███████╗███████╗
╚═╝  ╚═╝ ╚═════╝    ╚═╝   ╚══════╝╚═╝╚═╝  ╚═══╝      ╚═╝     ╚═╝╚═╝  ╚═╝╚══════╝╚══════╝
                                                                                        

Usage

Create immutable view model

Recommend create class as comparable like data class

data class HelloModel(
    val name: String = ""
)

Implement MazeListener

class HelloFragment : BaseFragment(), MazeListener<HelloModel> {

    override val layoutId: Int = R.layout.fragment_hello

    // Don't be in lifecycle of `Fragment`/`Activity`
    // In this case, `BaseFragment` is set to `retainInstance = true` basically
    // Set initial view model
    private val maze by lazy { Maze(HelloModel()) }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // Attach maze with user events
        // Event.id is used to filter in `main` function
        maze.attach(this, arrayOf(
            toolbar.navigationClicks()
                .map { ClickEvent(R.id.homeAsUp) },
            inputName.textChanges()
                .map { TextChangeEvent(R.id.inputName, it) }
        ))
    }

    override fun onDestroyView() {
        // Detach
        maze.detach()
        super.onDestroyView()
    }

    // Implement main function
    override fun main(sources: Sources<HelloModel>) = helloMain(sources)

    // Render view model
    override fun render(prev: HelloModel, curr: HelloModel) {
        val hello = getString(R.string.hello_message)
        textHello.text = hello.format(curr.name)
    }

    // Navigate somethings
    override fun navigate(navigation: Navigation) {
        when (navigation) {
            is Back -> activity?.onBackPressed()
        }
    }

    // Cleanup if Activity is finished
    override fun finish() = maze.finish()

    // Handle errors
    override fun error(t: Throwable) {
        t.printStackTrace()
    }
}

Implement main function

Implement main logic using Observables

fun helloMain(sources: Sources<HelloModel>): Sinks<HelloModel> {

    val model = sources.event
        .textChanges(R.id.inputName)
        .map(CharSequence::toString)
        .withLatestFrom(sources.model,
            BiFunction { name: String, model: HelloModel ->
                model.copy(name = name)
            })
        .cacheWithInitialCapacity(1)

    val navigation = sources.event
        .clicks(R.id.homeAsUp)
        .map { Back() }

    // `model` must be `ObservableCache`
    return Sinks(model, navigation)
}

Customize Navigations, Events

You can extend Navigations and/or Events if you want

Please refer to default Navigations, Events

More examples

videos

Install

repositories {
    jcenter()
}

compile "com.importre:kotlin-maze:$maze_version"

for test

testCompile "com.importre:kotlin-maze-test:$maze_version"

Slides

License

Apache 2.0 © Jaewe Heo