LoginSignup
3
1

More than 1 year has passed since last update.

FlutterでQiitaのアプリを作ってみる その5

Posted at

前回まではこちら

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.separatedPagedListViewに置き換え、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に慣れているので少し煩わしさを感じる
newItemsdynamic型なのが悪いのかな?少し脱線して検証

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を採用して再度起動
最後までスクロールすると次のページネーションを読み込むようになりました:tada:

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1