Building a Generic and Performant Networking Layer in Flutter
Tags: flutter, dart, ios, android
Published: 2022-03-24 02:52:07

**Implement a high-performant and scalable networking layer**

These days it’s hard to find an app that doesn’t perform any API calls. So might be yours. Luckily, Flutter provides packages out of the box to make this process a bit simpler. Examples of those could be easily found on pub.dev: [HTTP](https://pub.dev/packages/http) and [DIO](https://pub.dev/packages/dio).

Essentially, both packages allow you to assemble a network request and then perform it. Leaving to you error handling, JSON parsing, and all other aspects of mobile application networking.

For example, your API call looks like this:

{% embed https://gist.github.com/dev4jam/73139a3fb638fd40784643ef04850a2e %}


This code is totally fine for a small pet project. But you start noticing inconveniences when the project starts growing at the scale stage.

Specifically:

❌ Duplicate code
❌ Blocking main thread and dropping FPS
❌ Error-prone codebase
❌ More and more time on adding new services and API calls
❌ Unreliable error handling

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ak4ewzlxfpfcv26omvs7.gif)

Smells bad🤮. Right?


Here we’re going to tackle all these issues and implement high-performant and scalable networking layer. Let’s start from the very top. Ideally, our API call should look very simple:

{% embed https://gist.github.com/dev4jam/d57f7d650da767d4afb27e2d956c269c %}


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i9o9wb1rulm79x7amegr.gif)

Quite nice?


Now let’s start crafting our Networking Layer step by step with a help of one amazing package: [Freezed](https://pub.dev/packages/freezed) (yet another code generator for unions/pattern-matching and copy)!

**Step 0: Design our data model. Let it be something easy to grasp**

{% embed https://gist.github.com/dev4jam/e638cca091426adf92a32db14f5d283d %}


**Step 1: Create a flexible request body**

Request body encapsulated data to send along with your API call and could contain JSON object, binary data, text, etc.

{% embed https://gist.github.com/dev4jam/7242b401d2b0b4cd746c832719756272 %}


**Step 2: Create a request**

Our request will contain common parameters you might need to serve an API call: type (Get, Post, etc), API path, data to pass as a body, optionally query parameters and headers to override globally specified headers if needed:

{% embed https://gist.github.com/dev4jam/dec1a80dfded95f98c80c2fd7aef7c8c %}


**Step 3. Create a response**

Our response object will contain only raw data received from your API:

{% embed https://gist.github.com/dev4jam/b059d010716b63f610c609b1d4dc0ee8 %}


Now we are ready to implement the most intriguing part: network service/layer itself!

**Step 4. Let’s prototype our service with what we already have**

Our service needs a base URL and optionally DIO instance and HTTP headers. It will create a default DIO instance if not provided:

{% embed https://gist.github.com/dev4jam/1103416930f83caf7528279aa43731e5 %}


You can add other body types and conversions here so I’m leaving it to you.

Everything is ready to execute our request and handle exceptions:

{% embed https://gist.github.com/dev4jam/3fc1734f5c4ecf676f71831ea13eb50b %}


We can also add a method(s) to simplify authenticated API calls:

{% embed https://gist.github.com/dev4jam/05282a003427ee49e80988263cfc7b60 %}


Let’s summarise what we’ve got here:

* API call is prepared with all headers combined and parameters incapsulated
* API call is executed by DIO package
* Data returned from DIO is converted to your Model type provided
* Exceptions are handled and wrapped in a corresponding API response

But all this stuff still happens on main thread. And this is something we are going to fix now by utilizing a powerful yet simple [Isolates mechanism](https://dart.dev/guides/language/concurrency) in Dart.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cl071atn7t13df80xd8a.gif)

**Step 5. Make Networking Layer to be performant!**

The way Dart work is organized to pass data between Isolates we need to copy all parameters and then call a globally defined function. We will encapsulate all data in a private `PreparedNetworkRequest` class:

{% embed https://gist.github.com/dev4jam/0ea7d2cc76a1dfea8de43fa6dea26391 %}


And now all we need to do is to move request execution call along with exception handling into Isolate function:

{% embed https://gist.github.com/dev4jam/c43cc38e1b97d01f80d482f75dd9b611 %}


And call it in our Network Service:

{% embed https://gist.github.com/dev4jam/d1003800be7eb7f55773dfb89fc81bb8 %}


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkoye6olg4t4xo26jyn2.gif)

Huh! We’re done coding☺️.

Finally, our `NetworkService` looks like this:

{% embed https://gist.github.com/dev4jam/299108a5b506ac3837d1447804b39764 %}


**Recap of what we’ve achieved today**

✅ No duplicate code
✅ Not blocking main thread and dropping FPS
✅ Error-proof codebase
✅ Minimised time on adding new services and API calls
✅ Structured and strongly-typed error handling

**Resources**

[Concurrency in Dart](https://dart.dev/guides/language/concurrency)
[Freezed Dart package](https://pub.dev/packages/freezed)
[Dio Dart package](https://pub.dev/packages/dio)
[RealHTTP Modern Networking Layers in iOS Using Async/Await](https://betterprogramming.pub/modern-networking-layers-in-ios-using-async-await-ab98a4b5802c)
[Full source code available here](https://gist.github.com/dev4jam/ce094af46be6150cd0d20322e37dd958)

To get hand-picked the latest tech stories subscribe on my [Telegram Channel](https://t.me/itjedidev) where I post daily.

Happy coding!😉