trigger an api again once network goes off in RxSwift - rx-swift

Here is my code below. I need to call api multiple times until internet signal is on.
buttonAction.do(onNext: { [weak self] in
self?.activity.startAnimating()
apiManager.callApi()
.do(onNext: { [weak self] in
self?.activity.stopAnimating()
},onError: { [weak self ] error in
if let error = error as? NoNetwork {
self?.activity.stopAnimating(errorInfo:error,presentationStyle:.alert)
// here alert will be appear. On click ok button, need to trigger the API() again
}
})
.subscribe().disposed(by:bag)

Note that you didn't say how you want to deal with any other type of error so the code below just ignores them... Remove the filter if you want to show the alert for all errors.
let error = PublishSubject<Error>()
let retry = error
.filter { $0 is NoNetwork }
.map { $0.localizedDescription }
.flatMapFirst(presentScene(animated: true, scene: { message in
UIAlertController(title: "Error", message: message, preferredStyle: .alert)
.scene { $0.connectOK() }
}))
.share()
Observable.merge(buttonAction, retry)
.subscribe(onNext: { [activity] in
activity.startAnimating()
})
.disposed(by: bag)
Observable.merge(buttonAction, retry)
.flatMapFirst {
apiManager.callApi()
.catch { error.onNext($0); return .just(()) }
}
.subscribe(onNext: { [activity] in
activity.stopAnimating()
})
.disposed(by: bag)
The presentScene function comes from my CLE library. In this code activity is a standard UIActivityIndicatorView instead of whatever you are using.

Related

SwiftUI Delete a List Section

Is there a way to delete an entire list section with a function in SwiftUI? I have a list that you can add sections to as you please, but don't know how to delete them when you want to. I know that you can delete a list item itself using the deleteItems function, but I would like to use a button to delete the entire section. When I try to use the standard "deleteItems" function, it asks for "offsets: IndexSet". I am unsure what this IndexSet would be.
I can't find out what should go in the IndexSet field.
Here is my code
struct SampleView: View {
#Environment(\.managedObjectContext) private var viewContext
#Environment(\.presentationMode) var presentationMode
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Category.categoryString, ascending: false)],
animation: .default)
private var listedCategory: FetchedResults<Category>
#State var categoryString: String = ""
var body: some View {
VStack{
TextField("Enter Text Here", text: $categoryString)
Button {
let newlistedCategory = Category(context: viewContext)
newlistedCategory.categoryString = categoryString
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
categoryString = ""
} label: {
Text("Save")
}
List{
ForEach(listedCategory) { listedCategory in
if listedCategory.typeString == "Item" {
Section(header: Text(listedCategory.categoryString!)) {
Button {
//deleteCategories(offsets: <#T##IndexSet#>)
} label: {
Text("Delete")
}
}
}
}
}
Spacer()
}
}
private func deleteCategories(offsets: IndexSet) {
withAnimation {
offsets.map { listedCategory[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}

UNNotificationRequest only triggers once after device reboot

Local Notification only triggers once after device reboot. when i close and reopen the app it won't trigger even though i see it enters the function send() every time i run the app.
i have the trigger set to nil so it will trigger Imm . any ideas what am i getting wrong ?
Thank you so much
O.K
import SwiftUI
import UserNotifications
#main
struct ozApp: App {
var body: some Scene {
WindowGroup {
ContentView().onAppear { send() }
}
}
func send(){
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
print("All set!")
} else if let error = error {
print(error.localizedDescription)
}
}
let content = UNMutableNotificationContent()
content.title = "Message"
content.subtitle = "999"
content.sound = UNNotificationSound.default
let uuid = UUID().uuidString
let request = UNNotificationRequest(identifier: uuid , content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
}
}

How can execute operations sequentially and update UI

I'm trying to execute a bunch of operations sequentially and update my UI every time an operation it starts and it finishes (I have to update an operation status icon color).
Below my (working) code:
class SyncManager {
private let disposeBag = DisposeBag()
// MARK: - Private Init
private init() { }
// MARK: - Public Constants
static let shared = SyncManager()
let refreshTokenStatus = BehaviorRelay<SyncStatus>(value: .todo)
let updateCatalogDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
let insertDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
let getDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
func startDatabaseSync(completion: #escaping ((Result<Void, Error>) -> Void)) {
refreshTokenStatus.accept(.todo)
updateCatalogDataStatus.accept(.todo)
insertDataStatus.accept(.todo)
getDataStatus.accept(.todo)
RefreshTokenManager.shared.refreshToken().do(onSuccess: { [self]_ in
print("RefreshTokenManager onSuccess")
refreshTokenStatus.accept(.completed)
}, onError: { [self] error in
print("RefreshTokenManager onError: \(error)")
refreshTokenStatus.accept(.error)
}, onSubscribe: { [self] in
print("RefreshTokenManager onSubscribe")
refreshTokenStatus.accept(.running)
}).asObservable().concatMap { result in
UpdateCatalogDataSyncManager.shared.updateCatalogData().do(onSuccess: { [self] in
print("UpdateCatalogDataSyncManager onSuccess")
updateCatalogDataStatus.accept(.completed)
}, onError: { [self] error in
print("UpdateCatalogDataSyncManager onError: \(error)")
updateCatalogDataStatus.accept(.error)
}, onSubscribe: { [self] in
print("UpdateCatalogDataSyncManager onSubscribe")
updateCatalogDataStatus.accept(.running)
}).asObservable().concatMap { result in
GetDataSyncManager.shared.getData().do { [self] in
print("GetDataSyncManager onSuccess")
getDataStatus.accept(.completed)
} onError: { [self] error in
print("GetDataSyncManager onError: \(error)")
getDataStatus.accept(.error)
} onSubscribe: { [self] in
print("GetDataSyncManager onSubscribe")
getDataStatus.accept(.running)
} onDispose: {
print("GetDataSyncManager onDispose")
}.asObservable().concatMap { _ in
InsertDataWorkSyncManager.shared.insertData().do { [self] in
print("InsertDataWorkSyncManager onSuccess")
insertDataStatus.accept(.completed)
} onError: { [self] error in
print("InsertDataWorkSyncManager onError: \(error)")
insertDataStatus.accept(.error)
} onSubscribe: { [self] in
print("InsertDataWorkSyncManager onSubscribe")
insertDataStatus.accept(.running)
} onDispose: {
print("InsertDataWorkSyncManager onDispose")
}
}
}
}.subscribe { _ in
print("SyncManager onNext")
} onError: { error in
print("SyncManager onError: \(error)")
completion(.failure(error))
} onCompleted: {
print("SyncManager onCompleted")
completion(.success(()))
} onDisposed: {
print("SyncManager onDisposed")
}.disposed(by: disposeBag)
}
}
enum SyncStatus {
case todo
case completed
case error
case running
case partial
}
My ViewController:
SyncManager.shared.refreshTokenStatus.skip(1).subscribe(onNext: { status in
// Update UI
}).disposed(by: disposeBag)
SyncManager.shared.updateCatalogDataStatus.skip(1).subscribe(onNext: { status in
// Update UI
}).disposed(by: disposeBag)
SyncManager.shared.insertDataStatus.skip(1).subscribe(onNext: { status in
// Update UI
}).disposed(by: disposeBag)
I'm new to RxSwift (I've been using it for only a week) so I would like to know if there's a better approach to achieve my above goal.
Here is an idea that I think will work. It is very imperative conceptually which makes it hard to transcribe to the functional declarative paradigm of FRP. I kept the same external interface so it could be a drop in replacement.
class SyncManager {
private init() { }
static let shared = SyncManager()
let refreshTokenStatus = BehaviorRelay<SyncStatus>(value: .todo)
let updateCatalogDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
let insertDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
let getDataStatus = BehaviorRelay<SyncStatus>(value: .todo)
private let disposeBag = DisposeBag()
func startDatabaseSync(completion: #escaping (Result<Void, Error>) -> Void) {
let sync = Sync.startDatabaseSync()
disposeBag.insert(
sync.refreshTokenStatus.bind(to: refreshTokenStatus),
sync.updateCatalogDataStatus.bind(to: updateCatalogDataStatus),
sync.insertDataStatus.bind(to: insertDataStatus),
sync.getDataStatus.bind(to: getDataStatus),
sync.getDataStatus.subscribe(
onError: { error in
completion(.failure(error))
},
onCompleted: {
completion(.success(()))
}
)
)
}
}
struct Sync {
let refreshTokenStatus: Observable<SyncStatus>
let updateCatalogDataStatus: Observable<SyncStatus>
let getDataStatus: Observable<SyncStatus>
let insertDataStatus: Observable<SyncStatus>
static func startDatabaseSync() -> Sync {
let refreshTokenStatus = handle(RefreshTokenManager.shared.refreshToken(), after: .just(.completed))
.catchAndReturn(.error)
let updateCatalogDataStatus = handle(UpdateCatalogDataSyncManager.shared.updateCatalogData(), after: refreshTokenStatus)
.catchAndReturn(.error)
let getDataStatus = handle(GetDataSyncManager.shared.getData(), after: updateCatalogDataStatus)
.catchAndReturn(.error)
let insertDataStatus = handle(InsertDataWorkSyncManager.shared.insertData(), after: getDataStatus)
.catchAndReturn(.error)
return Sync(
refreshTokenStatus: refreshTokenStatus,
updateCatalogDataStatus: updateCatalogDataStatus,
getDataStatus: getDataStatus,
insertDataStatus: insertDataStatus
)
}
}
func handle(_ operation: Single<Void>, after: Observable<SyncStatus>) -> Observable<SyncStatus> {
after
.ignoreElements()
.asCompletable()
.andThen(
operation
.map { SyncStatus.completed }
.asObservable()
.startWith(SyncStatus.running)
)
.startWith(.todo)
}
enum SyncStatus {
case todo
case completed
case error
case running
}
If you were to re-arrange the rest of the code to be more in the Rx style, then you could probably make this much cleaner...

toggle to get notified in swiftui

I want to be able to notify the user of my app every day at a specific time. in this example, the time is noon
import SwiftUI
import UserNotifications
struct Alert: View {
#State var noon = false
func noonNotify() {
let content = UNMutableNotificationContent()
content.title = "Meds"
content.subtitle = "Take your meds"
content.sound = UNNotificationSound.default
var dateComponents = DateComponents()
dateComponents.hour = 14
dateComponents.minute = 38
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
// choose a random identifier
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
// add our notification request
UNUserNotificationCenter.current().add(request)
}
var body: some View {
VStack {
Toggle(isOn: $noon) {
Text("ThirdHour")
}
if noon {
noonNotify()
}
Button("Request Permission") {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
print("All set!")
} else if let error = error {
print(error.localizedDescription)
}
}
}
}
}
}
I created a func and when the toggle is true, the func will execute, but when it's false, then it won't. however, when I create an if statement, I get an error
Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols
can someone explain what I'm doing wrong?
You can't call a function like that. Everything inside var body: some View { has to be a View, and noonNotify() doesn't return a View.
Instead, add an onChange block, which will get triggered whenever noon changes.
Toggle(isOn: $noon) {
Text("ThirdHour")
}
.onChange(of: noon) { newValue in
if newValue {
noonNotify()
}
}

RxSwift trigger observable on button tap

I am creating a custom observable that will present a UIAlertController which would like to trigger an API Call when pressed. However, I can't seem to get the Alert to pop unless I manually subscribe to it. Is there a way to get this triggered from the viewModel?
MyController.swift
class MyController: UIViewController {
#IBOutlet weak var nextBarButton: UIBarButtonItem!
var viewModel: ViewModel!
override func viewDidLoad() {
super.viewDidLoad()
viewModel = ViewModel( nextBarButton.rx.tap.asDriver(), alertController()asDriver(onErrorJustReturn: ""))
}
func alertController() -> Observable<String> {
return Observable.create { [weak alert = self] observer in
guard let alert = alert else {
observer.on(.completed)
return Disposables.create()
}
let alertVc = UIAlertController(title: "My Title", message: "My Message", preferredStyle: .alert)
let submit = UIAlertAction(title: "Continue", style: .default) { _ in
observer.onNext("Test")
observer.on(.completed)
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in
observer.on(.completed)
}
alertVc.addAction(cancel)
alertVc.addAction(submit)
alert.present(alertVc, animated: true, completion: nil)
return Disposables.create {
alertVc.dismiss(animated: true, completion: nil)
}
}
}
}
ViewModel.swift
public final class ViewModel {
init(_ manager: SessionManager, _ trigger: Driver<Void>, _ alert: Driver<String>) {
let _ = trigger.withLatestFrom(alert)
.flatMap { text in
return manager.rx
.request(urlRequest: Api.test)
.retry(3)
.asDriverOnErrorJustComplete()
}
}
}
You forgot to subscribe() in your ViewModel and you should flatMapLatest when chaining events from UIButton. It should look like:
public final class ViewModel {
init(_ manager: SessionManager, _ trigger: Driver<Void>, _ alert: Driver<String>) {
let _ = trigger.flatMapLatest{ alert }
.flatMap { text in
return manager.rx
.request(urlRequest: Api.test)
.retry(3)
.asDriverOnErrorJustComplete()
}
.subscribe()
}
}

Resources