Kamran Bekirov Logo
Kamran Bekirov's Blog

Everything I know about Flutter

Back

Easier and safer way of storing API endpoints in Flutter

This was how I used to store API endpoints in Flutter:

class RestfulEndpoints {
  const RestfulEndpoints._();
 
  static const String baseUrl = 'https://example.com/api';
 
  static const String userInfo = '$baseUrl/user/info';
  static const String saveCard = '$baseUrl/user/card/save';
  static const String addToCart = '$baseUrl/cart/add';
}

Are you asking what's wrong with this approach? Can you tell me what's the method to get the user info? Or does save card endpoint need a request body? Or maybe it needs path parameters? What are they? ...

I have found a better way. Look at this beauty:

class RestfulEndpoints {
  const RestfulEndpoints._();
 
  static const String baseUrl = 'https://example.com/api';
 
  static Endpoint userInfo() {
    return Endpoint.get(
      url: '${baseUrl}/user',
    );
  }
 
  static Endpoint saveCard({
    required String cardType,
  }) {
    return Endpoint.post(
      url: '${baseUrl}/user/card/save?cardType=$cardType',
    );
  }
 
  static Endpoint addToCart({
    required String productId,
    required int quantity,
  }) {
    return Endpoint.post(
      url: '${baseUrl}/cart/add',
      body: { 
        'productId': productId,
        'quantity': quantity,
      },
    );
  }
}

I know. It's better.

Then you'll use it like this:

Future<User> getUser() async {
  final Endpoint endpoint = RestfulEndpoints.userInfo();
  final Response response = await locator<Dio>().fetch(
    endpoint.toRequestOptions(),
  );
 
  return User.fromJson(response.data['data']);
}

Of course, do your own kind of parsing, validation, onion layering, etc. The point is that the endpoint now contains all the information about... the endpoint.

To have this there are two things you need to have.

  1. Endpoint class:
class Endpoint {
  final String url;
  final String method;
  final dynamic body;
 
  const Endpoint({
    required this.url,
    required this.method,
    this.body,
  });
 
  factory Endpoint.get({
    required String url,
  }) {
    return Endpoint(
      url: Uri.encodeFull(url),
      method: 'GET',
    );
  }
 
  factory Endpoint.post({
    required String url,
    dynamic body,
  }) {
    return Endpoint(
      url: url,
      method: 'POST',
      body: body,
    );
  }
 
  factory Endpoint.put({
    required String url,
    dynamic body,
  }) {
    return Endpoint(
      url: url,
      method: 'PUT',
      body: body,
    );
  }
 
  factory Endpoint.delete({
    required String url,
  }) {
    return Endpoint(
      url: url,
      method: 'DELETE',
    );
  }
 
  factory Endpoint.patch({
    required String url,
    dynamic body,
  }) {
    return Endpoint(
      url: url,
      method: 'PATCH',
      body: body,
    );
  }
}
UserOrient - Feature Voting Board logo
Sponsored

UserOrient - Feature Voting Board

Tired of building features nobody uses? Let your users vote on what to build next with UserOrient. Stop wasting development time and build only what users actually want.

Add To Your Flutter App
  1. Endpoint class extension:
extension EndpointX on Endpoint {
  RequestOptions toRequestOptions() {
    return RequestOptions(
      method: method,
      baseUrl: url,
      data: body,
    );
  }
}

That's it.

If you want, add enums for methods, maybe add headers, store URL as Uri instead of String, add onSendProgress to the toRequestOptions method, etc. It's up to you.

Hope you find it useful.

Happy coding!


Kamran Bekirov

I'm Kamran Bekirov,

Flutter Developer who built over 80 mobile apps,

Founder of UserOrient - Flutter SDK for collecting user feedback.