api_bloc 3.0.1 copy "api_bloc: ^3.0.1" to clipboard
api_bloc: ^3.0.1 copied to clipboard

Flutter widgets that simplify the BLoC pattern implementation for REST APIs within an MVC architecture, significantly reducing boilerplate code.

Api BLoC #

Pub Version codecov

Flutter widgets designed to simplify the implementation of the BLoC pattern for REST APIs within an MVC architecture. Significantly reduces boilerplate code by automating BLoC pattern and test generation for handling REST API interactions.

Features #

  • Significantly reducing boilerplate code of bloc pattern to interact with REST API.
  • Generic classes for handling various API states such as loading, success, & error for READ states and idle, loading, success, failed & error for WRITE states.
  • Customizable builder and listener functions to respond to state changes.
  • Generate api bloc pattern & bloc test on command.

Getting Started #

To use this library, add api_bloc as a dependency in your pubspec.yaml file.

dependencies:
  api_bloc: ^3.0.1

and to use api_bloc_cli run this command in terminal.

dart pub global activate api_bloc

Fetching Scenario #

import 'package:api_bloc/api_bloc.dart';

class GetUserController extends ReadController {
  @override
  Future<void> onRequest(Map<String, dynamic> args) async {
    // Mock Delay
    await Future.delayed(const Duration(seconds: 1));

    Response response = await Dio().get(
      'https://reqres.in/api/users/2',
      onReceiveProgress: (received, total) {
        emit(ReadLoadingState<double>(data: received / total));
      },
    );

    emit(ReadSuccessState<GetUserModel>(
        data: GetUserModel.fromJSON(response.data)));
  }
}

Put the controller inside the [ApiBloc] widget.

import 'package:api_bloc/api_bloc.dart';

ApiBloc(
  controller: UserDetailController(),
  child: BlocBuilder<UserDetailController, ReadStates>(
    builder: (context, state, child) {
      if (state is ReadSuccessState<UserDetailModel>) {
      } else if (state is ReadErrorState) {}
      return Text(state.message);
    },
  ),
);

When the controller is first initiated, on ReadController it automatically runs the request function. If you want to rerun it, you can do it by calling:

controller.run();

Submitting Scenario #

import 'package:api_bloc/api_bloc.dart';

class UserUpdateController extends WriteController {
  @override
  Future<void> onRequest(Map<String, dynamic> args) async {
    // Delay to make the loading state more noticable.
    await Future.delayed(const Duration(milliseconds: 300));

    // Emit your success and failed state here ↓↓
    if (isSuccess) {
    emit(const WriteSuccessState<UserUpdateSuccessModel>(
        data: UserUpdateSuccessModel.test()));
    } else {
    emit(const WriteFailedState<UserUpdateFailedModel>(
        data: UserUpdateFailedModel.test()));
    }
  }
}

Put the controller inside the [ApiBloc] widget.

import 'package:api_bloc/api_bloc.dart';

ApiBloc(
  controller: UserUpdateController(),
  child: BlocConsumer<UserUpdateController, WriteStates>(
    listener: (context, state) {
      if (state is WriteSuccessState<UserUpdateSuccessModel>) {
      } else if (state is WriteFailedState<UserUpdateFailedModel>) {
      } else if (state is WriteErrorState) {}
    },
    builder: (context, state, child) {
      if (state is WriteLoadingState) {}
      return Text(state.message);
    },
  ),
);

Unlike the ReadController, the initial state of WriteControllerRequest is idle state, so to run the request you need to trigger the controller.run() manually.

Generating Api Bloc Structure (Optional) #

To quickly create a module, for example GET detail and GET list, also PUT update, POST create, and DELETE for a module called USER using this library, run this command in terminal:

dart run api_bloc --output lib/src --create user --read detail,list --write update,create,delete

It will generate this structure in your project:

📂 lib/src/user/
   📄 lib/src/user/user.dart 
   📄 lib/src/user/controllers/user_detail.dart 
   📄 lib/src/user/controllers/user_list.dart 
   📄 lib/src/user/controllers/user_update.dart 
   📄 lib/src/user/controllers/user_create.dart 
   📄 lib/src/user/controllers/user_delete.dart 
   📄 lib/src/user/models/user_detail.dart 
   📄 lib/src/user/models/user_list.dart 
   📄 lib/src/user/models/user_update.dart 
   📄 lib/src/user/models/user_create.dart 
   📄 lib/src/user/models/user_delete.dart 
   📄 lib/src/user/views/user_detail.dart 
   📄 lib/src/user/views/user_list.dart 
   📄 lib/src/user/views/user_update.dart 
   📄 lib/src/user/views/user_create.dart 
   📄 lib/src/user/views/user_delete.dart 
📂 test/src/user/
   📄 test/src/user/controllers/user_detail.dart 
   📄 test/src/user/controllers/user_list.dart 
   📄 test/src/user/controllers/user_update.dart 
   📄 test/src/user/controllers/user_create.dart 
   📄 test/src/user/controllers/user_delete.dart 
   📄 test/src/user/models/user_detail.dart 
   📄 test/src/user/models/user_list.dart 
   📄 test/src/user/models/user_update.dart 
   📄 test/src/user/models/user_create.dart 
   📄 test/src/user/models/user_delete.dart 
   📄 test/src/user/views/user_detail.dart 
   📄 test/src/user/views/user_list.dart 
   📄 test/src/user/views/user_update.dart 
   📄 test/src/user/views/user_create.dart 
   📄 test/src/user/views/user_delete.dart 

Sentry Integration #

You also can integrate this library with Sentry by creating custom controllers like this:

abstract class ReadSentryController extends BlocRequest<ReadStates> {
  ReadSentryController({
    this.autoRun = true,
    JSON args = const {},
  }) : super(value: const ReadLoadingState()) {
    if (autoRun) run(args);
  }

  @override
  Future<void> run([JSON args = const {}]) async {
    emit(const ReadLoadingState());
    final http = SentryHttpClient();
    try {
      await onRequest(http, args);
    } catch (e, s) {
      await onError(e, s);
    } finally {
      http.close();
    }
  }

  Future<void> onRequest(SentryHttpClient http, JSON args);

  Future<void> onError(dynamic e, StackTrace s) async {
    await Sentry.captureException(e, stackTrace: s);
    emit(ReadErrorState(message: e.toString(), data: s));
  }

  final bool autoRun;
}

abstract class WriteControllerSentryController extends BlocRequest<WriteControllerStates> {
  WriteControllerSentryController() : super(value: const WriteControllerIdleState());

  @override
  Future<void> run([JSON args = const {}]) async {
    emit(const WriteControllerLoadingState());
    final http = SentryHttpClient();
    try {
      await onRequest(http, args);
    } catch (e, s) {
      await onError(e, s);
    } finally {
      http.close();
    }
  }

  Future<void> onRequest(SentryHttpClient http, JSON args);

  Future<void> onError(dynamic e, StrackTrace s) async {
    await Sentry.captureException(e, stackTrace: s);
    emit(WriteControllerErrorState(message: e.toString(), data: s));
  }
}

Whenever you want to interact with the API, create a controller like this:

class ReadUserRequest extends ReadSentryController {

  Future<void> onRequest(http, args) async {
    await Future.delayed(const Duration(milliseconds: 300));
    final response = await http.get(Uri('http://baseUrl/api/user/123'));
    emit(ReadSuccessState<UserModel>(data: UserModel.fromJSON(jsonDecode(response.body))));
  }
}

Example #

6
likes
160
pub points
44%
popularity
screenshot

Publisher

verified publisherinidia.app

Flutter widgets that simplify the BLoC pattern implementation for REST APIs within an MVC architecture, significantly reducing boilerplate code.

Homepage
Repository (GitHub)
View/report issues

Topics

#bloc #rest-api #mvc #api

Documentation

API reference

Funding

Consider supporting this project:

ko-fi.com

License

MIT (LICENSE)

Dependencies

args, equatable, flutter

More

Packages that depend on api_bloc