Willyanto Wijaya Sulaiman Willyanto Wijaya Sulaiman - 6 months ago 204
iOS Question

Binding to a UIRefreshControl after refresh using RxSwift

I have a TableView of notification. I want to refresh by pull to refresh using UIRefreshControl. How to do that with rx-swift? This is my code. Why tableView not refreshed after I set value to variable data

var refreshControl = UIRefreshControl()
var disposeBag = DisposeBag()

let loadingData = ActivityIndicator()

var data: Observable<[Notification]>!

override func viewDidLoad() {
super.viewDidLoad()

self.view = v

v.tableView.registerClass(NotificationsViewCell.self, forCellReuseIdentifier: "Cell")
v.tableView.addSubview(refreshControl)
data = getNotifications()

configureTableDataSource()
configureActivityIndicatorsShow()

refreshControl.rx_controlEvent(.ValueChanged)
.flatMapLatest{ [unowned self] _ in
return self.getNotifications()
.trackActivity(self.loadingData)
}.subscribe(
onNext: {notification in
print("success")
self.data = Observable.just(notification) // NOT REFRESH TABLEVIEW
},
onError: { error in
print("Error \(error)")
},
onCompleted: {() in
print("complete")
},
onDisposed: {() in
print("disposed")
})
.addDisposableTo(disposeBag)
}


func configureTableDataSource(){
data
.retry(3)
.doOnError{ [weak self] error in
self?.v.emptyLabel.hidden = false
self?.v.retryButton.hidden = false
}
.doOnNext{ [weak self] result in
if result.count == 0 {
self?.v.emptyLabel.hidden = false
self?.v.emptyLabel.text = "Tidak ada bisnis favorit"
} else {
self?.v.emptyLabel.hidden = true
self?.v.retryButton.hidden = true
}
}
.trackActivity(loadingData)
.retryWhen{ _ in
self.v.retryButton.rx_tap
}
.asDriver(onErrorJustReturn: [])
.map{ results in
results.map(NotificationsViewModel.init)
}
.drive(v.tableView.rx_itemsWithCellIdentifier("Cell", cellType: NotificationsViewCell.self)) { (index, viewModel, cell) in
cell.viewModel = viewModel

let tap = UITapGestureRecognizer(target: self, action: #selector(self.goToProfile(_:)))
tap.numberOfTapsRequired = 1
cell.photo.tag = index
cell.photo.addGestureRecognizer(tap)
}
.addDisposableTo(disposeBag)
}

func configureActivityIndicatorsShow(){
loadingData
.driveNext{ isLoading in
if !isLoading {
self.v.indicatorView.stopAnimating()
} else {
self.v.indicatorView.startAnimating()
self.v.retryButton.hidden = true
self.v.emptyLabel.hidden = true
}
}
.addDisposableTo(disposeBag)

loadingData.asObservable()
.bindTo(refreshControl.rx_refreshing)
.addDisposableTo(disposeBag)
}

func getNotifications() -> Observable<[Notification]> {
let parameters = [
"token": NSUserDefaults.standardUserDefaults().objectForKey("token")! as! String
]
return string(.POST, NOTIFICATION_LIST, parameters: parameters)
.map { json in
return Notification.parseJSON(JSON.parse(json)["notifications"])
}
.observeOn(MainScheduler.instance)
}


EDIT::

var data = Variable<[Notification]>([])

override func viewDidLoad() {
getNotifications()
.retry(3)
.doOnError{ [weak self] error in
self?.v.emptyLabel.hidden = false
self?.v.retryButton.hidden = false
}
.doOnNext{ [weak self] result in
if result.count == 0 {
self?.v.emptyLabel.hidden = false
self?.v.emptyLabel.text = "Tidak ada notifikasi"
} else {
self?.v.emptyLabel.hidden = true
self?.v.retryButton.hidden = true
}
}
.trackActivity(loadingData)
.retryWhen{ _ in
self.v.retryButton.rx_tap
}
.bindTo(data)
.addDisposableTo(disposeBag)


refreshControl.rx_controlEvent(.ValueChanged)
.flatMapLatest{ [unowned self] _ in
return self.getNotifications()
.doOnError{ [weak self] error in
// This not call after the second pull to refresh if No network connection, so refresh control still appear
self?.refreshControl.endRefreshing()
}
.doOnCompleted{ [weak self] result in
self?.refreshControl.endRefreshing()
}
}.bindTo(data)
.addDisposableTo(disposeBag)
}

func configureTableDataSource(){
datas.asObservable()
.asDriver(onErrorJustReturn: [])
.map{ results in
results.map(NotificationsViewModel.init)
}
.drive(v.tableView.rx_itemsWithCellIdentifier("Cell", cellType: NotificationsViewCell.self)) { (index, viewModel, cell) in
cell.viewModel = viewModel
}
.addDisposableTo(disposeBag)
}


func configureActivityIndicatorsShow(){
loadingData
.driveNext{ isLoading in
if !isLoading {
self.v.indicatorView.stopAnimating()
} else {
self.v.indicatorView.startAnimating()
self.v.retryButton.hidden = true
self.v.emptyLabel.hidden = true
}
}
.addDisposableTo(disposeBag)
}

Answer

self.data = Observable.just(notification) is creating a new Observable and sending the new [Notification] element on that Observable, which no one is subscribed to.

You should be using a Subject such as Variable.

// instead of `var data: Observable<[Notification]>!`
let data = Variable<[Notification]>([])

// and then later, when you want to send out a new element:
self.data.value = notification

EDIT: To show you how to use this in conjunction with what you already have.

// this will update `data` upon `refreshControl` value change
refreshControl.rx_controlEvent(.ValueChanged)
    .flatMapLatest{ [unowned self] _ in
        return self.getNotifications()
    }
    .bindTo(data)
    .addDisposableTo(disposeBag)

// this will update `loadingData` when `data` gets a new element
data.asDriver().trackActivity(self.loadingData)

// bind to your table view
data.asDriver().drive(//.....

Also, consider moving the retry and retryWhen to happen sooner, instead of happening downstream where you currently have it (in the table view binding). Instead, I think it should belong in getNotifications.

Comments