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

Significantly reducing boilerplate code of BLoC pattern to efficiently handle API interactions.

Api BLoC #

A Flutter library for managing API calls using the BLoC pattern. This library provides a set of classes and utilities to simplify API calls and manage state changes.

Features #

  • Easily manage API calls and state changes using the BLoC pattern.
  • Generic classes for handling various API states such as loading, success, and error.
  • Customizable builder and listener functions to respond to state changes.
  • Automatic disposal of the controller to prevent memory leaks.
  • Generate api bloc pattern on command.

Getting Started #

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

dependencies:
  api_bloc: ^1.9.3

and to use api_bloc_cli run this command in terminal.

dart pub global activate api_bloc

Generating Api Bloc Structure (Optional) #

To make things faster on, let's say making GET detail and GET list also PUT update, POST create and DELETE using this library, we just need to run this command in terminal.

dart run api_bloc --output lib/src --create user --get detail,list --send update,create,delete

It will generating this structure in your project

lib/src/user/
   - user.dart
   - pages/
   - controllers/
     - get_user_detail_controller.dart
     - get_user_list_controller.dart
     - send_user_update_controller.dart
     - send_user_create_controller.dart
     - send_user_delete_controller.dart
   - models/
     - get_user_detail_model.dart
     - get_user_list_model.dart
     - send_user_update_model.dart
     - send_user_create_model.dart
     - send_user_delete_model.dart
   - widgets/
     - get_user_detail_widget.dart
     - get_user_list_widget.dart
     - send_user_update_widget.dart
     - send_user_create_widget.dart
     - send_user_delete_widget.dart

Now the things that left to do is writing the content of the controllers, widgets and models.

Fetching Scenario #

import 'package:api_bloc/api_bloc.dart';

class GetUserController extends GetController {
  @override
  Future<void> request(Map<String, dynamic> args) async {
    await Future.delayed(const Duration(seconds: 1));
    final response = await Dio().get('https://reqres.in/api/users/2');

    final model = GetUserModel.fromJson(response.data);
    emit(GetSuccessState<GetUserModel>(data: model));
  }
}
  • Put the controller inside [ApiBloc] widget.
import 'package:api_bloc/api_bloc.dart';

final controller = GetUserController();

ApiBloc.builder(
  controller: controller,
  builder: (context, state, child) {
    if (state is GetSuccessState<UserModel>) {
      return Text('Username: ${state.data!.username}');
    } else if (state is GetErrorState){
      return Text('Error occurred: ${state.message}');
    } else {
      return CircularProgressIndicator();
    }
  },
);

When the first time it initiate the controller, on GetController it's auto running the request function. But if you want to re run it, you can do it by calling

controller.run();

Sending Scenario #

import 'package:api_bloc/api_bloc.dart';

class CreateUserController extends SendController {
  @override
  // We're going to dispose it manually if we set it as false.
  bool get autoDispose => false;

  @override
  Future<void> request(Map<String, dynamic> args) async {
    await Future.delayed(const Duration(seconds: 1));
    final response = await Dio().post('https://reqres.in/api/users/2',
        data: FormData.fromMap({"name": args['name'], "job": args['job']}));

    if (response.statusCode == 201) {
      final model = CreateUserModel.fromJson(response.data);
      emit(SendSuccessState<CreateUserModel>(data: model));
    } else {
      emit(SendFailedState<Map<String, dynamic>>(
          data: response.data,
          message: "Expected response code output is 201"));
    }
  }
}
  • Put the controller inside [ApiBloc] widget.
import 'package:api_bloc/api_bloc.dart';

final controller = CreateUserController();

ApiBloc(
  controller: controller,
  listener: (context, state) {
    if (state is SendSuccessState<CreateUserModel>) {
      snackbar(context, message: "Succesfully creating new user with id #${state.data!.id}");
    } else if (state is SendFailedState) {
      snackbar(context, message: "Failed because ${state.message}", color: Colors.grey);
    } else if (state is SendErrorState) {
      snackbar(context, message: state.message, color: Colors.red);
    }
  },
  builder: (context, state, child) {
    if (state is SendLoadingState) {
      return TextButton(text: "Loading ...");
    }  else {
      return TextButton(text: "Create", onPressed: () => controller.run());
    }
  },
);

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

Using Extension #

Now you can easily customize how your ApiBloc handles different state scenarios using these new extensions:

  • onIdle: Handle SendIdleState and only work with SendController.
  • onLoading: Handle GetLoadingState and only work with GetController or SendLoadingState that only work with SendController.
  • onSuccess: Handle GetSuccessState and only work with GetController or SendSuccessState that only work with SendController.
  • onFailed: Handle SendFailedState and only work with SendController.
  • onError: Handle GetErrorState and only work with GetController or SendErrorState that only work with SendController.
import 'package:api_bloc/api_bloc.dart';

final controller = CreateUserController();

ApiBloc(
  controller: controller,
  child: TextButton(text: "Create", onPressed: () => controller.run()))
.onLoading(
  builder: (context, state, child) {
    return TextButton(text: "Loading ...");
  })
.onFailed(
  listener: (context, state, child) {
    snackbar(context, message: "Failed because ${state.message}", color: Colors.grey);
  })
.onSuccess<CreateUserModel>(
  listener: (context, state, child) {
    snackbar(context, message: "Succesfully creating new user with id #${state.data!.id}");
  })
.onError(
  listener: (context, state, child) {
    snackbar(context, message: state.message, color: Colors.red);
  })
.onState<BlocStates<Object>>(
  listener; (context, state) {
    snackbar(context, message: "You're in this custom state of ${state.runtimeType}");
  },
  builder: (context, state, child) {
    return Text("You're in this custom state of ${state.runtimeType}");
  }
);

Sentry Integration #

You can integrate this library with sentry by making custom controller like this.

abstract class GetSentryController extends BlocController<GetStates> {
  GetSentryController({
    this.autoRun = true,
    JSON args = const {},
  }) : super(value: const GetLoadingState()) {
    if (autoRun) run(args: args);
  }

  @override
  Future<void> run({JSON args = const {}}) async {
    emit(const GetLoadingState());
    final http = SentryHttpClient();
    try {
      await request(http, args);
    } catch (e, s) {
      emit(GetErrorState(message: e.toString(), data: s));
      await Sentry.captureException(e, stackTrace: s);
    } finally {
      http.close();
    }
  }

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

  final bool autoRun;
}

abstract class SendSentryController extends BlocController<SendStates> {
  SendSentryController() : super(value: const SendIdleState());

  @override
  Future<void> run({JSON args = const {}}) async {
    emit(const SendLoadingState());
    final http = SentryHttpClient();
    try {
      await request(http, args);
    } catch (e, s) {
      emit(SendErrorState(message: e.toString(), data: s));
      await Sentry.captureException(e, stackTrace: s);
    } finally {
      http.close();
    }
  }

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

and then whenever you want to interact with the api, you just need to make this controller:

class GetUserRequest extends GetSentryController {

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

Example #

9
likes
160
pub points
30%
popularity
screenshot

Publisher

verified publisherinidia.app

Significantly reducing boilerplate code of BLoC pattern to efficiently handle API interactions.

Homepage
Repository (GitHub)
View/report issues

Topics

#bloc #api

Documentation

API reference

Funding

Consider supporting this project:

ko-fi.com

License

MIT (license)

Dependencies

args, flutter

More

Packages that depend on api_bloc