【第八天 - Flutter Provider 架构教学】

前言

今日的程序码 => GIHUB


这篇,我要请求 https://jsonplaceholder.typicode.com/posts/ 这个网址的资料,并显示在手机上面。
怎麽使用 API 的话,可以查看 前一篇 的文章。

为什麽要使用 Provider

  • 方便管理使用者状态。(以登入、正在登入、尚未登入)
  • 方便分享资料。(有点类似资料共享)
  • 方便把逻辑、UI 分开

什麽事 Provider 呢?

  • 他是一个我们可以自己写控制器的地方。

可以看到下方,我们定义了一个 enmu,里面是状态。

  • 排序 by ID
  • 排序 by Title
  • 排序 by Body
  • 排序 by UserID

Provider 控制器 范例

下面则是定义一个私有的 sortState 和公开的 sortState,和 post 资料。
底下是一个 fetchData 的方法。他会去 call Api。当我们把 api 的资料拿到手後。我们就开始排序。最後我们排序完成後需要使用 notifyListeners,告知控制器,资料已经被更新。

enum SortState { sortWithId, sortWithTitle, sortWithBody, sortWithUserId }

class PostProvider extends ChangeNotifier {
PostRepository _postRepository = PostRepository();

SortState _sortState = SortState.sortWithId;

SortState get sortState => _sortState;

List<PostModel> _posts = [];

List<PostModel> get posts => _posts;

fetchData(SortState sortState) async {
  _sortState = sortState;
  _posts = await _postRepository.fetchData();
  if (_sortState == SortState.sortWithId) {
    _posts.sort((a, b) => a.id.compareTo(b.id));
  } else if (_sortState == SortState.sortWithTitle) {
    _posts.sort((a, b) => a.title.compareTo(b.title));
  } else if (_sortState == SortState.sortWithBody) {
    _posts.sort((a, b) => a.body.compareTo(b.body));
  } else if (_sortState == SortState.sortWithUserId) {
    _posts.sort((a, b) => a.userId.compareTo(b.userId));
  }
  notifyListeners();
}
}

怎麽使用 Provider 呢?

MultiProvider 元件是一个初始化的共享装置元件。
这边我们在 MaterialApp 外面建立了一个 MultiProvider,代表只要在 MaterialApp 里面都可以取到 PostProvider 的资料。

    return MultiProvider(
    providers: [
      ChangeNotifierProvider<PostProvider>(
        create: (context) => PostProvider(),
      ),
    ],
    child: MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    ),
  );

底下的元件要怎麽使用呢?

单存呼叫控制器的方法。

context.read<PostProvider>().fetchData(SortState.sortWithId);

读取资料 watch,这个方式只要有呼叫触发 notifyListeners,他就会重新去读取资料,不管这笔资料的值是否和原先的不一样,他都会重新 rebuild widget。使用方式可以在 build 方法里面的 return Widget 里面直接使用。

  @override
Widget build(BuildContext context) {
  return Text(context.watch<PostProvider>().posts[1].body);
}

读取资料 select,这是方法需要满足以下条件

  • 有触发 notifyListener
  • 资料的值必须和原先的值不一样
  • context.select 需要放在 return 外面
  • 被关注的值必须是不可动态变动的。
@override
Widget build(BuildContext context) {
  var posts = context.select((PostProvider p) => p.posts);
  return Text(posts[1].body);
}

注意:

在这边,我分享一个 ISSUE

可以看到 select relies on the value obtained to be immutable
因此如果我们使用 select 的方式,就要把我们的 provider 里面要更改成这样

      _posts = [..._posts];
      _posts.sort((a, b) => a.id.compareTo(b.id));

MyHomePage

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    // 一进到画面就取资料
    context.read<PostProvider>().fetchData(SortState.sortWithId);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          actions: <Widget>[
            PopupMenuButton(
                icon: Icon(Icons.more_vert),
                itemBuilder: (context) => [
                      PopupMenuItem(
                        child: Text('使用 userId 排序'),
                        value: SortState.sortWithUserId,
                      ),
                      PopupMenuItem(
                        child: Text('使用 id 排序'),
                        value: SortState.sortWithId,
                      ),
                      PopupMenuItem(
                        child: Text('使用 title 排序'),
                        value: SortState.sortWithTitle,
                      ),
                      PopupMenuItem(
                        child: Text('使用 body 排序'),
                        value: SortState.sortWithBody,
                      )
                    ],
                onSelected: (SortState value) {
                  context.read<PostProvider>().fetchData(value);
                })
          ],
        ),
        body: MyListView());
  }
}

MyListView

class MyListView extends StatelessWidget {
  const MyListView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: context.watch<PostProvider>().posts.length,
      itemBuilder: (context, index) {
        var post = context.watch<PostProvider>().posts[index];
        return Container(
            decoration: BoxDecoration(
                borderRadius: BorderRadius.all(Radius.circular(16)),
                color: Colors.white,
                border: Border.all(color: Colors.blueAccent, width: 2.0)),
            margin: EdgeInsets.all(8),
            padding: EdgeInsets.all(8),
            child: RichText(
              text: TextSpan(
                style: DefaultTextStyle.of(context).style,
                children: <TextSpan>[
                  TextSpan(
                    text: post.id.toString() + ". " + post.title,
                    style: TextStyle(fontSize: 18, color: Colors.red),
                  ),
                  TextSpan(
                    text: '\n' + post.body,
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  TextSpan(
                    text: "\nUser ID:" + post.userId.toString(),
                    style: TextStyle(fontSize: 18),
                  ),
                ],
              ),
            ));
      },
    );
  }
}


<<:  GCP硬碟加大

>>:  Day 17:专案03 - PTT 八卦版爬虫02 | session、post

DAY07 - [CSS+RWD] 导览列

今日文章目录 > - 导览列 > - 练习演示 > - 遇到的问题 > -...

Day17 Preparation of Gin and Env

Preface 由於之後我们会解说到像是Gin搭配RDBMS或是Gin搭配Cache....等,因此...

【Day 9】Introduction - Practice 1

题目 美术馆有n 种票,票价为 p1、p2、p3 直到 pn,所需张数为 x1、x2、x3 直到 x...

Day 16: Structural patterns - Flyweight

目的 当有大量重复物件时,抽离物件相同部分的资料,并用专属的工厂管理,好减少重复的资料,减少记忆体的...

[Day 17 - npm] 哆啦A梦有百宝袋,我有套件管理工具npm

前端的社群发展愈来愈蓬勃,延伸出各式各样基於 HTML、CSS、JS 的开源套件,像是 Bootst...