-
Notifications
You must be signed in to change notification settings - Fork 42
Mobius and Mobile Apps
As discussed when talking about configuration, a Mobius.Builder
is useful if you want to be able to start the same loop many times from different starting points. One example of this is when connecting a MobiusLoop
to your mobile app.
Whether you’re using Activities, Fragments, UIViewControllers or some other abstractions, you typically have some concept of a lifecycle or restoring state. There may or may not be a saved state available when your component starts, but if there is some saved state, you should start from it instead from starting from a default state. On top of that there are usually lifecycle callbacks where you need to pause and subsequently resume the execution.
These cases are examples of starting from different Model
objects, and the reason why we use Mobius.Builder
when connecting Mobius to our app. It allows Mobius to keep track of state for you, and create new loops as required.
We will start by creating a Mobius.Builder
:
let loopBuilder: Mobius.Builder<MyLoopTypes> = Mobius
.loop(update: myUpdate, effectHandler: myEffectHandler)
.withInitiator(myInit)
.withEventSource(myEventSource)
.logger(ConsoleLogger<MyLoopTypes>(tag: "my_app"))
To manage its lifecycle we will be using a MobiusLoop.Controller
, which allows the loop to be stopped and resumed from the last state.
let controller = MobiusController(builder: loopBuilder, defaultModel: MyModel.makeDefault());
Above we have created a MobiusLoop.Controller
and now we are going to to hook it up to the lifecycle events of our UIViewController
:
class MyViewController: UIViewController {
private let loopController: MobiusController<MyLoopTypes>
// ...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loopController.connectView(AnyConnectable<MyModel, MyEvent>(self.connectViews))
loopController.start()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
loopController.stop()
loopController.disconnectView()
}
//...
}
Now let's implement the one part we’ve left out: connectViews()
. The argument to MobiusController.connectView(...)
is a Connectable
with the form Connectable<Model,Event>
; it receives Model
s instead of Effect
s. We hold on to the eventConsumer
and derive events from UIButton
interactions:
private var eventConsumer: Consumer<MyEvent>?
// ...
@objc func emitEvent() {
eventConsumer?(MyEvent.buttonPressed)
}
func connectViews(consumer: @escaping Consumer<MyEvent>) -> Connection<MyModel> {
self.eventConsumer = consumer
button.addTarget(self, action: #selector(emitEvent), for: .touchUpInside)
let accept: (MyModel) -> Void = { model in
// Keep in mind that this closure can be called from a background thread
DispatchQueue.main.async {
self.myLabel.text = model.getText()
}
}
let dispose = {
self.eventConsumer = nil
self.button.removeTarget(nil, action: nil, for: .allEvents)
}
return Connection(acceptClosure: accept, disposeClosure: dispose)
}
This becomes the one place where we hook up event listeners to the UI and update the UI based on the Model
. Now our MobiusLoop
gets created whenever the UIViewController
starts, and it’ll stop and resume from where it left off whenever the UIViewController
disappears and reappears.
Getting Started
Reference Guide
Patterns