import MVVMC
import RxSwift
import RxCocoa
import RTVModel
import RTVWebAPI
public class SettingsViewModel: ViewModel {
public var fetchedNotifications: Driver<[NotificationItem]> = .empty()
public var fetchedNotificationsFailed: Driver<String> = .empty()
public var notificationCount: Driver<Int> = .empty()
'''''''''''''''
public var userLoginName: Driver<String> = .empty()
///// userLoginName getting is a optional String.
'''''''''''''''''
public var fetchedUserLoginNameFailed: Driver<String> = .empty()
public func bindNotificationEvents(with trigger: Driver<Void>) {
let webService: Driver<RTVInformationListWebService> = trigger
.map { RTVInformationListParameters() }
.webService()
let result = webService.request()
notificationCount = result.success().map { $0.informationList.maxCount }
fetchedNotifications = result.success()
.map {$0.informationList.notifications}
-------> .map {$0.map {NotificationItem.init(notification: $0)}}
///////////////////////////////////////////////////////////////
Error (Value of optional type 'String?' must be unwrapped to a value of type 'String')
///////////////////////////////////////////////////////////////
fetchedNotificationsFailed = Driver.merge(fetchedNotificationsFailed, result.error().map { $0.message })
}
public func bindUserInfoEvents(with trigger: Driver<Void>) {
let webService: Driver<RTVMobileMenuWebService> = trigger
.map { RTVMobileMenuParameters() }
.webService()
let result = webService.request()
userLoginName = result.success().map { ($0.mobileMenuInfo.username) }
fetchedUserLoginNameFailed = Driver.merge(fetchedUserLoginNameFailed, result.error().map { $0.message })
}
}
extension RTVAPIError {
fileprivate var message: String {
var message = "\(self.localizedDescription)"
if let codeNumber = self.codeNumber {
message += "\n(\(codeNumber))"
}
return message
}
}
This is not really the way you should be using it, since the point of Driver is not to error, but you obviously have an error state, therefore, Observable or Signal would be better.
However, you need to split your signal into successful ones and error ones, something like this:
fetchedNotifications = result.success()
.map {$0.informationList.notifications}
.share(replay: 1)
let success = fetchedNotifications
.filter { $0 != nil }
.map { $0.map { NotificationItem.init(notification: $0) } }
let error = fetchedNotifications
.filter { $0 == nil } // Here would be your "error" state which you can merge later
I might be off with the syntax, I wrote this from memory.
I fixed it by using the catchOnNil
.catchOnNil { return }
Related
I've got this simple view model to check a phone number's status before registering a user. But I've got this error:
Instance method 'withLatestFrom' requires that 'BehaviorRelay' conform to 'SharedSequenceConvertibleType'
Here's the code:
import Foundation
import RxSwift
import RxCocoa
protocol RegisterViewModelling {
var openRegistrationData: Signal<String> { get }
var showErrorMessage: Signal<String> { get }
var sendButtonActive: Driver<Bool> { get }
var phoneNumberText: BehaviorRelay<String> { get }
var tapSendButton: PublishRelay<Void> { get }
}
final class RegisterViewModel: RegisterViewModelling {
var openRegistrationData: Signal<String>
let showErrorMessage: Signal<String>
let sendButtonActive: Driver<Bool>
let phoneNumberText: BehaviorRelay<String> = BehaviorRelay<String>(value: "")
let tapSendButton: PublishRelay<Void> = PublishRelay<Void>()
init(getPhoneNumberStatus: GetPhoneNumberStatusUseCase) {
sendButtonActive = phoneNumberText
.asDriver(onErrorDriveWith: .empty())
.map(shouldButtonActive(number:))
let isRegistered = tapSendButton
.withLatestFrom(phoneNumberText)
.flatMap(getPhoneNumberStatus.get(number:))
.share()
showErrorMessage = isRegistered
.asSignal(onErrorSignalWith: .just(true))
.filter { $0 == true }
.map { _ in () }
.map(getErrorMessage)
openRegistrationData = isRegistered
.asSignal(onErrorSignalWith: .just(true))
.filter { $0 == false }
.withLatestFrom(phoneNumberText) // ERROR: Instance method 'withLatestFrom' requires that 'BehaviorRelay<String>' conform to 'SharedSequenceConvertibleType'
}
}
private func shouldButtonActive(number: String) -> Bool {
return !number.isEmpty && number.count <= 15
}
private func getErrorMessage() -> String {
return "Phone number has been registered."
}
protocol GetPhoneNumberStatusUseCase {
func get(number: String) -> Observable<Bool>
}
What went wrong here? Why won't withLatestFrom work at that line but it worked fine on the others? Thanks.
I think it because you have convert isRegistered to Signal before use withLatestFrom. You can try to move asSignal() to below withLatestFrom
a property from a singleton is nil when checkin is value.
calling this function from viewDidLoad like this
Task {
do {
try await CXOneChat.shared.connect(environment: .NA1, brandId: 1111, channelId: "keyID")
self.checkForConfig()
} catch {
print(error.localizedDescription)
}
}
this connect function checks for several issues and load from network the config
public func connect(environment: Environment, brandId: Int, channelId: String) async throws {
self.environment = environment
self.brandId = brandId
self.channelId = channelId
try connectToSocket()
channelConfig = try await loadChannelConfiguration()
generateDestinationId()
generateVisitor()
if customer == nil {
customer = Customer(senderId: UUID().uuidString, displayName: "")
try await authorizeCustomer()
} else if authorizationCode.isEmpty{
try await reconnectCustomer()
} else {
try await authorizeCustomer()
}
}
this work ok the self.checkForConfig does some stuff with config in singleton object.
after tapping a button and go for another ViewController. call this func
func loadThread() {
do {
if CXOneChat.shared.getChannelConfiguration()?.settings.hasMultipleThreadsPerEndUser ?? false {
try CXOneChat.shared.loadThreads()
} else {
try CXOneChat.shared.loadThread()
}
} catch {
print(error)
}
}
depending on the config call one or another config value load one or the function. in this case the getChannelConfiguration() is nil and call to loadThread().
func loadThread(threadId: UUID? = nil) throws {
guard let config = channelConfig else {
throw CXOneChatError.missingChannelConfig
}
let eventType = config.isLiveChat ? EventType.recoverLivechat : EventType.recoverThread
guard let brandId = brandId else { throw CXOneChatError.invalidBrandId }
guard let channelId = channelId else { throw CXOneChatError.invalidChannelId }
guard let id = getIdentity(with: false) else { throw CXOneChatError.invalidCustomerId }
let retrieveThread = EventFactory.shared.recoverLivechatThreadEvent(brandId: brandId, channelId: channelId, customer: id, eventType: eventType, threadId: threadId)
guard let data = getDataFrom(retrieveThread) else { throw CXOneChatError.invalidData }
let string = getStringFromData(data)
socketService.send(message: string)
}
here checks for he config in singleton object but throws error because config is nil.
my question is why is nil config in this check. the config is downloaded and store. but when get the values got a null value. Im missing some code here or what I is wrong with this.
I am new to Firebase and Swift. My previous question was very vague due to a misunderstanding on my part. In a class named "A" for example I am trying to create an object of class "B" that contains the fetchARImageTargets function that I have below. I am trying to assign the array ARImageTargets to a var in class "A" however, the listAll completion is not returned in time, which results in the var being empty. Is there a way that I can edit my function or class to avoid the var being set prematurely?
let ARImageTargetStorageRef = Storage.storage().reference().child("ImageTargets")
self.fetchARImageTargets(ref: ARImageTargetStorageRef)
func fetchARImageTargets(ref: StorageReference) {
ref.listAll { (result, error) in
if let error = error {
print(error)
}
for prefix in result.prefixes {
self.fetchARImageTargets(ref: prefix)
}
for item in result.items {
item.getMetadata { (metadata, error) in
if let error = error {
print(error)
} else {
var imageTarget = ARImageTarget()
item.downloadURL(completion: { (url, error) in
imageTarget.ImageURL = url
})
imageTarget.Id = metadata?.customMetadata?["Id"] as String?
let width = metadata?.customMetadata?["PhysicalWidth"] as String?
imageTarget.PhysicalWidth = CGFloat(truncating: NumberFormatter().number(from: width!)!)
self.ARImageTargets.append(imageTarget)
}
}
}
}
}
I'd like to have an index controller function that returns an array of entities if no request parameter is set or a single entity if the id parameter is set. However, I'd like to always receive an array, in the latter case it just contains only one element.
Here's my function:
final class AddressController {
func index(_ req: Request) throws -> Future<[Address]> {
if let id = try? req.query.get(UUID.self, at: "id") {
// THIS IS NOT WORKING...
return Address.find(id, on: req)
} else {
return Address.query(on: req).all()
}
}
}
final class AddressController {
func index(_ req: Request) throws -> Future<[Address]> {
if let id = try? req.query.get(UUID.self, at: "id") {
return Address.find(id, on: req).map {
guard let address = $0 else { return [] }
return [address]
}
} else {
return Address.query(on: req).all()
}
}
}
Is there a way to create a throwing function with a specified Error sub type that will be thrown? Below code is an example of how I'd like it to be.
protocol ValidatorError: Error {
var somePrintableThingy: String { get }
}
protocol Validator {
associatedType T
var model: T { get set }
init(model: T)
func validate() throws where Error: ValidatorError
}
Example Use Case
class SomeModelErrorNotValidatorError: Error {
case invalidUsername
}
class SomeModelError: ValidatorError {
case invalidUsername
var somePrintableThingy: String {
switch self { case .invalidUsername: return "haha" }
}
}
class SomeModel {
var username: String = ""
init(username: String) { self.username = username }
}
class SomeModelValidator: Validator {
var model: SomeModel
init(model: SomeModel) { self.model = model }
func validate() throws {
guard self.model.username.isEmpty else {
// this line should be fine
throw SomeModelError.invalidUsername
// if i replace it with this line it should not be fine
throw SomeModelErrorNotValidatorError.invalidUsername
}
}
}
class SomeModelViewController: UIViewController {
// .. some other codes here
func buttonClicked(_ sender: Any? = nil) {
let model = SomeModel(username: self.txtUsername.text ?? "")
let validator = SomeModelValidator(model: model)
do {
try validator.validate()
} catch let error {
// since I already know that this is a `SomeModelError`
// I could just do this
print(error.somePrintableThingy)
}
}
}
PS: The possible workaround for this is to create a non throwing function with a callback but I don't want to do that
PS PS: Another possible workaround for this would be to use a linting tool, but that's too much.