Flutter了解之入门篇9-3(状态管理库)

目录
  1. BLoC (状态管理库中Star最高)
  2. GetIt

1. BLoC (状态管理库中Star最高)

更多的是一种设计模式,按照这种设计模式可以实现很多种状态管理。
基于Stream / Observable范式。BLoC 依赖 Stream和 StreamController实现,组件通过Sinks发送更新状态的事件,然后再通过 Streams 通知其他组件更新。事件处理和通知刷新的业务逻辑都是由BLoC完成,从而实现业务逻辑与UI层的分离(有点类似 Redux),并且逻辑部分可以复用和可以单独进行单元测试。

添加依赖库
  bloc插件
  flutter_bloc插件
    提供了BlocProvider、BlocBuilder、BlocListener、BlocConsumer、RepositoryProvider等。
  bloc_package插件
    快速在Flutter/Dart中实现BLoC模式的插件。
  1. 3个重要概念
  1. Cubit(继承自管理状态数据的BlocBase)
  可以管理任意类型的数据,包括基本类型到复杂对象。
  Cubit调用emit构建新的状态数据前需要给状态数据一个初始值。
  当状态数据发生改变时会触发onChange回调,出错时会触发onError回调。
  UI界面可以通过调用 Cubit 对外暴露的更新状态方法触发状态更新,而在 onChange 中会得到更新前后的状态,从而可以触发界面刷新。 

例:
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
  @override
  void onChange(Change<int> change) {
    super.onChange(change);
    print(change);
  }
  @override
  void onError(Object error, StackTrace stackTrace) {
    print('$error, $stackTrace');
    super.onError(error, stackTrace);
  }
}
  1. BlocObserver(可以同时监听所有的Cubit的变化)
例:
class CounterCubit extends Cubit<int> {
  CounterCubit({initial = 0}) : super(initial);
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}
class MyBlocObserver extends BlocObserver {
  @override
  void onCreate(BlocBase bloc) {
    print('BloC Observer onCreate:  ${bloc.state}');
    super.onCreate(bloc);
  }
  @override
  void onChange(BlocBase bloc, Change change) {
    print('BloC Observer onChange: $change');
    super.onChange(bloc, change);
  }
  @override
  void onClose(BlocBase bloc) {
    print('BloC Observer onClose: ${bloc.state}');
    super.onClose(bloc);
  }
  @override
  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
    print('Bloc Observer onError: $error, $stackTrace');
    super.onError(bloc, error, stackTrace);
  }
  @override
  void onEvent(Bloc bloc, Object? event) {
    print('Bloc Observer onEvent: $event, ${bloc.state}');
    super.onEvent(bloc, event);
  }
}
void main() {
  Bloc.observer = MyBlocObserver();
  final cubit = CounterCubit();
  cubit.increment();
  print('after increment: ${cubit.state}');
  cubit.decrement();
  print('after decrement: ${cubit.state}');
  final anotherCubit = CounterCubit(10);
  anotherCubit.increment();
  cubit.close();
  anotherCubit.close();
}
/*
控制台输出如下:
BloC Observer onCreate:  0
BloC Observer onChange: Change { currentState: 0, nextState: 1 }
BloC Observer onChange: Change { currentState: 1, nextState: 0 }
BloC Observer onCreate:  10
BloC Observer onChange: Change { currentState: 10, nextState: 11 }
BloC Observer onClose: 0
BloC Observer onClose: 11
*/
  1. BLoc(继承自BlocBase,比Cubit更高级)
使用events而不是暴露的函数来更新状态。
在Bloc内部有一个onEvent方法,通过EventTransformer将event转换为更新状态的方法来刷新状态数据。每个event都可以有对应的 EventHandler来处理该 event,完成后再通过 emit 触发通知状态更新。当状态转变前会调用 onTransition,有当前的状态、触发更新的event、下一个状态。

例:
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc(int initialState) : super(initialState) {
    on<IncrementEvent>((event, emit) => emit(state + 1));
    on<DecrementEvent>((event, emit) => emit(state - 1));
  }
  @override
  void onTransition(Transition<CounterEvent, int> transition) {
    print(
        'Current: ${transition.currentState}, Next: ${transition.nextState}, Event: ${transition.event}');
    super.onTransition(transition);
  }
}
void main() {
  Bloc.observer = MyBlocObserver();
  final counterBloc = CounterBloc(5);
  counterBloc.add(IncrementEvent());
  counterBloc.add(DecrementEvent());
  counterBloc.close();
}
/*
输出如下:
Current: 5, Next: 6, Event: Instance of 'IncrementEvent'
BloC Observer onChange: Change { currentState: 5, nextState: 6 }
Current: 6, Next: 5, Event: Instance of 'DecrementEvent'
BloC Observer onChange: Change { currentState: 6, nextState: 5 }
BloC Observer onClose: 5
*/
  1. 实现一个简单状态管理类(SimpleBLocProvider)

需要放置在组件树中,因此肯定是Widget。由于内部还需要维护数据,因此继承自StatefulWidget。
需要一个builder属性用来存放原先UI组件的构建,且builder需要携带最新的state状态数据用来更新UI组件。
还需要一个Bloc逻辑组件来获取最新的状态数据。

定义如下:
class SimpleBlocProvider<T> extends StatefulWidget {
  // typedef StateBuilder<T> = Widget Function(T state);
  final StateBuilder<T> builder;
  final BlocBase<T> bloc;
  const SimpleBlocProvider(
      {Key? key, required this.builder, required this.bloc})
      : super(key: key);
  @override
  _SimpleBlocProviderState<T> createState() => _SimpleBlocProviderState<T>();
}

例:
SimpleBlocProvider<int> (builder: (count) => Text('$count'),)

要实现BLoC刷新,需要监听BLoC状态数据的变化。BLoC 是基于Stream实现的,可以使用Stream的listen方法来监听Stream流数据的变化。

listen方法定义如下:
// 可以在 listen 的 onData 中调用 setState 刷新界面。组件销毁时需要取消监听,在_SimpleBlocProviderState 中定义一个属性_streamSubscription存储 listen 方法的返回值,并在 dispose 中取消监听。
StreamSubscription<T> listen(void onData(T event)?, {Function? onError, void onDone()?, bool? cancelOnError});

例:
_streamSubscription = widget.bloc.stream.listen((data) {
  setState(() {
    _state = data;
  });
});
@override
Widget build(BuildContext context) {
  return widget.builder(_state);
}
@override
void dispose() {
  _streamSubscription.cancel();
  super.dispose();
}

完整代码如下

typedef StateBuilder<T> = Widget Function(T state);
class SimpleBlocProvider<T> extends StatefulWidget {
  final StateBuilder<T> builder;
  final BlocBase<T> bloc;
  const SimpleBlocProvider(
      {Key? key, required this.builder, required this.bloc})
      : super(key: key);
  @override
  _SimpleBlocProviderState<T> createState() => _SimpleBlocProviderState<T>();
}
class _SimpleBlocProviderState<T> extends State<SimpleBlocProvider<T>> {
  late T _state;
  late StreamSubscription<T> _streamSubscription;
  @override
  void initState() {
    _state = widget.bloc.state;
    super.initState();
    _streamSubscription = widget.bloc.stream.listen((data) {
      setState(() {
        _state = data;
      });
    });
  }
  @override
  Widget build(BuildContext context) {
    return widget.builder(_state);
  }
  @override
  void dispose() {
    _streamSubscription.cancel();
    super.dispose();
  }
}

==================
使用

class CounterCubit extends Cubit<int> {
  CounterCubit({initial = 0}) : super(initial);
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
  @override
  void onChange(Change<int> change) {
    super.onChange(change);
  }
}
class SimpleBlocCounterPage extends StatelessWidget {
  final counter = CounterCubit();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc 计数器'),
      ),
      body: Center(
        child: SimpleBlocProvider<int>(
          builder: (count) => Text(
            '$count',
            style: TextStyle(
              fontSize: 32,
              color: Colors.blue,
            ),
          ),
          bloc: counter,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        tooltip: '点击增加',
        child: Icon(Icons.add),
      ),
    );
  }
}

===========================
使用2(Bloc)

class Person {
  final String name;
  final String gender;
  const Person({required this.name, required this.gender});
}
abstract class PersonEvent {}
class UsingCnNameEvent extends PersonEvent {}
class UsingEnNameEvent extends PersonEvent {}
class PersonBloc extends Bloc<PersonEvent, Person> {
  PersonBloc(Person person) : super(person) {
    on<UsingCnNameEvent>(
        (event, emit) => emit(Person(name: '狗', gender: '雄性')));
    on<UsingEnNameEvent>(
        (event, emit) => emit(Person(name: 'Dog', gender: 'male')));
  }
}
class SimpleBlocCounterPage extends StatelessWidget {
  final personBloc = PersonBloc(Person(name: '狗', gender: '雄性'));
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc 事件'),
      ),
      body: Center(
        child: SimpleBlocProvider<Person>(
          builder: (person) => Text(
            '姓名:${person.name},性别:${person.gender}',
            style: TextStyle(
              fontSize: 22,
              color: Colors.blue,
            ),
          ),
          bloc: personBloc,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          personBloc.add(UsingEnNameEvent());
        },
        tooltip: '点击增加',
        child: Icon(Icons.add),
      ),
    );
  }
}
  1. BlocProvider

和Provider用法类似,作者为同一人。从Provider迁移到Bloc会很简单。

// 共享状态
final counter =  CounterCubit();
BlocProvider.value(
  value: counter,
  child: SomeWidget(),
);

// 监听状态的部分变化
final isPositive = context.select((CounterBloc b) => b.state >= 0);

// 多状态
MultiBlocProvider(
  providers: [
    BlocProvider<BlocA>(
      create: (BuildContext context) => BlocA(),
    ),
    BlocProvider<BlocB>(
      create: (BuildContext context) => BlocB(),
    ),
    BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
    ),
  ],
  child: ChildA(),
)

和Provider的区别
  BlocProvider:通过Stream.listen方法监听状态数据改变来更新UI。
  Provider:状态对象实现ChangeNotifier接口,并在状态数据改变时调用notifyListeners来通知更新UI。

示例

class BlocCounterWrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterCubit(),
      child: BlocCounterPage(),
    );
  }
}
class BlocCounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc 计数器'),
      ),
      body: Center(
        child: BlocBuilder<CounterCubit, int>(
          builder: (context, count) => Text(
            '$count',
            style: TextStyle(
              fontSize: 32,
              color: Colors.blue,
            ),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read<CounterCubit>().increment();
        },
        tooltip: '点击增加',
        child: Icon(Icons.add),
      ),
    );
  }
}
  1. BlocBuilder
// BlocBuilder既可以配合BlocProvider在组件树中使用Bloc对象,也可以单独拥有自己的Bloc对象。
class BlocBuilder<B extends BlocBase<S>, S> extends BlocBuilderBase<B, S> {
  const BlocBuilder({
    Key? key,
    required this.builder,
    B? bloc,
    // 按条件刷新。返回bool值的回调方法,根据前后状态来决定是否要刷新界面。
    // typedef BlocBuilderCondition<S> = bool Function(S previous, S current);
    BlocBuilderCondition<S>? buildWhen,
  }) : super(key: key, bloc: bloc, buildWhen: buildWhen);
  final BlocWidgetBuilder<S> builder;
  @override
  Widget build(BuildContext context, S state) => builder(context, state);
}

绑定状态对象有两种方式:
  1. 没有指定bloc参数时,它会通过BlocProvider和context自动向上寻找匹配的状态对象。这部分代码在其父类BlocBuilderBase(StatefulWidget类型)的State对象中实现,实际上使用的还是context.read来完成的:
    @override
    void initState() {
      super.initState();
      _bloc = widget.bloc ?? context.read<B>();
      _state = _bloc.state;
    }
  2. 指定了bloc参数时,则使用指定的bloc对象而无需BlocProvider提供。用法类似GetX的GetBuilder。

示例 (BlocBuilder 按条件刷新)

abstract class PersonalEvent {}
// 获取数据事件
class FetchEvent extends PersonalEvent {}
// 成功事件
class FetchSucessEvent extends PersonalEvent {}
// 失败事件
class FetchFailedEvent extends PersonalEvent {}

enum LoadingStatus {
  loading, //加载
  success, //加载成功
  failed,  //加载失败
}
class PersonalResponse {
  PersonalEntity? personalProfile;
  LoadingStatus status = LoadingStatus.loading;
  PersonalResponse({this.personalProfile, required this.status});
}

class PersonalBloc extends Bloc<PersonalEvent, PersonalResponse> {
  final String userId;
  PersonalEntity? _personalProfile;
  PersonalBloc(PersonalResponse initial, {required this.userId})
      : super(initial) {
    on<FetchEvent>((event, emit) {  // 请求网络数据
      getPersonalProfile(userId);
    });
    on<FetchSucessEvent>((event, emit) {  // 加载成功后,用请求得到的个人信息对象和加载状态构建新的 PersonalResponse 对象,使用 emit 通知界面刷新。
      emit(PersonalResponse(
        personalProfile: _personalProfile,
        status: LoadingStatus.success,
      ));
    });
    on<FetchFailedEvent>((event, emit) {  // 加载失败,置空PersonalResponse的个人信息对象,并且标记加载状态为失败。
      emit(PersonalResponse(
        personalProfile: null,
        status: LoadingStatus.failed,
      ));
    });
    on<RefreshEvent>((event, emit) {
      getPersonalProfile(userId);
    });
    // 请求数据
    add(FetchEvent());  
  }
  void getPersonalProfile(String userId) async {
    _personalProfile = await JuejinService().getPersonalProfile(userId);
    if (_personalProfile != null) {
      add(FetchSucessEvent());
    } else {
      add(FetchFailedEvent());
    }
  }
}
class PersonalHomePage extends StatelessWidget {
  PersonalHomePage({Key? key}) : super(key: key);
  final personalBloc = PersonalBloc(
      PersonalResponse(
        personalProfile: null,
        status: LoadingStatus.loading,
      ),
      userId: '70787819648695');
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<PersonalBloc, PersonalResponse>(
      bloc: personalBloc,
      builder: (_, personalResponse) {
        print('build PersonalHomePage');
        if (personalResponse.status == LoadingStatus.loading) {
          return Center(
            child: Text('加载中...'),
          );
        }
        if (personalResponse.status == LoadingStatus.failed) {
          return Center(
            child: Text('请求失败'),
          );
        }
        PersonalEntity personalProfile = personalResponse.personalProfile!;
        return Stack(
          children: [
            CustomScrollView(
              slivers: [
                _getBannerWithAvatar(context, personalProfile),
                _getPersonalProfile(personalProfile),
                _getPersonalStatistic(personalProfile),
              ],
            ),
            Positioned(
              top: 40,
              right: 10,
              child: IconButton(
                onPressed: () {
                  personalBloc.add(FetchEvent());
                },
                icon: Icon(
                  Icons.refresh,
                  color: Colors.white,
                ),
              ),
            ),
          ],
        );
      },
      buildWhen: (previous, next) {  // 按条件刷新
        if (previous.personalProfile == null || next.personalProfile == null) {
          return true;
        }
        return previous.personalProfile!.userId != next.personalProfile!.userId;
      },
    );
  }
  // 其他代码略
}
  1. BlocListener 状态监听组件

当状态改变后会调用listener参数给定的回调函数(没有返回值),做一些后置处理(显示弹窗提醒或确认、显示状态信息、后置拦截器效果、数据上传、离线存储)。

示例(App退出登录前的二次确认)

// 登录状态(已登陆、已退出、退出确认)
enum LoginStatus { logon, logout, logoutConfirm }
class LoginCubit extends Cubit<LoginStatus> {
  LoginCubit({initial = LoginStatus.logout}) : super(initial);
  void login() => emit(LoginStatus.logon);
  void logout() => emit(LoginStatus.logout);
  void logoutConfirm() => emit(LoginStatus.logoutConfirm);
}

按钮和BlocListener都需要使用状态数据,因此使用BlocProvider放置在上层为BlocListener和 BlocBuilder同时提供状态数据。
class BlocListenerWrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => LoginCubit(),
      child: BlocListenerDemo(),
    );
  }
}

BlocListener部分代码如下:
class BlocListenerDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BlocListener 示例'),
      ),
      body: Center(
        child: BlocListener<LoginCubit, LoginStatus>(
          listener: (context, loginSatus) async {
            if (loginSatus == LoginStatus.logout ||
                loginSatus == LoginStatus.logon) {
              ScaffoldMessenger.of(context)
                ..hideCurrentSnackBar()
                ..showSnackBar(SnackBar(
                  content:
                      Text(loginSatus == LoginStatus.logout ? '已退出登录' : '登录成功'),
                  duration: Duration(seconds: 1),
                ));
            } else {
              var confirmed = await _confirmLogout(context);
              if (confirmed == true) {
                context.read<LoginCubit>().logout();
              }
            }
          },
          child: BlocBuilder<LoginCubit, LoginStatus>(
            builder: (context, loginSatus) => TextButton(
              child: Text(
                loginSatus == LoginStatus.logon ? '退出登录' : '登录',
                style: TextStyle(
                  fontSize: 24.0,
                ),
              ),
              onPressed: () {
                if (loginSatus == LoginStatus.logon) {
                  context.read<LoginCubit>().logoutConfirm();
                } else {
                  context.read<LoginCubit>().login();
                }
              },
            ),
          ),
        ),
      ),
    );
  }
  1. BlocConsumer(组合BlocBuilder和BlocListener)

BlocBuilder构建的页面按说不应该是 BlocListener 的子组件,而是同级结构,可使用BlocConsumer来解决。

// 支持构建响应式组件的同时,监听状态变化。
// 支持按条件调用builder刷新组件、按条件调用listener状态监听回调。
const BlocConsumer({
  Key? key,
  required this.builder,  // 
  required this.listener, // 状态改变后会调用
  this.bloc,  // 会自动从当前的BuildContext 中查找对应类型的状态对象
  this.buildWhen, // 接收前后的状态对象,返回 bool 值,若为 true 才会刷新组件
  this.listenWhen, // 接收前后的状态对象,返回 bool 值,若为 true 才会调用listener回调
}) : super(key: key);

看一下BlocConsumer的build方法:
// 基于BlocBuilder实现的,在BlocBuilder的builderWhen中根据listenWhen的返回值来决定是否调用listener回调方法,从而实现了BlocBuilder和BlocListener的聚合。
@override
Widget build(BuildContext context) {
  if (widget.bloc == null) context.select<B, int>(identityHashCode);
  return BlocBuilder<B, S>(
    bloc: _bloc,
    builder: widget.builder,
    buildWhen: (previous, current) {
      if (widget.listenWhen?.call(previous, current) ?? true) {
        widget.listener(context, current);
      }
      return widget.buildWhen?.call(previous, current) ?? true;
    },
  );
}
  1. RepositoryProvider(共享状态)
父子组件传值的方式:
  1. 构造函数传值
    父组件将子组件需要的对象通过构造函数传递给子组件;
    如果组件嵌套很深,传递数据对象需要层层传递,将导致代码很难维护。
  2. 单例对象
    构建单例对象,使得父子组件使用的是同一个对象;
    需要自己构建单例类,而实际上要传递的对象可能存在很多个实例。
  3. 容器
    将对象存入容器中,父子组件使用的时候直接从容器中获取。
    如果往容器存储不定数量的实例对象是不合适的。

flutter_bloc插件提供了一种基于组件的依赖注入方式解决这类问题,通过使用 RepositoryProvider,可以为组件树的子组件提供共享对象,这个共享对象只限在组件树中使用,可以通过Provider的方式访问该对象。
RepositoryProvider实际上是Provider的一个子类,通过注册单例的方式实现组件树对象共享,因此其注册的对象会随着Provider的注销而销毁,而且这个对象无需是Bloc子类。因此在无法使用Bloc传输共享对象时,可以使用RepositoryProvider来完成。

看一下RepositoryProvider类的实现:
class RepositoryProvider<T> extends Provider<T>
    with RepositoryProviderSingleChildWidget {
  RepositoryProvider({
    Key? key,
    required Create<T> create,
    Widget? child,
    bool? lazy,
  }) : super(
          key: key,
          create: create,
          dispose: (_, __) {},
          child: child,
          lazy: lazy,
        );
  RepositoryProvider.value({
    Key? key,
    required T value,
    Widget? child,
  }) : super.value(
          key: key,
          value: value,
          child: child,
        );
  static T of<T>(BuildContext context, {bool listen = false}) {
    try {
      // RepositoryProvider相比Provider只是将静态方法of的listen参数默认设置为false了,也就是不监听状态对象的变化。
      return Provider.of<T>(context, listen: listen);
    } on ProviderNotFoundException catch (e) {
      if (e.valueType != T) rethrow;
      throw FlutterError(
        '''
        RepositoryProvider.of() called with a context that does not contain a repository of type $T.
        No ancestor could be found starting from the context that was passed to RepositoryProvider.of<$T>().
        This can happen if the context you used comes from a widget above the RepositoryProvider.
        The context used was: $context
        ''',
      );
    }
  }
}
再看一下RepositoryProviderSingleChildWidget:
// 一个空的Mixin,仅为了方便MultiRepositoryProvider推断RepositoryProvider的类型。
mixin RepositoryProviderSingleChildWidget on SingleChildWidget {}

从上面可以知道,创建对象共享有2种方式:
  1. create方式(通过调用一个方法创建新的对象)。
  2. value方式(共享一个已有的对象)。

从子组件中访问共享对象(2种方式)
  1. 方式1
    context.read<T>()
  2. 方式2
    RepositoryProvider.of<T>(context)

如果有多个对象需要共享,可以使用MultiRepositoryProvider

MultiRepositoryProvider(
  providers: [
    RepositoryProvider<RepositoryA>(
      create: (context) => RepositoryA(),
    ),
    RepositoryProvider<RepositoryB>(
      create: (context) => RepositoryB(),
    ),
    RepositoryProvider<RepositoryC>(
      create: (context) => RepositoryC(),
    ),
  ],
  child: ChildA(),
)
实际上 RepositoryProvider 借用Provider 实现了一个组件树上的局部共享对象容器。通过这个容器,为RepositoryProvider的子组件树注入了共享对象,使得子组件可以从 context 中或使用RepositoryProvider.of 静态方法获取共享对象。通过这种方式避免了组件树的层层传值,使得代码更为简洁和易于维护。

RepositoryProvider.value(
  child: CustomScrollView(
    slivers: [
      const BannerWithAvatar(),
      const PersonalProfile(),
      const PersonalStatistic(),
    ],
  ),
  value: personalProfile,
),
使用context.read<PersonalEntity>()就可以从 RepositoryProvider 中取出personalProfile对象了

2. GetIt 状态管理(优点:不需要BuildContext)

最初的设计是用于完成依赖注入DI和IOC容器的功能,有点类似JavaSpring的Bean容器。
GetIt容器(全局Map对象):可先往里存入对象,需要时直接取出。容器中的对象是全局的,可用来做数据同步。

注册(存入)
  // 存入的对象一般是单例
  GetIt.instance.registerSingleton<T>(T object);
  // 懒加载方式注册
  GetIt.instance.registerLazySingleton<T>(FactoryFunc<T> func)
获取
  // 获取容器中的对象
  GetIt.instance.get<T>();
依赖库
  1. git_it
    // 对Flutter的最低SDK版本有要求(如版本7.1.2,需要SDK2.12.x以上版本)
    基础的服务管理工具,提供了容器帮助代码找到对应的服务提供对象
  2. git_it_mixin
    GetIt 的扩展,使得GetIt可以完全应用于状态管理
  3. git_it_hooks
    GetIt 的扩展,用于flutter_hooks的场景

示例

// dynamic_listener.dart文件
import 'package:home_framework/models/dynamic_entity.dart';
abstract class DynamicListener {  // 抽离逻辑需求
  // 更新新闻后
  void dynamicUpdated(String id, DynamicEntity updatedDynamic);
  // 添加新闻后
  void dynamicAdded(DynamicEntity newDynamic);
}

// dynamic_page.dart文件
class _DynamicPageState extends State<DynamicPage> implements DynamicListener {
  @override
  void initState() {
    super.initState();
    // 注册到GetIt容器。在其他页面可获取_DynamicPageState对象后调用更新或添加方法来更新本页面。
    GetIt.instance.registerSingleton<DynamicListener>(this);
  }
  void dynamicUpdated(String id, DynamicEntity updatedDynamic) {  // 更新新闻的具体实现
    int index = _listItems.indexWhere((element) => element.id == id);
    if (index != -1) {
      setState(() {
        _listItems[index] = updatedDynamic;
      });
    }
  }
  void dynamicAdded(DynamicEntity newDynamic) {  // 添加新闻的具体实现
    setState(() {
      _listItems.insert(0, newDynamic);
    });
  }
}

// 新增页面
var response = await DynamicService.post(newFormData);
if (response.statusCode == 200) {
  Dialogs.showInfo(context, '添加成功');
  GetIt.instance
      .get<DynamicListener>()
      .dynamicAdded(DynamicEntity.fromJson(response.data));
  Navigator.of(context).pop();
}

// 编辑页面
if (response.statusCode == 200) {
  Dialogs.showInfo(context, '保存成功');
  // 处理成功更新后的业务
  _handleUpdated(newFormData);
  Navigator.of(context).pop();
}
// 处理更新,如果图片更新了才更新动态图片内容
void _handleUpdated(Map<String, String> newFormData) {
  _dynamicEntity.title = newFormData['title'];
  _dynamicEntity.content = newFormData['content'];
  if (newFormData.containsKey('imageUrl')) {
    _dynamicEntity.imageUrl = newFormData['imageUrl'];
  }
  GetIt.instance.get<DynamicListener>().dynamicUpdated(
      _dynamicEntity.id,
      _dynamicEntity,
  );
}

// 详情页面
void _updateViewCount() async {
  try {
    var response = await DynamicService.updateViewCount(_dynamicEntity.id);
    if (response.statusCode == 200) {
      setState(() {
        _dynamicEntity.viewCount = response.data['viewCount'];
        GetIt.instance.get<DynamicListener>().dynamicUpdated(
              _dynamicEntity.id,
              _dynamicEntity,
            );
      });
    }
  } catch (e) {
    print(e.toString());
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容