Why my flutter http network calls are slow?

1,709

It seems this question is there for many people. So, let me answer my own question.

Can flutter remember the network connection? Yes it can.

Flutter only require one network call to the same API to remember the connection. From the second call onward to the same API, it will use its "cached" memory giving you a big performance boost.

So first remember, this only works if you are calling the same API multiple times. If you are calling different APIs, this will not work. However in many apps, you have an API that built by the API team and you will be calling the same throughput the app.

The solution is to use flutter http.Client. Then share the same http.Client across the calls you make to the same API. You will see only first call takes time for "connection", rest of the calls do not take that time.

An example is available in flutter http pub page. It says ,

If you're making multiple requests to the same server, you can keep open a persistent connection by using a Client rather than making one-off requests. If you do this, make sure to close the client when you're done:

Check below example. It is only for your reference, not the best way of using this.

main.dart

import 'package:flutter/material.dart';
import 'package:network_test/role_service.dart';
import 'package:network_test/user_role_service.dart';
import 'package:network_test/user_service.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var startTime = "";
  var endTime = "";

  void _network() async {
    var client = http.Client();

    RoleService _roleService = RoleService();
    UserService _userService = UserService();
    UserRoleService _userRoleService = UserRoleService();

    String authToken = "****";

    String uid = "555555";
    try {
      await _roleService.getAllRoles(authToken, client);
      //await _roleService.getAllRoles(authToken, client);
      await _userService.getUserByUID(authToken, uid, client);
      await _userService.getUserByID(authToken, 27, client);
      await _userRoleService.getUserRoleByUser(authToken, 27, client);
    } finally {
      client.close();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              "Start Time: " + startTime,
              style: Theme.of(context).textTheme.headline4,
            ),
            Text(
              "End Time: " + endTime,
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _network,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

role_service.dart

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:network_test/role.dart';
import 'dart:convert' as convert;
import 'dart:io';

class RoleService with ChangeNotifier {
  late List<Role> _roles;
  String link2 = "https://api2.somewhere.com/userrel";

  /// Return roles
  List<Role> returnRoles() {
    return _roles;
  }

  /// Get all Roles
  Future<void> getAllRoles(String authToken, Client client) async {
    try {
      var data = await client.get(Uri.parse(link2 + "/role/getall"),
          headers: {HttpHeaders.authorizationHeader: "Bearer $authToken"});

     
      var jsonData =
          convert.json.decode(data.body).cast<Map<String, dynamic>>();
      _roles = jsonData.map<Role>((json) => Role.fromJson(json)).toList();
      print(_roles[0].roleName);
    } catch (error) {
      print(error);
      throw error;
    }
  }
}

now I told you that above is not the best practice. Why? Because you will be creating and destroying the http.Client in many different places. Let's pay attention to a better practice.

In almost every app, we use State Management. I am a fan of Provider, it could be anything of your choice. i figured out the best way is to let the state management to remember the creation of http.Client. Since I am using Provider, I created the following class.

import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';

class ConnectionService with ChangeNotifier {
  http.Client _client = http.Client();

  http.Client returnConnection() {
    return _client;
  }
}

And this is my main class

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  runApp(MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (context) => ConnectionService()),
    ],
    child: MyApp(),
  ));
}

Now when the app opens, I call the ConnectionService class to make the connection and do my API calls such as checking user authentication, user access etc. And only the first call is taking its time to build the connection, other calls do not.

Share:
1,709
PeakGen
Author by

PeakGen

CTO

Updated on January 01, 2023

Comments

  • PeakGen
    PeakGen over 1 year

    I am developing a flutter application with network activities. To get data, I am connecting to a REST API, this API is fast as it should.

    For more information, this API is using AWS API Gateway and AWS Lambda along with other AWS technologies.

    Below is my code, connecting to network.

    class RoleService with ChangeNotifier {
      NavLinks _navLinks = NavLinks();
      late List<Role> _roles;
    
    
    
      /// Get all Roles
      Future<void> getAllRoles(String authToken) async {
        try {
          var data = await http.get(
            Uri.parse("https://api2.example.com/userrel/roles/getall"),
            headers: {HttpHeaders.authorizationHeader: "Bearer $authToken"},
          );
          var jsonData =
              convert.json.decode(data.body).cast<Map<String, dynamic>>();
          _roles = jsonData.map<Role>((json) => new Role.fromJson(json)).toList();
          print(_roles);
        } catch (error) {
          print(error);
          throw error;
        }
      }
    }
    

    You can see the postman performance of the above API call below. For flutter testing, i am using Huawei p30 Lite android phone.

    enter image description here

    Then, when I execute the same API call in flutter, this is what I get.

    enter image description here

    Observing the outputs from postman I can see it has cached the DNS Lookup, TCP Handshake and SSL Handshake. postman does this after calling the API base URI for the first time. Then from the 2nd time onwards, the DNS Lookup etc are cached saving lot of time in future API calls to the same base URI.

    But in flutter the "Connection established" time is high, even though the time to retrieve data is only few milliseconds.

    How can I avoid the connection delays and get the maximum performance? If caching the SSL, DNS Lookup etc is the solution, how can I do that in flutter?

  • Lalliantluanga Hauhnar
    Lalliantluanga Hauhnar over 2 years
    can you please upload riverpod implementation