florval 0.2.0 copy "florval: ^0.2.0" to clipboard
florval: ^0.2.0 copied to clipboard

Generate type-safe Flutter/Dart API clients from OpenAPI specs with status-code Union types, Riverpod integration, and cursor-based pagination.

florval #

Pub Version Dart License: MIT

Generate type-safe Flutter/Dart API clients from OpenAPI specs — with status-code-level response handling, Riverpod integration, and cursor-based pagination.

Inspired by orval for React. florval brings the same level of automation to Flutter: one command turns your OpenAPI spec into production-ready Dart code.

Why florval? #

Most OpenAPI code generators for Flutter treat every response as a single success type. Real APIs return different shapes for different status codes — 200 returns a User, 404 returns nothing, 422 returns ValidationError.

florval generates freezed sealed classes so you can pattern-match on every status code with Dart 3 switch expressions:

final response = await client.getUser(id: 42);

switch (response) {
  case GetUserResponseSuccess(:final data) => Text(data.name),
  case GetUserResponseNotFound()           => Text('User not found'),
  case GetUserResponseServerError(:final data) => Text(data.message),
  case GetUserResponseUnknown(:final statusCode) => Text('Unexpected: $statusCode'),
}

No more try/catch guessing. No more response.statusCode == 200 checks.

Features #

  • Status-code Union types — freezed sealed classes for every endpoint response
  • freezed 3.x models — immutable data classes with copyWith, JSON serialization
  • dio clients — clean HTTP clients, no Retrofit, full control over your Dio instance
  • Riverpod 3.x integration — Notifiers for GET, Mutation API for POST/PUT/DELETE
  • Cursor-based paginationfetchMore() with automatic data accumulation
  • Auto-invalidation — mutations automatically refresh related GET providers
  • Retry@Riverpod(retry:) generation from config
  • OpenAPI 3.0 & 3.1 — v3.0 specs are normalized to v3.1 automatically
  • multipart/form-data — file uploads with MultipartFile support
  • Watch mode — auto-regenerate on spec file changes
  • Zero runtime dependency — generated code depends only on dio, freezed, and optionally Riverpod

Quick Start #

1. Install #

Add florval as a dev dependency:

dev_dependencies:
  florval: ^0.1.0

2. Initialize #

dart run florval init

This creates a florval.yaml config file. Edit schema_path to point to your OpenAPI spec.

3. Generate #

dart run florval generate

4. Build #

dart run build_runner build --delete-conflicting-outputs

This runs freezed, json_serializable, and riverpod_generator on the generated code.

Generated output #

lib/api/generated/
├── models/           # freezed data classes
├── responses/        # status-code Union types (sealed classes)
├── clients/          # dio API clients (grouped by tag)
├── providers/        # Riverpod Notifiers & Mutations (optional)
└── api.dart          # barrel file

Generated Code #

Models #

@freezed
abstract class Pet with _$Pet {
  const factory Pet({
    required int id,
    required String name,
    String? tag,
    Category? category,
  }) = _Pet;

  factory Pet.fromJson(Map<String, dynamic> json) => _$PetFromJson(json);
}

Status-Code Union Types #

@freezed
sealed class GetPetResponse with _$GetPetResponse {
  const factory GetPetResponse.success(Pet data) = GetPetResponseSuccess;
  const factory GetPetResponse.notFound() = GetPetResponseNotFound;
  const factory GetPetResponse.serverError(Error data) = GetPetResponseServerError;
  const factory GetPetResponse.unknown(int statusCode, dynamic body) = GetPetResponseUnknown;
}

Dio Client #

class PetsApiClient {
  final Dio _dio;

  PetsApiClient(this._dio);

  Future<GetPetResponse> getPet({required int petId}) async {
    try {
      final response = await _dio.get('/pets/$petId');
      return switch (response.statusCode) {
        200 => GetPetResponse.success(Pet.fromJson(response.data)),
        404 => GetPetResponse.notFound(),
        500 => GetPetResponse.serverError(Error.fromJson(response.data)),
        _ => GetPetResponse.unknown(response.statusCode!, response.data),
      };
    } on DioException catch (e) {
      if (e.response != null) { /* error status code handling */ }
      rethrow;
    }
  }
}

You provide your own Dio instance — configure base URL, interceptors, retry, and auth however you like.

Riverpod Providers (optional) #

GET → Notifier:

@Riverpod(retry: _retry)
class GetPet extends _$GetPet {
  @override
  FutureOr<GetPetResponse> build({required int petId}) async {
    final client = ref.watch(petsApiClientProvider);
    return client.getPet(petId: petId);
  }
}

POST/PUT/DELETE → Mutation:

final createPetMutation = Mutation<CreatePetResponse>();

Auto-invalidation helper (opt-in):

Future<CreatePetResponse> createPet(
  MutationTarget ref, {required CreatePetRequest body}
) async {
  return createPetMutation.run(ref, (tsx) async {
    final client = tsx.get(petsApiClientProvider);
    final result = await client.createPet(body: body);
    ref.container.invalidate(listPetsProvider);
    return result;
  });
}

Cursor-Based Pagination #

Configure in florval.yaml:

riverpod:
  pagination:
    - operation_id: listPets
      cursor_param: after
      next_cursor_field: nextCursor
      items_field: items

Generated Notifier with fetchMore():

@Riverpod(retry: _retry)
class ListPets extends _$ListPets {
  @override
  FutureOr<PaginatedData<Pet>> build() async { /* initial fetch */ }

  Future<void> fetchMore() async { /* loads next page, appends to list */ }
}

Configuration #

Full florval.yaml reference:

florval:
  schema_path: openapi.yaml              # Required. Path to OpenAPI spec.
  output_directory: lib/api/generated     # Output directory.

  client:
    base_url_env: API_BASE_URL            # Env var name for base URL.
    timeout: 30000                        # Request timeout (ms).

  riverpod:
    enabled: false                        # Generate Riverpod providers.
    auto_invalidate: false                # Invalidate GET providers after mutations.
    retry:                                # Riverpod-level retry for GET providers.
      max_attempts: 3
      delay: 1000                         # Initial delay (ms), linear backoff.
    pagination:                           # Cursor-based pagination endpoints.
      - operation_id: listItems
        cursor_param: after
        next_cursor_field: nextCursor
        items_field: items

CLI #

# Generate config template
dart run florval init
dart run florval init --config custom.yaml --force

# Generate code
dart run florval generate
dart run florval generate --config custom.yaml
dart run florval generate --schema api.yaml --output lib/api/
dart run florval generate --watch
dart run florval generate --verbose

Requirements #

Your project's dependencies #

dependencies:
  dio: ^5.0.0
  freezed_annotation: ^3.0.0
  json_annotation: ^4.0.0
  # Only if riverpod.enabled: true
  riverpod: ^3.0.0
  riverpod_annotation: ^3.0.0

dev_dependencies:
  build_runner: ^2.4.0
  freezed: ^3.0.0
  json_serializable: ^6.0.0
  # Only if riverpod.enabled: true
  riverpod_generator: ^3.0.0
  florval: ^0.1.0

OpenAPI Version Support #

Version Support
OpenAPI 3.1 Full
OpenAPI 3.0 Full (auto-normalized to 3.1)
Swagger 2.0 Partial (auto-normalized to 3.1)

License #

MIT

4
likes
0
points
530
downloads

Publisher

verified publisherencer.co.jp

Weekly Downloads

Generate type-safe Flutter/Dart API clients from OpenAPI specs with status-code Union types, Riverpod integration, and cursor-based pagination.

Repository (GitHub)
View/report issues

Topics

#openapi #code-generation #dio #riverpod #freezed

License

unknown (license)

Dependencies

args, openapi_spec_plus, path, recase, yaml

More

Packages that depend on florval