Flutter教程-实现flutter_bloc的MultiBlocBuilder

2,027 阅读2分钟

简介

实现可同时嵌套多个BlocBuilder的MultiBlocBuilder

  • flutter_bloc提供了MultiBlocProvider、MultiBlocListener
  • 作为强迫症患者怎么能没有MultiBlocBuilder这种东西存在,我们来实现它

完成后的使用示例

// 1. main.dart
runApp(MultiBlocProvider(
    providers: [
      BlocProvider<ThemeBloc>(
        builder: (context) => ThemeBloc(),
      ),
      BlocProvider<RongCloudBloc>(
        builder: (context) => RongCloudBloc(),
      )
    ],
    child: App(),)
);
// 2. app.dart
MultiBlocBuilder(
    blocBuilders: <ExtendedBlocBuilder>[
      ExtendedBlocBuilder(
          bloc: _rongCloudBloc,
          condition: (dynamic preState, dynamic state) {
            return (preState as RongCloudState).status !=
                (state as RongCloudState).status;
          }),
      ExtendedBlocBuilder(
        bloc: _themeBloc,
      ),
    ],
    builder: (BuildContext context) {
      return MaterialApp(
        theme: _themeBloc.currentState.themeData,
        debugShowCheckedModeBanner: true,
        home: HomePage(),
        onGenerateRoute: Application.router.generator,
      );
    }
);

实现步骤

  1. 实现ExtendedBlocBuilder
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

typedef ExtendedBlocBuilderCondition = bool Function(
    dynamic previous, dynamic current);

class ExtendedBlocBuilder {
  final Bloc bloc;

  final ExtendedBlocBuilderCondition condition;

  const ExtendedBlocBuilder({
    @required this.bloc,
    this.condition,
  }) : assert(bloc != null);
}

  1. 实现MultiBlocBuilder
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'extended_bloc_builder.dart';

typedef BlocContextBuilder = Widget Function(BuildContext context);

class MultiBlocBuilder<S> extends StatefulWidget {
  const MultiBlocBuilder({
    Key key,
    @required this.blocBuilders,
    @required this.builder,
  })  : assert(blocBuilders != null),
        assert(builder != null),
        super(key: key);

  final List<ExtendedBlocBuilder> blocBuilders;

  final BlocContextBuilder builder;

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

class _MultiBlocBuilderState extends State<MultiBlocBuilder> {
  @override
  Widget build(BuildContext context) {
    dynamic tree;
    List<ExtendedBlocBuilder> blocBuilders =
        widget.blocBuilders.reversed.toList();

    for (final ExtendedBlocBuilder blocBuilder in blocBuilders) {
      tree = ExtendedBlocListener(
        bloc: blocBuilder.bloc,
        listener: (BuildContext context, preState, state) {
          if (blocBuilder.condition?.call(preState, state) ?? true) {
            setState(() {});
          }
        },
        child: tree ?? widget.builder(context),
      );
    }
    return tree;
  }
}

typedef ExtendedBlocWidgetListener<S> = void Function(
    BuildContext context, S preState, S state);

class ExtendedBlocListener<E, S> extends ExtendedBlocListenerBase<E, S> {
  /// The [Bloc] whose state will be listened to.
  /// Whenever the bloc's state changes, `listener` will be invoked.
  final Bloc<E, S> bloc;

  /// The [ExtendedBlocWidgetListener] which will be called on every state change (including the `initialState`).
  /// This listener should be used for any code which needs to execute
  /// in response to a state change (`Transition`).
  /// The state will be the `nextState` for the most recent `Transition`.
  final ExtendedBlocWidgetListener<S> listener;

  /// The [Widget] which will be rendered as a descendant of the [BlocListener].
  final Widget child;

  const ExtendedBlocListener({
    Key key,
    @required this.bloc,
    @required this.listener,
    this.child,
  })  : assert(bloc != null),
        assert(listener != null),
        super(key: key, bloc: bloc, listener: listener);

  /// Clone the current [BlocListener] with a new child [Widget].
  /// All other values, including [Key], [Bloc] and [BlocWidgetListener] are
  /// preserved.
  ExtendedBlocListener<E, S> copyWith(Widget child) {
    return ExtendedBlocListener<E, S>(
      key: key,
      bloc: bloc,
      listener: listener,
      child: child,
    );
  }

  @override
  Widget build(BuildContext context) => child;
}

abstract class ExtendedBlocListenerBase<E, S> extends StatefulWidget {
  /// The [Bloc] whose state will be listened to.
  /// Whenever the bloc's state changes, `listener` will be invoked.
  final Bloc<E, S> bloc;

  /// The [ExtendedBlocWidgetListener] which will be called on every state change.
  /// This listener should be used for any code which needs to execute
  /// in response to a state change (`Transition`).
  /// The state will be the `nextState` for the most recent `Transition`.
  final ExtendedBlocWidgetListener<S> listener;

  const ExtendedBlocListenerBase({
    Key key,
    @required this.bloc,
    @required this.listener,
  }) : super(key: key);

  State<ExtendedBlocListenerBase<E, S>> createState() =>
      _ExtendedBlocListenerBaseState<E, S>();

  /// Returns a [Widget] based on the [BuildContext].
  Widget build(BuildContext context);
}

class _ExtendedBlocListenerBaseState<E, S>
    extends State<ExtendedBlocListenerBase<E, S>> {
  StreamSubscription<S> _subscription;

  S _previousState;
  S _state;

  @override
  void initState() {
    super.initState();
    _previousState = widget.bloc.currentState;
    _state = widget.bloc.currentState;
    _subscribe();
  }

  @override
  void didUpdateWidget(ExtendedBlocListenerBase<E, S> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.bloc.state != widget.bloc.state) {
      if (_subscription != null) {
        _unsubscribe();
        _previousState = widget.bloc.currentState;
        _state = widget.bloc.currentState;
      }
      _subscribe();
    }
  }

  @override
  Widget build(BuildContext context) => widget.build(context);

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

  void _subscribe() {
    if (widget.bloc.state != null) {
      _subscription = widget.bloc.state.listen((S state) {
        widget.listener.call(context, _previousState, state);
        _previousState = state;
      });
    }
  }

  void _unsubscribe() {
    if (_subscription != null) {
      _subscription.cancel();
      _subscription = null;
    }
  }
}