Mutating of RxSwift Driver value - swift

In my application I have an array of notifications. Notification can be read and unread.
When user clicks on the unread notification I need to change the model and reload data in my table view.
In my ViewModel I have output stream:
let notifications: Driver<[Notification]>
And aslo I have an input stream with notification click:
let touchSingleNotificationIntent = PublishSubject<Notification>()
When I do something like this I get the error that it's let constant and I cannot mutate it.
touchSingleNotificationIntent
.filter { !$0.isRead }
.do(onNext: { notification in
notification.isRead = true // I need to change state of the model immediately after user iteration
})
.map { $0.notificationID }
.flatMap(markNotificationAsRead) // http request which doesn't reply with current notification model status
.subscribe()
.disposed(by: bag)
Do you have any ideas how to make it mutable? Thanks.

Streams aren't Mutable at all (this is the same for Observable, Driver, and any other traits). They are "Read only", you read values off the stream over time.
In general, the conception Observables has a "value" is a bit wrong since Observables represent a value over time, and not just a single value.
What you would want to do is "take into account" your PublishSubject when building out your driver.
Something like this would work:
notifications = Observable
.combineLatest(touchedNotification, readNotification, otherEvent) { ($0, $1, $2) }
.map { ... map the three values into whatever makes sense for you }
.asDriver(onErrorJustReturn: ... fallback value ... }
Again, the most important fact to remember - You do not actually mutate streams, you only combine them, transform them, etc, to create a new stream that suits your needs.
Hope this helps you!

Parameters of onNext are let by default. You can define a new one with var, i.e. 'var newNotification = notification' and then return it after modifying.

Related

Combine prefetching and caching results

I'm looking for a way to prefetch data and cache it without any subscriptions made towards that subscriber or when first subscription is made.
For example :
products = getProducts.shareReply(scope : forever)
And when new subscribers subscribes it should receive the last data. If there is no subscribers it's shouldn't restart this publisher on next subscription instead it should return last cached data.
I'm already using shareReply operator with multicast, custom ReplaySubject and autoconnect but if there is no subscriptions towards the publisher next time something subscribes it will restart the publisher and new data will be fetched. And I don't want that.
This is code for shareReply operator :
extension Publisher {
func shareReplay(_ bufferSize: Int = 1) -> AnyPublisher<Output, Failure> {
return multicast(subject: ShareReplySubject(bufferSize))
.autoconnect()
.eraseToAnyPublisher()
}
}
I'm aware of it but I would like to turn AnyPublisher to something like Behaviour(CurrentValue)Subject I mean I want to turn anyPublisher to "hot publisher" and so it emits data regardless of if there is a subscriber or not.
You can do this by applying the multicast operator and calling connect on the result:
let (sharedProducts, sharedProductsTicket): (AnyPublisher<[Product], Never>, Cancellable) = {
let pub = getProducts()
.multicast(subject: CurrentValueSubject([]))
return (pub.eraseToAnyPublisher(), pub.connect())
}()
Now sharedProducts has a live subscription to getProducts() and stores the latest value published by getProducts(). Anyone who subscribes to sharedProducts immediately receives the latest value, and receives new values as they arrive.
The pub.connect() call returns a Cancellable that, when destroyed, cancels the inner subscription connecting getProducts() to the CurrentValueSubject. So you have to store that ticket somewhere. In my example, I store it in sharedProductsTicket.

Get specific .childrenCount from Firebase

In my new app (Project Control, iOS App Store ;)) I want users to take part of development decisions. For this I have added a path in my Firebase database called "claps". I would like to enter the number of the following in my TableView for the different concepts. I have tried the following
self.posts.append(Post(title: post_title, des: post_description, info: "\(post_date) - \(post_user) - \(post_claps) 👏", claps: Int(post_claps)))
for var item in self.posts {
g.ref.child("concepts").child(item.title).queryOrdered(byChild: "claps").observe(.childAdded) { (snapshotClaps: DataSnapshot!) in
item.claps = Int(snapshotClaps.childrenCount)
}
}
DispatchQueue.main.async() {
self.tableView.reloadData()
}
However, it does not yet represent the right one, but is one before it. I don't know how to make the reference more specific to really get only what's under claps.
This ist my Database:
Currently my output is 5 but it should be 4. You see its observing one "layer" to early. Help will be appreciated. Improvements too :)
UPDATE:
Through testing I could reveal that the problem is in the reference. The Int five is coming from the 5 Childs of "top-layer" "Journal". My problem is that I cant get any deeper in the structure because I don't have a specific String for .child()
Since you're observing the .childAdded event, your closure gets called for each matching child node. If you want to count the number of matching child nodes, you'll want to observe the .value event, which ensures your closure gets called for all matching nodes at once.
Something like:
g.ref.child("concepts").child(item.title).observe(.value) { (snapshotClaps: DataSnapshot!) in
item.claps = Int(snapshotClaps.childrenCount)
}
Note that I also removed the orderBy clause, since that has no useful meaning if all you use is the count.
create an Array and allow the firebase to populate it. or do something like
g.ref.child("concepts").child(item.title).observe(.value) { (snapshotClaps: DataSnapshot!) in
item.claps = Int(snapshotClaps.childrenCount)
}
observing value makes sure your closure gets its matching nodes.
There's a couple of great solutions but the issue in reading a node by .value is it reads in everything in that node.
While that would be fine for nodes that have a limited amount of data, it would overwhelm the device when the node contains a lot of data.
So another option is to leverage that Firebase executes all .childAdded events before .value events. That way, we can use a .value as a trigger that all nodes have been read.
Here's a function that uses .childAdded to iterate and count all of the users in the users node. Also, there's a .value observer that reads in just the last node, removes the .childAdded observer and passes the count back to the calling function via a completion handler. Remember that even though we are attaching both observers, the .childAdded events will all fire before the .value event.
func countUsers( completion: #escaping(Int) -> Void) {
var count = 0
let usersRef = self.ref.child("users")
usersRef.observe(.childAdded, with: { snapshot in
count+=1
})
let query = usersRef.queryOrderedByKey().queryLimited(toLast: 1)
query.observeSingleEvent(of: .value, with: { snapshot in
usersRef.removeAllObservers()
completion(count)
})
}
to call the function, here's the code
func getUserCount() {
self.countUsers(completion: { userCount in
print("number of users: \(userCount)")
})
}

RxJava2 Single take different route based on the item

I have the following code:
Single<Response<User>> single = service.registerUser();
single
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.map(Response::body)
.flatMap(parentsRepsitory::writeUser)
.observeOn(AndroidSchedulers.mainThread())
.flatMap(parentsRepsitory::getUser)
Where the parentsRepository is a repo wraping my realm database. The problems come when the server returns validation errors, however. So somewhere in my stream i want to have the equivalent of
if(response.code() == 201){
// CONTINUE STREAM USING THE LOGIC THAT HANDLES SUCCESS
}elseif(response.code() == 400){
// CONTINUE STREAM USING LOGIC TO HANDLE THE VALIDATION ERRORS
}
A solution I have previously implemented is as follows:
Observable<Response<User>> observable_from_api =
service.attemptLogin(username, password)
.share();
observable_from_api
.filter(response -> response.code() == HttpStatus.HTTP_STATUS_200_OK)
.//handle logic for success
observable_from_api
.filter(response -> response.code() == HttpStatus.HTTP_STATUS_400_BAD_REQUEST)
.//handle logic for validation errors
I don't like this solution for several different reasons. The main one being it just does not seem right. The second one being that the .share() method is only available on an Observable object. Since my network operation emits only one responce I would much rather use Single instead, but the .share() method is not available there.
Excuse me if this is a duplicate question, I have done some digging around and only found the solution I mentioned. I want to either see the optimal solution or be told explicitly that this is in fact the optimal solution.
I think you need to define which kind of data you want your consumer to receive. I assume you want to receive in the consumer a User object.
These are the signatures of the method that you should create:
Single<User> handleSuccess(Response<User> response)
Single<User> handleError(Response<User> response)
And then you create you stream in this way:
service.registerUser()
.flatMap(response -> {
if (response.success) {
return handleSuccess(response);
} else {
return handleError(response);
}
})
.subscribe(user -> logd("user: " + user.name));

Creating a user-controllable RxSwift Observer

I'm trying to implement user-driven refreshing in my Rx based networking code, and my current design is as follows:
Create a sink that has Void values passed into it every time the user initiates a refresh action
flatMap the latest .Next event on that sink's Observable into a new network call
Transform the network response into a new view model and pass that back into the view controller
The part I'm getting hung up on is how to create a sink for those events to go down. My current code is as follows:
func contactListModel() -> Observable<ContactListViewModel<Contact>> {
// Create a sink for refresh events
var refreshSink: AnyObserver<Void> = AnyObserver { event in }
let refreshObservable = Observable<Void>.create { observer in
refreshSink = observer
return NopDisposable.instance
}
// Define action handlers
let searchClosure = { (query: String?) in
self.contactsSearchTerm.value = query
}
let refreshClosure = refreshSink.onNext
// TODO: [RP] Make contact list view controller handle a nil view model to remove the need for this code
let initialViewModel = ContactListViewModel<Contact>(contacts: [], searchClosure: searchClosure, refreshClosure: refreshClosure)
// Perform an initial refresh
defer {
refreshSink.onNext()
}
// Set up subscription to push a new view model each refresh
return refreshObservable
.flatMapLatest {
return self.networking.request(.ListContacts)
}
.mapToObject(ListContactsResponse)
.map { response in
return ContactListViewModel(contacts: response.contacts, searchClosure: searchClosure, refreshClosure: refreshClosure)
}
.startWith(initialViewModel)
}
Now it's obvious why my code to create an event sink doesn't work here. The block being passed into refreshObservable's create method is only called once the observer is subscribed to, so the refreshSink won't be reassigned until then. Furthermore, if this observable is subscribed to more than once, the refreshSink variable will be reassigned.
So my question is this: how do I create an Observable that I can manually push events down? Or alternatively, is there a better design I could be using here?
I know ReactiveCocoa has the pipe static method on Signal that will do something like what I'm looking for, but I've found no equivalent in the Rx API.

RXJS : Idiomatic way to create an observable stream from a paged interface

I have paged interface. Given a starting point a request will produce a list of results and a continuation indicator.
I've created an observable that is built by constructing and flat mapping an observable that reads the page. The result of this observable contains both the data for the page and a value to continue with. I pluck the data and flat map it to the subscriber. Producing a stream of values.
To handle the paging I've created a subject for the next page values. It's seeded with an initial value then each time I receive a response with a valid next page I push to the pages subject and trigger another read until such time as there is no more to read.
Is there a more idiomatic way of doing this?
function records(start = 'LATEST', limit = 1000) {
let pages = new rx.Subject();
this.connect(start)
.subscribe(page => pages.onNext(page));
let records = pages
.flatMap(page => {
return this.read(page, limit)
.doOnNext(result => {
let next = result.next;
if (next === undefined) {
pages.onCompleted();
} else {
pages.onNext(next);
}
});
})
.pluck('data')
.flatMap(data => data);
return records;
}
That's a reasonable way to do it. It has a couple of potential flaws in it (that may or may not impact you depending upon your use case):
You provide no way to observe any errors that occur in this.connect(start)
Your observable is effectively hot. If the caller does not immediately subscribe to the observable (perhaps they store it and subscribe later), then they'll miss the completion of this.connect(start) and the observable will appear to never produce anything.
You provide no way to unsubscribe from the initial connect call if the caller changes its mind and unsubscribes early. Not a real big deal, but usually when one constructs an observable, one should try to chain the disposables together so it call cleans up properly if the caller unsubscribes.
Here's a modified version:
It passes errors from this.connect to the observer.
It uses Observable.create to create a cold observable that only starts is business when the caller actually subscribes so there is no chance of missing the initial page value and stalling the stream.
It combines the this.connect subscription disposable with the overall subscription disposable
Code:
function records(start = 'LATEST', limit = 1000) {
return Rx.Observable.create(observer => {
let pages = new Rx.Subject();
let connectSub = new Rx.SingleAssignmentDisposable();
let resultsSub = new Rx.SingleAssignmentDisposable();
let sub = new Rx.CompositeDisposable(connectSub, resultsSub);
// Make sure we subscribe to pages before we issue this.connect()
// just in case this.connect() finishes synchronously (possible if it caches values or something?)
let results = pages
.flatMap(page => this.read(page, limit))
.doOnNext(r => this.next !== undefined ? pages.onNext(this.next) : pages.onCompleted())
.flatMap(r => r.data);
resultsSub.setDisposable(results.subscribe(observer));
// now query the first page
connectSub.setDisposable(this.connect(start)
.subscribe(p => pages.onNext(p), e => observer.onError(e)));
return sub;
});
}
Note: I've not used the ES6 syntax before, so hopefully I didn't mess anything up here.