無限スクロールとは?
正式な呼び方はわかりませんが、リストが最後まで来たら、次のページを読み込む処理。SNSのタイムライン読み込みとかで普通に見るやつ。
サーバ側との連携が不可欠なので、いつも使うLaravel + ReactNativeで試す。
Laravel側の実装
まず、Laravel側の準備。とりあえず標準で準備されているUser関連のデータを利用する。
マイグレーション
.envでデータベースの設定をしたら、migrateとするだけでusersテーブルが生成されます。
php artisan migrate
データ生成
最初からあるFactoryを使ってデータを生成します。
DatabaseSeeder.phpに直接下記のように記述します。数はお好みで。
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run()
{
//Userデータを100件作る
factory(App\User::class,100)->create();
}
}
seederを実行します。
php artisan db:seed
これでデータができました。
ルート定義
続いて値をJSONで返すAPIを定義していきます。まずはルートから。
/api/usersでアクセスしたら値を返す想定。
<?php
use Illuminate\Http\Request;
Route::get('/users', 'UserController@index');
コントローラー定義
続いてControllerの作成。
php artisan make:controller UserController
10件ずつページネーションするのは下記のような記述でOK。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index(){
$users = \App\User::paginate(10);
return \Response::json($users);
}
}
リクエスト
問題はLaravelがどうページネーションをしてくれてるか?
とりあえず起動。
php artisan serve
普通にリクエストしてみる。
どうやら1ページ目が帰ってきてるよう。
重要なことは、Userデータ以外にもnext_pageやlast_pageなどが帰ってきてること。
特に何ページあるかは重要なのでlast_pageは重要な値。
で、どうやってページを送るのか?
{"current_page":1,"data":[{"id":1,"name":"Toney Marvin III","email":"dolly17@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":2,"name":"Kayli Walsh","email":"xgibson@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":3,"name":"Jamal Schmitt","email":"fjones@example.net","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":4,"name":"Kurtis Feeney","email":"arnaldo.mante@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":5,"name":"Alexandro Walsh","email":"skshlerin@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":6,"name":"Jessyca Towne","email":"wilmer.labadie@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":7,"name":"Favian Parker MD","email":"ccummerata@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":8,"name":"Osbaldo Jenkins","email":"ettie.parker@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":9,"name":"Dr. Beryl Nienow V","email":"justice72@example.net","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":10,"name":"Mr. London Spinka","email":"mazie.luettgen@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"}],"first_page_url":"http:\/\/localhost:8000\/api\/users?page=1","from":1,"last_page":10,"last_page_url":"http:\/\/localhost:8000\/api\/users?page=10","next_page_url":"http:\/\/localhost:8000\/api\/users?page=2","path":"http:\/\/localhost:8000\/api\/users","per_page":10,"prev_page_url":null,"to":10,"total":100}
ページ送り(page=2)
page=ページ番号というパラメータを付けて送ってみると、そのページが返る。素晴らしい。
ということはpageをlast_pageまでカウントしていけば、ページ送り(というか、ページの追加)ができるということです。
{"current_page":2,"data":[{"id":11,"name":"Dean Krajcik","email":"minnie.harvey@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":12,"name":"Ms. Rachelle Dibbert PhD","email":"marilou19@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":13,"name":"Gerson Emmerich Sr.","email":"mikel.schuster@example.net","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":14,"name":"Abner Kunde","email":"hmcclure@example.net","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":15,"name":"Prof. Sophia Rogahn V","email":"jaskolski.robin@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":16,"name":"Prof. Marshall Rodriguez II","email":"srowe@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":17,"name":"Prof. Kian O'Keefe","email":"beatrice.reichert@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":18,"name":"Prof. Mary Swaniawski MD","email":"mrenner@example.org","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":19,"name":"Mrs. Darby Homenick","email":"whowe@example.com","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"},{"id":20,"name":"Javon Price III","email":"mohammed96@example.net","email_verified_at":null,"created_at":"2018-10-29 21:47:36","updated_at":"2018-10-29 21:47:36"}],"first_page_url":"http:\/\/localhost:8000\/api\/users?page=1","from":11,"last_page":10,"last_page_url":"http:\/\/localhost:8000\/api\/users?page=10","next_page_url":"http:\/\/localhost:8000\/api\/users?page=3","path":"http:\/\/localhost:8000\/api\/users","per_page":10,"prev_page_url":"http:\/\/localhost:8000\/api\/users?page=1","to":20,"total":100}
Laravel側は以上です。
React Native側の実装
で、ReactNative側。とりあえず標準で利用できるもので対応してみる。
以下の通り。
- FlatListのonEndReached()で最後まで行ったかを確認。
- onEndReachedThresholdはどこまで読み込まれたらendとするか(とりあえず0として最後までとする)
- _fetchで読み込むが、newPageの+1ページを読み込み、表示配列のお尻に追加
- 後は自動バインディングでデータが追加・表示される
- last_page以下なら上記の処理を繰り返し、last_page以上では何もしない
- 1000ページあっても読み込まれるので、ページ数に関係なく条件値(max_page)も設定
という感じ。
import React from 'react';
import { StyleSheet, Text, View, FlatList } from 'react-native';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [], //描画するデータ(追記されていく)
page: 0, //現在のページ
last_page: 1, //last_pageで上書きされる
max_page: 100 //ページ数に関係なく上限を設置(last_pageが150でも100で止まる)
}
}
componentDidMount() {
this._fetch();
}
_fetch = () => {
if (this.state.page < this.state.last_page && this.state.page < this.state.max_page) {
const newPage = this.state.page + 1;
fetch(`http://localhost:8000/api/users?page=${newPage}`)
.then(
(response) => response.json()
)
.then((responseJson) => {
this.setState({
data: [...this.state.data, ...responseJson['data']], //今の配列に取得分を追加
page: newPage,
last_page: responseJson['last_page']
})
// console.log(responseJson['last_page'])
})
.catch((error) => {
console.log(error);
})
}else{
//何もしない
}
}
render() {
return (
<View style={{ flex: 1, paddingVertical: 40 }}>
<FlatList
data={this.state.data}
extraData={this.state.data}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={{ padding: 10 }}>
<Text
style={{ fontSize: 24 }}
onPress={() => alert(item.id)}
>{item.id} {item.name} {item.email}</Text>
</View>
)}
onEndReached={() => this._fetch()}
onEndReachedThreshold={0}
/>
</View>
);
}
}
ListFooterComponent={()=><ActivityIndicator size='large' animating />}を追加することで、ロード中、footerにspinnerが表示されます。
比較的簡単。便利。画面を引っ張って更新も簡単。
追記(2018年11月25日)
axiosつかったり、spinner入れたり、ListItem利用したりした改良版。
必要なライブラリのインストール。
npm install --save react-native-elements axios
以下実装。
import React from 'react';
import { StyleSheet, Text, View, FlatList, ActivityIndicator } from 'react-native';
import { ListItem } from 'react-native-elements';
import axios from 'axios';
export default class App extends React.Component {
state = {
data: [],
page: 0,
last_page: 1,
max_page: 100,
animating_status: true,
}
componentDidMount() {
this.getDataFromLaravel();
}
render() {
return (
<View style={{ flex: 1, paddingVertical: 40 }}>
<FlatList
data={this.state.data}
// extraData={this.state.data}
keyExtractor={(item) => item.id.toString()}
onEndReached={() => this.getDataFromLaravel()}
onEndReachedThreshold={0}
ListFooterComponent={() => <ActivityIndicator size='large' animating={this.state.animating_status} />}
renderItem={({ item }) => (
<ListItem
key={item.id}
title={item.id + ' ' + item.name}
subtitle={item.email}
hideChevron
onPress={() => alert(item.id)}
roundAvatar
avatar={{uri: 'https://s3.amazonaws.com/uifaces/faces/twitter/ladylexy/128.jpg'}}
/>
)}
/>
</View>
);
}
getDataFromLaravel = () => {
if (this.state.page < this.state.last_page && this.state.page < this.state.max_page) {
const newPage = this.state.page + 1;
axios
.get(`http://localhost:8000/api/users?page=${newPage}`)
.then(res => {
this.setState({
data: [...this.state.data, ...res.data.data],
page: newPage,
last_page: res.data['last_page'],
});
})
.catch(error => {
console.log(error);
})
} else {
this.setState({
animating_status: false,
});
alert('これ以上読み込めません。');
}
}
}
なぜかAvatarの書き方は古い?表記じゃないと表示されませんでした(本家、サンプル通りじゃ動かない)。