DEV Community

Cover image for Connecting Your Flutter Application to a Backend
Dima
Dima

Posted on

Connecting Your Flutter Application to a Backend

When developing a Flutter application, you probably want to keep development costs to a minimum, make the solution fast, reliable, and secure. In this guide (or rather a technical overview with comments), I will explain how to connect your Flutter application to a backend using HTTP REST API and gRPC. You will learn how to choose the right backend for your application, how to set it up, and how to connect your application to it.

What to choose?

It is not a secret that REST API is the most common way of communication between frontend and backend in modern web applications and microservices-based infrastructures. However, it is worth noting that microservices can use other communication methods as well.

HTTP REST API uses the HTTP protocol to transfer data between the client and the server. It is easy to use and understandable for most developers. To use it, you need to define the requests that your application should send to the server, as well as the structure of the responses that you expect to receive. This article provides an example using the Dio package.

gRPC (Google Remote Procedure Call) is a newer approach to communication between the client and the server, based on open-source RPC architecture. It uses the Protobuf message exchange format, which is a highly efficient format for exchanging messages with a high degree of packing (thanks to the forced use of http/2 features) for serializing structured data. In some cases, using gRPC API may be more efficient than REST API.

When developing an application, the frontend and backend are usually created by different people with different competencies. Tools such as swagger and redoc are used to provide usage instructions for REST. In the case of gRPC, the frontend receives almost ready-to-implement code. It is also worth considering the fact that if the project involves a web application, using gRPC for a mobile application may be too expensive.

Connecting with HTTP REST

To work with the REST API, I recommend using the Dio package because it is more functional and convenient than the standard http package. If you have created web applications, this is similar to axios.

To use Dio, you need to create a class for working with network connections, in which you will define the requests that your application should send to the server, as well as the structure of the responses you expect to receive.

“ flutter pub add dio ”

Add the dio package to your project

Let's create a class for working with network connections:

import 'package:dio/dio.dart';

class NetworkService {
  late final Dio _dio;
  final JsonEncoder _encoder = const JsonEncoder();
  static final NetworkService _instance = NetworkService.internal();

  NetworkService.internal();

  static NetworkService get instance => _instance;

  Future<void> initClient() async {
    _dio = Dio(
      BaseOptions(
        baseUrl: Constant.baseUrl,
        connectTimeout: 60000,
        receiveTimeout: 6000,
      ),
    );
// A place for interceptors. For example, for authentication and logging
  }

  Future<dynamic> get(
    String url, {
    Map<String, dynamic>? queryParameters,
  }) async {
    try {
      final response = await _dio.get(url, queryParameters: queryParameters);
      return response.data;
    } on DioError catch (e) {
      final data = Map<String, dynamic>.from(e.response?.data);
      throw Exception(data['message'] ?? "Error while fetching data");
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> download(String url, String path) async {
    return _dio.download(url, path).then((Response response) {
      if (response.statusCode! < 200 || response.statusCode! > 400) {
        throw Exception("Error while fetching data");
      }
      return response.data;
    }).onError((error, stackTrace) {
      throw Exception(error);
    });
  }

  Future<dynamic> delete(String url) async {
    return _dio.delete(url).then((Response response) {
      if (response.statusCode! < 200 || response.statusCode! > 400) {
        throw Exception("Error while fetching data");
      }
      return response.data;
    }).onError((DioError error, stackTrace) {
      _log(error.response);
    });
  }

  Future<dynamic> post(String url, {body, encoding}) async {
    try {
      final response = await _dio.post(url, data: _encoder.convert(body));
      return response.data;
    } on DioError catch (e) {
      throw Exception(e.response?.data['detail'] ?? e.toString());
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> postFormData(String url, {required FormData data}) async {
    try {
      final response = await _dio.post(url, data: data);
      return response.data;
    } on DioError catch (e) {
      throw Exception(e.response?.data['detail'] ?? e.toString());
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> patch(String url, {body, encoding}) async {
    try {
      final response = await _dio.patch(url, data: _encoder.convert(body));
      return response.data;
    } on DioError catch (e) {
      throw Exception(e.response?.data['detail'] ?? e.toString());
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> put(String url, {body, encoding}) async {
    try {
      final response = await _dio.put(url, data: _encoder.convert(body));
      return response.data;
    } on DioError catch (e) {
      throw e.toString();
    } catch (e) {
      rethrow;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Example

final NetworkService _client;

Future<String> login(LoginRequest loginRequest) async {
  try {
    final jsonData = await _client.post(
      "${Constant.baseUrl}/v1/auth/login",
      body: loginRequest.toJson()
    );
    return jsonData['access_token']
  } catch (e) {
    rethrow;
  }
}
Enter fullscreen mode Exit fullscreen mode

Connecting with gRPC

To work with gRPC, you need to use generated code based on proto files. Create a service for work, which will use the HelloClient class generated using gRPC. When you create an instance of HelloClient, you will need to pass it a channel that will be used to send requests.

Now, integrate the generated gRPC client into your Flutter application:

“ flutter pub add grpc ”

Add the grpc package to your project

Creating a service for work:

import 'package:grpc/grpc.dart';
//import your autogenerate code
import '../services/proto/hello.pbgrpc.dart';

class HelloService {

  ///here enter your host without the http part
  String baseUrl = "example.com";

  HelloService._internal();
  static final HelloService _instance = HelloService._internal();

  factory HelloService() => _instance;

  ///static HelloService instance that we will call when we want to make requests
  static HelloService get instance => _instance;
   ///HelloClient is the  class that was generated for us when we ran the generation command
  ///We will pass a channel to it to intialize it
  late HelloClient _helloClient;

  ///this will be used to create a channel once we create this class.
  ///Call HelloService().init() before making any call.
  Future<void> init() async {
    final channel = ClientChannel(
      baseUrl,
      port: 443,
      options: const ChannelOptions(),
    );
    _helloClient = HelloClient(channel);
  }

  ///provide public access to the HelloClient instance
  HelloClient get helloClient {
    return _helloClient;
  }
}
Enter fullscreen mode Exit fullscreen mode

In you main function initialize the HelloService class like below.

HelloService().init();
Enter fullscreen mode Exit fullscreen mode

Make gRPC call

Future<void> sayHello() async {
  try {
    HelloRequest helloRequest = HelloRequest();
    helloRequest.name = "Itachi";
    var res = await HelloService.instance.helloClient.sayHello(helloRequest);
  } catch (e) {
    print(e);
  }
}
Enter fullscreen mode Exit fullscreen mode

Comparing REST and gRPC for Flutter Applications

  • Advantages of REST: Simple, easy to understand, widely supported, cacheable, and suitable for most applications.
  • Disadvantages of REST: Higher latency, less efficient serialization, limited streaming capabilities.
  • Advantages of gRPC: Low latency, efficient serialization, streaming support, strong client libraries, and ideal for real-time applications and microservices.
  • Disadvantages of gRPC: Steeper learning curve, not as human-readable, less support in web browsers, and may be overkill for simple applications.

When choosing between REST and gRPC for your Flutter application, consider the specific needs and requirements of your app. REST is a solid choice for most applications, while gRPC is well-suited for real-time applications or those with complex communication patterns between services.

In this article, we explored how to organize a Flutter application with an API backend, focusing on two main approaches: HTTP REST and gRPC. We discussed designing APIs, implementing clients in Flutter.

By understanding the advantages and disadvantages of each approach, you can make an informed decision when designing your Flutter application with an API backend. Choose the best approach for your app based on its specific needs, and build efficient, scalable, and maintainable applications.

Top comments (0)