🏗️ Kotlin implementation of Point-Free's composable architecture

Overview

🧩 Komposable Architecture Maven Central Build Status License

Kotlin implementation of Point-Free's The Composable Architecture

🚧 Project Status

We've been using Komposable Architecture in production for almost a year now, and we haven't encountered any major issues. However, the API is still subject to change at least until we reach version 1.0. We definitely want to make the setup more straightforward, and we are thinking about ways to integrate jetpack navigation as well.

💡 Motivations

When the time of rewriting Toggl's mobile apps came, we decided that we would take the native approach rather than insisting on using Xamarin We quickly realized, however, that we could still share many things across the app, even if the apps didn't share a common codebase The idea behind using the same architecture allowed us to share specs, github issues, creates a single common language that both Android and iOS devs can use and even speeds up development of features that are already implemented in the other platform!

We chose to use Point-Free's Composable Architecture as the apps's architecture, which meant we had to set out to implement it in Kotlin. This repo is the result of our efforts!

🍎 Differences from iOS

While all the core concepts are the same, the composable architecture is still written with Swift in mind, which means not everything can be translated 1:1 to Kotlin. Here are the problems we faced and the solutions we found:

No KeyPaths

The lack of KeyPaths in Kotlin forces us to use functions in order to map from global state to local state.

No Value Types

There's no way to simply mutate the state in Kotlin like the Composable architecture does in Swift. The fix for this is the Mutable class which allows for the state to be mutated without requiring the user to explicitly return the new copy of the state, making the Reducers read a lot more like their iOS counterparts.

Subscriptions

Additionally we decided to extend Point-Free architecture with something called subscriptions. This concept is taken from the Elm Architecture. It's basically a way for us to leverage observable capabilities of different APIs, in our case it's mostly for observing data stored in Room Database.

📲 Sample App

To run the sample app, start by cloning this repo:

git clone [email protected]:toggl/komposable-architecture.git

Next, open Android Studio and open the newly created project folder. You'll want to run the todo-sample app.

For more examples take a look at Point-Free's swift samples

🚀 Installation

The latest release is available on Maven Central.

implementation 'com.toggl:komposable-architecture:0.1.1'

© Licence

Copyright 2021 Toggl LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

🧭 High-level View

This is a high level overview of the different parts of the architecture.

  • Views This is anything that can subscribe to the store to be notified of state changes. Normally this happens only in UI elements, but other elements of the app could also react to state changes.
  • Action Simple structs that describe an event, normally originated by the user, but also from other sources or in response to other actions (from Effects). The only way to change the state is through actions. Views dispatch actions to the store which handles them in the main thread as they come.
  • Store The central hub of the application. Contains the whole state of the app, handles the dispatched actions passing them to the reducers and fires Effects.
  • State The single source of truth for the whole app. This data class will be probably empty when the application start and will be filled after every action.
  • Reducers Reducers are pure functions that take the state and an action and produce a new state. Simple as that. They optionally result in an array of Effects that will asynchronously dispatch further actions. All business logic should reside in them.
  • Effects As mentioned, Reducers optionally produce these after handling an action. They are classes that return an optional action. All the effects emitted from a reducer will be batched, meaning the state change will only be emitted once all actions are dispatched.
  • Subscriptions Subscriptions are emitting actions based on some underling observable API and/or state changes.

There's one global Store and one AppState. But we can view into the store to get sub-stores that only work on one part of the state. More on that later.

There's also one main Reducer and multiple sub-reducers that handle a limited set of actions and only a part of the state. Those reducers are then pulled back and combined into the main reducer.

🔎 Getting into the weeds

Store & State

The Store exposes a flow which emits the whole state of the app every time there's a change and a method to dispatch actions that will modify that state. The State is just a data class that contains ALL the state of the application. It also includes the local state of all the specific modules that need local state. More on this later.

The store interface looks like this:

interface Store<State, Action : Any> {
    val state: Flow<State>
    fun dispatch(actions: List<Action>)
    // more code
}

And you can create a new store using:

createStore(
    initialState = AppState(),
    reducer = reducer,
    subscription = subscription,
    dispatcherProvider = dispatcherProvider,
    storeScopeProvider = application as StoreScopeProvider
)

actions are dispatched like this:

store.dispatch(AppAction.BackPressed)

and views can subscribe like this:

store.state
    .onEach { Log.d(tag, "The whole state: \($0)") }
    .launchIn(scope)

// or

store.state
    .map { it.email }
    .onEach { emailTextField.text = it }
    .launchIn(scope)

The store can be "viewed into", which means that we'll treat a generic store as if it was a more specific one which deals with only part of the app state and a subset of the actions. More on the Store Views section.

Actions

Actions are sealed classes, which makes it easier to discover which actions are available and also add the certainty that we are handling all of them in reducers.

sealed class EditAction {
    data class TitleChanged(val title: String) : EditAction()
    data class DescriptionChanged(val description: String) : EditAction()
    object CloseTapped : EditAction()
    object SaveTapped : EditAction()
    object Saved : EditAction()
}

These sealed actions are embedded into each other starting with the "root" AppAction

sealed class AppAction {
    class List(override val action: ListAction) : AppAction(), ActionWrapper
    
   class 
   Edit(
   override 
   val 
   action
   : 
   EditAction) : AppAction(), ActionWrapper
   
    
    object 
    BackPressed 
    : 
    AppAction()
}
   
  

So to dispatch an EditAction to a store that takes AppActions we would do

store.dispatch(AppAction.Edit(EditAction.TitleChanged("new title"))

But if the store is a view that takes EditActions we'd do it like this:

store.dispatch(EditAction.TitleChanged("new title"))

Reducers & Effects

Reducers are classes that implement the following interface:

interface Reducer<State, Action> {
    fun reduce(state: MutableValue<State>, action: Action): List<Effect<Action>>
}

The idea is they take the state and an action and modify the state depending on the action and its payload.

In order to dispatch actions asynchronously we use Effects. Reducers return an array of Effects. The store waits for those effects and dispatches whatever action they emit, if any.

An effect interface is also straightforward:

interface Effect<out Action> {
    suspend fun execute(): Action?
}

Subscriptions

Subscriptions are similar to effects:

fun interface Subscription<State, Action : Any> {
    fun subscribe(state: Flow<State>): Flow<Action>
}

The difference is that Subscriptions are not triggered by Actions. They start immediately after the store is created and continue emitting as long as the store exists.

Subscriptions are typically used to observe some data in the database:

class ListSubscription @Inject constructor(val todoDao: TodoDao) : Subscription
    {
    
   override 
   fun 
   subscribe(
   state
   : 
   Flow<
   AppState>): 
   Flow<
   AppAction> 
   =
        todoDao.getAll().map { 
   AppAction.
   List(
   ListAction.
   ListUpdated(it)) }
}
  

Or some other observable APIs like for example location services. Subscription flow can be also steered by state changes:

class ListSubscription @Inject constructor(val locationProvider: LocationProvider) : Subscription
    {
    
   override 
   fun 
   subscribe(
   state
   : 
   Flow<
   AppState>): 
   Flow<
   AppAction> 
   =
        
   if (state.isPermissionGranted) 
          locationProvider.observeCurrentLocation().map { 
   AppAction.
   Map(
   MapAction.
   LocationUpdated(it)) }
        
   else 
          flowOf()
}
  

Pullback

There's one app level reducer that gets injected into the store. This reducer takes the whole AppState and the complete set of AppActions.

The rest of the reducers only handle one part of that state, for a particular subset of the actions.

This aids in modularity. But in order to merge those reducers with the app level one, their types need to be compatible. That's what pullback is for. It converts a specific reducer into a global one.

class PullbackReducer<LocalState, GlobalState, LocalAction, GlobalAction>(
    private val innerReducer: Reducer<LocalState, LocalAction>,
    private val mapToLocalState: (GlobalState) -> LocalState,
    private val mapToLocalAction: (GlobalAction) -> LocalAction?,
    private val mapToGlobalState: (GlobalState, LocalState) -> GlobalState,
    private val mapToGlobalAction: (LocalAction) -> GlobalAction
) : Reducer
    {
    
   override 
   fun 
   reduce(
        
   state
   : 
   Mutable<
   GlobalState>,
        
   action
   : 
   GlobalAction
    ): 
   List<
   Effect<
   GlobalAction>> {
        
   val localAction 
   = mapToLocalAction(action)
            
   ?
   : 
   return noEffect()

        
   return innerReducer
            .reduce(state.map(mapToLocalState, mapToGlobalState), localAction)
            .map { effect 
   -> effect.map { action 
   -> action?.
   run(mapToGlobalAction) } }
    }
}
  

After we've transformed the reducer we can use combine to merge it with other reducers to create one single reducer that is then injected into the store.

Store Views

Similarly to reducers and pullback, the store itself can be "mapped" into a specific type of store that only holds some part of the state and only handles some subset of actions. Only this operation is not exactly "map", so it's called view.

class MutableStateFlowStore<State, Action : Any> private constructor(
    override val state: Flow<State>,
    private val dispatchFn: (List<Action>) -> Unit
) : Store
    {

    
   override 
   fun <
   ViewState, 
   ViewAction 
   : 
   Any> 
   view(
        
   mapToLocalState
   : (
   State) 
   -> 
   ViewState,
        
   mapToGlobalAction
   : (
   ViewAction) 
   -> 
   Action
   ?
    ): 
   Store<
   ViewState, 
   ViewAction> 
   = 
   MutableStateFlowStore(
        state 
   = state.map { mapToLocalState(it) }.distinctUntilChanged(),
        dispatchFn 
   = { actions 
   ->
            
   val globalActions 
   = actions.mapNotNull(mapToGlobalAction)
            dispatchFn(globalActions)
        }
    )
}
  

This method on Store takes two functions, one to map the global state into local state and another one to map local action to global action.

Different modules or features of the app use different store views so they are only able to listen to changes to parts of the state and are only able to dispatch certain actions.

Local State

Some features have the need of adding some state to be handled by their reducer, but maybe that state is not necessary for the rest of the application. Consider email & password fields in a theoretical Auth module.

To deal with this kind of state we do the following:

  • In the module's state use a public class with internal properties to store the needed local state
  • We store that property in the global state. So that state in the end is part of the global state and it behaves the same way, but can only be accessed from the module that needs it.

This is how could the AuthState look like:

data class AuthState(
    val user: Loadable<User>,
    val localState: LocalState
) {
    data class LocalState internal constructor(
        internal val email: Email,
        internal val password: Password
    ) {
        constructor() : this(Email.Invalid(""), Password.Invalid(""))
    }
}

This is how it looks in the global app state

data class AppState(
    val authLocalState: AuthState.LocalState = AuthState.LocalState(),
)

High-order reducers

High-order reducers are basically reducers that take another reducer (and maybe also some other parameters). The outer reducer adds some behavior to the inner one, maybe transforming actions, stopping them or doing something with them before sending them forward to the inner reducer.

The simplest example of this is a logging reducer, which logs every dispatched action to the console:

action.list.formatForDebug() is AppAction.Edit -> action.edit.formatForDebug() } ) return innerReducer.reduce(state, action) } } ">
class LoggingReducer(override val innerReducer: Reducer<AppState, AppAction>)
    : HigherOrderReducer<AppState, AppAction> {
    override fun reduce(
        state: MutableValue<AppState>,
        action: AppAction
    ): List<Effect<AppAction>> {
        Log.i(
            "LoggingReducer", when (action) {
                is AppAction.List -> action.list.formatForDebug()
                is AppAction.Edit -> action.edit.formatForDebug()
            }
        )

        return innerReducer.reduce(state, action)
    }
}
Comments
  • Rename dispatch to send for consistency with TCA

    Rename dispatch to send for consistency with TCA

    Summary

    As discussed in #23, using send instead of dispatch would be beneficial for individuals and teams that have previous experience with TCA (The Composable Architecture). This PR changes the name to send, deprecating the dispatch methods.

    Relationships

    Closes #23

    Technical considerations

    Tests

    Existing tests have been updated. No new tests have been added since this is just a rename that doesn't introduce any new behavior.

    Review pointers

    As suggested by @semanticer, @Deprecated annotations were added, I hope I did them correctly.

    opened by jonekdahl 6
  • Recommendation for state access in composables (warnings in todo-example)

    Recommendation for state access in composables (warnings in todo-example)

    Hi there,

    Thank you so much for making this! I'm pretty new to Android development, and I have a question regarding the todo-example. In the Page composables there are warnings that say Flow operator functions should not be invoked within composition. Can these safely be ignored, or is there some other pattern that should be use when accessing state in a composable?

    image

    opened by jonekdahl 4
  • Naming convention

    Naming convention

    Hi,

    We were looking at using this but noticed that Store has a method called dispatch whereas TCA nomenclature for it, as far as we understood it, was send, is there any reason in particular for this difference? It was rather confusing when some of us had (some) previous knowledge from iOS examples and were trying to make our way through the Android TODO app example.

    Thank you!

    opened by nyaray 4
  • 🏗️ Make Effects functional interfaces

    🏗️ Make Effects functional interfaces

    Summary

    This updates the interface definition of Effect to mark it as a functional interface (SAM)

    Technical considerations

    It wasn't possible to have a SAM whose only method was a suspending fun in the version of Kotlin that we were using when building the library. Newer version of Kotlin allow it (hence why I bumped it)

    Tests

    No behavioral changes were made. It makes sense for the classes inside the library to be properly inherited, but there are use cases for users to have small, anonymous effects (similar to how Reducers can be full blown classes or not)

    opened by heytherewill 2
  • 🚀 Installation and publishing magic

    🚀 Installation and publishing magic

    Summary

    I will have to add some internal docs with all the private information etc but otherwise, everything is ready and the library is now published on maven central.

    I mostly followed this tutorial.

    We can improve this even further with bitrise publishing in the future.

    Relationships

    Closes https://github.com/toggl/android/issues/2135

    opened by semanticer 2
  • ✅ Basic Todo sample app implementation

    ✅ Basic Todo sample app implementation

    Summary

    I'm trying to create a TODO sample app here demonstrating the basic principles of this architecture.

    Relationships

    Closes https://github.com/toggl/android/issues/2110

    Technical considerations

    The goal is to create a two-screen todo list app. One screen for the list of todos and one for creating a new one. I want to show how Komposable Architecture can be used in a "real" environment with dependency injection(Hilt) and a database(Room).

    List of concepts I want to introduce here:

    • [x] Action, State, Reducer, Store
    • [x] Store composition
    • [ ] ~~Higher-order reducers~~ - I decided to skip this for now, it would make the sample app even more complex
    • [x] Hilt injection
    • [x] ViewModel usage
    • [x] Jetpack Compose integration
    • [x] Databases observation via subscription
    • [ ] ~~Subscription composition~~ - I'm not gonna complicate it with this either
    • [x] Simple reducer unit testing
    • [x] (optional) Navigation

    I also want to include

    • [x] Brief readme with screenshots etc

    Tests

    I included one sample test of ListReducer and its actions.

    Review pointers

    This is still a draft but I wanted to share my progress to maybe get some early feedback

    opened by semanticer 2
  • Navigation compose

    Navigation compose

    Hi, I'm using navigation-compose version 2.4.1 and 2.4.0-alpha07 I have error when navigating back (pop()) reached the init route

    But in [komposable-architecture]/ todo-sample this version 2.4.0-alpha07 working fine and version 2.4.1 error as the same

    What is that problem? How can I fix it?

    Thank you!

    opened by nakvanna 1
  • Add subscription extension methods and sample code

    Add subscription extension methods and sample code

    Summary

    We(I) forgot about extension methods that would actually enable CompositeSubscription creation. I also added one more subscription to todo-sample to demonstrate this behavior.

    Relationships

    Closes https://github.com/toggl/android/issues/2260

    Review pointers

    Does this work?

    opened by semanticer 1
  • Setup basic todo-sample structure

    Setup basic todo-sample structure

    Summary

    This is more about testing CI integration than about anything else. But I'm also giving todo-sample some package structure in the process.

    Technical considerations

    TODO

    opened by semanticer 1
  • 🚀 Release 0.2.0

    🚀 Release 0.2.0

    Changelog

    Added

    • Test module komposable-architecture-test
    • mergeSubscriptions(subscriptions: List<Subscription<State, Action>>) with a list parameter

    Changed

    • Renamed dispatch to send for consistency with TCA (thanks @jonekdahl)
    • Effect is now a functional interface (thanks @heytherewill)
    • Coroutines updated 1.5.2 -> 1.6.4
    • Kotlin updated 1.5.21 -> 1.7.20

    Fixed

    • Just documentation

    https://github.com/toggl/komposable-architecture/compare/main...v0.2.0 https://github.com/toggl/komposable-architecture/compare/v0.1.1...v0.2.0

    opened by semanticer 0
  • Correct and improve PUBLISHING.md

    Correct and improve PUBLISHING.md

    Description

    I had the pleasure of following PUBLISHING.md today since I forgot how to publish, and I have a new machine with no keys or any other local setup. It turns out PUBLISHING.md is missing some important information and even contains some misinformation 🙈

    We need to fix this so that the future me or you have an easier time publishing next time.

    bug 
    opened by semanticer 0
  • ❓ How to handle some advanced features

    ❓ How to handle some advanced features

    Hi! I'm a big fan of TCA in SwiftUI and I'm working on a Kotlin multiplatform project in which we want to use this architecture for both platforms.

    We've managed to code a lot of the app but we are starting to face some rough edges, so I'd like to get some inspiration on how to solve them.

    The challenges we (mostly) related to state restoration once the app comes back from background after some time. We create our substores in runtime depending on the user navigation, so once the reference is destroyed we cannot recreate them and the app crashes.

    Some examples:

    State is a sealed class and we need to use a composable function for handling every when branch so we don't deal with nullables: (TCA's SwitchStore)

    sealed class ProfileState {
      object Loading : ProfileState()
      data class Empty(val state: EmptyProfileState) : ProfileState()
      data class Filled(val state: FilledProfileState): ProfileState()
    }
    
    val profileReducer = Reducer.combine(
      Reducer { ... },
      EmptyProfileState.reducer.pullback(...),
      FilledProfileState.reducer.pullback(...)
    )
    
    @Composable
    fun HomeTab() {
      val store = hiltViewModel<HomeStore>() // State isn't too deep here so we can create this viewmodel in build time
      
     // WhenStore and Case allows us to use stores with non-nullable state for the current case. But these stores
     // can only be created in runtime by passing through this piece of code.
      WhenStore(store) {
        Case<ProfileState.Loading, ProfileAction>(
          deriveState = ...,
          embedAction = ::identity
        ) { loadingProfileStore ->
          Navigator(HomeLoadingProfilePage(loadingProfileStore))
        }
        
        Case<EmptyProfileState, EmptyProfileAction>(
          deriveState = ...,
          embedAction = ...
        ) { emptyProfileStore ->
          Navigator(HomeEmptyProfilePage(emptyProfileStore))
        }
        
        Case<FilledProfileState, FilledProfileAction>(
          deriveState = ...,
          embedAction = ...
        ) { filledProfileStore ->
          Navigator(HomeFilledProfilePage(filledProfileStore))
        }    
      }
    }
    

    Once in one of those pages, if the activity is destroyed, when the framework tries to recreate the screen it cannot create the store again as it isn't parcelable. Creating a store manually is possible, but it wouldn't make sense as the global state won't be updated.


    Recursive state and navigation.

    data class TaskDetailState(
      val title: String,
      val body: String,
      val subtasks: List<TaskDetailState>,
      val childDetail: TaskDetailState? = null
    )
    
    sealed class TaskDetailAction {
      ...
      
      data class ChildTaskDetailAction(val action: TaskDetailAction) : TaskDetailAction()
    }
    
    val reducer = Reducer.recurse { ... } 
    
    // Code for creating substores would be
    val store: Store<TaskDetailState, TaskDetailAction> = ...
    
    store.derived<TaskDetailState?, TaskDetailAction>(
      deriveState = { it.childDetail },
      embedAction = { TaskDetailAction.ChildTaskDetailAction(it) } 
    )
    

    Again, these kind of substores must be created in runtime as we don't know how many levels of nesting the user will navigate. Besides, they all have the same type signature, so a parameter is needed for retrieving them from DI. The most obvious one being the Task id, which we don't know until runtime.


    TLDR How would you work with store which can only be created in runtime? How would you make them survive process death?

    Thanks for your time ❤️

    question 
    opened by Cotel 3
  • Rewrite one of the TCA samples in a form of tutorial

    Rewrite one of the TCA samples in a form of tutorial

    Description

    It would be nice to add one of the Point-Free's samples for direct comparison. We could turn this into a tutorial similar to https://github.com/square/workflow-kotlin/tree/main/samples/tutorial

    Definition of done

    • [ ] New Sample app is created
    • [ ] (Optional) A tutorial is created around this sample
    documentation enhancement sample code 
    opened by semanticer 0
Releases(v0.2.0)
  • v0.2.0(Dec 9, 2022)

    Changelog

    Added

    • Test module komposable-architecture-test
    • mergeSubscriptions(subscriptions: List<Subscription<State, Action>>) with a list parameter

    Changed

    • Renamed dispatch to send for consistency with TCA (thanks @jonekdahl)
    • Effect is now a functional interface (thanks @heytherewill)
    • Coroutines updated 1.5.2 -> 1.6.4
    • Kotlin updated 1.5.21 -> 1.7.20

    Fixed

    • Just documentation

    https://github.com/toggl/komposable-architecture/compare/v0.1.1...v0.2.0

    Source code(tar.gz)
    Source code(zip)
  • v0.1.1(Oct 4, 2021)

Owner
Toggl
Toggl
A framework for writing composable parsers based on Kotlin Coroutines.

Parsus A framework for writing composable parsers based on Kotlin Coroutines. val booleanGrammar = object : Grammar<BooleanExpression>() { val ws

Aleksei Semin 28 Nov 1, 2022
Arrow Endpoint offers a composable Endpoint datatype, that allows us easily define an Endpoint from which we can derive clients, servers & documentation.

Arrow Endpoint Arrow Endpoint offers a composable Endpoint datatype, that allows us easily define an Endpoint from which we can derive clients, server

ΛRROW 23 Dec 15, 2022
Arrow Endpoint offers a composable Endpoint datatype, that allows us easily define an Endpoint from which we can derive clients, servers & documentation.

Arrow Endpoint Arrow Endpoint offers a composable Endpoint datatype, that allows us easily define an Endpoint from which we can derive clients, server

ΛRROW 8 Oct 11, 2021
AndroidArchitecture - An Implementation of Google Recommended New Android Architecture with Kotlin

Android Architecture An Implementation of Google Recommended New Android Archite

Nayeem Shiddiki Abir 5 Jun 1, 2022
Skeleton project for show the architecture of Android project using MVVM, Clean Architecture and Kotlin coroutine Flow

ClearScoreDemo Skeleton project for showing the architecture of Android project using MVVM, Clean architecture and Kotlin coroutine Flow App Architect

Plabon Modak 1 Mar 6, 2022
FaceTimeClone app that implements Coroutines , mvvm architecture , clean architecture , navigation component , hilt , etc.... using kotlin language

This repository contains a FaceTimeClone app that implements Coroutines , mvvm architecture , clean architecture , navigation component , hilt , etc.... using kotlin language

null 17 Dec 13, 2022
A fork of our clean architecture boilerplate, this time using the Android Architecture Components

Android Clean Architecture Components Boilerplate Note: This is a fork of our original Clean Architecture Boilerplate, except in this repo we have swi

Buffer 1.3k Jan 3, 2023
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

Kittinun Vantasin 28 Dec 10, 2022
📒 NotyKT is a complete 💎Kotlin-stack (Backend + Android) 📱 application built to demonstrate the use of Modern development tools with best practices implementation🦸.

NotyKT ??️ NotyKT is the complete Kotlin-stack note taking ??️ application ?? built to demonstrate a use of Kotlin programming language in server-side

Shreyas Patil 1.4k Jan 4, 2023
A kotlin implementation of commutative encryption based on ECC ElGamal encryption over Curve25519

komuta A commutative encryption implementation. This is a naive implementation of commutative encryption using the ElGamal scheme applied over Curve25

Mircea Nistor 7 Nov 17, 2022
Ethereum Web3 implementation for mobile (android & ios) Kotlin Multiplatform development

Mobile Kotlin web3 This is a Kotlin MultiPlatform library that ... Table of Contents Features Requirements Installation Usage Samples Set Up Locally C

IceRock Development 32 Aug 26, 2022
Netflix inspired OTT Home Screen, Contains implementation in Reactjs, Kotlin React Wrapper, Jetpack Compose Web

Netflix-Clone-React Practising React by building Netflix Clone Requirements TMDB api key : Add TMDB API key to AppApi.kt Learning Resourcce Build Netf

Chetan Gupta 61 Nov 13, 2022
Slime World Format implementation written in Kotlin with Zstd for bukkit.

Slime Korld Easily create many slime worlds with the Slime Korld. What is Slime Korld? Slime Korld is a bukkit library written in Kotlin to make minec

Luiz Otávio 11 Nov 15, 2022
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) ??

Alex Candido 3 Jan 10, 2022
A simple, classic Kotlin MVI implementation based on coroutines with Android support, clean DSL and easy to understand logic

A simple, classic Kotlin MVI implementation based on coroutines with Android support, clean DSL and easy to understand logic

Nek.12 4 Oct 31, 2022
A kotlin multiplatform BLS12-381 implementation for chia key management

KBLS KBLS is a kotlin multiplatform BLS12-381 implementation built for creating cross platform chia applications. Tips are much appreciated and will d

ChiaChat 7 Nov 21, 2022
A pure Kotlin/Multiplatform implementation of group operations on Curve25519.

curve25519-kotlin A pure Kotlin/Multiplatform implementation of group operations on Curve25519. Gradle Kotlin DSL: dependencies { implementation("

Andrey Pfau 9 Dec 22, 2022
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

libill 4 Nov 20, 2022