Bobby Bobak

Multi-disciplinary engineer, designer and a tinkerer.

Manchester, UK


Remote Result type

Asynchronous API requests are an essential part of modern app development, and handling the state of these requests is critical to providing a seamless user experience.

In Swift, the Result type is a powerful tool for modeling the success or failure of these requests. However, sometimes it's necessary to also model a "loading" state in addition to the success and failure states.

To accomplish this, we can create a custom RemoteResult enum that extends Result and includes a loading case:


enum RemoteResult<Success, Failure> where Failure: Error {
	case loading
	case success(Success)
	case failure(Failure)
}
				

This allows us to model the state of an API request as either loading, success, or failure. We can then use this custom RemoteResult type to represent the result of an asynchronous API request.

To make it easier to convert between Result and RemoteResult, we can add an extension to initialize it from a given result value:


extension RemoteResult {
	init(_ result: Result<Success, Failure>) {
		switch result {
		case let .success(data):
				self = .success(data)
		case let .failure(error):
				self = .failure(error)
		}
	}
}
				

Now, we can use RemoteResult to represent the state of an asynchronous API request in SwiftUI. For example, let's say we have a ViewModel that makes an API request and updates its state accordingly:


class ViewModel: ObservableObject {
	@Published var artists: RemoteResult<[String], Error> = .loading

	@MainActor func fetchData() async {
		artists = .loading

		self.artists = RemoteResult(await fetchDataFromAPI())
	}
}
				

In this example, fetchData sets the artists to .loading, makes an API request, and updates artists to the result of the request. We can then use this ViewModel in a SwiftUI view to display the state of the API request:


struct ContentView: View {
	@ObservedObject var viewModel = ViewModel()

	var body: some View {
		switch viewModel.artists {
		case .loading:
			ProgressView()
		case let .success(data):
			List(data, id: \.self) { item in
				Text(item)
			}
		case let .failure(error):
			Text(error.localizedDescription)
		}
		.task {
			await viewModel.fetchData()
		}
	}
}
				

In this view, we use a switch statement to handle each possible state of the API request. When remoteResult is .loading, we display a ProgressView. When it's .success, we display a list of items. When it's .failure, we display an error message. We also call fetchData when the view appears to trigger the API request.

By using a custom RemoteResult type with a loading state, we can more accurately model the state of asynchronous API requests in Swift. This allows us to provide a better user experience by displaying loading indicators and error messages, and by updating the UI when the request completes successfully.

In conclusion, using a custom RemoteResult type with a loading state can improve the way we handle asynchronous API requests in Swift. By modeling the state of these requests more accurately, we can provide a better user experience and make our apps more reliable.