前回まではこちら
repository:
前回の続きでpagination対応やりたいと思います
ネイティブアプリのページネーションってfooterにボタンがある感じじゃないですよね
スクロールが下まで行ったら読み込んで追加で表示するみたいなものの方が多いイメージです
なので後者のものをやってみたい
調べるとすぐにこんなものが
まさしく!早速使わせていただこう
今までresponseの型を作らず、Mapとして扱ってきたがこのタイミングでclassも作ろう
そうなるとObjectMapper的なのも欲しい
探してみると色々あるのだが、結局getter/setter
を書かなきゃいけなかったりmapで変換したりと。
それなら自分で関数一つ実装するのと大差ない気がするので今回は自前で関数作成します
せっかくなのでファイルも分けたいと思います
article_list_item.dart
を作成し以下を追加します
class ArticleListItem {
final String? body;
final String? tags;
final String? title;
final String? url;
final String? profileImageUrl;
ArticleListItem(
this.body, this.tags, this.title, this.url, this.profileImageUrl);
factory ArticleListItem.fromJson(Map<String, dynamic> json) =>
ArticleListItem(
json['body'],
json['tags'].map((tag) => tag['name']).join(','),
json['title'],
json['url'],
json['user']['profile_image_url']);
}
各変数をoptionalにしているのはqiitaのドキュメントにrequiredの情報がなかったんでとりあえずoptionalにしてます
factoryの中でmapとか使って変換してたり微妙ですが、今はアーキテクチャとか考えてないので一旦これで。あとで考えたいなとは思ってます
そしたらgetArticles
の中でこの型に変更し、ドキュメントにあるコントローラも定義します
final PagingController<int, ArticleListItem> _pagingController =
PagingController(firstPageKey: 1);
Future<void> getArticles(int pageKey) async {
print(pageKey);
try {
var url = Uri.https('qiita.com', 'api/v2/items',
{'page': '$pageKey', 'per_page': '$_perPage'});
var response = await http.get(url);
final newItems = jsonDecode(response.body);
final articleListItems =
newItems.map((item) => ArticleListItem.fromJson(item));
final isLastPage = articleListItems.length < _perPage;
if (isLastPage) {
_pagingController.appendLastPage(articleListItems);
} else {
final nextPageKey = pageKey + 1;
_pagingController.appendPage(articleListItems, nextPageKey);
}
} catch (error) {
print(error);
_pagingController.error = error;
}
}
あとはListView.separated
をPagedListView
に置き換え、childだけ同じwidgetを使うようにします。コードは長いので割愛します。
これで実行。エラーになった...
type 'MappedListIterable<dynamic, dynamic>' is not a subtype of type 'List<ArticleListItem>'
dartのmap関数は任意の型にキャストしてあげないといけないみたい
書き方はいくつかあるらしい
// ここでList<ArticleListItem>になってると思ってた
final articleListItems =
newItems.map((item) => ArticleListItem.fromJson(item));
// 書き方1
final articleListItems = newItems
.map<ArticleListItem>((item) => ArticleListItem.fromJson(item))
.toList();
// 書き方2
final articleListItems = newItems
.map((item) => ArticleListItem.fromJson(item))
.cast<ArticleListItem>()
.toList();
他の言語のmapに慣れているので少し煩わしさを感じる
newItems
がdynamic
型なのが悪いのかな?少し脱線して検証
const dynamic items = [1,2,3,4];
final stringItems = items.map((i) => '$i').toList();
test(stringItems);
void test(List<String> items) {
items.forEach((i) => print(i));
}
エラーが出る。これが今回の現象とほぼ同じ
Uncaught Error: TypeError: Instance of 'JSArray<dynamic>': type 'JSArray<dynamic>' is not a subtype of type 'List<String>'
dynamic
型ではなく明示的にList<int>
としてみる
const List<int> items = [1,2,3,4];
final stringItems = items.map((i) => '$i').toList();
test(stringItems);
void test(List<String> items) {
items.forEach((i) => print(i));
}
エラー出ない!mapに型を教えなくてもList<String>
になってくれたっぽい
煩わしいわけではなくdartの勉強不足ですね。後で詳しく調べます。
一旦は書き方1を採用して再度起動
最後までスクロールすると次のページネーションを読み込むようになりました