Inrtroduzione
Redux è un architettura dove tutto lo stato dell’applicazione vive in un container. L’unico modo di cambiare questo stato è di creare un nuovo stato basato sullo stato corrente e richiederne il cambiamento.
Andiamo ad esaminare i vari layer:
- Store gestisce gli stati dell applicazione
- State determina quale view elements mostrare
- Action e’ un immutable data che descrive il cambiamento di stato (solitamente un enum)
- Reducer cambia lo stato dell’app utilizzando lo stato corrente e un’action (una funzione che ha come input stato e action e ritorna uno state)
Dopo questa piccola premessa passiamo alla pratica…
Demo
Per la nostra demo andremo a creare una semplice calcolatrice, quindi passiamo alla creazione di un nuovo progetto di tipo single view e installiamo il seguente framework tramite cocoapods:
- pod ‘ReSwift’
Per maggiori info riguardanti il framework è possibile visionare il seguente link
Creazione dell’interfaccia
La nostra view conterra due text field per gli input, una label per il visualizzare il risultato delle operazione aritmetiche e 4 bottoni per le quattro (addizione, sottrazione, moltiplicazione e divisione)
Implementazione del codice
Per prima cosa, andiamo a creare un nuovo file per dichiarare l’ AppState. Com’è possibile notare dall codice qui sotto, l’Appstate contiene solamente una variabile per il risultato delle operazione aritmetiche.
AppState
[swift]
import ReSwift
struct AppState: StateType {
var result = 0.0
}
[/swift]
Action
Come detto precedentemente l’action sono delle operazioni che descrivono un cambiamento di stato in questo caso addizione,sottrazione, moltiplicazione e divisione.
[swift]
import ReSwift
enum ActionCalculator: Action {
case addition(firstValue: String?, secondValue: String?)
case subtraction(firstValue: String?, secondValue: String?)
case multiplication(firstValue: String?, secondValue: String?)
case division(firstValue: String?, secondValue: String?)
}
[/swift]
Reducer
Il reducer sostanzialmente è una funzione che prende come input, l’action e lo stato dell’applicazione e ritorna il nuovo stato. In poche parole è qui che viene implementato la logica di bussiness, tutta via per operazioni più complesse si crea un middleware.
[swift]
import ReSwift
func calculatorReducer(action: Action, state: AppState?) -> AppState {
var state = state ?? AppState()
switch action {
case ActionCalculator.addition(let first, let second):
state.result = Double.stringToDouble(stringVal: first) + Double.stringToDouble(stringVal: second)
case ActionCalculator.multiplication(let first, let second):
state.result = Double.stringToDouble(stringVal: first) * Double.stringToDouble(stringVal: second)
case ActionCalculator.subtraction(let first, let second):
state.result = Double.stringToDouble(stringVal: first) – Double.stringToDouble(stringVal: second)
case ActionCalculator.division(let first, let second):
state.result = Double.stringToDouble(stringVal: first) / Double.stringToDouble(stringVal: second)
default:
break
}
return state
}
[/swift]
View
In redux il view controller deve essere il più semplice possibile ovvero deve contenere solamente gli elementi grafici e non deve effettuare nessun altra operezione.
[swift]
import UIKit
import ReSwift
let mainStore = Store<AppState>(reducer: calculatorReducer, state: nil)
class ViewController: UIViewController {
@IBOutlet weak var resultLabel: UILabel!
@IBOutlet weak var secondValueLabel: UITextField!
@IBOutlet weak var firstValueLabel: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
mainStore.subscribe(self)
}
override func viewWillDisappear(_ animated: Bool) {
mainStore.unsubscribe(self)
}
@IBAction func divide(_ sender: Any) {
mainStore.dispatch(ActionCalculator.division(firstValue: firstValueLabel.text, secondValue: secondValueLabel.text))
}
@IBAction func add(_ sender: Any) {
mainStore.dispatch(ActionCalculator.addition(firstValue: firstValueLabel.text, secondValue: secondValueLabel.text))
}
}
extension ViewController: StoreSubscriber {
func newState(state: AppState){
DispatchQueue.main.async { [weak self] in
guard let self = self else {return}
self.resultLabel.text = "\(state.result)"
}
}
}
[/swift]
Il codice completo dell’ applicazione è disponibile su github.