Flutter Development Framework Based on Dloc

Flutter Bloc Basic 说明

基于 Bloc 框架封装的快速开发架构

工程结构

|-- flutter_bloc_basic
    |-- flutter_bloc_basic.dart           # library 定义
    |-- global.dart                       # 全局初始化类  三方库初始化调用处
    |-- basic                             # 基类目录
    |   |-- base_app.dart                 # MaterialApp基类
    |   |-- base_repository_state.dart    # 自动注入Repository的StatefulWidget->State基类
    |   |-- base_state.dart               # 不需要Repository的StatefulWidget->State基类
    |   |-- repository_bloc.dart          # 需要使用Repository的Bloc需使用此基类
    |-- common                            # 通用包
    |   |-- typedefs.dart                 # typedef方法定义
    |   |-- theme_bloc                    # 主题切换Bloc
    |       |-- theme_bloc.dart
    |       |-- theme_event.dart
    |       |-- theme_state.dart
    |-- net                               # Http相关
    |   |-- base_dio.dart                 # Dio默认实例
    |   |-- base_exceptions.dart          # 自定义异常类,异常处理
    |   |-- base_repository.dart          # Repository基类,管理Http请求,dispose生命周期内自动取消
    |   |-- base_response.dart            # Response基类
    |   |-- base_response_converter.dart  # Response基类 数据转换器
    |   |-- net.dart                      # net Export类
    |   |-- result.dart                   # 返回给调用层数据结构
    |-- router
    |   |-- app_router.dart               # 路由管理实例
    |   |-- not_found_page.dart           # 路由NotFound返回页
    |   |-- router_provider.dart          # 定义路由回调抽象类
    |   |-- routes.dart                   # 配置路由
    |-- utils
        |-- local_storage_util.dart       # SharedPreferences工具类
        |-- log_util.dart                 # 打印日志工具类
        |-- navigator_util.dart           # 导航工具类
        |-- screen                        #屏幕自动适配工具类
            |-- auto_size.dart
            |-- auto_size_screen_util.dart
            |-- binding.dart

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_bloc_basic

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  flutter_bloc_basic: ^0.0.2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_bloc_basic/flutter_bloc_basic.dart'; 

Download Details:

Author: 

Source Code: https://pub.dev/packages/flutter_bloc_basic

#flutter #bloc #framework 

Flutter Development Framework Based on Dloc
Hunter  Krajcik

Hunter Krajcik

1662084780

Flutter_form_bloc: Create Beautiful forms in Flutter

form_bloc

Easy Form State Management using BLoC pattern. Separate the Form State and Business Logic from the User Interface.

🔥👉 Documentation and Tutorials 👈🔥

  • [x] Synchronous Field Validation
  • [x] Asynchronous Field Validation
  • [x] Easy Loading and Initializing
  • [x] Wizard / Stepper Forms
  • [x] Submission Progress
  • [x] Success / Failure Response
  • [x] Serializable Form
  • [x] Submission Errors to Fields
  • [x] Dynamic Fields
  • [x] Conditional Fields
  • [x] List Fields
  • [x] Group Fields
  • [x] CRUD Support
  • [x] Beautiful Built-In Widgets

Examples

Maintainers

PackagePub
form_blocpub package
flutter_form_blocpub package

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_form_bloc

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  flutter_form_bloc: ^0.30.1

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_form_bloc/flutter_form_bloc.dart';

example/lib/main.dart

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        inputDecorationTheme: InputDecorationTheme(
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(20),
          ),
        ),
      ),
      builder: (context, child) {
        return FormThemeProvider(
          theme: FormTheme(
            checkboxTheme: CheckboxFieldTheme(
              canTapItemTile: true,
            ),
            radioTheme: RadioFieldTheme(
              canTapItemTile: true,
            ),
          ),
          child: child!,
        );
      },
      home: AllFieldsForm(),
    );
  }
}

class AllFieldsFormBloc extends FormBloc<String, String> {
  final text1 = TextFieldBloc();

  final boolean1 = BooleanFieldBloc();

  final boolean2 = BooleanFieldBloc();

  final select1 = SelectFieldBloc(
    items: ['Option 1', 'Option 2'],
    validators: [FieldBlocValidators.required],
  );

  final select2 = SelectFieldBloc(
    items: ['Option 1', 'Option 2'],
    validators: [FieldBlocValidators.required],
  );

  final multiSelect1 = MultiSelectFieldBloc<String, dynamic>(
    items: [
      'Option 1',
      'Option 2',
      'Option 3',
      'Option 4',
      'Option 5',
    ],
  );
  final file = InputFieldBloc<File?, String>(initialValue: null);

  final date1 = InputFieldBloc<DateTime?, Object>(initialValue: null);

  final dateAndTime1 = InputFieldBloc<DateTime?, Object>(initialValue: null);

  final time1 = InputFieldBloc<TimeOfDay?, Object>(initialValue: null);

  final double1 = InputFieldBloc<double, dynamic>(
    initialValue: 0.5,
  );

  AllFieldsFormBloc() : super(autoValidate: false) {
    addFieldBlocs(fieldBlocs: [
      text1,
      boolean1,
      boolean2,
      select1,
      select2,
      multiSelect1,
      date1,
      dateAndTime1,
      time1,
      double1,
    ]);
  }

  void addErrors() {
    text1.addFieldError('Awesome Error!');
    boolean1.addFieldError('Awesome Error!');
    boolean2.addFieldError('Awesome Error!');
    select1.addFieldError('Awesome Error!');
    select2.addFieldError('Awesome Error!');
    multiSelect1.addFieldError('Awesome Error!');
    date1.addFieldError('Awesome Error!');
    dateAndTime1.addFieldError('Awesome Error!');
    time1.addFieldError('Awesome Error!');
  }

  @override
  void onSubmitting() async {
    try {
      await Future<void>.delayed(const Duration(milliseconds: 500));

      emitSuccess(canSubmitAgain: true);
    } catch (e) {
      emitFailure();
    }
  }
}

class AllFieldsForm extends StatelessWidget {
  const AllFieldsForm({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => AllFieldsFormBloc(),
      child: Builder(
        builder: (context) {
          final formBloc = BlocProvider.of<AllFieldsFormBloc>(context);

          return Scaffold(
            appBar: AppBar(title: const Text('Built-in Widgets')),
            floatingActionButton: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                FloatingActionButton.extended(
                  heroTag: null,
                  onPressed: formBloc.addErrors,
                  icon: const Icon(Icons.error_outline),
                  label: const Text('ADD ERRORS'),
                ),
                const SizedBox(height: 12),
                FloatingActionButton.extended(
                  heroTag: null,
                  onPressed: formBloc.submit,
                  icon: const Icon(Icons.send),
                  label: const Text('SUBMIT'),
                ),
              ],
            ),
            body: FormBlocListener<AllFieldsFormBloc, String, String>(
              onSubmitting: (context, state) {
                LoadingDialog.show(context);
              },
              onSuccess: (context, state) {
                LoadingDialog.hide(context);

                Navigator.of(context).pushReplacement(
                    MaterialPageRoute(builder: (_) => const SuccessScreen()));
              },
              onFailure: (context, state) {
                LoadingDialog.hide(context);
                ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text(state.failureResponse!)));
              },
              child: ScrollableFormBlocManager(
                formBloc: formBloc,
                child: SingleChildScrollView(
                  physics: const ClampingScrollPhysics(),
                  padding: const EdgeInsets.all(24.0),
                  child: Column(
                    children: <Widget>[
                      TextFieldBlocBuilder(
                        textFieldBloc: formBloc.text1,
                        suffixButton: SuffixButton.obscureText,
                        decoration: const InputDecoration(
                          labelText: 'TextFieldBlocBuilder',
                          prefixIcon: Icon(Icons.text_fields),
                        ),
                      ),
                      RadioButtonGroupFieldBlocBuilder<String>(
                        selectFieldBloc: formBloc.select2,
                        decoration: const InputDecoration(
                          labelText: 'RadioButtonGroupFieldBlocBuilder',
                        ),
                        groupStyle: const FlexGroupStyle(),
                        itemBuilder: (context, item) => FieldItem(
                          child: Text(item),
                        ),
                      ),
                      CheckboxGroupFieldBlocBuilder<String>(
                        multiSelectFieldBloc: formBloc.multiSelect1,
                        decoration: const InputDecoration(
                          labelText: 'CheckboxGroupFieldBlocBuilder',
                        ),
                        groupStyle: const ListGroupStyle(
                          scrollDirection: Axis.horizontal,
                          height: 64,
                        ),
                        itemBuilder: (context, item) => FieldItem(
                          child: Text(item),
                        ),
                      ),
                      DateTimeFieldBlocBuilder(
                        dateTimeFieldBloc: formBloc.date1,
                        format: DateFormat('dd-MM-yyyy'),
                        initialDate: DateTime.now(),
                        firstDate: DateTime(1900),
                        lastDate: DateTime(2100),
                        decoration: const InputDecoration(
                          labelText: 'DateTimeFieldBlocBuilder',
                          prefixIcon: Icon(Icons.calendar_today),
                          helperText: 'Date',
                        ),
                      ),
                      DateTimeFieldBlocBuilder(
                        dateTimeFieldBloc: formBloc.dateAndTime1,
                        canSelectTime: true,
                        format: DateFormat('dd-MM-yyyy  hh:mm'),
                        initialDate: DateTime.now(),
                        firstDate: DateTime(1900),
                        lastDate: DateTime(2100),
                        decoration: const InputDecoration(
                          labelText: 'DateTimeFieldBlocBuilder',
                          prefixIcon: Icon(Icons.date_range),
                          helperText: 'Date and Time',
                        ),
                      ),
                      TimeFieldBlocBuilder(
                        timeFieldBloc: formBloc.time1,
                        format: DateFormat('hh:mm a'),
                        initialTime: TimeOfDay.now(),
                        decoration: const InputDecoration(
                          labelText: 'TimeFieldBlocBuilder',
                          prefixIcon: Icon(Icons.access_time),
                        ),
                      ),
                      SwitchFieldBlocBuilder(
                        booleanFieldBloc: formBloc.boolean2,
                        body: const Text('SwitchFieldBlocBuilder'),
                      ),
                      DropdownFieldBlocBuilder<String>(
                        selectFieldBloc: formBloc.select1,
                        decoration: const InputDecoration(
                          labelText: 'DropdownFieldBlocBuilder',
                        ),
                        itemBuilder: (context, value) => FieldItem(
                          isEnabled: value != 'Option 1',
                          child: Text(value),
                        ),
                      ),
                      Row(
                        children: [
                          IconButton(
                            onPressed: () => formBloc.addFieldBloc(
                                fieldBloc: formBloc.select1),
                            icon: const Icon(Icons.add),
                          ),
                          IconButton(
                            onPressed: () => formBloc.removeFieldBloc(
                                fieldBloc: formBloc.select1),
                            icon: const Icon(Icons.delete),
                          ),
                        ],
                      ),
                      CheckboxFieldBlocBuilder(
                        booleanFieldBloc: formBloc.boolean1,
                        body: const Text('CheckboxFieldBlocBuilder'),
                      ),
                      CheckboxFieldBlocBuilder(
                        booleanFieldBloc: formBloc.boolean1,
                        body: const Text('CheckboxFieldBlocBuilder trailing'),
                        controlAffinity:
                            FieldBlocBuilderControlAffinity.trailing,
                      ),
                      SliderFieldBlocBuilder(
                        inputFieldBloc: formBloc.double1,
                        divisions: 10,
                        labelBuilder: (context, value) =>
                            value.toStringAsFixed(2),
                      ),
                      SliderFieldBlocBuilder(
                        inputFieldBloc: formBloc.double1,
                        divisions: 10,
                        labelBuilder: (context, value) =>
                            value.toStringAsFixed(2),
                        activeColor: Colors.red,
                        inactiveColor: Colors.green,
                      ),
                      SliderFieldBlocBuilder(
                        inputFieldBloc: formBloc.double1,
                        divisions: 10,
                        labelBuilder: (context, value) =>
                            value.toStringAsFixed(2),
                      ),
                      ChoiceChipFieldBlocBuilder<String>(
                        selectFieldBloc: formBloc.select2,
                        itemBuilder: (context, value) => ChipFieldItem(
                          label: Text(value),
                        ),
                      ),
                      FilterChipFieldBlocBuilder<String>(
                        multiSelectFieldBloc: formBloc.multiSelect1,
                        itemBuilder: (context, value) => ChipFieldItem(
                          label: Text(value),
                        ),
                      ),
                      BlocBuilder<InputFieldBloc<File?, String>,
                              InputFieldBlocState<File?, String>>(
                          bloc: formBloc.file,
                          builder: (context, state) {
                            return Container();
                          })
                    ],
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

class LoadingDialog extends StatelessWidget {
  static void show(BuildContext context, {Key? key}) => showDialog<void>(
        context: context,
        useRootNavigator: false,
        barrierDismissible: false,
        builder: (_) => LoadingDialog(key: key),
      ).then((_) => FocusScope.of(context).requestFocus(FocusNode()));

  static void hide(BuildContext context) => Navigator.pop(context);

  const LoadingDialog({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async => false,
      child: Center(
        child: Card(
          child: Container(
            width: 80,
            height: 80,
            padding: const EdgeInsets.all(12.0),
            child: const CircularProgressIndicator(),
          ),
        ),
      ),
    );
  }
}

class SuccessScreen extends StatelessWidget {
  const SuccessScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Icon(Icons.tag_faces, size: 100),
            const SizedBox(height: 10),
            const Text(
              'Success',
              style: TextStyle(fontSize: 54, color: Colors.black),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 10),
            ElevatedButton.icon(
              onPressed: () => Navigator.of(context).pushReplacement(
                  MaterialPageRoute(builder: (_) => const AllFieldsForm())),
              icon: const Icon(Icons.replay),
              label: const Text('AGAIN'),
            ),
          ],
        ),
      ),
    );
  }
}

Download Details:

Author: GiancarloCode
Source Code: https://github.com/GiancarloCode/form_bloc 
License: MIT

#flutter #dart #bloc 

Flutter_form_bloc: Create Beautiful forms in Flutter
Hunter  Krajcik

Hunter Krajcik

1662017580

Flutter Package That Simplifies injection & Usage Of Bloc/Cubit

Hooked Bloc

Flutter package that simplifies injection and usage of Bloc/Cubit. The library is based on the concept of hooks originally introduced in React Native and adapted to Flutter. Flutter hooks allow you to extract view's logic into common use cases and reuse them, which makes writing widgets faster and easier.

Motivation

When you want to use Bloc/Cubit in your application you have to provide an instance of the object down the widgets tree for state receivers. This is mostly achieved by BlocBuilder along with BlocProvider and enlarges complexity of the given widget.

Each time you have to use BlocBuilder, BlocListener or BlocSelector. What if we could use the power of Flutter hooks?

So, instead of this:

  @override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: ...,
    body: BlocProvider<RealLifeCubit>(
      create: (context) =>
      RealLifeCubit()
        ..loadData(),
      child: BlocListener<RealLifeCubit, hooked.BuildState>(
        listenWhen: (_, state) => state is ErrorState,
        listener: (context, state) {
          // Show some view on event
        },
        child: BlocBuilder<RealLifeCubit, hooked.BuildState>(
          buildWhen: (_, state) =>
              [LoadedState, LoadingState, ShowItemState]
                  .contains(state.runtimeType),
          builder: (BuildContext context, hooked.BuildState state) {
            return // Build your widget using `state`
          },
        ),
      ),
    ),
  );
}

We can have this:

  @override
Widget build(BuildContext context) {
  final cubit = useBloc<RealLifeCubit>();

  useBlocListener<RealLifeCubit, BuildState>(cubit, (cubit, value, context) {
    // Show some view on event
  }, listenWhen: (state) => state is ErrorState);

  final state = useBlocBuilder(
    cubit,
    buildWhen: (state) =>
        [LoadedState, LoadingState, ShowItemState].contains(
          state.runtimeType,
        ),
  );

  return // Build your widget using `state`
}

This code is functionally equivalent to the previous example. It still rebuilds the widget in the proper way and the right time. Whole logic of finding adequate Cubit/Bloc and providing current state is hidden in useBloc and useBlocBuilder hooks.

Full example can be found in here

Setup

Install package

Run command:

flutter pub add hooked_bloc

Or manually add the dependency in the pubspec.yaml

dependencies:
  # Library already contains flutter_hooks package
  hooked_bloc:

After that you can (it's optional) initialize the HookedBloc:

void main() async {
  // With GetIt or Injectable
  await configureDependencies();

  runApp(
    HookedBlocConfigProvider(
      injector: () => getIt.get,
      builderCondition: (state) => state != null, // Global build condition
      listenerCondition: (state) => state != null, // Global listen condition
      child: const MyApp(),
    )
  );

  // Or you can omit HookedBlocInjector(...)
  // and allow library to find the cubit in the widget tree
}

Then you can simply start writing your widget with hooks

// Remember to inherit from HookWidget
class MyApp extends HookWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // At start obtain a cubit instance
    final cubit = useBloc<CounterCubit>();
    // Then observe state's updates
    // `buildWhen` param will override builderCondition locally
    final state = useBlocBuilder(cubit, buildWhen: (state) => state <= 10);
    // Create a listener for the side-effect
    useBlocListener(cubit, (cubit, value, context) {
      ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text("Button clicked"),
          ));
    });

    // Build widget's tree without BlocProvider
    return MaterialApp(
      home: Scaffold(
        floatingActionButton: FloatingActionButton(
          onPressed: () => cubit.increment(), // Access cubit in tree
          child: const Icon(Icons.add),
        ),
        // Consume state without BlocBuilder
        body: Center(child: Text("The button has been pressed $state times")),
      ),
    );
  }
}

Basics

Existing hooks

Hooked Bloc already comes with a few reusable hooks:

NameDescription
useBlocReturns required Cubit/Bloc
useBlocFactoryReturns desired Cubit/Bloc by creating it with provided factory
useBlocBuilderReturns current Cubit/Bloc state - similar to BlocBuilder
useBlocListenerInvokes callback - similar to BlocListener
useActionListenerInvokes callback, but independent of Bloc/Cubit state

useBloc

useBloc hook tries to find Cubit using the cubit provider, or - if not specified - looks into the widget tree.

  @override
Widget build(BuildContext context) {
  // The hook will provide the expected object
  final cubit = useBloc<SimpleCubit>(
    // For default hook automatically closes cubit
    closeOnDispose: true,
  );

  return // Access provided cubit
}

useBlocFactory

useBlocFactory hook tries to find factory using provided injection method and then returns cubit created by it

class SimpleCubitFactory extends BlocFactory<SimpleCubit> {
  bool _value = true;

  @override
  SimpleCubit create() {
    return _value ? SimpleCubitA() : SimpleCubitB();
  }

  // This is example method which you can add to configure your cubit creation on run
  void configure(bool value) {
    _value = value;
  }
}

@override
Widget build(BuildContext context) {
  // The hook will provide the expected object
  final cubit = useBlocFactory<SimpleCubit, SimpleCubitFactory>(
    onCubitCreate: (cubitFactory) {
      cubitFactory.configure(false);
    }
  );

  return // Access provided cubit
}

useBlocBuilder

useBlocBuilder hook rebuilds the widget when new state appears


final CounterCubit cubit = CounterCubit("My cubit");

@override
Widget build(BuildContext context) {
  // The state will be updated along with the widget
  // For default the state will be updated basing on `builderCondition`
  final int state = useBlocBuilder(cubit);

  return // Access provided state
}

useBlocListener

useBlocListener hook allows to observe cubit's states that represent action (e.g. show Snackbar)


final EventCubit cubit = EventCubit();

@override
Widget build(BuildContext context) {
  // Handle state as event independently of the view state
  useBlocListener(cubit, (_, value, context) {
    _showMessage(context, (value as ShowMessage).message);
  }, listenWhen: (state) => state is ShowMessage);

  return // Build your widget
}

useActionListener

useActionListener hook is similar to the useBlocListener but listens to the stream different than state's stream and can be used for actions that require a different flow of notifying.

Because of that your bloc/cubit must use BlocActionMixin

class MessageActionCubit extends EventCubit with BlocActionMixin<String, BuildState> {

  // The method used to publish events
  @override
  void dispatch(String action) {
    super.dispatch(action);
  }
}

Then, consume results as you would do with useBlocListener

  @override
Widget build(BuildContext context) {
  // Handle separate action stream with values other than a state type
  useActionListener(cubit, (String action) {
    _showMessage(context, action);
  });

  return // Build your widget
}

Instead of BlocActionMixin you can use one of our classes: ActionCubit or ActionBloc

class MessageActionCubit extends ActionCubit<BuildState, String>  {
  // The method used to publish events
  @override
  void dispatch(String action) {
    super.dispatch(action);
  }
}
class MessageActionBloc extends ActionBloc<BuildState, BlocEvent, String>  {
  // The method used to publish events
  @override
  void dispatch(String action) {
    super.dispatch(action);
  }
}

Contribution

We accept any contribution to the project!

Suggestions of a new feature or fix should be created via pull-request or issue.

feature request:

Check if feature is already addressed or declined

Describe why this is needed

Just create an issue with label enhancement and descriptive title. Then, provide a description and/or example code. This will help the community to understand the need for it.

Write tests for your hook

The test is the best way to explain how the proposed hook should work. We demand a complete test before any code is merged in order to ensure cohesion with existing codebase.

Add it to the README and write documentation for it

Add a new hook to the existing hooks table and append sample code with usage.

Fix

Check if bug was already found

Describe what is broken

The minimum requirement to report a bug fix is a reproduction path. Write steps that should be followed to find a problem in code. Perfect situation is when you give full description why some code doesn't work and a solution code.

Write tests for your hook

The test should show that your fix corrects the problem. You can start with straightforward test and then think about potential edge cases or other places that can be broken.

Add it to the README and write documentation for it

If your fix changed behavior of the library or requires any other extra steps from user, this should be fully described in README.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add hooked_bloc

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  hooked_bloc: ^1.2.0

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:hooked_bloc/hooked_bloc.dart';

example/lib/main.dart

import 'package:example/di/injector.dart';
import 'package:example/page/home_page.dart';
import 'package:flutter/material.dart';
import 'package:hooked_bloc/hooked_bloc.dart';

void main() async {
  // With GetIt or Injectable
  await configureDependencies();

  runApp(
    HookedBlocConfigProvider(
      injector: () => getIt.get,
      builderCondition: (state) => state != null, // Global build condition
      listenerCondition: (state) => state != null, // Global listen condition
      child: const MyApp(),
    ),
  );
}

// Quickstart example counter page
// Remember to inherit from HookWidget
// class MyApp extends HookWidget {
//   const MyApp({Key? key}) : super(key: key);
//
//   @override
//   Widget build(BuildContext context) {
//     // At start obtain a cubit instance
//     final cubit = useCubit<CounterCubit>();
//     // Then observe state's updates
//     //`buildWhen` param will override builderCondition locally
//     final state = useCubitBuilder(cubit, buildWhen: (state) => state <= 10);
//     // Create a listener for the side-effect
//     useCubitListener(cubit, (cubit, value, context) {
//       print("Button clicked");
//     });
//
//     // Build widget's tree without BlocProvider
//     return MaterialApp(
//       home: Scaffold(
//         floatingActionButton: FloatingActionButton(
//           onPressed: () => cubit.increment(), // Access cubit in tree
//           child: const Icon(Icons.add),
//         ),
//         // Consume state without BlocBuilder
//         body: Center(child: Text("The button has been pressed $state times")),
//       ),
//     );
//   }
// }

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  final appName = "Hooked Bloc";

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: appName,
      theme: ThemeData(primarySwatch: Colors.blue),
      home: HomePage(title: appName),
    );
  }
}

Download Details:

Author: Iteo
Source Code: https://github.com/Iteo/hooked_bloc 
License: MIT license

#flutter #dart #bloc 

Flutter Package That Simplifies injection & Usage Of Bloc/Cubit

A Handy Bloc Base for Future Appixi's Apps

knt_bloc

A handy bloc base for future Appixi's apps

Getting Started

This project is a starting point for a Dart package, a library module containing code that can be shared easily across multiple Flutter or Dart projects.

For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add knt_bloc

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  knt_bloc: ^2.1.3

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:knt_bloc/knt_bloc.dart'; 

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:knt_bloc/knt_bloc.dart';

import 'bloc/book_bloc.dart';
import 'page/main_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => BookBloc(),
      child: MaterialApp(
        title: 'KntBloc Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MainPage(),
      ),
    );
  }
} 

Download Details:

Author: 

Source Code: https://pub.dev/packages/knt_bloc

#bloc #dart #flutter 

A Handy Bloc Base for Future Appixi's Apps
Mike  Kozey

Mike Kozey

1661408541

Bloc_services: Package Has Implementations for Ease Of Stream Handling

bloc_services

A package which has useful implementations of streams for bloc considered with the clean architecture way and is fully tested

Features

  • Written for the usage with Clean Architecture
  • Provides Stream mixins when can be intergrated with blocs/cubits
  • Uses Either from dartz to get the proper stream data.

Getting Started

Add to the pubspec dependencies

dependencies:
  bloc_services: <latest-version>
  flutter_bloc: <latest-version>

Usage

Check the full example in the example directory on how to use it effectively with bloc.

class ExampleBloc
    extends Bloc<ExampleEvent, ExampleState>
    with
        MultipleStreamMixin{
  MultipleStreamExampleDartBloc() : super(MultipleStreamExampleInitial());

  @override
  Stream<MultipleStreamExampleState> mapEventToState(
    MultipleStreamExampleEvent event,
  ) async* {}

  @override
  Map<Object, StreamData<Object, Object>> get streams => {};

}

Mix your bloc with the MultipleStreamMixin when mixed it has an override called streams

It's a Map<Object, StreamData<Object, Object>> where your can pass a unique key (any object, check the example directory for a detailed implementation) and a StreamData which has a field stream

  • stream has the return Type of Either<L,R> where the L is considered as an error and the R is considered as a valid data
  • StreamData has 2 Type Parameters <L,R> these types will be used as the return type of the field stream
  • when ever a stream emits R of the either a function name onStreamData will be called which has 2 params one is the key which was given in the streams map in the override and the other is the data of the R;
  • onStreamError is called whenever the L of the either is called

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add bloc_services

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  bloc_services: ^0.0.6

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:bloc_services/bloc_services.dart';

example/lib/main.dart

import 'package:example/features/stream_bloc_example/page/stream_bloc_page.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const StreamBlocPage(),
    );
  }
}

Download Details:

Author: Afsal102
Source Code: https://github.com/Afsal102/bloc_services 
License: MIT license

#flutter #dart #bloc #services 

Bloc_services: Package Has Implementations for Ease Of Stream Handling
Hunter  Krajcik

Hunter Krajcik

1661157125

GQL_bloc_gen: Bloc Statemanagment Created with Artemis

TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them.

Features

TODO: List what your package can do. Maybe include images, gifs, or videos.

Getting started

TODO: List prerequisites and provide or point to information on how to start using the package.

Usage

TODO: Include short and useful examples for package users. Add longer examples to /example folder.

const like = 'sample';

Additional information

TODO: Tell users more about the package: where to find more information, how to contribute to the package, how to file issues, what response they can expect from the package authors, and more.

Installing

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add gql_bloc_gen

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  gql_bloc_gen: ^0.0.2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:gql_bloc_gen/gql_bloc_gen.dart';

Original article source at: https://pub.dev/packages/gql_bloc_gen 

#flutter #dart #bloc 

GQL_bloc_gen: Bloc Statemanagment Created with Artemis
Lawson  Wehner

Lawson Wehner

1660968060

Helper Blocs for Handling in-app-purchase & Admob Flow

knt_monetize_bloc

Helper blocs for handling in-app-purchase & admob flow.

Getting Started

Config your admob ID

Config your in-app-purchase

Recommends using get_it & injectable for dependencies injection.

  • Refer section Registering third party types for create instances of BLoC & service classes in this package.
  • These Bloc & service need to be singleton (refer @singleton annotation with injectable).

BLoC pattern knowledge, refer library fluter_bloc.

Use fluter_bloc library:

  • Create BlocProvider at application level for this package's blocs.
  • Use BlocBuilder & BlocListener for handling state from these blocs.

If your Android application enable Proguard rule, add these:

     #In app Purchase
     -keep class com.amazon.** {*;}
     -keep class com.dooboolab.** { *; }
     -keep class com.android.vending.billing.**
     -dontwarn com.amazon.**
     -keepattributes *Annotation*

Common flow

For Admob:

Step 1: Create instances of AdmobConfig & AdmobService & FrequentlyAdsBloc and/or StaticAdsBloc.

Step 2: At main() top-level function, initialize MobileAd by inserting this code:

       Future<void> main() async {
         WidgetsFlutterBinding.ensureInitialized();
         await getIt<AdmobService>().init();
         runApp(App());
       }

Step 3: Assume that SplashPage is your app's first page, then insert these code inside initState():

      Future.delayed(Duration.zero, () {
          context.read<FrequentlyAdsBloc>().add(PrepareFrequentlyAdEvent());
      }

Step 4: Whenever you need to show InterstitialAd, just call:

      context.read<FrequentlyAdsBloc>().add(ShowFrequentlyAdEvent(_adTag));

(I recommend create a unique _adTag to identify which screen had requested ad unit. This is helpful when handle state with BlocListener.)

For in-app-purchase

  • Step 1: Create instances of IapConfig & IapService & SubscriptionBloc.
  • Step 2: Create PremiumPage which shows UI for premium-feature, contains SKU list / restore purchase option / term & privacy.
  • Step 3:
    • For showing SKU list: Using SubscriptionBloc#FetchListSkuEvent.
    • For request subscription on SKU item: Using SubscriptionBloc#RequestSubscriptionEvent.
    • For request restore purchase: Using SubscriptionBloc#RestoreSubscriptionEvent.
  • Step 4 (Optional): Create instances of FreeUsageCounterBloc to handle user's free usage flow.

Note


This project is a starting point for a Dart package, a library module containing code that can be shared easily across multiple Flutter or Dart projects.

For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add knt_monetize_bloc

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  knt_monetize_bloc: ^3.0.0

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:knt_monetize_bloc/knt_monetize_bloc.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:knt_bloc/knt_bloc.dart';
import 'package:knt_monetize_bloc/knt_monetize_bloc.dart';

import 'di.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp(bloc: await adsBloc));
}

class MyApp extends StatelessWidget {
  final FrequentlyAdsBloc bloc;

  const MyApp({
    Key? key,
    required this.bloc,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => bloc,
      child: MaterialApp(
        title: 'Knt Monetized Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const PageOne(),
      ),
    );
  }
}

class PageOne extends StatefulWidget {
  const PageOne({Key? key}) : super(key: key);

  @override
  State<PageOne> createState() => _PageOneState();
}

class _PageOneState extends State<PageOne> {
  bool _loading = false;

  @override
  void initState() {
    super.initState();
    Future.delayed(Duration.zero, () {
      context.read<FrequentlyAdsBloc>().add(PrepareFrequentlyAdEvent());
    });
  }

  void _onPressed() {
    setState(() {
      _loading = true;
    });
    Future.delayed(
      const Duration(milliseconds: 1500),
      () {
        if (!mounted) {
          return;
        }
        context
            .read<FrequentlyAdsBloc>()
            .add(const ShowFrequentlyAdEvent('PageOne'));
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return BlocListener<FrequentlyAdsBloc, BaseState>(
      listener: (context, state) {
        if (!mounted) {
          return;
        }
        if (state is ClosedFrequentlyAdState) {
          setState(() {
            _loading = false;
          });
          PageTwo.push(context);
        }
      },
      child: Scaffold(
        body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.orange,
          child: Center(
            child: _loading
                ? const CircularProgressIndicator(
                    color: Colors.white,
                  )
                : ElevatedButton(
                    onPressed: _onPressed,
                    child: const Text(
                      'Page 1, move to Page 2',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 16.0,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
          ),
        ),
      ),
    );
  }
}

class PageTwo extends StatefulWidget {
  static void push(BuildContext context) {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => const PageTwo()),
    );
  }

  const PageTwo({Key? key}) : super(key: key);

  @override
  State<PageTwo> createState() => _PageTwoState();
}

class _PageTwoState extends State<PageTwo> {
  bool _loading = false;

  @override
  void initState() {
    super.initState();
    Future.delayed(Duration.zero, () {
      context.read<FrequentlyAdsBloc>().add(PrepareFrequentlyAdEvent());
    });
  }

  void _onPressed() {
    setState(() {
      _loading = true;
    });
    Future.delayed(
      const Duration(milliseconds: 1500),
      () {
        if (!mounted) {
          return;
        }
        context
            .read<FrequentlyAdsBloc>()
            .add(const ShowFrequentlyAdEvent('PageTwo'));
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return BlocListener<FrequentlyAdsBloc, BaseState>(
      listener: (context, state) {
        if (!mounted) {
          return;
        }
        if (state is ClosedFrequentlyAdState) {
          setState(() {
            _loading = false;
          });
          PageThree.push(context);
        }
      },
      child: Scaffold(
        body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.blue,
          child: Center(
            child: _loading
                ? const CircularProgressIndicator(
                    color: Colors.white,
                  )
                : ElevatedButton(
                    onPressed: _onPressed,
                    child: const Text(
                      'Page 2, move to Page 3',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 16.0,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
          ),
        ),
      ),
    );
  }
}

class PageThree extends StatelessWidget {
  static void push(BuildContext context) {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => const PageThree()),
    );
  }

  const PageThree({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        height: double.infinity,
        color: Colors.purple,
        child: Center(
          child: ElevatedButton(
            onPressed: () {
              Navigator.of(context).popUntil((route) => route.isFirst);
            },
            child: const Text(
              'Page 3, Pop Till Page 1',
              style: TextStyle(
                color: Colors.white,
                fontSize: 16.0,
                fontWeight: FontWeight.w600,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Original article source at: https://pub.dev/packages/knt_monetize_bloc 

#flutter #dart #bloc 

Helper Blocs for Handling in-app-purchase & Admob Flow
Rocio  O'Keefe

Rocio O'Keefe

1660638480

Riverpod_bloc_stream: BlocStreamProvider for Riverpod

riverpod_bloc_stream

A BlocStreamProvider for the riverpod package.

Usage

A simple usage example:

import 'package:bloc_stream/bloc_stream.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_bloc_stream/riverpod_bloc_stream.dart';

// Define our actions, state and bloc.
typedef CounterAction = void Function(CounterBloc, void Function(int));

CounterAction increment() => (b, add) => add(b.value + 1);
CounterAction decrement() => (b, add) => add(b.value - 1);

class CounterBloc extends BlocStream<int> {
  CounterBloc() : super(0);
}

// Create the provider
final counterBloc = BlocStreamProvider((ref) => CounterBloc());

void main() async {
  final container = ProviderContainer();
  // Access the bloc directly
  final bloc = container.read(counterBloc);

  bloc.add(increment());
  bloc.add(increment());

  await Future.delayed(Duration());

  // Or access the bloc's value
  // Prints '2'
  print(container.read(counterBloc.value));

  container.dispose();
}

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add riverpod_bloc_stream

With Flutter:

 $ flutter pub add riverpod_bloc_stream

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  riverpod_bloc_stream: ^7.0.3

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:riverpod_bloc_stream/riverpod_bloc_stream.dart';

example/riverpod_bloc_stream_example.dart

import 'package:riverpod/riverpod.dart';
import 'package:riverpod_bloc_stream/riverpod_bloc_stream.dart';

typedef CounterAction = BlocStreamAction<int>;
CounterAction increment() => (count, add) => add(count + 1);
CounterAction decrement() => (count, add) => add(count - 1);

class CounterBloc extends BlocStream<int> {
  CounterBloc() : super(0);
}

final counter = BlocStreamProvider<CounterBloc, int>((ref) => CounterBloc());

void main() async {
  final container = ProviderContainer();
  final bloc = container.read(counter.bloc);

  bloc.add(increment());
  bloc.add(increment());

  await Future.delayed(Duration());

  // Prints '2'
  print(container.read(counter));

  container.dispose();
}

Original article source at: https://pub.dev/packages/riverpod_bloc_stream 

#flutter #dart #bloc #stream 

Riverpod_bloc_stream: BlocStreamProvider for Riverpod
Hunter  Krajcik

Hunter Krajcik

1660379220

Artemis_bloc_gen: Generate Bloc for Artimis Generated Classes

Artemis_bloc_gen

Generate Bloc for Artimis Generated Classes

Installing

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add artemis_bloc_gen

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  artemis_bloc_gen: ^0.0.10

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:artemis_bloc_gen/artemis_bloc_gen.dart';

Original article source at: https://pub.dev/packages/artemis_bloc_gen 

#flutter #dart #bloc 

Artemis_bloc_gen: Generate Bloc for Artimis Generated Classes
Rocio  O'Keefe

Rocio O'Keefe

1659801120

Search_bar_bloc: A Flutter Package for Creating A SearchBar

SearchBar with Bloc

This package a SearchBar with Bloc state management.

The purpose of this package is to give an easy way to implement a search bar in a stateless widget.

Features

The composition of the search bar is customizable and more parameters will be handled in the future.

Getting started

If you're not familiar with bloc state management and the flutter_bloc package here are the resources you need :

Usage

Here is a quick example of how to implement the search bar with a BlocProvider.

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => SearchBarCubit(),
      child: Column(
        children: [
          const SearchBar(hintText: "Search something..."),
          BlocBuilder<SearchBarCubit, SearchBarState>(
              buildWhen: (previous, current) => previous.content != current.content,
              builder: (context, state) {
                return Text("You are searching : ${state.content}");
              }),
        ],
      ),
    );
  }

Installing

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add search_bar_bloc

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  search_bar_bloc: ^1.0.1

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:search_bar_bloc/search_bar_bloc.dart';

example/README.md

Bloc Provider Example

Here is a quick example of how to implement the search bar with a BlocProvider.

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => SearchBarCubit(),
      child: Column(
        children: [
          const SearchBar(hintText: "Search something..."),
          BlocBuilder<SearchBarCubit, SearchBarState>(
              buildWhen: (previous, current) => previous.content != current.content,
              builder: (context, state) {
                return Text("You are searching : ${state.content}");
              }),
        ],
      ),
    );
  }

Bloc Provider Example

Here is a quick example of how to implement the search bar with a BlocListener.

class BlockBuilderExampleWidget extends StatelessWidget {
  const BlockBuilderExampleWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocListener<SearchBarCubit, SearchBarState>(
      listenWhen: (previous, current) => previous.content != current.content,
      listener: (context, state) {
        print("You are searching : ${state.content}");
      },
      child: Container(),
    );
  }

}

Author: B0berman
Source Code: https://github.com/B0berman/search_bar_bloc 
License: BSD-3-Clause license

#flutter #dart #bloc #search 

Search_bar_bloc: A Flutter Package for Creating A SearchBar

A Easy and Simple Implementation Of The BLoC Pattern in Flutter/Dart

Bloc Implementation

Github: https://github.com/Jules-Media/Bloc_Implementation
Pub.dev: https://pub.dev/packages/bloc_implementation

Features #

This Package helps you implement the Bloc Pattern.
The BLoC (Buisness Logic Component) Pattern is a Pattern recommmended by Google Developers to use in your Flutter Projects. The BLoC Pattern uses Streams and Sinks to exchange Information between two Widgets, that have no other Connection. This is especially useful if you extract your widgets and as a consequence have a ton of widgets for a single Screen.

Getting started

Get the package and your ready to start. It's that easy

Usage

The BlocParent Class is something like the BlocProvider in other Packages. The Bloc is an abtract class / interface you can implement into your Blocs.

Example:

final _bloc = Bloc();

Additional information

This Package helps you implement the Bloc Pattern used in Flutter. Bloc stands for Buisness Logic Component

© Julian Schumacher 2022

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add bloc_implementation

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  bloc_implementation: ^1.1.3

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:bloc_implementation/bloc_implementation.dart'; 

example/main.dart

library bloc_implementation;

import 'package:bloc_implementation/bloc_implementation.dart' show BlocParent;

import 'package:flutter/material.dart';

import 'blocs/main_bloc.dart';
import 'screens/homescreen.dart';

void main(List<String> args) {
  runApp(const ExampleApp());
}

class ExampleApp extends StatefulWidget {
  const ExampleApp({Key? key}) : super(key: key);

  @override
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  @override
  Widget build(BuildContext context) {
    return BlocParent(
      bloc: MainBloc(),
      child: const MaterialApp(
        home: Homescreen(),
      ),
    );
  }
} 

Download Details:

Author: Jules-Media

Source Code: https://github.com/Jules-Media/Bloc_Implementation

#flutter #dart #bloc 

A Easy and Simple Implementation Of The BLoC Pattern in Flutter/Dart
Mike  Kozey

Mike Kozey

1657524780

A Library That Aims to Make It Simple To Use The BLoC Design Pattern

bloc_small

Getting Started

A library that aims to make it simple to use the BLoC design pattern to separate presentation code from business logic and state. While this library aims to make the project 100% compliant with the BLoC pattern, it also acknowledges that 100% compliance is not always advisable or necessary. This library has been customized to make BloC easier to understand, avoid useless function calls, and focus on solving logic.

How to use

class CounterBloc {
  var _counter = Bloc<int>.broadcast(initialValue: 0);

  void incrementCounter() {
    _counter.add(_counter.value + 1);
  }

  void decrementCounter() {
    if (_counter.value > 0) _counter.add(_counter.value - 1);
  }

  void dispose() {
    _counter.dispose();
  }
}

The bloc can now be used elsewhere in the project:

  final bloc = CounterBloc();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    bloc.dispose();
  }

wrap it with StreamBuilder to listen for changed events and return data:

  StreamBuilder<int>(
                initialData: bloc._counter.value,
                stream: bloc._counter.stream,
                builder: (context, snapshot) {
                  return Text(
                    '${snapshot.data}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                }),

  floatingActionButton: Wrap(
    spacing: 5,
    children: [
      FloatingActionButton(
        onPressed: bloc.incrementCounter, //__counter + 1
        tooltip: 'Increment',
      child: Icon(Icons.add),
    ),
      FloatingActionButton(
        onPressed: bloc.decrementCounter, //__counter - 1
        tooltip: 'decrement',
        child: Icon(Icons.remove),
    ),
  ],
), //

Installing

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add bloc_small

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  bloc_small: ^0.0.3

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:bloc_small/bloc_small.dart';

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:bloc_small/bloc_small.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class CounterBloc {
  var _counter = Bloc<int>.broadcast(initialValue: 0);

  void incrementCounter() {
    _counter.add(_counter.value + 1);
  }

  void decrementCounter() {
    if (_counter.value > 0) _counter.add(_counter.value - 1);
  }

  void dispose() {
    _counter.dispose();
  }
}

class _MyHomePageState extends State<MyHomePage> {
  final bloc = CounterBloc();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    bloc.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            StreamBuilder<int>(
                initialData: bloc._counter.value,
                stream: bloc._counter.stream,
                builder: (context, snapshot) {
                  return Text(
                    '${snapshot.data}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                }),
          ],
        ),
      ),
      floatingActionButton: Wrap(
        spacing: 5,
        children: [
          FloatingActionButton(
            onPressed: bloc.incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
          FloatingActionButton(
            onPressed: bloc.decrementCounter,
            tooltip: 'decrement',
            child: Icon(Icons.remove),
          ),
        ],
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Original article source at: https://pub.dev/packages/bloc_small

#flutter #dart #bloc 

A Library That Aims to Make It Simple To Use The BLoC Design Pattern

Una Guía Completa De La Arquitectura BLoC De Flutter

La arquitectura BLoC ha existido durante algún tiempo en la comunidad de Flutter, y podemos decir con seguridad que a la comunidad le encanta. La forma en que el patrón nos permite aislar nuestra lógica y hacerla más comprobable es simplemente haciendo algunos cambios en nuestra aplicación, es algo bastante impresionante.

En este tutorial, aprenderemos el concepto de BLoC y su flujo general mediante la creación de una aplicación simple y, al final de este tutorial, ya deberíamos poder escribir aplicaciones que implementen la arquitectura BLoC.

La versión de BLoC que vamos a utilizar en este tutorial es 8.0.1 (la última versión en el momento de escribir este artículo)

Algunas teorías sobre el bloque

Hay tres componentes principales de la arquitectura BLoC:

  1. Estados
    Los estados son todas las condiciones en las que se encuentra nuestra página o aplicación. Por ejemplo, si obtenemos la API de una página, nuestra página tendrá al menos tres estados:
  • LoadingState
    En este estado, es posible que deseemos mostrar un cargador en nuestra página para que el usuario comprenda que estamos tratando de obtener algunos datos.
  • ErrorFetchDataState
    En este estado, es posible que deseemos mostrar un texto de error, un botón de reintento o mostrar una alerta que informe al usuario que algo anda mal con nuestra llamada de red.
  • SuccessFetchDataState
    En este estado, podemos mostrar una lista de tarjetas o datos, en función de los datos que obtengamos de la respuesta.
  1. Eventos Los
    eventos son acciones que envía la interfaz de usuario para mutar el estado de nuestra página (¿suena familiar, desarrolladores de redux?)
  2. BloC
    BloC actúa como manejadores de acciones y mutadores de estado, también podemos hacer nuestra lógica comercial aquí, por ejemplo, obtener API, almacenar preferencias compartidas, etc. Algunas personas los llaman reductores.

A juzgar por sus teorías conceptuales, BLoC es más o menos lo mismo que redux o la arquitectura componible, por lo que si entiende uno de ellos, ya ha entendido BLoC.

Codifiquemos

Navegue hasta el editor de código de su elección y cree un nuevo proyecto de Flutter con cualquier nombre que se le ocurra. Luego, después de que Flutter termine de crear el repetitivo para nosotros, agregue flutter_blocla dependencia de pubspec.yamlmanera similar.


dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.1
  equatable: ^2.0.3

Reemplace todo en el main.dartcon este código

import 'package:flutter/material.dart';

void main() {
  runApp(const HomeSreen());
}

class HomeSreen extends StatefulWidget {
  const HomeSreen({Key? key}) : super(key: key);

  @override
  State<HomeSreen> createState() => _HomeSreenState();
}

class _HomeSreenState extends State<HomeSreen> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("Cool App")),
        body: Center(
          child: ElevatedButton(
            child: const Text("Fetch Data"),
            onPressed: () {},
          ),
        ),
      ),
    );
  }
}

En el código anterior, creamos un widget de pantalla de inicio con un botón en el centro de la página. Muy claro.

Lo que haremos a continuación es agregar varias funcionalidades a nuestra aplicación, nuestra aplicación final podría mostrar una lista de tarjetas de comida o un mensaje de error dependiendo de la respuesta que obtengamos de nuestra API.

En aras de la simplicidad, no vamos a llegar a ningún punto final real, solo vamos a seleccionar aleatoriamente el resultado cada vez que el usuario presione el botón.

Cree un nuevo archivo llamado food.dart, este archivo contendrá el modelo de nuestra tarjeta de alimentos.

class Food {
  final String id;
  final String name;
  final String thumbnailURL;
  final String price;

  Food({
    required this.id,
    required this.name,
    required this.thumbnailURL,
    required this.price,
  });
}

Cree otro archivo nuevo llamado food_generator.dart, este archivo nos proporcionará todos los datos ficticios que se muestran en la aplicación. Aquí está el código:

import 'package:bloctorial/food.dart';

class FoodGenerator {
  static List<Food> generateDummyFoods() {
    return [
      Food(
        id: "1",
        name: "Nasi Goreng",
        thumbnailURL:
            "https://asset.kompas.com/crops/MrdYDsxogO0J3wGkWCaGLn2RHVc=/84x60:882x592/750x500/data/photo/2021/11/17/61949959e07d3.jpg",
        price: "Rp18.000",
      ),
      Food(
        id: "2",
        name: "Sate Ayam",
        thumbnailURL:
            "https://photos.smugmug.com/Indonesia-2016/i-9hLVhWh/0/X3/indonesian-sate-ayam-1-X3.jpg",
        price: "Rp20.000",
      ),
      Food(
        id: "3",
        name: "Ayam Bakar Taliwang",
        thumbnailURL:
            "https://photos.smugmug.com/Indonesia-2016/i-Q4M8Pkt/0/X3/ayam-taliwang-jakarta-1-X3.jpg",
        price: "R25.000",
      ),
      Food(
        id: "4",
        name: "Bebek Goreng",
        thumbnailURL:
            "https://photos.smugmug.com/City-Guides/i-bMfqjcX/0/X3/jakarta-travel-guide-21-X3.jpg",
        price: "Rp18.000",
      ),
      Food(
        id: "5",
        name: "Bakso",
        thumbnailURL:
            "https://photos.smugmug.com/Indonesia-2016/i-vG378LH/0/X3/indonesian-food-14-X3.jpg",
        price: "Rp17.000",
      ),
    ];
  }
}

A continuación, procedamos a nuestro primer código relacionado con BLoC, aquí vamos a crear las clases para nuestro estado y eventos.

Cree un nuevo archivo llamado home_state.darty agregue el siguiente código dentro del archivo:

import 'package:bloctorial/food.dart';
import 'package:equatable/equatable.dart';

abstract class HomeState extends Equatable {
  const HomeState();
}

class HomeInitialState extends HomeState {
  const HomeInitialState();

  @override
  List<Object?> get props => [];
}

class HomeLoadingState extends HomeState {
  const HomeLoadingState();

  @override
  List<Object?> get props => [];
}

class HomeErrorFetchDataState extends HomeState {
  final String errorMessage;
  const HomeErrorFetchDataState({
    required this.errorMessage,
  });

  @override
  List<Object?> get props => [];
}

class HomeSuccessFetchDataState extends HomeState {
  final List<Food> foods;
  const HomeSuccessFetchDataState({
    required this.foods,
  });

  @override
  List<Object?> get props => [];
}

Según los requisitos, nuestra aplicación solo puede hacer una cosa en este momento, que es intentar obtener datos cuando se presiona el botón. A partir de ahí, podemos imaginar que nuestra página debería tener al menos tres estados:

  1. HomeLoadingState
    Este es el estado cuando estamos esperando la respuesta de la API, es posible que queramos mostrar algún tipo de indicador de cargador o brillo para que el usuario sepa que todavía se está procesando.
  2. HomeErrorFetchDataState
    Aquí es cuando recibimos un error de la API y es posible que queramos mostrar una alerta o mostrar algunos widgets de error.
  3. HomeSuccessFetchDataState
    Aquí es cuando recibimos con éxito nuestros datos de alimentos de la API, es posible que deseemos mostrar una lista de tarjetas de alimentos aquí.

Además de los tres estados mencionados anteriormente, también tenemos que crear otro HomeInitialState. Este es el primer estado de la página ya que no queríamos mostrar el cargador cuando un usuario ve la página. Más sobre esto a continuación.

A continuación, debemos definir las acciones que la página puede enviar, puedes imaginar que las acciones son cualquier cosa que desencadena cambios de estado. Cree un nuevo archivo llamado home_event.darty agregue este código a continuación.

import 'package:equatable/equatable.dart';

abstract class HomeEvent extends Equatable {
  const HomeEvent();
}

class FetchDataEvent extends HomeEvent {
  @override
  List<Object?> get props => [];
}

Dado que nuestra página de inicio solo puede hacer una cosa en este momento, obtener datos, tenemos que crear este evento de obtención de datos como uno de nuestros eventos HomeEvent. Recuerde que enviar eventos es cómo nuestro widget le dice al BLoC que quiere hacer algo.

Entonces, por ejemplo, si desea acceder a los rastreadores cuando vemos la página, puede agregar otro evento aquí.

Finalmente, tenemos que crear una clase BloC, esta clase actúa como un controlador de acciones que hace toda la lógica comercial y devuelve el resultado a nuestra página a través de su construcción de estado. Cree un nuevo archivo llamado home_bloc.darty agregue el siguiente código:

import 'package:bloctorial/home_state_event.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class HomeBloc extends Bloc<HomeEvent, HomeState> {
  HomeBloc() : super(const HomeInitialState()) {
    on<FetchDataEvent>(_onFetchDataEvent);
  }

  void _onFetchDataEvent(
    FetchDataEvent event,
    Emitter<HomeState> emitter,
  ) {}
}

home_bloc.dart

Nuestro HomeBlocextiende la clase Bloc base que espera dos parámetros: nuestras clases de eventos y estados. Esto es para decirle al Bloc que estamos tratando con eventos y estados de tipo HomeEvent y HomeState respectivamente, lo que responde a la pregunta de por qué los creamos como clases abstractas.

Dentro del constructor, debemos proporcionar a la clase nuestro estado inicial a través de su superconstructor. Como no estamos haciendo nada cuando vemos la página por primera vez, podemos crear otro estado vacío, en este caso, el archivo HomeInitialState.

Pero si, por ejemplo, desea mostrar directamente el brillo cuando el usuario abre la página, podría considerar establecerlo HomeLoadingStatecomo su estado inicial en el constructor.

Dentro del cierre del superconstructor, tenemos que enumerar todos los eventos que se pueden enviar a nuestro HomeBloc, y como solo tenemos una acción, solo ponemos una. O si prefiere una explicación más amigable, la línea 6 se puede entender de la siguiente manera:

Cuando HomeBloc recibe un FetchDataEvent, intentará manejar el evento ejecutando el controlador que se le ha dado, en este caso, la _onFetchDataEventfunción.

Ahora, ¿qué hacemos dentro del _onFetchDataEvent?

Recuerde que queremos obtener datos y devolver la respuesta a nuestra página de inicio. En este caso, solo retrasaré la ejecución de la función durante dos segundos para simular llamadas de red y generar un booleano aleatorio para determinar si obtuvimos una lista de alimentos o un error.

Y mientras procesamos toda esa lógica, queremos mostrar un cargador en nuestra página, por lo que el algoritmo será así:

  1. cambiar de estado aHomeLoadingState
  2. retrasar la ejecución de la función durante dos segundos para simular una llamada de red
  3. random un booleano, si es verdadero, cambiamos el estado a HomeSuccessFetchDataState, trayendo una lista de comida ficticia, de lo contrario, cambiamos el estado a HomeErrorFetchDataState.

Agregue algunos cambios a la _onFetchDataEvent función:

void _onFetchDataEvent(
    FetchDataEvent event,
    Emitter<HomeState> emitter,
  ) async {
    emitter(const HomeLoadingState());
    await Future.delayed(const Duration(seconds: 2));
    bool isSucceed = Random().nextBool();
    if (isSucceed) {
      List<Food> _dummyFoods = FoodGenerator.generateDummyFoods();
      emitter(HomeSuccessFetchDataState(foods: _dummyFoods));
    } else {
      emitter(const HomeErrorFetchDataState(
        errorMessage: "something went very wrong :(",
      ));
    }
  }

¡Felicidades! Acabamos de terminar nuestro código BLoC.

 

Una última cosa por hacer es modificar nuestro widget (pantalla de inicio) para que pueda escuchar las mutaciones de estado que ocurren dentro del BLoC. Una vez hecho esto, podemos cambiar nuestra interfaz de usuario de acuerdo con nuestro estado (por ejemplo, mostrando algún botón de intentarlo de nuevo cuando ocurrió un error, etc.).

Afortunadamente, la biblioteca BLoC nos tiene cubiertos. ¡Todo lo que tenemos que hacer es envolver nuestro widget en un BlocConsumerwidget y estamos listos para comenzar!

Navegue main.darty cambie el código a lo siguiente:

import 'package:bloctorial/home_bloc.dart';
import 'package:bloctorial/home_state_event.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(const HomeSreen());
}

class HomeSreen extends StatefulWidget {
  const HomeSreen({Key? key}) : super(key: key);

  @override
  State<HomeSreen> createState() => _HomeSreenState();
}

class _HomeSreenState extends State<HomeSreen> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("Cool App")),
        body: BlocConsumer<HomeBloc, HomeState>(
          listener: (context, state) {
            
          },
          builder: (context, state) {
            if (state is HomeLoadingState) {}
            if (state is HomeSuccessFetchDataState) {}
            if (state is HomeErrorFetchDataState) {}

            return Center(
              child: ElevatedButton(
                child: const Text("Fetch Data"),
                onPressed: () {},
              ),
            );
          },
        ),
      ),
    );
  }
}

BlocConsumer requiere que definamos dos propiedades, a saber, oyente y constructor.

El bloque de código del constructor se activará cada vez que cambie el estado. Por lo tanto, tenemos que enumerar todos los estados posibles, por lo que podemos ver muchos if state is; es porque tenemos que manejar específicamente cada uno de los estados.

El bloque de escucha también se ejecutará cuando cambie el estado. La diferencia entre el oyente y el constructor es que el constructor espera que le devolvamos un widget. En otras palabras, what kind of UI you want to show to the user when the state is X, Y, or Z. Por otro lado, el oyente no requiere que devolvamos un widget. Este es el lugar donde hacemos efectos secundarios, como mostrar alertas, barras de bocadillos, rastreadores de visitas, etc.

Agregue los widgets en cada uno de los estados dentro del generador, de modo que nuestra pantalla de inicio se vuelva como la siguiente:


class HomeSreen extends StatefulWidget {
  const HomeSreen({Key? key}) : super(key: key);

  @override
  State<HomeSreen> createState() => _HomeSreenState();
}

class _HomeSreenState extends State<HomeSreen> {
  
  Widget _buildFoodCard(Food food) {
    return Padding(
      padding: const EdgeInsets.symmetric(
        horizontal: 10,
        vertical: 4,
      ),
      child: Card(
        elevation: 5,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            ClipRRect(
              borderRadius: const BorderRadius.only(
                topLeft: Radius.circular(5.0),
                topRight: Radius.circular(5.0),
              ),
              child: Image.network(
                food.thumbnailURL,
                height: 100,
                width: double.infinity,
                fit: BoxFit.cover,
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(
                horizontal: 14,
                vertical: 16,
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    food.name,
                    style: const TextStyle(
                      fontSize: 14,
                    ),
                    maxLines: 1,
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 14),
                    child: Row(
                      children: [
                        Expanded(
                          flex: 1,
                          child: Padding(
                            padding: const EdgeInsets.only(
                              right: 8,
                            ),
                            child: Text(
                              food.price,
                              style: const TextStyle(
                                fontWeight: FontWeight.w700,
                              ),
                            ),
                          ),
                        ),
                        const Expanded(
                          flex: 0,
                          child: Icon(
                            Icons.arrow_forward_ios_rounded,
                            color: Colors.blueAccent,
                          ),
                        ),
                      ],
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("Cool App")),
        body: BlocConsumer<HomeBloc, HomeState>(
          listener: (context, state) {},
          builder: (context, state) {
            if (state is HomeLoadingState) {
              return const CircularProgressIndicator();
            }
            if (state is HomeSuccessFetchDataState) {
              return Center(
                child: ListView.builder(
                  shrinkWrap: true,
                  itemBuilder: (context, index) {
                    return _buildFoodCard(state.foods[index]);
                  },
                  itemCount: state.foods.length,
                ),
              );
            }
            if (state is HomeErrorFetchDataState) {
              return Center(
                child: Column(
                  children: [
                    Text(state.errorMessage),
                    ElevatedButton(
                      child: const Text("Fetch Data"),
                      onPressed: () {},
                    ),
                  ],
                ),
              );
            }

            return Center(
              child: ElevatedButton(
                child: const Text("Fetch Data"),
                onPressed: () {},
              ),
            );
          },
        ),
      ),
    );
  }
}

Para HomeLoadingState, acabo de devolver un CircularActivityIndicatorcomo cargador para HomeSuccessFetchDataState, devuelvo un ListViewpara que el usuario pueda ver todos nuestros alimentos y devolvemos un texto de error con un botón de reintento en ErrorState.

Lo siguiente es que tenemos que envolver nuestro HomeScreeninterior a BlocProvideren la runAppfunción. Esto debe hacerse para proporcionar HomeScreenacceso al bloque a todos sus hijos. De lo contrario, obtendremos un error que indica que no se encuentra el BLoC.

void main() {
  runApp(
    BlocProvider(
      create: (context) => HomeBloc(),
      child: const HomeSreen(),
    ),
  );
}

Después de eso, también tenemos que agregar la capacidad de enviar eventos en nuestro widget de inicio, para poder hacerlo. Tenemos que agarrar el HomeBlocinterior del widget HomeScreen y, como ya lo hemos proporcionado a través de BlocProvider, podemos leerlo desde el contexto.

Agregue una HomeBlocpropiedad y una initStatefunción en el widget HomeScreen como esta:

class HomeSreen extends StatefulWidget {
  const HomeSreen({Key? key}) : super(key: key);

  @override
  State<HomeSreen> createState() => _HomeSreenState();
}

class _HomeSreenState extends State<HomeSreen> {
  late HomeBloc bloc;

  @override
  void initState() {
    super.initState();
    bloc = context.read<HomeBloc>();
  }
  
  //rest of the code
  Widget _buildFoodCard(Food food) 
  ...

  @override
  Widget build(BuildContext context)
  ...
}

Y finalmente, agregue el código de envío del evento dentro de cada onPressedfunción:

onPressed: () {
   bloc.add(FetchDataEvent());
},

Nuestra final main.dartse verá así:

import 'package:bloctorial/food.dart';
import 'package:bloctorial/home_bloc.dart';
import 'package:bloctorial/home_state_event.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(
    BlocProvider(
      create: (context) => HomeBloc(),
      child: const HomeSreen(),
    ),
  );
}

class HomeSreen extends StatefulWidget {
  const HomeSreen({Key? key}) : super(key: key);

  @override
  State<HomeSreen> createState() => _HomeSreenState();
}

class _HomeSreenState extends State<HomeSreen> {
  late HomeBloc bloc;

  @override
  void initState() {
    super.initState();
    bloc = context.read<HomeBloc>();
  }
  
  Widget _buildFoodCard(Food food) {
    return Padding(
      padding: const EdgeInsets.symmetric(
        horizontal: 10,
        vertical: 4,
      ),
      child: Card(
        elevation: 5,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            ClipRRect(
              borderRadius: const BorderRadius.only(
                topLeft: Radius.circular(5.0),
                topRight: Radius.circular(5.0),
              ),
              child: Image.network(
                food.thumbnailURL,
                height: 100,
                width: double.infinity,
                fit: BoxFit.cover,
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(
                horizontal: 14,
                vertical: 16,
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    food.name,
                    style: const TextStyle(
                      fontSize: 14,
                    ),
                    maxLines: 1,
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 14),
                    child: Row(
                      children: [
                        Expanded(
                          flex: 1,
                          child: Padding(
                            padding: const EdgeInsets.only(
                              right: 8,
                            ),
                            child: Text(
                              food.price,
                              style: const TextStyle(
                                fontWeight: FontWeight.w700,
                              ),
                            ),
                          ),
                        ),
                        const Expanded(
                          flex: 0,
                          child: Icon(
                            Icons.arrow_forward_ios_rounded,
                            color: Colors.blueAccent,
                          ),
                        ),
                      ],
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("Cool App")),
        body: BlocConsumer<HomeBloc, HomeState>(
          listener: (context, state) {},
          builder: (context, state) {
            if (state is HomeLoadingState) {
              return const CircularProgressIndicator();
            }
            if (state is HomeSuccessFetchDataState) {
              return Center(
                child: ListView.builder(
                  shrinkWrap: true,
                  itemBuilder: (context, index) {
                    return _buildFoodCard(state.foods[index]);
                  },
                  itemCount: state.foods.length,
                ),
              );
            }
            if (state is HomeErrorFetchDataState) {
              return Center(
                child: Column(
                  children: [
                    Text(state.errorMessage),
                    ElevatedButton(
                      child: const Text("Fetch Data"),
                      onPressed: () {
                        bloc.add(FetchDataEvent());
                      },
                    ),
                  ],
                ),
              );
            }

            return Center(
              child: ElevatedButton(
                child: const Text("Fetch Data"),
                onPressed: () {
                  bloc.add(FetchDataEvent());
                },
              ),
            );
          },
        ),
      ),
    );
  }
}

Intente ejecutar la aplicación, ¡y listo! ¡Nuestra aplicación debería estar terminada con los requisitos cumplidos!

Conclusión

BLoC es más o menos lo mismo que redux o la arquitectura componible, todos ellos tienen una instancia que actúa como un controlador de acciones donde hacemos toda la lógica comercial allí. Y la interfaz de usuario envía acciones de mutación de estado y escucha los cambios de estado para realizar las actualizaciones necesarias.

¡Feliz codificación!

Esta historia se publicó originalmente en https://betterprogramming.pub/a-comprehensive-guide-to-flutters-bloc-architecture-d2d16022b0b9

#flutter #bloc 

Una Guía Completa De La Arquitectura BLoC De Flutter
坂本  健一

坂本 健一

1654956000

FlutterのBLoCアーキテクチャの包括的なガイド

BLoCアーキテクチャは、Flutterコミュニティでかなり前から存在しており、コミュニティはそれを愛していると言っても過言ではありません。このパターンを使用してロジックを分離し、テストしやすくする方法は、アプリでいくつかの変更を加えるだけです。これは非常にすばらしいことです。

このチュートリアルでは、簡単なアプリを作成してBLoCの概念とその全体的なフローを学習します。このチュートリアルの終わりまでに、BLoCアーキテクチャを実装するアプリを作成できるようになっているはずです。

このチュートリアルで使用するBLoCバージョンは8.0.1(執筆時点での最新バージョン)です。

ブロックに関するいくつかの理論

BLoCアーキテクチャには3つのコアコンポーネントがあります。

  1. 状態
    状態は、ページまたはアプリが存在する可能性のあるすべての状態です。たとえば、ページからAPIを取得する場合、ページには少なくとも3つの状態があります。
  • LoadingState
    この状態では、ページにローダーを表示して、データをフェッチしようとしていることをユーザーが理解できるようにすることができます。
  • ErrorFetchDataState
    この状態では、エラーテキストや再試行ボタンを表示したり、ネットワーク呼び出しで問題が発生していることをユーザーに通知するアラートを表示したりすることができます。
  • SuccessFetchDataState
    この状態では、応答から取得したデータに基づいて、カードまたはデータのリストを表示できます。
  1. イベント
    イベントは、ページの状態を変更するためにUIが送信するアクションです(おなじみのように聞こえますが、Redux開発者ですか?)
  2. BloC
    BloCは、アクションハンドラーおよび状態ミューテーターとして機能します。ここでも、APIのフェッチ、共有設定の保存などのビジネスロジックを実行できます。一部の人々はそれらをレデューサーと呼びます。

その概念理論から判断すると、BLoCはreduxまたはコンポーザブルアーキテクチャとほとんど同じであるため、それらの1つをたまたま理解していれば、BLoCをほぼ理解していることになります。

コーディングしましょう

選択したコードエディタに移動し、思いつく限りの名前で新しいFlutterプロジェクトを作成します。次に、Flutterがボイラープレートの作成を完了した後、そのようなflutter_bloc依存関係を追加します。pubspec.yaml


dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.1
  equatable: ^2.0.3

main.dartのすべてをこのコードに置き換えます

import 'package:flutter/material.dart';

void main() {
  runApp(const HomeSreen());
}

class HomeSreen extends StatefulWidget {
  const HomeSreen({Key? key}) : super(key: key);

  @override
  State<HomeSreen> createState() => _HomeSreenState();
}

class _HomeSreenState extends State<HomeSreen> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("Cool App")),
        body: Center(
          child: ElevatedButton(
            child: const Text("Fetch Data"),
            onPressed: () {},
          ),
        ),
      ),
    );
  }
}

上記のコードでは、ページの中央にボタンがあるホーム画面ウィジェットを作成します。かなり簡単です。

次に行うことは、アプリにいくつかの機能を追加することです。最終的なアプリでは、APIから取得した応答に応じて、フードカードのリストまたはエラーメッセージを表示できます。

簡単にするために、実際のエンドポイントにヒットすることはありません。ユーザーがボタンを押すたびに結果をランダム化するだけです。

、という名前の新しいファイルを作成しますfood.dart。このファイルには、フードカードのモデルが含まれます。

class Food {
  final String id;
  final String name;
  final String thumbnailURL;
  final String price;

  Food({
    required this.id,
    required this.name,
    required this.thumbnailURL,
    required this.price,
  });
}

、という名前の別の新しいファイルを作成しますfood_generator.dart。このファイルは、アプリに表示されるすべてのダミーデータを提供します。コードは次のとおりです。

import 'package:bloctorial/food.dart';

class FoodGenerator {
  static List<Food> generateDummyFoods() {
    return [
      Food(
        id: "1",
        name: "Nasi Goreng",
        thumbnailURL:
            "https://asset.kompas.com/crops/MrdYDsxogO0J3wGkWCaGLn2RHVc=/84x60:882x592/750x500/data/photo/2021/11/17/61949959e07d3.jpg",
        price: "Rp18.000",
      ),
      Food(
        id: "2",
        name: "Sate Ayam",
        thumbnailURL:
            "https://photos.smugmug.com/Indonesia-2016/i-9hLVhWh/0/X3/indonesian-sate-ayam-1-X3.jpg",
        price: "Rp20.000",
      ),
      Food(
        id: "3",
        name: "Ayam Bakar Taliwang",
        thumbnailURL:
            "https://photos.smugmug.com/Indonesia-2016/i-Q4M8Pkt/0/X3/ayam-taliwang-jakarta-1-X3.jpg",
        price: "R25.000",
      ),
      Food(
        id: "4",
        name: "Bebek Goreng",
        thumbnailURL:
            "https://photos.smugmug.com/City-Guides/i-bMfqjcX/0/X3/jakarta-travel-guide-21-X3.jpg",
        price: "Rp18.000",
      ),
      Food(
        id: "5",
        name: "Bakso",
        thumbnailURL:
            "https://photos.smugmug.com/Indonesia-2016/i-vG378LH/0/X3/indonesian-food-14-X3.jpg",
        price: "Rp17.000",
      ),
    ];
  }
}

次に、最初のBLoC関連のコードに進みましょう。ここでは、状態とイベントのクラスを作成します。

名前の付いた新しいファイルを作成し、home_state.dartファイル内に次のコードを追加します。

import 'package:bloctorial/food.dart';
import 'package:equatable/equatable.dart';

abstract class HomeState extends Equatable {
  const HomeState();
}

class HomeInitialState extends HomeState {
  const HomeInitialState();

  @override
  List<Object?> get props => [];
}

class HomeLoadingState extends HomeState {
  const HomeLoadingState();

  @override
  List<Object?> get props => [];
}

class HomeErrorFetchDataState extends HomeState {
  final String errorMessage;
  const HomeErrorFetchDataState({
    required this.errorMessage,
  });

  @override
  List<Object?> get props => [];
}

class HomeSuccessFetchDataState extends HomeState {
  final List<Food> foods;
  const HomeSuccessFetchDataState({
    required this.foods,
  });

  @override
  List<Object?> get props => [];
}

要件から、私たちのアプリは現時点で1つのことしか実行できません。それは、ボタンが押されたときにデータをフェッチしようとすることです。そこから、ページには少なくとも3つの状態が必要であると想像できます。

  1. HomeLoadingState
    これは、API応答を待っているときの状態です。ある種のローダーインジケーターまたはきらめきを表示して、ユーザーにまだ処理中であることを知らせたい場合があります。
  2. HomeErrorFetchDataState
    これは、APIからエラーを受け取ったときであり、アラートを表示したり、いくつかのエラーウィジェットを表示したりする場合があります。
  3. HomeSuccessFetchDataState
    これは、APIから食品データを正常に受信したときに、ここに食品カードのリストを表示したい場合があります。

上記の3つの状態に加えて、別の状態も作成する必要がありますHomeInitialState。ユーザーがページを表示したときにローダーを表示したくなかったため、これはページの最初の状態です。これについては、以下で詳しく説明します。

次に、ページが送信できるアクションを定義する必要があります。アクションは、状態の変化をトリガーするものであると想像できます。名前の付いた新しいファイルを作成し、home_event.dartこのコードを以下に追加します。

import 'package:equatable/equatable.dart';

abstract class HomeEvent extends Equatable {
  const HomeEvent();
}

class FetchDataEvent extends HomeEvent {
  @override
  List<Object?> get props => [];
}

現時点では、ホームページで実行できるのはデータのフェッチの1つだけなので、このフェッチデータイベントをの1つとして作成する必要がありますHomeEvent。イベントの送信は、ウィジェットがBLoCに何かをしたいことを伝える方法であることを忘れないでください。

したがって、たとえば、ページを表示するときにトラッカーをヒットしたい場合は、ここに別のイベントを追加できます。

最後に、BloCクラスを作成する必要があります。このクラスは、すべてのビジネスロジックを実行し、状態の構築を通じて結果をページに返すアクションハンドラーとして機能します。という新しいファイルを作成しhome_bloc.dart、次のコードを追加します。

import 'package:bloctorial/home_state_event.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class HomeBloc extends Bloc<HomeEvent, HomeState> {
  HomeBloc() : super(const HomeInitialState()) {
    on<FetchDataEvent>(_onFetchDataEvent);
  }

  void _onFetchDataEvent(
    FetchDataEvent event,
    Emitter<HomeState> emitter,
  ) {}
}

home_bloc.dart

私たちHomeBlocは、イベントクラスと状態クラスの2つのパラメーターを期待する基本Blocクラスを拡張します。これは、HomeEvent型とHomeState型のイベントと状態をそれぞれ処理していることをブロックに通知するためのものであり、抽象クラスとしてそれらを作成する理由の質問に答えます。

コンストラクター内で、スーパーコンストラクターを介してクラスに初期状態を提供する必要があります。初めてページを表示するときは何もしていないので、別の空の状態(この場合は。)を作成できHomeInitialStateます。

ただし、たとえば、ユーザーがページを開いたときにきらめきを直接表示したい場合HomeLoadingStateは、コンストラクターで初期状態として設定することを検討してください。

スーパーコンストラクタークロージャー内で、に送信される可能性のあるすべてのイベントをリストする必要がありますHomeBloc。アクションは1つしかないため、1つだけ配置します。または、よりわかりやすい説明が必要な場合は、6行目は次のように理解できます。

HomeBlocは、を受信するFetchDataEventと、与えられたハンドラー(この場合は_onFetchDataEvent関数)を実行してイベントを処理しようとします。

さて、私たちは内部で何をし_onFetchDataEventますか?

データをフェッチして、応答をホームページに返したいことを忘れないでください。この場合、関数の実行を2秒間遅らせてネットワーク呼び出しをシミュレートし、ランダムなブール値を生成して、食べ物のリストを取得したかエラーを取得したかを判断します。

そして、これらすべてのロジックを処理している間、ページにローダーを表示したいので、アルゴリズムは次のようになります。

  1. 状態をに変更しますHomeLoadingState
  2. ネットワーク呼び出しをシミュレートするために、関数の実行を2秒間遅らせます
  3. ランダムなブール値。trueの場合は状態をに変更しHomeSuccessFetchDataState、ダミーフードのリストを表示します。それ以外の場合は、状態をに変更しHomeErrorFetchDataStateます。

_onFetchDataEvent 関数にいくつかの変更を追加します。

void _onFetchDataEvent(
    FetchDataEvent event,
    Emitter<HomeState> emitter,
  ) async {
    emitter(const HomeLoadingState());
    await Future.delayed(const Duration(seconds: 2));
    bool isSucceed = Random().nextBool();
    if (isSucceed) {
      List<Food> _dummyFoods = FoodGenerator.generateDummyFoods();
      emitter(HomeSuccessFetchDataState(foods: _dummyFoods));
    } else {
      emitter(const HomeErrorFetchDataState(
        errorMessage: "something went very wrong :(",
      ));
    }
  }

おめでとう!BLoCコードが完成しました。

 

最後に行うことの1つは、ウィジェット(ホーム画面)を変更して、BLoC内で発生している状態の変化をリッスンできるようにすることです。これが完了すると、状態に応じてUIを変更できます(たとえば、エラーが発生したときに再試行ボタンを表示するなど)。

幸い、BLoCライブラリでカバーされています。ウィジェットをウィジェットでラップするだけで、BlocConsumer準備が整います。

に移動しmain.dartて、コードを次のように変更します。

import 'package:bloctorial/home_bloc.dart';
import 'package:bloctorial/home_state_event.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(const HomeSreen());
}

class HomeSreen extends StatefulWidget {
  const HomeSreen({Key? key}) : super(key: key);

  @override
  State<HomeSreen> createState() => _HomeSreenState();
}

class _HomeSreenState extends State<HomeSreen> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("Cool App")),
        body: BlocConsumer<HomeBloc, HomeState>(
          listener: (context, state) {
            
          },
          builder: (context, state) {
            if (state is HomeLoadingState) {}
            if (state is HomeSuccessFetchDataState) {}
            if (state is HomeErrorFetchDataState) {}

            return Center(
              child: ElevatedButton(
                child: const Text("Fetch Data"),
                onPressed: () {},
              ),
            );
          },
        ),
      ),
    );
  }
}

BlocConsumerでは、リスナーとビルダーの2つのプロパティを定義する必要があります。

コードのビルダーブロックは、状態が変化するたびにトリガーされます。したがって、考えられるすべての状態をリストする必要があります。そのため、多くの状態を確認できますif state is。それは、それぞれの状態を具体的に処理する必要があるためです。

状態が変化すると、リスナーブロックも実行されます。リスナーとビルダーの違いは、ビルダーがウィジェットを返すことを期待していることです。言い換えれば、what kind of UI you want to show to the user when the state is X, Y, or Z。一方、リスナーはウィジェットを返す必要はありません。これは、アラート、スナックバー、ヒットトラッカーなどを表示するなどの副作用を行う場所です。

ビルダー内の各状態にウィジェットを追加して、HomeScreenが次のようになるようにします。


class HomeSreen extends StatefulWidget {
  const HomeSreen({Key? key}) : super(key: key);

  @override
  State<HomeSreen> createState() => _HomeSreenState();
}

class _HomeSreenState extends State<HomeSreen> {
  
  Widget _buildFoodCard(Food food) {
    return Padding(
      padding: const EdgeInsets.symmetric(
        horizontal: 10,
        vertical: 4,
      ),
      child: Card(
        elevation: 5,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            ClipRRect(
              borderRadius: const BorderRadius.only(
                topLeft: Radius.circular(5.0),
                topRight: Radius.circular(5.0),
              ),
              child: Image.network(
                food.thumbnailURL,
                height: 100,
                width: double.infinity,
                fit: BoxFit.cover,
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(
                horizontal: 14,
                vertical: 16,
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    food.name,
                    style: const TextStyle(
                      fontSize: 14,
                    ),
                    maxLines: 1,
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 14),
                    child: Row(
                      children: [
                        Expanded(
                          flex: 1,
                          child: Padding(
                            padding: const EdgeInsets.only(
                              right: 8,
                            ),
                            child: Text(
                              food.price,
                              style: const TextStyle(
                                fontWeight: FontWeight.w700,
                              ),
                            ),
                          ),
                        ),
                        const Expanded(
                          flex: 0,
                          child: Icon(
                            Icons.arrow_forward_ios_rounded,
                            color: Colors.blueAccent,
                          ),
                        ),
                      ],
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("Cool App")),
        body: BlocConsumer<HomeBloc, HomeState>(
          listener: (context, state) {},
          builder: (context, state) {
            if (state is HomeLoadingState) {
              return const CircularProgressIndicator();
            }
            if (state is HomeSuccessFetchDataState) {
              return Center(
                child: ListView.builder(
                  shrinkWrap: true,
                  itemBuilder: (context, index) {
                    return _buildFoodCard(state.foods[index]);
                  },
                  itemCount: state.foods.length,
                ),
              );
            }
            if (state is HomeErrorFetchDataState) {
              return Center(
                child: Column(
                  children: [
                    Text(state.errorMessage),
                    ElevatedButton(
                      child: const Text("Fetch Data"),
                      onPressed: () {},
                    ),
                  ],
                ),
              );
            }

            return Center(
              child: ElevatedButton(
                child: const Text("Fetch Data"),
                onPressed: () {},
              ),
            );
          },
        ),
      ),
    );
  }
}

の場合HomeLoadingStateCircularActivityIndicatorのローダーとしてaHomeSuccessFetchDataStateを返しListView、ユーザーがすべての食品を表示できるようにaを返し、の再試行ボタンが付いたエラーテキストを返しますErrorState

次に、関数HomeScreen内のaをラップする必要があります。これは、およびそのすべての子にブロックへのアクセスを提供するために実行する必要があります。そうしないと、BLoCが見つからないことを示すエラーが発生します。BlocProviderrunAppHomeScreen

void main() {
  runApp(
    BlocProvider(
      create: (context) => HomeBloc(),
      child: const HomeSreen(),
    ),
  );
}

その後、ホームウィジェットにイベント送信機能も追加する必要があります。HomeScreenウィジェットの内部を取得する必要があります。HomeBlocすでに、を介して提供しているためBlocProvider、コンテキストから読み取ることができます。

次のように、HomeScreenウィジェットにHomeBlocプロパティと関数を追加します。initState

class HomeSreen extends StatefulWidget {
  const HomeSreen({Key? key}) : super(key: key);

  @override
  State<HomeSreen> createState() => _HomeSreenState();
}

class _HomeSreenState extends State<HomeSreen> {
  late HomeBloc bloc;

  @override
  void initState() {
    super.initState();
    bloc = context.read<HomeBloc>();
  }
  
  //rest of the code
  Widget _buildFoodCard(Food food) 
  ...

  @override
  Widget build(BuildContext context)
  ...
}

そして最後に、すべてのonPressed関数内にイベント送信コードを追加します。

onPressed: () {
   bloc.add(FetchDataEvent());
},

ファイナルmain.dartは次のようになります。

import 'package:bloctorial/food.dart';
import 'package:bloctorial/home_bloc.dart';
import 'package:bloctorial/home_state_event.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(
    BlocProvider(
      create: (context) => HomeBloc(),
      child: const HomeSreen(),
    ),
  );
}

class HomeSreen extends StatefulWidget {
  const HomeSreen({Key? key}) : super(key: key);

  @override
  State<HomeSreen> createState() => _HomeSreenState();
}

class _HomeSreenState extends State<HomeSreen> {
  late HomeBloc bloc;

  @override
  void initState() {
    super.initState();
    bloc = context.read<HomeBloc>();
  }
  
  Widget _buildFoodCard(Food food) {
    return Padding(
      padding: const EdgeInsets.symmetric(
        horizontal: 10,
        vertical: 4,
      ),
      child: Card(
        elevation: 5,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            ClipRRect(
              borderRadius: const BorderRadius.only(
                topLeft: Radius.circular(5.0),
                topRight: Radius.circular(5.0),
              ),
              child: Image.network(
                food.thumbnailURL,
                height: 100,
                width: double.infinity,
                fit: BoxFit.cover,
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(
                horizontal: 14,
                vertical: 16,
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    food.name,
                    style: const TextStyle(
                      fontSize: 14,
                    ),
                    maxLines: 1,
                  ),
                  Padding(
                    padding: const EdgeInsets.only(top: 14),
                    child: Row(
                      children: [
                        Expanded(
                          flex: 1,
                          child: Padding(
                            padding: const EdgeInsets.only(
                              right: 8,
                            ),
                            child: Text(
                              food.price,
                              style: const TextStyle(
                                fontWeight: FontWeight.w700,
                              ),
                            ),
                          ),
                        ),
                        const Expanded(
                          flex: 0,
                          child: Icon(
                            Icons.arrow_forward_ios_rounded,
                            color: Colors.blueAccent,
                          ),
                        ),
                      ],
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("Cool App")),
        body: BlocConsumer<HomeBloc, HomeState>(
          listener: (context, state) {},
          builder: (context, state) {
            if (state is HomeLoadingState) {
              return const CircularProgressIndicator();
            }
            if (state is HomeSuccessFetchDataState) {
              return Center(
                child: ListView.builder(
                  shrinkWrap: true,
                  itemBuilder: (context, index) {
                    return _buildFoodCard(state.foods[index]);
                  },
                  itemCount: state.foods.length,
                ),
              );
            }
            if (state is HomeErrorFetchDataState) {
              return Center(
                child: Column(
                  children: [
                    Text(state.errorMessage),
                    ElevatedButton(
                      child: const Text("Fetch Data"),
                      onPressed: () {
                        bloc.add(FetchDataEvent());
                      },
                    ),
                  ],
                ),
              );
            }

            return Center(
              child: ElevatedButton(
                child: const Text("Fetch Data"),
                onPressed: () {
                  bloc.add(FetchDataEvent());
                },
              ),
            );
          },
        ),
      ),
    );
  }
}

アプリを実行してみてください。出来上がりです。要件を満たした状態でアプリを終了する必要があります。

結論

BLoCは、reduxまたはコンポーザブルアーキテクチャとほとんど同じです。それらすべてに、すべてのビジネスロジックを実行するアクションハンドラとして機能する1つのインスタンスがあります。また、UIは状態変更アクションを送信し、状態の変化をリッスンして必要な更新を実行します。

ハッピーコーディング!

このストーリーは、もともとhttps://betterprogramming.pub/a-comprehensive-guide-to-flutters-bloc-architecture-d2d16022b0b9で公開されました

#flutter #bloc 

FlutterのBLoCアーキテクチャの包括的なガイド
Lawson  Wehner

Lawson Wehner

1652287503

BLoC management of simple, filtered, sorted and paged lists

flutter_list_bloc

This is very much work in progress prototype, for the time being it should be considered as an example of using list_bloc in flutter

Features and bugs

Please file feature requests and bugs at the issue tracker.

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_list_bloc

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:  flutter_list_bloc: ^3.1.2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_list_bloc/flutter_list_bloc.dart';

Author: Apexlabs-ai
Source Code: https://github.com/apexlabs-ai/list_bloc 
License: 

#flutter #dart #bloc 

BLoC management of simple, filtered, sorted and paged lists