From 12b4efe6a9865cee65a46a9fb96a734bbf27c759 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Tue, 3 Jul 2018 14:31:25 +0300 Subject: [PATCH] Add: replaceDataSource method to RxNetworkOperationModel. Add: customErrorHandler constructor parameter to RxNetworkOperationModel and it heirs. --- CHANGELOG.md | 5 +++ LeadKit.podspec | 2 +- LeadKit.xcodeproj/project.pbxproj | 10 +++++ .../GeneralDataLoadingViewModel.swift | 28 +++++++++----- .../PaginationDataLoadingModel.swift | 17 +++++++-- .../DataLoading/RxDataLoadingModel.swift | 13 ++++++- .../DataLoading/RxNetworkOperationModel.swift | 23 ++++++++++-- ...neralDataLoadingViewModel+Extensions.swift | 37 +++++++++++++++++++ 8 files changed, 116 insertions(+), 19 deletions(-) create mode 100644 Sources/Extensions/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel+Extensions.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 5075874b..33145d52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### 0.8.5 + +- **Add**: `replaceDataSource` method to `RxNetworkOperationModel`. +- **Add**: `customErrorHandler` constructor parameter to `RxNetworkOperationModel` and it heirs. + ### 0.8.4 - **Fix**: Add `SeparatorCell` to `Core-iOS-Extension`. diff --git a/LeadKit.podspec b/LeadKit.podspec index 09ae80d6..6dcfc5ee 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "0.8.4" + s.version = "0.8.5" s.summary = "iOS framework with a bunch of tools for rapid development" s.homepage = "https://github.com/TouchInstinct/LeadKit" s.license = "Apache License, Version 2.0" diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index a07f8d5b..478e16ee 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -356,6 +356,10 @@ 673CF4392063E7CE00C329F6 /* GeneralDataLoadingController+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 673CF4372063E7CE00C329F6 /* GeneralDataLoadingController+DefaultImplementation.swift */; }; 673CF43A2063E7CE00C329F6 /* GeneralDataLoadingController+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 673CF4372063E7CE00C329F6 /* GeneralDataLoadingController+DefaultImplementation.swift */; }; 673CF43B2063E80B00C329F6 /* GeneralDataLoadingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 673CF4172063D50700C329F6 /* GeneralDataLoadingController.swift */; }; + 6741C40F20EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6741C40E20EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift */; }; + 6741C41020EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6741C40E20EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift */; }; + 6741C41120EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6741C40E20EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift */; }; + 6741C41220EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6741C40E20EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift */; }; 674AF55C1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */; }; 674AF55D1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */; }; 674AF55E1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */; }; @@ -737,6 +741,7 @@ 673CF42B2063DE5900C329F6 /* TextPlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextPlaceholderView.swift; sourceTree = ""; }; 673CF4332063E29B00C329F6 /* TextWithButtonPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextWithButtonPlaceholder.swift; sourceTree = ""; }; 673CF4372063E7CE00C329F6 /* GeneralDataLoadingController+DefaultImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GeneralDataLoadingController+DefaultImplementation.swift"; sourceTree = ""; }; + 6741C40E20EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GeneralDataLoadingViewModel+Extensions.swift"; sourceTree = ""; }; 674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+LoadingIndicator.swift"; sourceTree = ""; }; 6762131720A0BBA30034EEF1 /* TableSection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TableSection+Extensions.swift"; sourceTree = ""; }; 676B22A1206A626D002E9F8A /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = ""; }; @@ -1426,6 +1431,7 @@ children = ( 673CF4372063E7CE00C329F6 /* GeneralDataLoadingController+DefaultImplementation.swift */, 673CF4102063ABD100C329F6 /* GeneralDataLoadingState+Extensions.swift */, + 6741C40E20EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift */, ); path = GeneralDataLoading; sourceTree = ""; @@ -2651,6 +2657,7 @@ 671463A21EB33FF600EAB194 /* Animatable.swift in Sources */, 677452A9206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift in Sources */, 671462501EB3396E00EAB194 /* StaticCursor.swift in Sources */, + 6741C40F20EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift in Sources */, 67EB7FC7206148D000BDD9FB /* TotalCountCursorListingResult.swift in Sources */, 67745286206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift in Sources */, 67955D52206D216B0021ECD2 /* Singleton.swift in Sources */, @@ -2713,6 +2720,7 @@ 673CF4132063ABD100C329F6 /* GeneralDataLoadingState+Extensions.swift in Sources */, 6713C24320AF189100875921 /* RxNetworkOperationModel.swift in Sources */, 671462921EB3396E00EAB194 /* CGImage+Crop.swift in Sources */, + 6741C41120EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift in Sources */, 671463861EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, 671463921EB3396E00EAB194 /* TemplateDrawingOperation.swift in Sources */, 67745282206256A20024EEEF /* RxDataLoadingModel.swift in Sources */, @@ -2800,6 +2808,7 @@ 67ED2BE120B44DEB00508B3E /* InitializableView.swift in Sources */, 67274775206CCF1200725163 /* ViewText.swift in Sources */, 6727476B206CCCA500725163 /* ViewBackground.swift in Sources */, + 6741C41220EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift in Sources */, 671462831EB3396E00EAB194 /* DataRequest+Extensions.swift in Sources */, 67ED2BE820B44F4300508B3E /* InitializableView+DefaultImplementation.swift in Sources */, 677452B820627FE00024EEEF /* PaginationWrappable.swift in Sources */, @@ -3014,6 +3023,7 @@ 67FD4383206BD24B005B0C64 /* EqutableOptionalArray.swift in Sources */, 671463811EB3396E00EAB194 /* PaddingDrawingOperation.swift in Sources */, 67EB7FDB20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */, + 6741C41020EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift in Sources */, A6F32C0A1F6EBE5B00AC08EE /* String+LocalizedComponent.swift in Sources */, 671463291EB3396E00EAB194 /* BaseViewModel.swift in Sources */, 671462AD1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */, diff --git a/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel.swift b/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel.swift index e404dd09..39c61c89 100644 --- a/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel.swift +++ b/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel.swift @@ -39,11 +39,15 @@ open class GeneralDataLoadingViewModel: BaseViewModel { /// /// - Parameters: /// - dataSource: A single element sequence. + /// - customErrorHandler: Custom error handler for state update. Pass nil for default error handling. /// - emptyResultChecker: Closure for checking result on empty state. public init(dataSource: DataSourceType, + customErrorHandler: LoadingModel.ErrorHandler? = nil, emptyResultChecker: @escaping LoadingModel.EmptyResultChecker = { _ in false }) { - loadingModel = LoadingModel(dataSource: dataSource, emptyResultChecker: emptyResultChecker) + loadingModel = LoadingModel(dataSource: dataSource, + customErrorHandler: customErrorHandler, + emptyResultChecker: emptyResultChecker) loadingModel.stateDriver .drive(loadingStateRelay) @@ -52,7 +56,12 @@ open class GeneralDataLoadingViewModel: BaseViewModel { loadingModel.reload() } - /// Returns driver that emits current loading state + /// Returns observable that emits current loading state. + open var loadingStateObservable: Observable { + return loadingStateRelay.asObservable() + } + + /// Returns driver that emits current loading state. open var loadingStateDriver: Driver { return loadingStateRelay.asDriver() } @@ -85,17 +94,16 @@ open class GeneralDataLoadingViewModel: BaseViewModel { currentLoadingState = newState } + /// Replaces current data source of loading model with new one. + /// + /// - Parameter dataSource: A single element sequence. + public func replaceDataSource(with newDataSource: DataSourceType) { + loadingModel.replaceDataSource(with: newDataSource) + } + /// Reload data. public func reload() { loadingModel.reload() } } - -public extension GeneralDataLoadingViewModel where ResultType: Collection { - - convenience init(dataSource: DataSourceType) { - self.init(dataSource: dataSource) { $0.isEmpty } - } - -} diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift index 1056102b..6e51d588 100644 --- a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift @@ -39,8 +39,19 @@ public final class PaginationDataLoadingModel Bool - public override init(dataSource: Cursor, emptyResultChecker: @escaping PaginationEmptyResultChecker) { - super.init(dataSource: dataSource, emptyResultChecker: emptyResultChecker) + /// Model initializer with cursor, empty result checker and custom error handler. + /// + /// - Parameters: + /// - dataSource: Data source cursor for paginated data loading. + /// - customErrorHandler: Custom error handler for state update. Pass nil for default error handling. + /// - emptyResultChecker: Empty result checker closure. + public override init(dataSource: Cursor, + customErrorHandler: ErrorHandler? = nil, + emptyResultChecker: @escaping PaginationEmptyResultChecker) { + + super.init(dataSource: dataSource, + customErrorHandler: customErrorHandler, + emptyResultChecker: emptyResultChecker) } override open func reload() { @@ -61,7 +72,7 @@ public final class PaginationDataLoadingModel: RxNetworkOper let emptyResultChecker: EmptyResultChecker - public init(dataSource: DataSourceType, emptyResultChecker: @escaping EmptyResultChecker) { + /// Model initializer with data source, empty result checker and custom error handler. + /// + /// - Parameters: + /// - dataSource: Data source for data loading. + /// - customErrorHandler: Custom error handler for state update. Pass nil for default error handling. + /// - emptyResultChecker: Empty result checker closure. + public init(dataSource: DataSourceType, + customErrorHandler: ErrorHandler? = nil, + emptyResultChecker: @escaping EmptyResultChecker) { + self.emptyResultChecker = emptyResultChecker - super.init(dataSource: dataSource) + super.init(dataSource: dataSource, customErrorHandler: customErrorHandler) } open func reload() { diff --git a/Sources/Classes/DataLoading/RxNetworkOperationModel.swift b/Sources/Classes/DataLoading/RxNetworkOperationModel.swift index e005ec19..567a48c9 100644 --- a/Sources/Classes/DataLoading/RxNetworkOperationModel.swift +++ b/Sources/Classes/DataLoading/RxNetworkOperationModel.swift @@ -29,16 +29,26 @@ open class RxNetworkOperationModel: Net public typealias DataSourceType = LoadingStateType.DataSourceType public typealias ResultType = DataSourceType.ResultType + public typealias ErrorHandler = (Error, LoadingStateType) -> LoadingStateType + private let stateRelay = BehaviorRelay(value: .initialState) var currentRequestDisposable: Disposable? - var dataSource: DataSourceType + private(set) var dataSource: DataSourceType + + private let errorHandler: ErrorHandler open var stateDriver: Driver { return stateRelay.asDriver() } - public init(dataSource: DataSourceType) { + /// Model initializer with data source and custom error handler. + /// + /// - Parameters: + /// - dataSource: Data source for network operation. + /// - customErrorHandler: Custom error handler for state update. Pass nil for default error handling. + public init(dataSource: DataSourceType, customErrorHandler: ErrorHandler? = nil) { + self.errorHandler = customErrorHandler ?? { .errorState(error: $0, after: $1) } self.dataSource = dataSource } @@ -51,8 +61,15 @@ open class RxNetworkOperationModel: Net requestResult(from: dataSource) } + /// Replaces current data source with new one. + /// + /// - Parameter newDataSource: A new data source to use. + public func replaceDataSource(with newDataSource: DataSourceType) { + dataSource = newDataSource + } + func onGot(error: Error) { - state = .errorState(error: error, after: state) + state = errorHandler(error, state) } func onGot(result: ResultType, from dataSource: DataSourceType) { diff --git a/Sources/Extensions/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel+Extensions.swift b/Sources/Extensions/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel+Extensions.swift new file mode 100644 index 00000000..bb31f5f0 --- /dev/null +++ b/Sources/Extensions/DataLoading/GeneralDataLoading/GeneralDataLoadingViewModel+Extensions.swift @@ -0,0 +1,37 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public extension GeneralDataLoadingViewModel { + + func updateResultManually(to newResult: ResultType) { + updateStateManually(to: .result(newResult: newResult, from: .just(newResult))) + } + +} + +public extension GeneralDataLoadingViewModel where ResultType: Collection { + + convenience init(dataSource: DataSourceType) { + self.init(dataSource: dataSource) { $0.isEmpty } + } + +}