今日的程式碼 => GIHUB
這篇,我要請求 https://jsonplaceholder.typicode.com/posts/ 這個網址的資料,並顯示在手機上面。
怎麼使用 API 的話,可以查看 前一篇 的文章。
可以看到下方,我們定義了一個 enmu,裡面是狀態。
下面則是定義一個私有的 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();
}
}
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));
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());
}
}
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),
),
],
),
));
},
);
}
}