The Secret to Easy API Calls: Interceptors in SwiftUI, Compose, and Flutter
Imagine building an app that needs to fetch data from a server. You'd write code to make API calls, parse the data, and handle any errors. This can be a lot of repetitive code, especially if you're using the same API calls in different parts of your app.
Enter network call interceptors! They're like little helpers that sit between your app and the server, handling the tedious tasks of making API calls and processing data. This makes your code cleaner, more reusable, and easier to maintain.
Let's see how this works in SwiftUI, Compose, and Flutter:
1. SwiftUI
In SwiftUI, we can use URLSession to make API calls. We can create an interceptor that handles the request and response:
// Interceptor class
class APIInterceptor {
static func intercept(url: URL, completion: @escaping (Data?, Error?) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(nil, error)
return
}
if let data = data {
completion(data, nil)
}
}.resume()
}
}
Now, you can easily use this interceptor in your SwiftUI views:
struct MyView: View {
@State private var data: [String] = []
var body: some View {
List(data, id: \.self) { item in
Text(item)
}
.onAppear {
APIInterceptor.intercept(url: URL(string: "https://api.example.com/data")!) { data, error in
if let error = error {
print("Error fetching data: \(error)")
} else if let data = data {
// Parse the data to an array of strings
let decoder = JSONDecoder()
if let decodedData = try? decoder.decode([String].self, from: data) {
self.data = decodedData
}
}
}
}
}
}
2. Compose
Compose uses Kotlin coroutines for asynchronous operations. Here's how you can create an interceptor in Compose:
object APIInterceptor {
suspend fun <T> intercept(url: String, parser: (String) -> T): T {
try {
val response = HttpClient.get(url)
val data = response.body<String>()
return parser(data) // Parse the data to the desired type
} catch (e: Exception) {
throw e
}
}
}
You can then call the interceptor from a Compose Composable function:
@Composable
fun MyScreen() {
val data = remember { mutableStateListOf<String>() }
LaunchedEffect(Unit) {
try {
val fetchedData = APIInterceptor.intercept("https://api.example.com/data") {
// Parse the JSON string to an array of strings
Gson().fromJson(it, Array<String>::class.java).toList()
}
data.addAll(fetchedData)
} catch (e: Exception) {
Log.e("MyScreen", "Error fetching data: ${e.message}")
}
}
// Display the data using Compose components
Column {
data.forEach { item ->
Text(item)
}
}
}
3. Flutter
Flutter uses the http package for API calls. Here's an interceptor example:
class APIInterceptor {
static Future<dynamic> intercept(String url) async {
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
// Parse the response body to the desired type
return jsonDecode(response.body);
} else {
throw Exception("API request failed with status code ${response.statusCode}");
}
} catch (e) {
throw e;
}
}
}
Then, in your Flutter widget:
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
List<String> data = [];
@override
void initState() {
super.initState();
fetchData();
}
Future<void> fetchData() async {
try {
final fetchedData = await APIInterceptor.intercept("https://api.example.com/data");
setState(() {
data = fetchedData.cast<String>().toList();
});
} catch (e) {
print("Error fetching data: ${e.toString()}");
}
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index]),
);
},
);
}
}
Benefits of Interceptors
Clean Code: Less repetitive code, easier to read and understand.
Reusability: The same interceptor can be used for multiple API calls.
Error Handling: Centralized error handling for all API calls.
Flexibility: You can add features like logging, authentication, and data caching to the interceptor.
Handling Errors
Interceptors make error handling easier. In the examples above, we catch errors and either print them to the console or throw exceptions that can be handled by the calling code. You can customize this error handling based on your app's requirements.
Parsing Data
The key is to use a parser to convert the data from the server into the format you need. In the examples, we've used JSON decoding to parse the data into arrays of strings. You can use other parsers depending on the data format.
Key Takeaway
Using network call interceptors in your declarative UI frameworks can significantly improve the organization and efficiency of your API interactions. By taking care of the repetitive tasks of making requests and handling responses, interceptors allow you to focus on the core logic of your app.
๐๐ Let me know if you see any areas where I can make this explanation even better! I'm always up for suggestions and improvements. ๐๐
Clap-clap-clap... clap-clap-clap... Keep clapping until you hit 10! ๐
Want to take your Flutter knowledge to the next level? Check out my recent series on Flutter interview questions for all levels. Get ready to impress in your next interview!
Let's keep sharing, learning, and practicing! ๐ค๐ป
Subscribe to my newsletter
Read articles from Mukund Jogi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Mukund Jogi
Mukund Jogi
๐ Iโm from: Ahmedabad, Gujarat, India ๐ Designation: Project Manager (at 7Span) ๐ป Tech Stack: Flutter, Android , iOS ๐ง Let's connect: โฃ LinkedIn: https://linkedin.com/in/mukund-a-jogi/ โฃ Twitter: https://twitter.com/mukundjogi โฃ Medium: https://medium.com/@mukundjogi