prescribing rx responsibly
TRANSCRIPT
![Page 1: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/1.jpg)
PRESCRIBING RX RESPONSIBLY 💊
2 0 1 7
![Page 2: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/2.jpg)
2
AGENDA
01RX INTRO
02WHEN TO USE RX (OR NOT)
03RX BEST PRACTICES
04CONCLUSION AND TAKEAWAYS
![Page 3: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/3.jpg)
WHAT IS RX?
![Page 4: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/4.jpg)
4
today’s talk
not today’s talk
RX-BERG
![Page 5: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/5.jpg)
W H A T I S R X ?
5
•RxSwift - Swift implementation of ReactiveX
•Follows the “Observer pattern”
•Declarative way of defining the data flow in your app
•Avoid “callback hell”
•Data flow is handled via manageable streams
![Page 6: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/6.jpg)
W H A T I S R X ?
6
STREAMS
Observable<WaterMolecule>
Observable<Bool>
Observable<MeetUp>
of things. One thing at a time.
![Page 9: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/9.jpg)
W H A T I S R X ?
9
Observable
RX ECOSYSTEM
Variable
Subject
PublishSubject
Driver
DisposeBag
BehaviorSubject
Observer
![Page 10: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/10.jpg)
W H A T I S R X ?
10
Observable
RX ECOSYSTEM
Variable
Subject
PublishSubject
Driver
DisposeBag
BehaviorSubject
Observer
![Page 11: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/11.jpg)
WHEN TO USE RX
![Page 12: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/12.jpg)
12
01User actions (button taps, text field delegates)
02Async operations (Network calls, processing)
03Bindings (VC!!<-> VM !!<-> Model)
L I S T
WHEN TO USE RX
04Prevent code 🍝
![Page 13: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/13.jpg)
B U T T O N A C T I O N
13
WITHOUT RX
@IBAction func logoTapped(_ sender: UIButton) { dismissUntilHome() }
navBar.logoButton !=> dismissUntilHome !!>>> rx_disposeBag
WITH RX
Drag and drop to create IBAction function. A bit more complicated if it is nested in a custom view.
We are using Fira Code font: https://github.com/tonsky/FiraCode
![Page 14: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/14.jpg)
D A T E P I C K E R
14
WITH RX
WITHOUT RX
Drag and drop to create IBAction function. A bit more complicated if it is nested in a custom view, or number of date pickers are not constant.
datePicker.rx.date !=> viewModel.endDate !!>>> rx_disposeBag
@IBAction func datePicked(_ sender: UIDatePicker) { viewModel.endDate = sender.date }
![Page 15: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/15.jpg)
T E X T F I E L D
15
WITH RX
titleField.textView.rx.text.orEmpty !!<-> viewModel.title !!>>> rx_disposeBag
Create binding in view controller.
WITHOUT RX
Set up delegate for the text field to listen for edit events to update view model, and manually trigger UI update when view model’s property has changed.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String)
var title: String = "" { didSet { updateTextFields() } }
![Page 16: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/16.jpg)
SCROLLING UNDER
S C R O L L V I E W
![Page 17: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/17.jpg)
S C R O L L V I E W
17
WITH RX
tableView.rx_scrolledUnderTop !=> viewModel.showTopGradient !!>>> rx_disposeBag tableView.rx_scrolledUnderBottom !=> viewModel.showBottomGradient !!>>> rx_disposeBag
Create binding in view controller.
WITHOUT RX
Set up delegate extensions and do the calculation within the method, at multiple places for multiple classes:
func scrollViewDidScroll(_ scrollView: UIScrollView)
![Page 18: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/18.jpg)
P A G I N A T I O N
18
SET UP DATA CONTROLLER
func getPaginatedData<T: RealmSwift.Object>(resource: Resource, loadNextPageTrigger: Observable<Void>, dataParser: @escaping (Data) !-> ([T], Int)) !-> Observable<[T]> { let existingObjects: [T] = Realm.ip_objects(type: T.self)!?.toArray() !?? [] return recursiveGetPaginatedData(resource: resource, lastModified: lastModifiedDate, dataParser: dataParser, loadedSoFar: [], page: 1, loadNextPageTrigger: loadNextPageTrigger).startWith(existingObjects) }
func recursiveGetPaginatedData<T: RealmSwift.Object>(resource: Resource, dataParser: @escaping (Data) !-> ([T], Int), loadedSoFar: [T], page: Int, loadNextPageTrigger: Observable<Void>) !-> Observable<[T]> { guard let urlRequest = URLRequest(builder: URLRequestBuilder(resource: resource, paginationPage: page, authenticationToken = authenticationToken) else { return Observable.just(loadedSoFar) } return networkOperationQueue.add(dataRequest: urlRequest).observeOn(MainScheduler.instance) .flatMap { data !-> Observable<[T]> in var justLoaded = loadedSoFar let (models, paginationTotalItems) = dataParser(data) justLoaded.append(contentsOf: models) if justLoaded.count !== paginationTotalItems { Realm.ip_add(justLoaded, update: true, configuration: self.realmConfiguration) return Observable.just(justLoaded) } return Observable.concat([ Observable.just(justLoaded), Observable.never().takeUntil(loadNextPageTrigger), Observable.deferred { self.recursiveGetPaginatedData(resource: resource, dataParser: dataParser, loadedSoFar: justLoaded, page: page + 1, loadNextPageTrigger: loadNextPageTrigger) } ]) } }
Functions of the network call in data controller:
![Page 19: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/19.jpg)
P A G I N A T I O N
19
SET UP VIEW MODEL
func opportunities(loadNextPageTrigger: Observable<Void>) !-> Observable<[OpportunityModel]> { return getPaginatedData(resource: Resource.opportunities, loadNextPageTrigger: loadNextPageTrigger) { (data) !-> ([OpportunityRealmModel], Int) in let opportunitiesModel = try! OpportunitiesModel(node: data) return (opportunitiesModel.opportunities, opportunitiesModel.total) } .map { $0 as [OpportunityModel] } }
Function of the API call in data controller:
Where we make the API call in view model:
dataController.opportunities(loadNextPageTrigger: nextPageTrigger.asObservable()) .map { $0.map { OpportunityCellViewModel(opportunity: $0) } } .subscribe( onNext: { self.opportunityCellViewModels = $0 self.hasMoreOpportunities = true }, onError: { Logger.error($0) NotificationCenter.postMessage(type: .requestFailure) self.hasMoreOpportunities = false }, onCompleted: { self.opportunityCellViewModels.append(EndOfListViewModel()) self.hasMoreOpportunities = false }) !!>>> rx_disposeBag
![Page 20: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/20.jpg)
P A G I N A T I O N
20
GET NEXT PAGE IN VIEW MODEL
func nextPage() { nextPageTrigger.fire() }
How we get the next page in the view model:
![Page 21: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/21.jpg)
N E T W O R K C A L L S
21
CHAINED NETWORK CALLS
guard let s3Object = requestS3Object(for: .opportunity) else { return nil }
return s3Object.observeOn(MainScheduler.instance).flatMap { s3Object !-> Observable<Bool> in opportunity.imageURL = URL(string: s3Object.publicURL) opportunity.imageKey = s3Object.key guard let presignedURL = URL(string: s3Object.presignedURL) else { return Observable.error(RxURLSessionError.requestCreationError) } return self.uploadImage(data: imageData, to: presignedURL) }.observeOn(MainScheduler.instance).flatMap { imageUploadSuccess !-> Observable<Data> in requestBuilder.data = opportunity.toJson() guard let urlRequest = URLRequest(builder: requestBuilder) else { return Observable.error(RxURLSessionError.requestCreationError) } return self.networkOperationQueue.add(dataRequest: urlRequest) }
![Page 22: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/22.jpg)
R E A C H A B I L I T Y
22
CREATE REACHABILITY SERVICE
class DefaultReachabilityService: ReachabilityService { private let _reachabilitySubject: BehaviorSubject<ReachabilityStatus> var reachability: Observable<ReachabilityStatus> { return _reachabilitySubject.asObservable() } let _reachability: Reachability init() throws { guard let reachabilityRef = Reachability() else { throw ReachabilityServiceError.failedToCreate } let reachabilitySubject = BehaviorSubject<ReachabilityStatus>(value: .unreachable) let backgroundQueue = DispatchQueue(label: "reachability.wificheck") reachabilityRef.whenReachable = { reachability in backgroundQueue.async { reachabilitySubject.on(.next(.reachable(viaWiFi: reachabilityRef.isReachableViaWiFi))) } } reachabilityRef.whenUnreachable = { reachability in backgroundQueue.async { reachabilitySubject.on(.next(.unreachable)) } } try reachabilityRef.startNotifier() _reachability = reachabilityRef _reachabilitySubject = reachabilitySubject } }
How we create observable for reachability of network (by Krunoslav Zaher):
![Page 23: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/23.jpg)
R E A C H A B I L I T Y
23
DISPLAY REACHABILITY MESSAGE
reachabilityService.reachability .skip(1) .throttle(10, scheduler: MainScheduler.instance) .observeOn(MainScheduler.instance) .subscribe(onNext: { $0.reachable ? self.hideMessage() : self.showMessage(.lostConnection) }) !!>>> disposeBag
How we subscribe to reachability observable:
![Page 24: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/24.jpg)
B L U E T O O T H
24
SUBSCRIBING TO A BLUETOOTH STREAM
class AwesomeViewController: UIViewController { let viewModel = DeviceStatusViewModel()
@IBOutlet weak var batteryImageView: UIImageView!
func viewDidLoad() { bindToViewModel() }
override func bindToViewModel() { super.viewDidLoad()
viewModel.devicesManager.batteryStatus .subscribeOn(MainScheduler.instance) .subscribe(next: { batteryStatus in self.batteryImageView.image = self.batteryImageForStatus(batteryStatus) }) !!>>> rx_diposeBag } }
![Page 25: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/25.jpg)
L O O K S G R E A T B U T …
25
STACKTRACE HELL
![Page 26: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/26.jpg)
RX BEST PRACTICES
![Page 27: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/27.jpg)
B E S T P R A C T I C E S
27
infix operator !=> : Binding infix operator !!>>> : Binding
public func !=> <T, P: ObserverType>(left: Variable<T>, right: P) !-> Disposable where P.E !== T { return left.asObservable().bindTo(right) }
public func !=> (left: UIButton, right: @escaping () !-> Void) !-> Disposable { return left.rx.tap.subscribe(onNext: { right() }) }
CREATE OPERATORS FOR COMMON TASKSSyntax sugar that greatly reduces boilerplate code:
![Page 28: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/28.jpg)
B E S T P R A C T I C E S
28
public func !!<-> <T>(property: ControlProperty<T>, variable: Variable<T>) !-> Disposable { let bindToUIDisposable = variable .asObservable() .bindTo(property)
let bindToVariable = property .subscribe( onNext: { n in variable.value = n }, onCompleted: { bindToUIDisposable.dispose() } ) return Disposables.create(bindToUIDisposable, bindToVariable) }
TWO-WAY BINDING
![Page 29: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/29.jpg)
S C R O L L V I E W
29
SCROLL VIEW EXTENSIONS (AS PROMISED)
extension UIScrollView { public var rx_scrolledUnderTop: Observable<Bool> { return self.rx.contentOffset .map { $0.y > 0 } .distinctUntilChanged() }
public var rx_scrolledUnderBottom: Observable<Bool> { return self.rx.contentOffset .map { $0.y < self.contentSize.height - self.frame.size.height - 1 } .distinctUntilChanged() } }
Create extension for scroll view.
![Page 30: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/30.jpg)
B E S T P R A C T I C E S
30
cell.viewOpportunityOverlayView.rx_tapGesture !=> { self.showOpportunityDetail(opportunityVM.opportunity) } !!>>> cell.cellDisposeBag
WATCH OUT FOR CELL REUSEBe sure to reset bindings on cell reuse! In view controller:
override func prepareForReuse() { super.prepareForReuse() cellDisposeBag = DisposeBag() }
In table view cell:
![Page 31: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/31.jpg)
B E S T P R A C T I C E S
31
func bindToViewModel() { Observable.combineLatest(vm.passwordValid, vm.passwordIsMinLength) { $0 !&& $1 } !=> passwordReqsLabel.rx_hidden !!>>> rx_disposeBag
vm.emailAddress !<- emailAddressField.rx_text !!>>> rx_disposeBag vm.password !<- passwordField.rx_text !!>>> rx_disposeBag vm.passwordConfirmation !<- confirmPasswordField.rx_text !!>>> rx_disposeBag }
@IBOutlet weak var settingsButton: UIButton! { didSet { settingsButton !=> showSettingsVC !!>>> rx_disposeBag } }
DESIGNATED METHOD FOR BINDING
![Page 32: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/32.jpg)
B E S T P R A C T I C E S
32
class DeviceManager {
private var batteryStatus = Variable<BatteryLevel>(.low)
public var batteryStatusObs = batteryStatus.asObservable()
}
PUBLIC VS. PRIVATE
![Page 33: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/33.jpg)
B E S T P R A C T I C E S
33
extension ObservableType {
public func ip_repeatingTimeouts( interval dueTime: RxTimeInterval,
element: E, scheduler: SchedulerType = MainScheduler.instance ) !-> Observable<E> {
return Observable.of( self.asObservable(), debounce(dueTime, scheduler: scheduler).map { _ in element } )
.merge() } }
REPEATING TIMEOUTS
![Page 34: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/34.jpg)
CONCLUSIONS
![Page 35: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/35.jpg)
35
• What are you reacting to?
• Are you using a struct or a class?
• Observable vs. Variable?
• Does the subscription need to update things on the screen?
• Will the view update while it’s being displayed?
ASK YOURSELF…
C O N C L U S I O N S
![Page 36: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/36.jpg)
36
CLOSING THOUGHTS
C O N C L U S I O N S
© Christian Howland
![Page 37: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/37.jpg)
37
• RxMarbles.com
• ReactiveX.io
• https://github.com/IntrepidPursuits/swift-wisdom
• https://github.com/ReactiveX/RxSwift
• rxswift.slack.com
USEFUL LINKS
C O N C L U S I O N S
![Page 38: Prescribing RX Responsibly](https://reader034.vdocuments.net/reader034/viewer/2022042513/58e4a01b1a28abf5428b5f95/html5/thumbnails/38.jpg)
THANKS!