Skip to content

Instantly share code, notes, and snippets.

@acrookston acrookston/draft.swift
Last active Jan 23, 2019

Embed
What would you like to do?
Exploring view/controller state with Loading / Content / Error (LCE)
/// ContentLoader is a lightweight state manager for loading and displaying content and/or errors.
/// It allows you to repeatedly make requests and display new content or cached content in the event of an error.
/// Commonly known as RemoteData or LCE (Loading / Content / Error).
/// Inspired by https://tech.instacart.com/lce-modeling-data-loading-in-rxjava-b798ac98d80
///
final class ContentLoader<T> {
init() { }
@discardableResult func loader(_ loader: (() -> Void)?) -> ContentLoader<T> {
self.loader = loader
return self
}
@discardableResult func update(_ update: (() -> Void)?) -> ContentLoader<T> {
self.update = update
return self
}
private var loader: (() -> Void)?
private var update: (() -> Void)?
var loading: Bool = false
var content: T?
var error: Error?
var hasContent: Bool { return content != nil }
var hasError: Bool { return error != nil }
func load() {
loading = true
loader?()
}
func success(content: T) {
self.content = content
self.loading = false
update?()
}
func failure(error: Error?) {
self.error = error
self.loading = false
update?()
}
}
// usage psudocode
class ViewController {
let contentLoader = ContentLoader<JSON>()
init {
self.contentLoader.loader({
async {
loadData { data, error in
if let json = JSON(data) {
self.contentLoader.success(json)
} else {
self.contentLoader.failure(error ?? ParsingError())
}
}
}
}).update({
updateTheUI()
}).load()
}
func updateTheUI() {
// Handle the state enum to draw correct UI
}
}
import BrightFutures
import Result
/// ContentLoader is a lightweight state manager for loading and displaying content and/or errors.
/// It allows you to repeatedly make requests and display new content or cached content in the event of an error.
/// Commonly known as RemoteData or LCE (Loading / Content / Error).
/// Inspired by https://tech.instacart.com/lce-modeling-data-loading-in-rxjava-b798ac98d80
///
final class ContentLoader<T> {
init() { }
private var loader: (() -> Void)?
private var update: (() -> Void)?
var loading: Bool = false
var content: T?
var error: Error?
var hasContent: Bool { return content != nil }
var hasError: Bool { return error != nil }
@discardableResult func loader(_ loader: (() -> Void)?) -> ContentLoader<T> {
self.loader = loader
return self
}
@discardableResult func onUpdate(_ update: (() -> Void)?) -> ContentLoader<T> {
self.update = update
return self
}
func load(loader: (() -> Void)?) {
loading = true
if let loader = loader {
loader()
} else if let loader = self.loader {
loader()
} else {
loading = false
failure(error: ContentLoaderError.noLoadingMethod)
}
}
func load(future: Future<T, AnyError>) {
loading = true
future
.onSuccess { self.success(content: $0) }
.onFailure { self.failure(error: $0.error) }
.onComplete { _ in self.loading = false }
}
func success(content: T) {
self.content = content
self.loading = false
update?()
}
func failure(error: Error?) {
self.error = error
self.loading = false
update?()
}
/// Does not cancel any ongoing async work
func reset() {
error = nil
content = nil
loading = false
}
}
enum ContentLoaderError: Error {
case noLoadingMethod
}
// Pseudocode
// Problems:
// - no cached state
// - can't display error over existing content
// - not easily reusable
// Is less verbose, perhaps quicker to understand.
class ViewController {
enum State {
case loading
case success(JSON?)
case failure(Error?)
}
var state: State {
didSet { updateTheUI() }
}
init {
load()
}
func load() {
state = .loading
async {
loadData { data, error in
if let json = JSON(data) {
self.state = .success(json)
} else {
self.state = .failure(error ?? ParsingError())
}
}
}
}
func updateTheUI() {
// Handle the state enum to draw correct UI
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.