Skip to content
Abhi Muktheeswarar edited this page Nov 27, 2022 · 15 revisions

Multiplatform

In common / shared module's build.gradle.kts

kotlin {
  sourceSets {
      val commonMain by getting {
          dependencies {
              implementation("com.msabhi:flywheel:1.1.5-RC")
          }
      }
  }
}

Flywheel supports all the available platforms when used as a dependency for a Kotlin Multiplatform project, since the core of the Flywheel is defined in the common module itself, there are no platform-specific core implementations. Flywheel uses the multithreaded version of kotlinx.coroutines. Even though it is production-ready, care should be taken to handle threading properly in non-JVM platforms.

Android

In app's build.gradle.kts

dependencies {

    implementation("com.msabhi:flywheel-android:1.1.5-RC")
}
  • We recommend using Flywheel with ViewModel.
  • The StateReserve can be scoped to ViewModel's lifecycle. But, don't use the viewModelScope, since it uses Dispatchers.Main. Provide your own CoroutineScope that uses Dispatchers.Default + SupervisorJob() For ease of use, you can use the getDefaultScope() provided with Flywheel.
  • You should call stateReserve.terminate() in viewModels's onCleared(), which cancels the scope.
  • Flywheel's StatReserve can also be used at the application level for use-cases like maintaining the user's authentication state, app configuration, etc...

Please check the provided sample counter app for a better understanding.

JVM

Almost similar to Android. You can use Flywheel with any architecture that fits your application.

Apple

Usually, the Kotlin Multiplatform approach is used in combination with a Kotlin Multiplatform project where Flywheel will be added as a common module's dependency. Still, Flywheel can be used as a standalone framework with Apple platforms using Swift Package Manager.

Swift Package Manager

You can use the Swift Package Manager to install Flywheel by adding the proper description to your Package.swift file:

// swift-tools-version:5.3
import PackageDescription

let package = Package(
    name: "YOUR_PROJECT_NAME",
    dependencies: [
        .package(url: "https://github.com/abhimuktheeswarar/Flywheel.git", from: "1.1.5-RC"),
    ]
)

Then run swift build whenever you get prepared.

To help creating StateReserve easier from Swift, we have provided a helper class StateReserveHolder

open class StateReserveHolder<S : State>(
    initialState: S,
    reduce: Reduce<S>,
    config: StateReserveConfig?,
)

This is just an optional wrapper, feel free to use the StateReserve directly.

A simple counter app

import SwiftUI
import flywheel

struct ContentView: View {
    
    @ObservedObject var viewModel = CounterViewModel()
    
    var body: some View {
        VStack(alignment: .center)    {
            HStack( content: {
                Spacer()
                Button("DECREMENT")  {viewModel.decrement()}
                Spacer()
                Text("\(viewModel.state.count)")
                Spacer()
                Button("INCREMENT")  {viewModel.increment()}
                Spacer()
            })
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

class CounterState : Equatable,Hashable  {
    
    var count :  Int
    
    init() {
        count = 0
    }
    
    static func == (lhs: CounterState, rhs: CounterState) -> Bool {
        debugPrint("equality check")
        return lhs.count == rhs.count
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(count)
    }
}

class CounterIncrementAction : Action {}
class CounterDecrementAction : Action {}
class CounterResetAction : Action {}


class CounterViewModel : ObservableObject {
    
    private lazy var stateReserveHolder = StateReserveHolder(initialState:  CounterState(), reduce: reduce.self, config: nil)
    
    @Published var  state : CounterState = CounterState()
    
    
    init() {
        
        stateReserveHolder.states.collect(collector: Collector<CounterState> { counterState in
            self.state = counterState
            
        }) { (unit, error) in
            // code which is executed if the Flow object completed
        }
        
        stateReserveHolder.actions.collect(collector: Collector<Action> { action in
            debugPrint("action = \(action) | state = \(self.state.count)")
            if(self.state.count ==  -10) {
                self.dispatch(action: CounterResetAction())
            }
            
        }) { (unit, error) in
            
        }
        
        CounterSideEffect.init(stateReserve: stateReserveHolder.stateReserve, dispatchers: DispatcherProviderImpl.init())
    }
    
    func reduce(action:Action,currentState :CounterState)  -> CounterState {
        let newState = currentState
        
        switch action {
        case _ as CounterIncrementAction:
            newState.count += 1
        case _ as CounterDecrementAction:
            newState.count -= 1
        case _ as CounterResetAction:
            newState.count = 0
        default:
            break
        }
        
        return newState
    }
    
    private func dispatch(action : Action)  {
        stateReserveHolder.dispatch(action: action)
    }
    
    func increment()  {
        dispatch(action: CounterIncrementAction())
    }
    
    func decrement()  {
        dispatch(action: CounterDecrementAction())
    }
}



class CounterSideEffect: SideEffect<CounterState> {
    
    override init(stateReserve: StateReserve<CounterState>, dispatchers: DispatcherProvider) {
        super.init(stateReserve: stateReserve, dispatchers: dispatchers)
        stateReserve.actionStates.collect(collector: Collector<ActionStateAlways<Action,CounterState>> { actionState in
            self.handle(action: actionState.action, state: actionState.state)
            
        }) { (unit, error) in
            // code which is executed if the Flow object completed
        }
        
    }
    
    func handle(action: Action, state :CounterState) {
        debugPrint("count = \(state.count)")
        if(state.count ==  10) {
            self.dispatch(action: CounterResetAction())
        }
    }
}

Please look at the CoroutineHelper file provided in the sample iOS app module, which provides wrappers for collecting a Flow<S> in Swift.

JavaScript

Though, we don't want to compete with Redux. Flywheel will be provided as a standalone npm package soon. As of now, Add Flywheel as a dependency to your common/shared module in your Kotlin Multiplatform project.

Other platforms

Add Flywheel as a dependency to your common/shared module in your Kotlin Multiplatform project.

Clone this wiki locally