mobile_api

A Dart/Flutter package for REST and GraphQL API integration.

Features

  • REST client with shared headers, JSON decoding, typed success/failure results, and one retry after token refresh
  • GraphQL client with typed list/detail helpers and refresh support
  • Secure token/cache helpers backed by flutter_secure_storage
  • Optional network connectivity checks
  • Multipart/form-data upload support
  • Optional logger endpoint for captured API errors

Getting started

Add the package to your pubspec.yaml:

dependencies:
  mobile_api: ^0.0.9+16

Import it:

import 'package:mobile_api/mobile_api.dart';

Configuration

final cache = ICacheRepoImpl();

final apiConfig = ApiConfig(
  apiUrl: Uri.parse('https://api.example.com'),
  refreshTokenPath: '/api/accounts/refresh',
  loggerPath: '/logger',
  appCache: cache,
  checkNetwork: CheckNetwork(),
  headers: const {
    HttpHeadersConst.marketplace: 'ml',
    HttpHeadersConst.userAgent: 'mlApp',
    HttpHeadersConst.acceptLanguage: 'tr',
    'referer': 'https://teststore.hibeta.kz/',
    'x-hibeta-country-locked': 'true',
    'x-hibeta-language': 'tr',
    'x-hibeta-storefront-host': 'teststore.hibeta.kz',
    'x-hibeta-tenant-country': 'KZ',
    'x-hibeta-tenant-mode': 'single-country',
  },
);

Tokens are read from CoreCacheKey.accessToken and CoreCacheKey.refreshToken.

await cache.saveAll(
  CacheKeyBundle.tokenPair(
    access: accessToken,
    refresh: refreshToken,
  ),
);

Projects with a custom refresh payload or token model can extend IBOAuth2Token and configure the refresh parser:

class ProjectToken extends IBOAuth2Token {
  ProjectToken({
    required super.accessToken,
    required super.refreshToken,
    required this.tenantId,
  });

  factory ProjectToken.fromJson(Map<String, dynamic> json) => ProjectToken(
        accessToken: json['accessToken'] as String,
        refreshToken: json['refreshToken'] as String,
        tenantId: json['tenantId'] as String,
      );

  final String tenantId;
}

final apiConfig = ApiConfig<ProjectToken>(
  apiUrl: Uri.parse('https://api.example.com'),
  refreshTokenPath: '/api/accounts/refresh',
  appCache: cache,
  refreshBodyBuilder: (refreshToken) => {
    'refresh_token': refreshToken,
    'grant_type': 'refresh_token',
    'client_id': 'mobile-app',
  },
  refreshTokenFromJson: ProjectToken.fromJson,
);

REST usage

final httpApi = IHttpImpl(apiConfig: apiConfig);

final result = await httpApi.baseMethod<UserResponse, DefaultErrorResponse>(
  '/api/users/me',
  requestType: RequestType.get,
  dataFromJson: UserResponse.fromJson,
  errorFromJson: DefaultErrorResponse.fromJson,
);

switch (result) {
  case Success(value: final user):
    // use user
  case Failure(exception: final error):
    // handle error
}

Repositories can pass operation-specific headers directly without creating a wrapper API client:

final result = await httpApi.baseMethod<OrderResponse, DefaultErrorResponse>(
  '/api/orders',
  requestType: RequestType.post,
  dataFromJson: OrderResponse.fromJson,
  errorFromJson: DefaultErrorResponse.fromJson,
  body: order.toJson(),
  headers: {'Idempotency-Key': operationId},
);

Use baseMethodJson when a successful response can be a top-level JSON array or scalar. The decoder receives the complete decoded JSON value:

final result = await httpApi.baseMethodJson<List<UserResponse>, DefaultErrorResponse>(
  '/api/users',
  requestType: RequestType.get,
  dataFromJson: (json) => (json as List<Object?>)
      .map((item) => UserResponse.fromJson(item as Map<String, dynamic>))
      .toList(),
  errorFromJson: DefaultErrorResponse.fromJson,
);

For endpoints that return 204 No Content or another successful empty body, provide an emptySuccessBuilder so the typed result can still be a success:

final result = await httpApi.baseMethod<BooleanResponse, DefaultErrorResponse>(
  '/api/users/me',
  requestType: RequestType.delete,
  dataFromJson: BooleanResponse.fromJsonPhone,
  errorFromJson: DefaultErrorResponse.fromJson,
  emptySuccessBuilder: (_) => BooleanResponse(isSuccess: true),
);

GraphQL usage

final graphQlApi = IGraphQlImpl(
  apiConfig: apiConfig.copyWith(
    apiUrl: Uri.parse('https://api.example.com/graphql'),
  ),
);

final result = await graphQlApi.queryList<UserResponse, DefaultErrorResponse>(
  path: '''
query users {
  users {
    pageInfo { hasNextPage }
    items { id name }
  }
}
''',
  field: 'users',
  dataFromJson: UserResponse.fromJson,
  errorFromJson: DefaultErrorResponse.fromJson,
);

Development certificates

By default, the package uses normal platform certificate validation. For local development against a host with a self-signed certificate, set allowBadCertificates: true in ApiConfig. Do not enable it for production builds.

Libraries

mobile_api
Typed REST and GraphQL helpers for Flutter applications.