Blog Infos
Author
Published
Topics
Published
Topics

A Dive into Combine, Merge, Zip and Transform Functions for Efficient Flow Management

Photo by christian buehner on Unsplash

 

In modern reactive programming, managing asynchronous data streams is a crucial skill. Kotlin’s Flow API provides a powerful toolkit for dealing with such streams, enabling developers to handle data emissions and transformations seamlessly. In this article, we’ll delve into three essential functions within the Flow API — combinemerge, and zip. These functions allow developers to orchestrate multiple flows of data, enabling elegant solutions to complex scenarios. We’ll explore each function in detail, showcasing their usage, benefits, and real-world examples.

Introduction

Kotlin’s Flow API offers an elegant and efficient way to manage asynchronous data streams. When dealing with multiple data sources, it’s often necessary to synchronize and combine these streams to perform specific operations. The combinemerge, and zip functions play a pivotal role in achieving this synchronization. Whether you’re handling notifications, updates, or any scenario that involves data streams, these functions can be your secret weapons.

Merging Streams with .merge()

The merge function is a workhorse for interleaving data streams. It fuses multiple flows into a single flow, emitting values in the order they arrive from the source flows. Let’s explore the quickNotifications() function —

Imagine you want to display quick notifications to users for activities like comments, likes, and posts. Using merge, you can interleave these notifications efficiently, enhancing user engagement. The sequence of results will print as follows based on our artificial simulation.

  1. Likes with a slight delay (50ms).
  2. Comments with a moderate delay (200ms).
  3. New posts with a balanced delay (100ms).

As a result, users receive notifications that closely mimic real-time interactions, thanks to strategically implemented delays. The example illustrate how merge maintains the order of notifications while ensuring a dynamic user experience.

.merge() diagrammatic representation

Code
private fun quickNotifications(){
    viewModelScope.launch {
        val likesActivity: Flow<Like> = flowOf(*likesList.toTypedArray()).onEach {
            delay(50)
        }
        val commentsActivity: Flow<Comment> = flowOf(*commentsList.toTypedArray()).onEach {
            delay(200)
        }
        val postsActivity: Flow<Post> = flowOf(*postsList.toTypedArray()).onEach {
            delay(100)
        }
        
        merge(likesActivity, commentsActivity, postsActivity).collect {
            when(it) {
                is Comment -> {
                    Log.i("Quick Notification", "${it.userName} commented on your post: ${it.commentText}")
                }
                is Like -> {
                    Log.i("Quick Notification", "${it.userName} reacted with ${it.reactionName} to your post: ${it.postTitle}")
                }
                is Post -> {
                    Log.i("Quick Notification", "${it.userName} added a new post: ${it.postTitle}")
                }
            }
        }
    }
}
Example Data Input

You can find the data class and example data here.

Example Data Output
Alice reacted with 👍 to your post: Exploring the Cosmos
Alice added a new post: Exploring the Cosmos
Rahul reacted with ❤ to your post: Starry Night Photography
Grace reacted with 👏 to your post: Astronomy Basics
Alice commented on your post: This is amazing!
Bob added a new post: Starry Night Photography
Charlie reacted with 👍 to your post: Starry Night Photography
Charlie reacted with ❤️ to your post: Astronomy Basics
Charlie added a new post: Astronomy 2 Basics
Bob reacted with 👏 to your post: Exploring the Cosmos
Charlie commented on your post: Beautiful shot!

As we can see, the activities are emitted in the order of the delays — likes, posts, comments, and more. This is why the output shows these activities interleaved based on their delay values. The merge function combines these activities in the order in which they are emitted, regardless of their individual delays.

Combining Data Streams with .combine()

.combine is a function provided by Kotlin’s Flow API. It allows us to combine two or more flows into a single flow, emitting values whenever any of the source flows emit a new value. This is useful for scenarios where you want to react to changes in multiple data sources and perform some operation on the combined data. Let’s look at a real-world example —

Consider a social media app where users can like, comment, and post. The function summarizedNotifications() combines these activities into concise notifications. The sequence of results will print as follows —

  1. Likes and comments are combined for each corresponding post.
  2. The combined data is then merged with the posts, forming comprehensive notifications.

The use of the delay function simulates real-time interactions for a more authentic user experience. For instance, users liking a post followed by a few comments followed by more reactions. This simulation adds a layer of realism to your app’s notification system.

.combine() diagrammatic representation

Code

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

Jobs

private fun summarizedNotifications() {
    viewModelScope.launch {
        val likesActivity: Flow<Like> = flowOf(*likesList.toTypedArray()).onEach {
                delay(50)
            }
        val commentsActivity: Flow<Comment> = flowOf(*commentsList.toTypedArray()).onEach {
            delay(200)
        }
        val postsActivity: Flow<Post> = flowOf(*postsList.toTypedArray()).onEach {
            delay(100)
        }
        likesActivity.combine(commentsActivity) { like, comment ->
            "${like.userName} reacted with ${like.reactionName} on ${like.postTitle}\n" +
                    "and ${comment.userName} commented ${comment.commentText} on ${comment.postTitle}"
        }.combine(postsActivity) { activity, post ->
            Log.i("Notification", "$activity .. Also ${post.userName} added a new post '${post.postTitle}'")
        }.collect()
    }
}
Example Data Input

You can find the data class and example data here.

Example Data Output
Rahul reacted with ❤ on Starry Night Photography
    and Alice commented This is amazing! on Exploring the Cosmos .. Also Alice added a new post 'Exploring the Cosmos'
Rahul reacted with ❤ on Starry Night Photography
    and Alice commented This is amazing! on Exploring the Cosmos .. Also Bob added a new post 'Starry Night Photography'
Grace reacted with 👏 on Astronomy Basics
    and Alice commented This is amazing! on Exploring the Cosmos .. Also Bob added a new post 'Starry Night Photography'
Charlie reacted with 👍 on Starry Night Photography
    and Alice commented This is amazing! on Exploring the Cosmos .. Also Bob added a new post 'Starry Night Photography'
Charlie reacted with 👍 on Starry Night Photography
    and Alice commented This is amazing! on Exploring the Cosmos .. Also Charlie added a new post 'Astronomy 2 Basics'
...
...
...

Based on the delays provided in the code, likes will be emitted every 50 milliseconds, comments every 200 milliseconds, and posts every 100 milliseconds. The delay values influence the order in which the activities are emitted and combined.

As you can see, the activities are emitted in the order of the delays — likes, posts, and then comments. This is why Alice’s first like (refer input data) doesn’t appear first in the output. The delay of 50 milliseconds for likes causes the subsequent activities to be emitted and combined. As a result, the combination that striked (Rahul’s like) after her like at 100ms is the one that is actually printed.

Tip💡 — Do try .combine() with all three flows in the same function call to see the difference.

combine(likes, comments, posts) {likes, comments, posts -> 
  // log here
}.collect()
Pairing Streams with .zip()

.zip is a function provided by Kotlin’s Flow API. It combines values from multiple flows into pairs or tuples, emitting a new pair whenever all source flows have emitted a new value. The zip function orchestrates multiple flows, pairing their emissions to trigger actions based on specific criteria. Take a look at the personalizedNotifications() function for example —

Suppose you’re interested in notifying users when their likes and comments match for a given post. By zipping likes and comments, you can detect such instances and create personalized notifications. The zip function ensures that the emissions are paired according to their chronological order, facilitating precise matches.

.zip() diagrammatic representation

Code
private fun personalizedNotifications() {
    viewModelScope.launch {
        val likesActivity: Flow<Like> = flowOf(*likesList.toTypedArray())
        val commentsActivity: Flow<Comment> = flowOf(*commentsList.toTypedArray())
        
        likesActivity.zip(commentsActivity) { like, comment ->
            if(like.userName == comment.userName && like.postTitle == comment.postTitle) {
                Log.i("Notification","${like.userName} reacted and commented on your post ${like.postTitle}")
            }
        }.collect()
    }
}

As you can see, the .zip operation compares items with the same index from different flows 1:1 and emits a combined value if the conditions are met.

It won’t log a notification till it finds a reaction and like from the same user as per our code block above.

Example Output
Alice reacted and commented on your post Exploring the Cosmos

If for example, we change the input data to have

Like("Alice", "Astronomy Basics", "👏")
// instead of 
Like("Grace", "Astronomy Basics", "👏")
// in the likes list
New Output
Alice reacted and commented on your post Exploring the Cosmos
Alice reacted and commented on your post Astronomy Basics
Other Example Use Cases

The provided examples mirror social media interactions, but these operators have broader applicability —

  • E-commerce — Tracking product orders, payment confirmations, and shipping updates using .zip or .combine.
  • Real-time Collaborations — In collaborative apps, like document editing, .merge can synchronize changes from multiple users.
  • Multi-Source Data — Combining data from various sources (APIs, databases) using .zip or .combine for unified processing.
Pro Tip 💡
Using .transform before using any of the above—

The difference between using .transform followed by .collect and directly using .merge().collect() or any of the above followed by .collect() lies in the separation of concerns and the ability to encapsulate different processing logic.

When you use .transform followed by .collect, you have the advantage of encapsulating the transformation logic within the specific flow that emits the raw data (likes, comments, or posts). This makes your code more modular and easier to understand. It separates the transformation process from the collection process, making your code more maintainable and allowing you to focus on the specific transformation of each item.

On the other hand, directly using .merge().collect() for transformation means you’re embedding the transformation logic within the collection process. While this approach might work for simpler cases, it can become less readable and harder to manage as the complexity of the transformation logic grows. It also makes it more challenging to reuse or modify the transformation logic independently.

Consider the following benefits of using .transform followed by .collect

  1. Modularity — Each flow (likesFlowcommentsFlowpostsFlow) encapsulates its own transformation logic, making it easier to maintain and understand.
  2. Reusability — If you need the same transformation logic elsewhere, you can reuse the flow with .transform without duplicating the logic.
  3. Clarity — The separation of transformation and collection logic improves code readability and makes it clear what each part of the code is doing.

In the end, the approach you choose depends on the complexity of your use case and your preference for code organization. For simpler transformations, using .merge().collect() directly might be suitable, while for more complex and modular scenarios, separating transformation using .transform followed by .collect is recommended.

Conclusion

In the realm of reactive programming, Kotlin’s Flow API offers a treasure trove of tools to manage asynchronous data streams effectively. The combinemerge, and zip functions elevate your coding game by enabling synchronization, interleaving, and pairing of data streams. By understanding their nuances, one can build applications that deliver seamless and engaging user experiences.

Disclaimer

The code snippets provided in this article are intended for educational purposes only and may not fully represent production-ready code. The focus is on explaining the concepts and functionalities of the Flow API functions.

With the knowledge gained from this exploration, you’re well-equipped to wield the power of the Flow API’s combinemerge, and zip functions in your projects.

Code and Closing Remarks

To make it easier here are two files NotificationsViewModel.kt and Data.kt that have all the previously mentioned code along with the sample data.

If you liked the content, please feel free to leave your valuable feedback or appreciation. I am always looking to learn and collaborate with fellow developers.

Follow me on Medium for more articles — Medium Profile

Connect with me on LinkedIn for collaboration — LinkedIn Profile

Happy Coding!

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu