Rxswift Map and Flatmap Difference

RxSwift map and flatMap difference

flatMap is similar to map, but it transforms element of observable to an observable of sequences. The example you use is relatively simple, it is simply sending and Observable mapped into something else.

Here is quote from Reactive extension documentation,

The FlatMap operator transforms an Observable by applying a function
that you specify to each item emitted by the source Observable, where
that function returns an Observable that itself emits items. FlatMap
then merges the emissions of these resulting Observables, emitting
these merged results as its own sequence.

This method is useful, for example, when you have an Observable that
emits a series of items that themselves have Observable members or are
in other ways transformable into Observables, so that you can create a
new Observable that emits the complete collection of items emitted by
the sub-Observables of these items.

If you extend the example a bit, you will know that flatMap actually transforms each element into a sequence.

Notice that you used,

student.onNext(ryan)

Remove your dename2 and add this code below,

let studentObservable: PublishSubject<Student> = PublishSubject()

let deneme2 = student.flatMap({ val -> Observable<Student> in
return studentObservable.map { val in Student(score: val.score + 10) }
})

deneme2.subscribe(onNext: {
print("StudentFlatMAP: \($0.score)")
})

student.onNext(ryan)

studentObservable.onNext(Student(score: 80))
studentObservable.onNext(Student(score: 90))
studentObservable.onNext(Student(score: 100))

Now, you can see that map would simply transform a value from sequence and new Observable is created, while flatMap transforms it into sequence. Now, each of the flatMapped elements can themselves emit values since they are stream themselves.

What is the difference between merge and flatmap operator in RxSwift

The merge operator and flatMap both merge the output of several observables into one observable. The difference is in where the observables being merged come from.

For the merge operator, there must be a static number of observables already in existence (for the static merge method) or a sequence of observables that are emitted together (for the non-static merge method).

For the flatMap operator, the observables that are being merged are generated dynamically by the closure passed to the operator.

What difference does it make to subscribe to a normal type and a observable type in here?

map operator is used to transform the value and pass the transformed value to next operator where as flatmap flattens the hierarchy of observables and exposes it as single operator

Use case for map and flatmap

In general you use map if you wanna transform the value you have received in your current operator and pass it on to next operator usually synchronously. Example in your case no matter what value is assigned to BehaviorRelay String you wanna return "hhh" which is straight forward synchronous value transformation so map makes sense

string
.map { _ in "hhh" }
.subscribe(onNext: { element in
print(element)
}, onCompleted: {
print("completed")
}
)

flatmap is used to flatten the hierarchy of observables and expose it to next operator simply as a single observable. Example assume you are implementing search, once the value is triggered, you wanna make API call and pass the response to next operator. Now making API call is a asynchronous operation and you know that simple map will not cut through then you might use flatmap

let string = BehaviorRelay<String>(value: "abcd")
string
.flatMap { s -> Observable<Response> in
return Observable<Response>.create({ (observer) -> Disposable in
//make api call here
//pass data / error using observer.onNext or observer.onError()
return Disposables.create()
})
}
.subscribe(onNext: { element in
print(element)
}, onCompleted: {
print("completed")
}
)

So here actual API call is made by observable inside the flatMap operator but for outer world it looks like BehaviorRelay itself transformed the value of type string to Response.

Which is perfect because one need not know the nitty-gritty involved in making API call :)

But to be really honest, if you are really implementing search you would rather choose flatMapLatest rather than flatmap. Read more on flatMap and flatMapLatest for better understanding :)

What is the difference between Do and Map?

in addition to what SPatel mentioned,

do: will not modify the emitted elements but rather just pass them through and has no effect on the actual subscription.

whereas

map: transforms each value of a sequence before emitting on the actual subscription

check this for reference:
https://medium.com/ios-os-x-development/learn-and-master-️-the-basics-of-rxswift-in-10-minutes-818ea6e0a05b

Confusion about flatMapLatest in RxSwift

It's not clear what your confusion is about. Are you questioning the difference between flatMap and flatMapLatest? flatMap will map to a new Observable, and if it needs to flatMap again, it will in essence merge the two mapped Observables into one. If it needs to flatMap again, it will merge it again, etc.

With flatMapLatest, when a new Observable is mapped, it overwrites the last Observable if there was one. There is no merge.

EDIT:
In response to your comment, the reason you aren't seeing any "------>3:" print is because those rx_request Observables were disposed before they could compete, because flatMapLatest received a new element, and this mapped to a new Observable. Upon disposal, rx_request probably cancels the request and will not run the callback where you're printing. The old Observable is disposed because it no longer belongs to anyone when the new one takes its place.

How to observe upper Observables with flatMap in RxSwift?

Another option would be:

player.asObservable()
.flatMap { Observable.combineLatest(Observable.just($0.age), $0.score.asObservable()) }

From a category theory POV, this is considered a cleaner approach (you are "lifting" the age value into the monad,) but YMMV.

Also, Subjects should never be held in var, they should always be lets. It makes no sense to replace a Subject with a different one.

It would likely be better in the long run to remove the subject from your Player struct and just make it:

struct Player {
var age: Int
var score: Int
}

What is the difference between concatMap and flatMap in RxJava



As you wrote, the two functions are very similar and the subtle difference is how the output is created ( after the mapping function is applied).

Flat map uses merge operator while concatMap uses concat operator.

As you see the concatMap output sequence is ordered - all of the items emitted by the first Observable being emitted before any of the items emitted by the second Observable,

while flatMap output sequence is merged - the items emitted by the merged Observable may appear in any order, regardless of which source Observable they came from.

Proper usage of RxSwift to chain requests, flatMap or something else?

With RxSwift you want to use Observables whenever possible, therefore I recommend you to refactor the downloadAllTasks method to return an Observable<Task>. This should be fairly trivial by just looping through the elements instead of emitting the array directly:

// In downloadAllTasks() -> Observable<Task>
for task in receivedTasks {
observable.onNext(task)
}

If this is not possible for whatever reason, there is also an operator for that in RxSwift:

// Converts downloadAllTasks() -> Observable<[Task]> to Observable<Task>
downloadAllTasks().flatMap{ Observable.from($0) }

In the following code I will be using the refactored downloadAllTasks() -> Observable<Task> method because it's the cleaner approach.

You can then map your tasks to get their id (assuming your Task type has the id: Int64 property) and flatMap with the downloadAllTasks function to get an Observable<TaskDetails>:

let details : Observable<TaskDetails> = downloadAllTasks()
.map{ $0.id }
.flatMap(getTaskDetails)

Then you can use the toArray() operator to gather the whole sequence and emit an event containing all elements in an array:

let allDetails : Observable<[TaskDetails]> = details.toArray()

In short, without type annotations and sharing the tasks (so you won't download them only once):

let tasks = downloadAllTasks().share()

let allDetails = tasks
.map{ $0.id }
.flatMap(getTaskDetails)
.toArray()

EDIT: Note that this Observable will error when any of the detail downloads encounters an error. I'm not exactly sure what's the best way to prevent this, but this does work:

let allDetails = tasks
.map{ $0.id }
.flatMap{ id in
getTaskDetails(id: id).catchError{ error in
print("Error downloading task \(id)")
return .empty()
}
}
.toArray()

EDIT2: It's not gonna work if your getTaskDetails returns an observable that never completes. Here is a simple reference implementation of getTaskDetails (with String instead of TaskDetails), using JSONPlaceholder:

func getTaskDetails(id: Int64) -> Observable<String> {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/\(id)")!
return Observable.create{ observer in
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
observer.onError(error)
} else if let data = data, let result = String(data: data, encoding: .utf8) {
observer.onNext(result)
observer.onCompleted()
} else {
observer.onError("Couldn't get data")
}
}
task.resume()

return Disposables.create{
task.cancel()
}
}
}

RxSwift how to skip map depending on previous result?

This is a bit of a tough question to answer because on the one hand you ask a bog simple question about skipping a map while on the other hand you ask for "most RxSwift idiomatic way of doing this," which would require more changes than simply jumping the map.

If I just answer the basic question. The solution would be to have checkModel return a Maybe rather than a Single.


Looking at this code from a "make it more idiomatic" perspective, a few more changes need to take place. A lot of what I'm about to say comes from assumptions based on the names of the functions and expectations as to what you are trying to accomplish. I will try to call out those assumptions as I go along...

The .observe(on: ConcurrentDispatchQueueScheduler(qos: .background)) is likely not necessary. URLSession already emits on the background.

The parseJson function probably should not return an Observable type at all. It should just return a ModelObject. This assumes that the function is pure; that it doesn't perform any side effect and merely transforms a Data into a ModelObject.

func parseJson(_ data: Data) throws -> ModelObject

The checkModel function should probably not return an Observable type. This really sounds like it should return a Bool and be used to filter the model objects that don't need further processing out. Here I'm assuming again that the function is pure, it doesn't perform any side-effect, it just checks the model.

func checkModel(_ modelObject: ModelObject) -> Bool

Lastly, the processObject function presumably has side effects. It's likely a consumer of data and therefore shouldn't return anything at all (i.e., it should return Void.)

func processObject(_ modelObject: ModelObject)

Udpdate: In your comments you say you want to end with a Completable. Even so, I would not want this function to return a completable because that would make it lazy and thus require you to subscribe even when you just want to call it for its effects.

You can create a generic wrap operator to make any side-effecting function into a Completable:

extension Completable {
static func wrap<T>(_ fn: @escaping (T) -> Void) -> (T) -> Completable {
{ element in
fn(element)
return Completable.empty()
}
}
}

If the above functions are adjusted as discussed above, then the Observable chain becomes:

let getAndProcess = URLSession.shared.rx.data(request:request)
.map(parseJson)
.filter(checkModel)
.flatMap(Completable.wrap(processObject))
.asCompletable()

The above will produce a Completable that will execute the flow every time it's subscribed to.

By setting things up this way, you will find that your base functions are far easier to test. You don't need any special infrastructure, not even RxText to make sure they are correct. Also, it is clear this way that parseJson and checkModel aren't performing any side effects.

The idea is to have a "Functional Core, Imperative Shell". The imperative bits (in this case the data request and the processing) are moved out to the edges while the core of the subscription is kept purely functional and easy to test/understand.



Related Topics



Leave a reply



Submit