dklimkin
Posted on March 24, 2022
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 and 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:
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
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:
Quite nice?
Now let’s start crafting our Networking Layer step by step with a help of one amazing package: Freezed (yet another code generator for unions/pattern-matching and copy)!
Step 0: Design our data model. Let it be something easy to grasp
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.
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:
Step 3. Create a response
Our response object will contain only raw data received from your API:
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:
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:
We can also add a method(s) to simplify authenticated API calls:
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 in Dart.
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:
And now all we need to do is to move request execution call along with exception handling into Isolate function:
And call it in our Network Service:
Huh! We’re done coding☺️.
Finally, our NetworkService
looks like this:
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
Freezed Dart package
Dio Dart package
RealHTTP Modern Networking Layers in iOS Using Async/Await
Full source code available here
To get hand-picked the latest tech stories subscribe on my Telegram Channel where I post daily.
Happy coding!😉
Posted on March 24, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.