## Notes about declaring types ### `ScreenModule` #### `State` When declaring `State`, and deciding where and how to declare the individual pieces of content for a `Screen`, there are generally three kinds of content: - **Dynamic** - these are parts of the screen that can change _while it is running_ - `username` and `password` fields for a login screen are great examples of this, as they change when the user types. Dynamic `State` properties are declared as a `var`. - **Screen invariant** - pieces of content that will _never_ change during the lifetime of a screen instance, but could differ from instance to instance. For example, we could add a `message` property to the login screen for describing why a user needs to login - e.g., "Log in to change your settings", or "Confirm your login to delete your account". Invariant `State` properties are declared as a `let`. - **Constant** - These are things that are always the same, no matter what. For example, the login screen in our fictitious app will _always_ have the title "Login". Constants aren't added to `State`. ```swift enum State: Equatable { var username: String var password: String let message: String } ``` #### `Action` An interesting way to think about `Action` cases is as functions in a protocol. E.g., consider the set of `Actions`: ```swift enum Action: Equatable { case didTapButton case didEnterValue(String) } ``` is analgous to this protocol: ```swift protocol MyScreenActionDelegate { func didTapButton() func didEnterValue(_ value: String) } ``` The nice thing about `Actions` as enum cases is that: - you can collect them - e.g. as an undo stack, or for unit testing purposes; - they can be comparable for easy unit testing - e.g., to make sure a a `View` produces the proper actions in response to user actions. - they are a concrete type - so they can be nested within another type (as we do in a `ScreenModule`), and they play well with generics. #### `createScreen` There are a few convenience versions of `createScreen` available. The most commonly used - besides the no-argument version - is the one that allows for a specific initial state. In a login screen example, if we wanted to pre-populate our username field with the last username used, we can call a version of `createScreen` that allows us to specify an initial state: ```swift let initialState = LoginScreenModule.State(username: "Billie") let screen = LoginScreenModule.createScreen(with: initialState) ``` Note that it's quite common to create your own versions of `createScreen`, to allow for even more concise usage: ```swift public static func createScreen(username: String) -> Screen { let initialState = LoginScreenModule.State(username: username) return createScreen(with: initialState) } // Client usage: let screen = LoginScreenModule.createScreen(username: "Billie") ``` #### Default types ##### `ScreenModule` / `StoreModule` You don't _have_ to declare all of the value types in a `ScreenModule`. For, example if you don't need an `Output`, there's no need to declare an empty enum for it. When left out, a `ScreenModule`'s' `Output` will be declared for you as `NoOutput`, which is in fact just an empty enum. `Action` defaults to `NoAction`, and `State` to `EmptyState`. The only component with no default value is the `createScreen` function - this function must always be defined. ```swift enum BlankModule: ScreenModule { static func createScreen(with store: BlankStore) -> Screen { return Screen(store, UIViewController()) } } final class BlankStore: LassoStore { } ``` Technically speaking, `Action`, `Output`, and `State` are all associated types in the `StoreModule` protocol. `ScreenModule` _is_ a protocol with `StoreModule` conformance plus the notion of a `Store` and `UIViewController` grouped as a `Screen` ##### `FlowModule` The `FlowModule` protocol also has defaults for the types you can declare. Similar to `ScreenModule`, `Output` is defaulted to `NoOutput` in a `FlowModule`. The `RequiredContext` associated type is defaulted to `UIViewController`. If your module has no special placement requirements, you can leave out the `RequiredContext`: ```swift enum MyFlowModule: FlowModule { enum Output: Equatable { case somethingHappened } } ``` Furthermore, in cases where you're writing a module that will not emit any output, you can leave out that declaration, too. There are also some pre-defined modules for common situations, so you can even get away with _not_ explicitly declaring your own `FlowModule`. These are: - `NoOutputFlow` - `Output` = `NoOutput` - `RequiredContext` = `UIViewController` - `NoOutputNavigationFlow` - `Output` = `NoOutput` - `RequiredContext` = `UINavigationController`