Flutter狀態管理Provider(三)基於Provider的代碼框架

前言

當你瞭解了Provider,並打算用到你的項目中,這篇文章能夠幫你快速進入實戰開發。git

Demo倉庫地址 入口:main_provider.dart -- 代碼框架github

爲什麼要有代碼框架

前面的筆記介紹了Provider的簡易使用Demo和源碼。json

從Demo到項目落地,有個過程。bash

數據與UI的交互自己,說簡單也簡單,說複雜也複雜。不一樣的人寫出來,必然是不盡相同的。光狀態管理就多種方式。框架

  • 數據處理
  • 事件分發
  • 狀態管理-組件內/跨組件/跨頁面

咱們得有個代碼框架,減小學習成本,減小後期維護成本。接下來就是基於Provider,同時結合項目體會,寫了一個代碼框架。less

業務場景

假定咱們有一個社區的應用。咱們有兩個頁面async

  • 帖子列表頁面,展現帖子部分正文/點贊與否。點擊帖子,進入到帖子詳情頁面,但不可點贊/取消點贊。
  • 帖子詳情頁面,展現帖子正文內容/點贊與否。可點贊/取消點贊,同時當回到列表頁,須要顯示最新的點贊狀態。

效果圖

需求分析

  • 1.進入列表頁,請求server列表,展現。
  • 2.點擊列表某一項,傳id,跳轉詳情頁。
  • 3.進入詳情頁,根據id請求server詳情,展現。
  • 4.詳情頁點贊,ask server,詳情頁更新點贊狀態,全局通知點贊狀態變動。
  • 5.列表頁接收通知,變動對應帖子的點贊狀態。

框架思路

數據處理與UI分離。 這是移動開發遵照的框架規則,好比MVP,MVVM等等。 ide

這是一個大體的結構描述。注意點:

  • ChangeNotifierProvider把咱們實現了InteritedElement,Element部分。
  • MyWidget是頁面組件,須要咱們本身畫
  • MyModel是數據處理地方。
  • Notify時,並無把Model(subject) 傳遞出去,而是要從Inherited中獲取。

技術要點

  • ChangNotifier:實現Listenable接口,觀察者模式實現。
  • ChangNotifierProvider:ChangNotifier做爲其參數,ChangNotifier.notifyListeners觸發其刷新邏輯
  • ItemRefresher:列表Item單個刷新功能,參考 Provider中的Selector
  • MultiProvider:Provider庫中,方便Widget使用多個Provider,不用的話,就嵌套多層Provider
  • EventBus:event_bus:^1.1.1 pub上事件總線的某個實現
  • SmartRefresher :上拉下拉刷新加載組件

框架實現

帖子實體類

首先咱們要有個實體類定義帖子。post

class PostBean {
  int id;//惟一標識
  String content; // 正文
  bool isLike;// 點贊與否
複製代碼

Mock Server

咱們從客戶端的視角來看,須要一個Server。這裏咱們mock下。Client與Server用JSON交互。Server應該具有如下接口:獲取列表,獲取詳情,請求點贊學習

class PostServer{
    ///獲取列表
    //返回JSON列表,可轉換爲PostBean列表
    Future<List<Map<String, dynamic>>> loadPosts() async

    ///獲取詳情
    //返回JSON,可轉換爲PostBean對象
    Future<Map<String, dynamic>> getPostDetail(int id) async

    ///請求點贊
    //返回是否操做成功 {"success": true}
    Future<Map<String, dynamic>> like(int id, bool toLike) async
}
複製代碼

事件總線

EventBus eventBus = EventBus();
class BaseEvent {
  void fire() {
    eventBus.fire(this);
  }
}
class PostLikeEvent extends BaseEvent with ChangeNotifier{
  int id;
  bool isLike;

  PostLikeEvent(this.id, this.isLike);
}
複製代碼

頁面構建

咱們有兩個頁面,列表頁和詳情頁。 頁面分紅兩個組件:Widget和Model。

  • Widget UI部分
  • Model能夠理解成MVVM的ViewModel或者MVP的Presenter。負責數據的獲取和處理。

列表頁

PostListModel

class PostListModel with ChangeNotifier {
  var posts = new List<PostBean>();
  ///smartRefresher的刷新控制器
  RefreshController refreshController = RefreshController();
  ///解除事件監聽方法
  VoidCallback _eventDispose;
  /// 單個刷新的ChangeNotifier
  PostListItemListenable itemListenable;

  PostListModel() {
    itemListenable = new PostListItemListenable();
  }
  ///訂閱PostLikeEvent
  void subscribePostLike() {
    StreamSubscription subscription =
        eventBus.on<PostLikeEvent>().listen((event) {
        ///拿到event,更新下當前頁面對應post的isLike狀態
      posts?.firstWhere((post) => post.id == event.id, orElse: () => null)
          ?.isLike = event.isLike;
    });
    _eventDispose = () => subscription.cancel();
  }
  ///加載數據
  void loadData({VoidCallback callback}) {
    PostServer.instance().loadPosts().then((jsonList) {
      posts = jsonList.map((json) => PostBean.map(json)).toList();
      notifyListeners();
      callback.call();
    }).catchError((e) => print(e));
  }
  ///下拉刷新,數據獲取到後,通知smartRefresher
  void refresh() {
    loadData(callback: () => refreshController.refreshCompleted());
  }
  ///ChangeNotifier的 解除監聽方法。
  @override
  void dispose() {
    super.dispose();
    _eventDispose?.call();
  }
}
複製代碼

PostListItemListenable

class PostListItemListenable with ChangeNotifier {
  int id;
}
複製代碼

PostListWidget

class PostListWidget extends StatefulWidget {
  .....省略部分代碼
}

class _PostListWidgetState extends State<PostListWidget> {
  PostListModel _listModel;
  @override
  void initState() {
    super.initState();
    ///初始化構建Model,同時加載數據
    _listModel = PostListModel()..loadData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      .....
      ///多Provider使用,提早設置好
      body: MultiProvider(
        providers: [
          ChangeNotifierProvider.value(value: _listModel),
          ChangeNotifierProvider.value(value: _listModel.itemListenable),
        ],
        child: Consumer<PostListModel>(
          builder: (context, model, child) {
            Widget child = ListView.separated(
                  itemBuilder: (itemContext, index) {
                    return _buildListItem(itemContext, model.posts[index]);
                  },....);
            ///設置 SmartRefresher: refreshController,onRefresh。
            ///onRefresh回調。widget不處理數據相關的回調,而是交給model處理
            return SmartRefresher(
              controller: model.refreshController,
              enablePullDown: true,
              enablePullUp: true,
              onRefresh: () => model.refresh(),
              child: child,
            );
          },
        ),
      ),
    );
  }

  Widget _buildListItem(BuildContext context, PostBean post) {
    ///ItemRefresher 自定義的列表item刷新利器
    return ItemRefresher<PostListItemListenable, PostBean>(
      value: post,
      shouldRebuild: (itemListenable, value) =>
          (itemListenable.id != null && itemListenable.id == value.id),
      builder: (context, value, child) {
        return PostItemWidget(
          post: value,
          click: _skipPostDetail,
        );
      },
    );
  }

  _skipPostDetail(BuildContext context, PostBean post) {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (context) => PostDetailWidget(id: post.id),
    ));
  }
複製代碼

PostItemWidget

class PostItemWidget extends StatelessWidget {
  final PostBean post;

///點擊回調處理
  final void Function(BuildContext context, PostBean post) click;

  const PostItemWidget({Key key, this.post, this.click}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onTap: () => click?.call(context, post),
        child: Container(
          height: 80,
          child: Row(
            children: <Widget>[
              Expanded(
                child: Text(
                  "${post?.content}",
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
              ),
              Container(
                width: 50,
                child: Icon(
                  Icons.favorite,
                  color: (post?.isLike ?? false) ? Colors.red : Colors.grey,
                ),
              ),
            ],
          ),
        ));
  }
}
複製代碼

詳情頁及點贊

詳情頁結構與列表頁類似。咱們單獨點出來 點讚的處理部分。

PostDetailModel

class PostDetailModel with ChangeNotifier {
  PostBean post;

  initPost(int id) {
    PostServer.instance().getPostDetail(id).then((json) {
      post = PostBean.map(json);
      notifyListeners();
    }).catchError((e) => print(e));
  }

  likePost(bool toLike) {
    PostServer.instance().like(post.id, toLike).then((result) {
      if (result["success"]) {
        post.isLike = toLike;
        ///EventBus 全局事件通知
        PostLikeEvent(post.id, toLike).fire();
      }
      ///通知PostDetailWidget刷新
      notifyListeners();
    }).catchError((e) => print(e));
  }
}
複製代碼

這篇文章中涉及到了EventBus,MultiProvider,Selector等等,咱們後續會分析分析。

相關文章
相關標籤/搜索