一覧表示画面で、スクロールすると次々にデータを読み込んでいく、無限スクロールを簡単に作る方法を紹介します。
無限スクロールを作る方法がいろいろあるのですが、初心者の自分でもできたのでこれが一番簡単かと思います。
データを取得するAPIを準備する
今回はAPI経由でデータを取得し、一覧表示するのでまずAPIを準備します。
今回は、以下のswaggerで記載したtitleのみが一覧で返されるAPIを用意しました。次のデータがあれば、レスポンスのoffsetに次の位置が返り、なければ返りません。
openapi: 3.0.0
info:
title: sample
version: '1.0'
description: sample
contact:
name: murapon
servers:
- url: 'http://localhost:31180'
paths:
/list:
get:
operationId: get-list
summary: 一覧取得
description: 一覧取得
parameters:
- schema:
type: integer
default: '10'
in: query
name: limit
description: limit
- schema:
type: integer
default: '0'
in: query
name: offset
description: offset
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/response_list_get'
components:
schemas:
response_list_get:
title: reponse_list_get
description: 一覧取得APIのレスポンスモデル
type: object
properties:
list:
type: array
items:
type: object
properties:
title:
type: string
description: タイトル
required:
- title
total_count:
type: integer
description: 総件数
next_offset:
type: integer
description: 次のoffset
required:
- list
- total_count
flutterでswaggerを読み込んで、APIからデータを取得できるようにする
FlutterでSwagger(openapi-generator)を使う方法を参考にしてください。
一覧表示を作る
以下が、flutter側の全ソースです。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:openapi/api.dart';
class SamplePage extends StatefulWidget {
const SamplePage({
Key key,
}) : super(key: key);
@override
_SamplePageState createState() => _SamplePageState();
}
class _SamplePageState extends State<SamplePage> {
List<Map> list = null;
bool loading = false;
int offset = 0;
@override
Widget build(BuildContext context) {
var length = list?.length ?? 0;
return Scaffold(
appBar: AppBar(title: Text("一覧")),
body: new ListView.builder(
itemBuilder: (context, index) {
if (index == length && offset!=null) {
_load();
return new Center(
child: new Container(
margin: const EdgeInsets.only(top: 8.0),
width: 32.0,
height: 32.0,
child: const CircularProgressIndicator(),
),
);
} else if (index > length) {
return null;
}
if(list.length > index) {
var event = list[index];
return Container(
child: ListTile(title: Text(event['title'])),
);
} else {
return null;
}
},
),
);
}
Future<void> _load() async {
if (loading || offset == null) {
return null;
}
loading = true;
try {
var client = ApiClient(basePath: "http://10.0.2.2:31180");
var defaultApi = DefaultApi(client);
int limit = 3; // 一度に取得する件数
var response = await defaultApi.getListWithHttpInfo(
limit:limit, offset: offset);
ResponseListGet results = ResponseListGet.fromJson(jsonDecode(response.body));
setState(() {
offset = results.nextOffset;
if (list == null) {
list = <Map>[];
}
results.list.forEach((dynamic item) {
list
.add({'title': item.title as String});
});
});
} catch (e) {
print(e.toString());
} finally {
loading = false;
}
}
}
最初の、
List<Map> list = null;
bool loading = false;
int offset = 0;
で、初期化し、_load();
を実行します。
_load();
の中で、APIからデータを取得し、setState
を使って、取得したデータをlist
に入れつつ、再描画処理を実行し、初期表示を行います。
2回目は、取得したoffset
の値があれば、再度APIを実行しなければ、実行しません。
API経由で全データを取得し終わったら、それ以上スクロールしても何もしないという実装にしたかったので、loading
とoffset
で制御しましたが、これを二つを消せば、何度も_load()を実行し続けることが可能です。_load()の中身を変えることで、全部表示したらまた最初から表示し直すなどの表示も可能です。