Sync Multiple Data Sources in SwiftUI with Combine

Synchronizing Data Fetching from Multiple Sources with Combine 🚂

Welcome to our tutorial on using the Combine framework to efficiently sync fetching data from multiple sources in SwiftUI! In this tutorial, we will explore how to use Combine to load data from different sources, such as APIs, and combine them into a cohesive whole. We will also show how to provide a completion callback when all data has been loaded, allowing you to track the progress of your data loading and take appropriate actions.

By the end of this tutorial, you will have a good understanding how to use Combine to load data from multiple sources in your SwiftUI applications.

Let's get started!

Getting started with the Combine framework for data loading in SwiftUI

In the context of SwiftUI, the Combine framework can be used to load data from a variety of sources and provide a completion callback when all data has been loaded. This ensures all data is available before rendering the UI. Using Combine for data loading has several advantages, such as managing multiple streams of data, handling errors and cancellations, and providing a completion callback.

Data loading is an important aspect of building efficient and responsive SwiftUI applications. By using the Combine framework, we can avoid common pitfalls such as blocking the main thread or overloading the network. Instead, we can focus on building robust and performant user interfaces.

💡

At Tapforce, we prioritize a seamless user experience in our mobile app development. To reduce wait times and create a more efficient app experience, we load and prepare all necessary data before the home screen is displayed. This approach ensures optimal performance without sacrificing functionality or usability.

Loading data from multiple sources is a common task for developers. While Combine provides an efficient way to manage asynchronous data streams, it also presents some challenges. One of the biggest challenges is ensuring that data is loaded correctly and efficiently.

A common mistake is loading one batch of data, waiting for it to be loaded, and then moving on to the next data source. This approach can be time-consuming, especially if the data sources are related to each other and require additional calculations. Additionally, errors during the data loading process can cause the entire operation to crash, leaving the user with incomplete data.

To avoid these issues, developers must ensure that they are properly synchronizing data fetching from multiple sources. They should also consider error handling and implementing a backup plan in case of data loading failures.

Setting up a basic data loading pipeline

This example demonstrates how to set up a basic data loading pipeline using the Combine framework in a SwiftUI application. The data we will work with is a list of cryptocurrency prices retrieved from a publicly available API.

To start, we define a view model class that handles the data loading and management. This class uses the URLSession publisher to create a publisher that emits data from a network request to the API. We then subscribe to the publisher using the .sink operator and provide a closure that updates the cryptoPrices property with the received data and sets the loadingCompleted flag to true to indicate that the data has been loaded. To ensure that updates to the cryptoPrices and loadingCompleted properties occur on the main thread, we use the receive(on:) operator.

In summary, the ContentView struct in our SwiftUI application displays different content to the user based on the state of the data loading process. When the view appears, it calls the fetchCryptoPrices method of the CryptoViewModel instance to initiate the data loading process.

If an error occurs during the data loading process, an error message is displayed. If the data is still being loaded, a loading indicator is displayed. If the data has been successfully loaded, a list of cryptocurrency prices is displayed.

This approach enables the ContentView view to show the appropriate content to the user as the data is being loaded and processed.

Fetch Data from Multiple Sources

Before diving into using Combine frameworks to load data from multiple sources as one stream, let's first take a look at how to retrieve data from multiple APIs in a simple way. In this example, we will be using two separate APIs to retrieve data: one for cryptocurrency prices and one for stock quotes.

In this example, we defined two view models: the CryptoViewModel and the StockViewModel. Each handles data loading and management for cryptocurrency prices and stock prices, respectively. Both view models use the URLSession publisher to create a publisher that emits data from a network request to a publicly available API.

When the ContentView view appears, it calls the fetchCryptoPrices and fetchStocks methods of the CryptoViewModel and StockViewModel instances, respectively. Both methods initiate the data loading process for their respective data sources.

However, this implementation has a limitation: we must check the loading status and error state for each data source separately. This can become cumbersome as the number of data sources increases. In the next example, we'll explore how the Combine framework can simplify this process.

Using Combine's zip operator to merge data from multiple sources

This example demonstrates how to use the Combine framework's zip operator to retrieve data from two different sources (a cryptocurrency prices API and a stock quotes API) and combine them into a single stream of data. To do so, we first update our view model classes (CryptoViewModel and StockViewModel) to return a publisher of the data. Then, we create a new DataManager class that uses the zip operator to combine the data from the two view models into a single stream.

Once we have our data stream set up, we can use the sink operator to subscribe to it and receive updates whenever new data becomes available. In this case, we update the loadingCompleted flag to indicate that the data has been loaded and update the data property with the received data.

Using the zip operator enables us to handle the data loading process in a more streamlined way. We no longer have to check the loading status of each data source separately. This can prevent partial updates or race conditions that could occur if the data was loaded asynchronously and displayed as soon as it was available.

The retry(#) modifier allows us to automatically retry the API request a couple times if it fails due to a network or server error. This can improve the reliability of the data loading process and reduce the likelihood of errors being displayed to the user.

Making your app faster and more efficient with caching and automatic data updates

If your database allows you to listen for changes, you can further improve your code by implementing listeners that detect changes in the data. Once the necessary data is loaded on start-up, you can continuously listen for changes and automatically update the data without requiring any manual action from the user. Caching the loaded data can also significantly improve app performance by eliminating the need to repeatedly fetch the same data, saving valuable time and resources.

This approach can greatly enhance the user experience by ensuring that the data is always up-to-date without the need for constant manual updates. Additionally, implementing listeners for automatic data updates and caching the loaded data can improve app performance overall.

In the next article, we will explore how to implement these features using Firebase as an example.

Wrapping up: Key takeaways from tutorial

This tutorial has demonstrated how to use the Combine framework to efficiently sync data loading from multiple sources in SwiftUI. We have covered loading data from different sources, such as APIs, and combining them into a cohesive whole. Additionally, we have shown how to use the zip operator to merge data from multiple sources into a single stream of data, making it easier to manage the data loading process and avoid common pitfalls such as blocking the main thread or overloading the network.

Consider using the Combine framework in your own projects to streamline the data loading process and build more responsive, efficient user interfaces. Thank you for following along, and we hope this article has been helpful!

Happy coding! 👋

Nazar Kvyatkovsky

Nazar Kvyatkovsky

Mobile team leader & iOS Engineer at TapForce