custom_bloc 0.1.9 copy "custom_bloc: ^0.1.9" to clipboard
custom_bloc: ^0.1.9 copied to clipboard

A simple wrapper around stream controller/rxdart stream for easier implementation of bloc pattern

A simple custom stream builder library based on BLOC pattern. Uses stream underneath.

Features #

This allows easy adding of data from network and disposing of bloc

How To Use Custom Bloc #

void main() async {
  String type = 'multi';
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MaterialApp(
    home: type == 'one'
        ? const Example()
        : type == 'two'
        ? const Example2()
        : const ExampleMulti(),
  ));
}

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

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  final counterBloc = CounterBloc();

  @override
  void dispose() {
    super.dispose();
    counterBloc.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: CustomStreamBuilder(
                stream: counterBloc.behaviorSubject,
                dataBuilder: (context, data) {
                  return ListView.separated(
                    itemCount: 1,
                    scrollDirection: Axis.horizontal,
                    padding:
                    const EdgeInsets.symmetric(horizontal: 4, vertical: 24),
                    itemBuilder: (context, index) {
                      return Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 12),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                'index: $index: data: $data',
                                style: const TextStyle(fontSize: 34),
                              ),
                            ],
                          ));
                    },
                    separatorBuilder: (context, index) {
                      return const Padding(
                        padding: EdgeInsets.symmetric(vertical: 10.0),
                        child: VerticalDivider(),
                      );
                    },
                  );
                },
                loadingBuilder: (context) => const Center(
                  child: CircularProgressIndicator(),
                ),
                errorBuilder: (context, error) => Text(
                  error,
                  style: const TextStyle(),
                ),
              ),
            ),
            const SizedBox(
              height: 34,
            ),
            Row(
              children: [
                TextButton(
                    onPressed: () {
                      counterBloc.fetchCurrent(false);
                    },
                    child: const Text(
                      'Add Value',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      counterBloc.resetData();
                    },
                    child: const Text(
                      'Set to no data',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      counterBloc.fetchCurrent(true);
                    },
                    child: const Text(
                      'Add Error',
                      style: TextStyle(),
                    )),
              ],
            ),
            const SizedBox(
              height: 34,
            )
          ],
        ),
      ),
    );
  }
}

class CounterBloc extends BaseBloc<int, String> {
  int value = 0;

  CounterBloc() {
    fetchCurrent(false);
  }

  fetchCurrent(bool addError) async {
    setAsLoading();

    if (!addError) {
      await Future.delayed(const Duration(seconds: 2));
      value++;
      addToModel(value);
    } else {
      value = 0;
      addToError('Could not fetch data');
    }
  }

  resetData() {
    value = 0;
    setAsNoContent();
  }

  invalidate() {
    invalidateBaseBloc();
  }

  dispose() {
    disposeBaseBloc();
  }
}

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

  @override
  State<Example2> createState() => _Example2State();
}

class _Example2State extends State<Example2> {
  final counterBloc = CounterBloc();
  final newCounterBloc = CounterBloc2();

  @override
  void dispose() {
    super.dispose();
    counterBloc.dispose();
    newCounterBloc.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: CustomStreamBuilder.twoSubject(
                streams: [
                  counterBloc.behaviorSubject,
                  newCounterBloc.behaviorSubject
                ],
                dataBuilder2: (context, BaseModel<int?, String?> data,
                    BaseModel<double?, String?> secondData) {
                  var counterBlocData = data.model;
                  var counterBlocState = data.itemState;
                  var counterBlocDataError = data.error;
                  var newCounterBlocData = secondData.model;
                  var newCounterBlocState = secondData.itemState;
                  var newCounterBlocError = secondData.error;

                  return Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 12),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          if (counterBlocState == ItemState.loading)
                            const Center(
                              child: CircularProgressIndicator(),
                            )
                          else if (counterBlocState == ItemState.hasError)
                            Text(
                              'counterBlocData error: $counterBlocDataError',
                              style: const TextStyle(fontSize: 34),
                            )
                          else if (counterBlocState == ItemState.noContent)
                              const SizedBox()
                            else
                              Text(
                                'counterBlocData: $counterBlocData',
                                style: const TextStyle(fontSize: 34),
                              ),
                          const SizedBox(
                            height: 34,
                          ),
                          if (newCounterBlocState == ItemState.loading)
                            const Center(
                              child: CircularProgressIndicator(),
                            )
                          else if (newCounterBlocState == ItemState.hasError)
                            Text(
                              'newCounterBlocError error: $newCounterBlocError',
                              style: const TextStyle(fontSize: 34),
                            )
                          else if (newCounterBlocState == ItemState.noContent)
                              const SizedBox()
                            else
                              Text(
                                'newCounterBlocData: $newCounterBlocData',
                                style: const TextStyle(fontSize: 34),
                              ),
                        ],
                      ));
                },
                loadingBuilder: (context) => const Center(
                  child: CircularProgressIndicator(),
                ),
                errorBuilder: (context, error) => Text(
                  error.toString(),
                  style: const TextStyle(),
                ),
              ),
            ),
            const SizedBox(
              height: 34,
            ),
            Wrap(
              children: [
                TextButton(
                    onPressed: () {
                      counterBloc.fetchCurrent(false);
                    },
                    child: const Text(
                      'Add Value to stream 1',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      counterBloc.resetData();
                    },
                    child: const Text(
                      'Set to no data stream 1',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      counterBloc.fetchCurrent(true);
                    },
                    child: const Text(
                      'Add Error to stream 1',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      newCounterBloc.fetchCurrent(false);
                    },
                    child: const Text(
                      'Add Value to stream 2',
                      style: TextStyle(color: Colors.red),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      newCounterBloc.resetData();
                    },
                    child: const Text(
                      'Set to no data stream 2',
                      style: TextStyle(color: Colors.red),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      newCounterBloc.fetchCurrent(true);
                    },
                    child: const Text(
                      'Add Error to stream 2',
                      style: TextStyle(color: Colors.red),
                    )),
              ],
            ),
            const SizedBox(
              height: 34,
            )
          ],
        ),
      ),
    );
  }
}

class CounterBloc2 extends BaseBlocInfiniteLoadingFactoryBloc<double, String> {
  double value = 0;

  fetchCurrent(bool addError) async {
    setAsLoading();

    if (!addError) {
      await Future.delayed(const Duration(seconds: 2));
      value += 0.5;
      addToModel(value);
    } else {
      value = 0;
      addToError('Could not fetch data');
    }
  }

  @override
  fetchNextPage(
      {Function()? onSuccessfulFetch, Function()? onFailedFetch}) async {
    await Future.delayed(const Duration(seconds: 2));
    value += 0.5;
    addToModel(value);
  }

  resetData() {
    value = 0;
    setAsNoContent();
  }

  invalidate() {
    invalidateBaseBloc();
  }

  dispose() {
    disposeBaseBloc();
  }
}

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

  @override
  State<ExampleMulti> createState() => _ExampleMultiState();
}

class _ExampleMultiState extends State<ExampleMulti> {
  final counterBloc = CounterBloc();
  final newCounterBloc = CounterBloc2();
  final newCounterBloc3 = CounterBloc3();

  @override
  void dispose() {
    super.dispose();
    counterBloc.dispose();
    newCounterBloc.dispose();
    newCounterBloc3.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: CustomStreamBuilder.multiSubject(
                streams: [
                  counterBloc.behaviorSubject,
                  newCounterBloc.behaviorSubject,
                  newCounterBloc3.behaviorSubject
                ],
                itemBuilderMulti: (context, data) {
                  var counterBlocData = data.first.model as int?;
                  var counterBlocState = data.first.itemState;
                  var counterBlocDataError = data.first.error as String?;

                  var newCounterBlocData = data.elementAt(1).model as double?;
                  var newCounterBlocState = data.elementAt(1).itemState;
                  var newCounterBlocError = data.elementAt(1).error as String?;

                  var newCounterBlocData3 = data.elementAt(2).model as double?;
                  var newCounterBlocState3 = data.elementAt(2).itemState;
                  var newCounterBlocError3 = data.elementAt(2).error as String?;

                  return Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 12),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          if (counterBlocState == ItemState.loading)
                            const Center(
                              child: CircularProgressIndicator(),
                            )
                          else if (counterBlocState == ItemState.hasError)
                            Text(
                              'counterBlocData error: $counterBlocDataError',
                              style: const TextStyle(fontSize: 34),
                            )
                          else if (counterBlocState == ItemState.noContent)
                              const SizedBox()
                            else
                              Text(
                                'counterBlocData: $counterBlocData',
                                style: const TextStyle(fontSize: 34),
                              ),
                          const SizedBox(
                            height: 34,
                          ),
                          if (newCounterBlocState == ItemState.loading)
                            const Center(
                              child: CircularProgressIndicator(),
                            )
                          else if (newCounterBlocState == ItemState.hasError)
                            Text(
                              'newCounterBlocError error: $newCounterBlocError',
                              style: const TextStyle(fontSize: 34),
                            )
                          else if (newCounterBlocState == ItemState.noContent)
                              const SizedBox()
                            else
                              Text(
                                'newCounterBlocData: $newCounterBlocData',
                                style: const TextStyle(fontSize: 34),
                              ),
                          if (newCounterBlocState3 == ItemState.loading)
                            const Center(
                              child: CircularProgressIndicator(),
                            )
                          else if (newCounterBlocState3 == ItemState.hasError)
                            Text(
                              'newCounterBlocError error: $newCounterBlocError3',
                              style: const TextStyle(fontSize: 34),
                            )
                          else if (newCounterBlocState3 == ItemState.noContent)
                              const SizedBox()
                            else
                              Text(
                                'newCounterBlocData: $newCounterBlocData3',
                                style: const TextStyle(fontSize: 34),
                              ),
                        ],
                      ));
                },
              ),
            ),
            const SizedBox(
              height: 34,
            ),
            Wrap(
              children: [
                TextButton(
                    onPressed: () {
                      counterBloc.fetchCurrent(false);
                    },
                    child: const Text(
                      'Add Value to stream 1',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      counterBloc.resetData();
                    },
                    child: const Text(
                      'Set to no data stream 1',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      counterBloc.fetchCurrent(true);
                    },
                    child: const Text(
                      'Add Error to stream 1',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      newCounterBloc.fetchCurrent(false);
                    },
                    child: const Text(
                      'Add Value to stream 2',
                      style: TextStyle(color: Colors.red),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      newCounterBloc.resetData();
                    },
                    child: const Text(
                      'Set to no data stream 2',
                      style: TextStyle(color: Colors.red),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      newCounterBloc.fetchCurrent(true);
                    },
                    child: const Text(
                      'Add Error to stream 2',
                      style: TextStyle(color: Colors.red),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      newCounterBloc3.fetchCurrent(false);
                    },
                    child: const Text(
                      'Add Value to stream 3',
                      style: TextStyle(color: Colors.purple),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      newCounterBloc3.resetData();
                    },
                    child: const Text(
                      'Set to no data stream 3',
                      style: TextStyle(color: Colors.purple),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      newCounterBloc3.fetchCurrent(true);
                    },
                    child: const Text(
                      'Add Error to stream 3',
                      style: TextStyle(color: Colors.purple),
                    )),
              ],
            ),
            const SizedBox(
              height: 34,
            )
          ],
        ),
      ),
    );
  }
}

class CounterBloc3 extends BaseBloc<double, String> {
  double value = 0;

  CounterBloc3() {
    fetchCurrent(false);
  }

  fetchCurrent(bool addError) async {
    setAsLoading();

    if (!addError) {
      await Future.delayed(const Duration(seconds: 2));
      value += 0.5;
      addToModel(value);
    } else {
      value = 0;
      addToError('Could not fetch data');
    }
  }

  resetData() {
    value = 0;
    setAsNoContent();
  }

  invalidate() {
    invalidateBaseBloc();
  }

  dispose() {
    disposeBaseBloc();
  }
}

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

  @override
  State<ExampleCustomBlocBuilder> createState() =>
      _ExampleCustomBlocBuilderState();
}

class _ExampleCustomBlocBuilderState extends State<ExampleCustomBlocBuilder> {
  final counterBloc = CounterBloc();

  @override
  void dispose() {
    super.dispose();
    counterBloc.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: CustomBlocBuilder(
                  stream: counterBloc.behaviorSubject,
                  builder: (context, itemState, data, error) {
                    if (itemState == ItemState.hasData) {
                      return ListView.separated(
                        itemCount: 1,
                        scrollDirection: Axis.horizontal,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 4, vertical: 24),
                        itemBuilder: (context, index) {
                          return Padding(
                              padding:
                              const EdgeInsets.symmetric(horizontal: 12),
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(
                                    'index: $index: data: $data',
                                    style: const TextStyle(fontSize: 34),
                                  ),
                                ],
                              ));
                        },
                        separatorBuilder: (context, index) {
                          return const Padding(
                            padding: EdgeInsets.symmetric(vertical: 10.0),
                            child: VerticalDivider(),
                          );
                        },
                      );
                    } else if (itemState == ItemState.loading) {
                      return const Center(
                        child: CircularProgressIndicator(),
                      );
                    } else if (itemState == ItemState.loading) {
                      return Text(
                        error ?? '',
                        style: const TextStyle(),
                      );
                    }
                    return const SizedBox();
                  }),
            ),
            const SizedBox(
              height: 34,
            ),
            Row(
              children: [
                TextButton(
                    onPressed: () {
                      counterBloc.fetchCurrent(false);
                    },
                    child: const Text(
                      'Add Value',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      counterBloc.resetData();
                    },
                    child: const Text(
                      'Set to no data',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      counterBloc.fetchCurrent(true);
                    },
                    child: const Text(
                      'Add Error',
                      style: TextStyle(),
                    )),
              ],
            ),
            const SizedBox(
              height: 34,
            )
          ],
        ),
      ),
    );
  }
}

class CounterInfiniteScrollingBloc
    extends BaseBlocInfiniteLoadingFactoryBloc<double, String> {
  int get page => pageNumber;

  int get itemsPerPage => perPage;

  double value = 0;

  @override
  initFetch() async {
    super.initFetch();

    setAsLoading();

    await Future.delayed(const Duration(seconds: 2));
    value += 0.5;
    addToModel(value);

    incrementPageCountAndStartLoaderSubject();
  }

  @override
  fetchNextPage(
      {Function()? onSuccessfulFetch, Function()? onFailedFetch}) async {
    bool isSuccessful = await Future.delayed(const Duration(seconds: 2));

    if (isSuccessful) {
      value += 0.5;
      addToModel(value);
      incrementPageCountAndStartLoaderSubject();
    } else {
      stopLoaderSubject();
    }
  }

  resetData() {
    value = 0;
    setAsNoContent();
  }

  invalidate() {
    invalidateBaseBloc();
  }

  dispose() {
    disposeBaseBloc();
  }
}

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

  @override
  State<ExampleInfiniteScrolling> createState() =>
      _ExampleInfiniteScrollingState();
}

class _ExampleInfiniteScrollingState extends State<ExampleInfiniteScrolling> {
  final counterBloc = CounterInfiniteScrollingBloc();

  @override
  void dispose() {
    super.dispose();
    counterBloc.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: CustomInfiniteScrollingStreamBuilder(
                loaderStream: counterBloc.loaderSubject,
                builder: (context, loadingData) => CustomStreamBuilder(
                  stream: counterBloc.behaviorSubject,
                  dataBuilder: (context, data) {
                    return NotificationListener(
                      onNotification: (notification) {
                        if (notification is ScrollNotification) {
                          counterBloc.sink.add(notification);
                        }
                        return true;
                      },
                      child: ListView.separated(
                        itemCount: 1,
                        scrollDirection: Axis.horizontal,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 4, vertical: 24),
                        itemBuilder: (context, index) {
                          return Padding(
                              padding:
                              const EdgeInsets.symmetric(horizontal: 12),
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(
                                    'index: $index: data: $data',
                                    style: const TextStyle(fontSize: 34),
                                  ),
                                ],
                              ));
                        },
                        separatorBuilder: (context, index) {
                          return const Padding(
                            padding: EdgeInsets.symmetric(vertical: 10.0),
                            child: VerticalDivider(),
                          );
                        },
                      ),
                    );
                  },
                  loadingBuilder: (context) => const Center(
                    child: CircularProgressIndicator(),
                  ),
                  errorBuilder: (context, error) => Text(
                    error,
                    style: const TextStyle(),
                  ),
                ),
              ),
            ),
            const SizedBox(
              height: 34,
            ),
            Row(
              children: [
                TextButton(
                    onPressed: () {
                      counterBloc.fetchNextPage();
                    },
                    child: const Text(
                      'Add Value',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      counterBloc.resetData();
                    },
                    child: const Text(
                      'Set to no data',
                      style: TextStyle(),
                    )),
                const SizedBox(
                  width: 34,
                ),
                TextButton(
                    onPressed: () {
                      counterBloc.fetchNextPage();
                    },
                    child: const Text(
                      'Add Error',
                      style: TextStyle(),
                    )),
              ],
            ),
            const SizedBox(
              height: 34,
            )
          ],
        ),
      ),
    );
  }
}


copied to clipboard
7
likes
130
points
295
downloads

Publisher

verified publisherfolarin.co

Weekly Downloads

2024.07.06 - 2025.01.18

A simple wrapper around stream controller/rxdart stream for easier implementation of bloc pattern

Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

flutter, rxdart

More

Packages that depend on custom_bloc