This simple app was written to introduce basic operations of some frameworks. Such as "Realm" and "RxSwift". Also this app was written with MVVM pattern with Coordinators.
protocol AppCoordinatorProtocol {
var window: UIWindow { get }
var navigationController: UINavigationController { get }
func start()
func startDatasVC(userModel: UserModel, editRelay: PublishRelay<UserModel?>?)
func showAddUserAlert(_ relay: PublishRelay<String?>)
func showError(with msg: String)
}
class AppCoordinator: AppCoordinatorProtocol {
var window: UIWindow
var navigationController: UINavigationController
init(window: UIWindow) {
self.window = window
self.navigationController = UINavigationController()
self.window.rootViewController = navigationController
}
func start() {
self.window.makeKeyAndVisible()
let mainVC = MainVC.instantiate()
mainVC.appCoordinator = self
mainVC.mainViewModel = MainViewModel()
self.navigationController.setViewControllers([mainVC], animated: true)
}
...
}
let newUser = UserModel()
newUser.id = UUID().uuidString
newUser.username = username
newUser.selected = false
do {
try db.write {
self.db.add(newUser)
}
} catch {
//error
}
if let usersResult = db.objects(UserModel.self) {
let usersArray: [UserModel] = Array(usersResult)
print(usersArray)
} else {
//error
}
do {
try db.write {
user.selected = !user.selected
}
} catch {
//error
}
"Completable" is a type of RxSwift traits. "Completable" finishes with 2 functions. First is ".completed", second is ".error(SomeError)". In real use-case, we can use this trait when we need only information about completion or error result. For example when uploading or inserting datas. In this app I also used "Completable" trait in order to creating new UserModel:
func addUser(username: String) -> Completable {
return Completable.create { [weak self] observer in
let maybeError = RealmError(msg: "Realm add user error")
guard let self = self else {
observer(.error(maybeError))
return Disposables.create()
}
let newUser = UserModel()
newUser.id = UUID().uuidString
newUser.username = username
newUser.selected = false
do {
try self.db?.write {
self.db?.add(newUser)
observer(.completed)
}
} catch {
observer(.error(maybeError))
}
return Disposables.create {}
}
}
Second trait type which I used in this application is "Single". In this trait you have 2 chances to finish. First is ".success(SomeData)", second is ".failure(SomeError)". In this app I used this trait in order to fetch users:
func fetchUsers() -> Single<[UserModel]> {
return Single<[UserModel]>.create { [weak self] observer in
let maybeError = RealmError(msg: "Realm fetch error")
guard let self = self else {
observer(.failure(maybeError))
return Disposables.create()
}
if let usersResult = self.db?.objects(UserModel.self) {
let usersArray: [UserModel] = Array(usersResult)
observer(.success(usersArray))
} else {
observer(.failure(maybeError))
}
return Disposables.create {}
}
}
Third trait which I want to introduce is "Driver". This trait is to work with UI. Because this trait works on MainThread. For instance, I used this trait when user opens alert dialog in order to add user and clicks "save" button:
...
let alertRelay = PublishRelay<String?>()
...
alertRelay
.asDriver(onErrorJustReturn: "")
.map { ($0 ?? "") }
.filter { $0 != "" }
.drive { [weak self] text in
self?.addUser(text)
}
.disposed(by: bag)
...