Flutter了解之入門篇9-3(狀態(tài)管理庫)

目錄
  1. BLoC (狀態(tài)管理庫中Star最高)
  2. GetIt

1. BLoC (狀態(tài)管理庫中Star最高)

更多的是一種設(shè)計模式,按照這種設(shè)計模式可以實現(xiàn)很多種狀態(tài)管理。
基于Stream / Observable范式。BLoC 依賴 Stream和 StreamController實現(xiàn),組件通過Sinks發(fā)送更新狀態(tài)的事件,然后再通過 Streams 通知其他組件更新。事件處理和通知刷新的業(yè)務(wù)邏輯都是由BLoC完成,從而實現(xiàn)業(yè)務(wù)邏輯與UI層的分離(有點類似 Redux),并且邏輯部分可以復(fù)用和可以單獨進(jìn)行單元測試。

添加依賴庫
  bloc插件
  flutter_bloc插件
    提供了BlocProvider、BlocBuilder、BlocListener、BlocConsumer、RepositoryProvider等。
  bloc_package插件
    快速在Flutter/Dart中實現(xiàn)BLoC模式的插件。
  1. 3個重要概念
  1. Cubit(繼承自管理狀態(tài)數(shù)據(jù)的BlocBase)
  可以管理任意類型的數(shù)據(jù),包括基本類型到復(fù)雜對象。
  Cubit調(diào)用emit構(gòu)建新的狀態(tài)數(shù)據(jù)前需要給狀態(tài)數(shù)據(jù)一個初始值。
  當(dāng)狀態(tài)數(shù)據(jù)發(fā)生改變時會觸發(fā)onChange回調(diào),出錯時會觸發(fā)onError回調(diào)。
  UI界面可以通過調(diào)用 Cubit 對外暴露的更新狀態(tài)方法觸發(fā)狀態(tài)更新,而在 onChange 中會得到更新前后的狀態(tài),從而可以觸發(fā)界面刷新。 

例:
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(可以同時監(jiān)聽所有的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而不是暴露的函數(shù)來更新狀態(tài)。
在Bloc內(nèi)部有一個onEvent方法,通過EventTransformer將event轉(zhuǎn)換為更新狀態(tài)的方法來刷新狀態(tài)數(shù)據(jù)。每個event都可以有對應(yīng)的 EventHandler來處理該 event,完成后再通過 emit 觸發(fā)通知狀態(tài)更新。當(dāng)狀態(tài)轉(zhuǎn)變前會調(diào)用 onTransition,有當(dāng)前的狀態(tài)、觸發(fā)更新的event、下一個狀態(tài)。

例:
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. 實現(xiàn)一個簡單狀態(tài)管理類(SimpleBLocProvider)

需要放置在組件樹中,因此肯定是Widget。由于內(nèi)部還需要維護(hù)數(shù)據(jù),因此繼承自StatefulWidget。
需要一個builder屬性用來存放原先UI組件的構(gòu)建,且builder需要攜帶最新的state狀態(tài)數(shù)據(jù)用來更新UI組件。
還需要一個Bloc邏輯組件來獲取最新的狀態(tài)數(shù)據(jù)。

定義如下:
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'),)

要實現(xiàn)BLoC刷新,需要監(jiān)聽BLoC狀態(tài)數(shù)據(jù)的變化。BLoC 是基于Stream實現(xiàn)的,可以使用Stream的listen方法來監(jiān)聽Stream流數(shù)據(jù)的變化。

listen方法定義如下:
// 可以在 listen 的 onData 中調(diào)用 setState 刷新界面。組件銷毀時需要取消監(jiān)聽,在_SimpleBlocProviderState 中定義一個屬性_streamSubscription存儲 listen 方法的返回值,并在 dispose 中取消監(jiān)聽。
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 計數(shù)器'),
      ),
      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會很簡單。

// 共享狀態(tài)
final counter =  CounterCubit();
BlocProvider.value(
  value: counter,
  child: SomeWidget(),
);

// 監(jiān)聽狀態(tài)的部分變化
final isPositive = context.select((CounterBloc b) => b.state >= 0);

// 多狀態(tài)
MultiBlocProvider(
  providers: [
    BlocProvider<BlocA>(
      create: (BuildContext context) => BlocA(),
    ),
    BlocProvider<BlocB>(
      create: (BuildContext context) => BlocB(),
    ),
    BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
    ),
  ],
  child: ChildA(),
)

和Provider的區(qū)別
  BlocProvider:通過Stream.listen方法監(jiān)聽狀態(tài)數(shù)據(jù)改變來更新UI。
  Provider:狀態(tài)對象實現(xiàn)ChangeNotifier接口,并在狀態(tài)數(shù)據(jù)改變時調(diào)用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 計數(shù)器'),
      ),
      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值的回調(diào)方法,根據(jù)前后狀態(tài)來決定是否要刷新界面。
    // 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);
}

綁定狀態(tài)對象有兩種方式:
  1. 沒有指定bloc參數(shù)時,它會通過BlocProvider和context自動向上尋找匹配的狀態(tài)對象。這部分代碼在其父類BlocBuilderBase(StatefulWidget類型)的State對象中實現(xiàn),實際上使用的還是context.read來完成的:
    @override
    void initState() {
      super.initState();
      _bloc = widget.bloc ?? context.read<B>();
      _state = _bloc.state;
    }
  2. 指定了bloc參數(shù)時,則使用指定的bloc對象而無需BlocProvider提供。用法類似GetX的GetBuilder。

示例 (BlocBuilder 按條件刷新)

abstract class PersonalEvent {}
// 獲取數(shù)據(jù)事件
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) {  // 請求網(wǎng)絡(luò)數(shù)據(jù)
      getPersonalProfile(userId);
    });
    on<FetchSucessEvent>((event, emit) {  // 加載成功后,用請求得到的個人信息對象和加載狀態(tài)構(gòu)建新的 PersonalResponse 對象,使用 emit 通知界面刷新。
      emit(PersonalResponse(
        personalProfile: _personalProfile,
        status: LoadingStatus.success,
      ));
    });
    on<FetchFailedEvent>((event, emit) {  // 加載失敗,置空PersonalResponse的個人信息對象,并且標(biāo)記加載狀態(tài)為失敗。
      emit(PersonalResponse(
        personalProfile: null,
        status: LoadingStatus.failed,
      ));
    });
    on<RefreshEvent>((event, emit) {
      getPersonalProfile(userId);
    });
    // 請求數(shù)據(jù)
    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 狀態(tài)監(jiān)聽組件

當(dāng)狀態(tài)改變后會調(diào)用listener參數(shù)給定的回調(diào)函數(shù)(沒有返回值),做一些后置處理(顯示彈窗提醒或確認(rèn)、顯示狀態(tài)信息、后置攔截器效果、數(shù)據(jù)上傳、離線存儲)。

示例(App退出登錄前的二次確認(rèn))

// 登錄狀態(tài)(已登陸、已退出、退出確認(rèn))
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都需要使用狀態(tài)數(shù)據(jù),因此使用BlocProvider放置在上層為BlocListener和 BlocBuilder同時提供狀態(tài)數(shù)據(jù)。
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構(gòu)建的頁面按說不應(yīng)該是 BlocListener 的子組件,而是同級結(jié)構(gòu),可使用BlocConsumer來解決。

// 支持構(gòu)建響應(yīng)式組件的同時,監(jiān)聽狀態(tài)變化。
// 支持按條件調(diào)用builder刷新組件、按條件調(diào)用listener狀態(tài)監(jiān)聽回調(diào)。
const BlocConsumer({
  Key? key,
  required this.builder,  // 
  required this.listener, // 狀態(tài)改變后會調(diào)用
  this.bloc,  // 會自動從當(dāng)前的BuildContext 中查找對應(yīng)類型的狀態(tài)對象
  this.buildWhen, // 接收前后的狀態(tài)對象,返回 bool 值,若為 true 才會刷新組件
  this.listenWhen, // 接收前后的狀態(tài)對象,返回 bool 值,若為 true 才會調(diào)用listener回調(diào)
}) : super(key: key);

看一下BlocConsumer的build方法:
// 基于BlocBuilder實現(xiàn)的,在BlocBuilder的builderWhen中根據(jù)listenWhen的返回值來決定是否調(diào)用listener回調(diào)方法,從而實現(xiàn)了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(共享狀態(tài))
父子組件傳值的方式:
  1. 構(gòu)造函數(shù)傳值
    父組件將子組件需要的對象通過構(gòu)造函數(shù)傳遞給子組件;
    如果組件嵌套很深,傳遞數(shù)據(jù)對象需要層層傳遞,將導(dǎo)致代碼很難維護(hù)。
  2. 單例對象
    構(gòu)建單例對象,使得父子組件使用的是同一個對象;
    需要自己構(gòu)建單例類,而實際上要傳遞的對象可能存在很多個實例。
  3. 容器
    將對象存入容器中,父子組件使用的時候直接從容器中獲取。
    如果往容器存儲不定數(shù)量的實例對象是不合適的。

flutter_bloc插件提供了一種基于組件的依賴注入方式解決這類問題,通過使用 RepositoryProvider,可以為組件樹的子組件提供共享對象,這個共享對象只限在組件樹中使用,可以通過Provider的方式訪問該對象。
RepositoryProvider實際上是Provider的一個子類,通過注冊單例的方式實現(xiàn)組件樹對象共享,因此其注冊的對象會隨著Provider的注銷而銷毀,而且這個對象無需是Bloc子類。因此在無法使用Bloc傳輸共享對象時,可以使用RepositoryProvider來完成。

看一下RepositoryProvider類的實現(xiàn):
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只是將靜態(tài)方法of的listen參數(shù)默認(rèn)設(shè)置為false了,也就是不監(jiān)聽狀態(tài)對象的變化。
      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 {}

從上面可以知道,創(chuàng)建對象共享有2種方式:
  1. create方式(通過調(diào)用一個方法創(chuàng)建新的對象)。
  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 實現(xiàn)了一個組件樹上的局部共享對象容器。通過這個容器,為RepositoryProvider的子組件樹注入了共享對象,使得子組件可以從 context 中或使用RepositoryProvider.of 靜態(tài)方法獲取共享對象。通過這種方式避免了組件樹的層層傳值,使得代碼更為簡潔和易于維護(hù)。

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

2. GetIt 狀態(tài)管理(優(yōu)點:不需要BuildContext)

最初的設(shè)計是用于完成依賴注入DI和IOC容器的功能,有點類似JavaSpring的Bean容器。
GetIt容器(全局Map對象):可先往里存入對象,需要時直接取出。容器中的對象是全局的,可用來做數(shù)據(jù)同步。

注冊(存入)
  // 存入的對象一般是單例
  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以上版本)
    基礎(chǔ)的服務(wù)管理工具,提供了容器幫助代碼找到對應(yīng)的服務(wù)提供對象
  2. git_it_mixin
    GetIt 的擴展,使得GetIt可以完全應(yīng)用于狀態(tài)管理
  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對象后調(diào)用更新或添加方法來更新本頁面。
    GetIt.instance.registerSingleton<DynamicListener>(this);
  }
  void dynamicUpdated(String id, DynamicEntity updatedDynamic) {  // 更新新聞的具體實現(xiàn)
    int index = _listItems.indexWhere((element) => element.id == id);
    if (index != -1) {
      setState(() {
        _listItems[index] = updatedDynamic;
      });
    }
  }
  void dynamicAdded(DynamicEntity newDynamic) {  // 添加新聞的具體實現(xiàn)
    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, '保存成功');
  // 處理成功更新后的業(yè)務(wù)
  _handleUpdated(newFormData);
  Navigator.of(context).pop();
}
// 處理更新,如果圖片更新了才更新動態(tài)圖片內(nèi)容
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());
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容