iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
Mobile Development

Flutter - 複製貼上到開發套件之旅系列 第 8

【第八天 - Flutter Provider 架構教學】

前言

今日的程式碼 => GIHUB

Yes
這篇,我要請求 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),
                  ),
                ],
              ),
            ));
      },
    );
  }
}


上一篇
【第七天 - Flutter Api、Json 物件教學】
下一篇
【第九天 - Flutter Bloc+Cubit 架構教學】
系列文
Flutter - 複製貼上到開發套件之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言