shinonome-inc / qiita_client_yo

【模擬開発案件】Qiitaクライアントアプリ(PlayGroundモバイルコース最終課題)
3 stars 0 forks source link

【質問】1回目の下端までのスクロールだけ記事の追加ができないことについて【Flutter最終課題・無限スクロール】 #12

Closed KobayashiYoh closed 2 years ago

KobayashiYoh commented 2 years ago

概要

お世話になっております。 Flutter最終課題について質問させていただきたいことがあり、issueを立てさせていただきました。

FeedPage内で無限スクロールができるような実装をしたのですが、最初の1回だけ次の記事を読み込んでそのまま下へスクロールすることができません。 1回目のみ下端までスクロールした後、少し上にスクロールしてからもう一度下端までスクロールしなければ記事を読み込んで下へスクロールすることができないのが現状です。 2回目以降は問題なく記事を読み込んでスクロールすることができています。

1回目のスクロールも2回目以降と同じように記事を追加で読み込んでスクロールするためには、どのような実装をする必要があるでしょうか。

自分で色々と考えて試行錯誤したのですが、上手くいきませんでした。 ご回答いただければ幸いです。

問題点

1回目に下端までスクロールした際、記事を追加で読み込んでスクロールすることができない。 そのため、一度上へスクロールした後、さらに下端までスクロールしなければ記事を追加で読み込むことができない。

実装したい内容

1回目に下端までスクロールした際、記事を追加で読み込んで下にスクロール可能にする

該当箇所のソースコード

https://github.com/shinonome-inc/mobile_yo/blob/feature/feed/mobile_qiita_app/lib/pages/feed_page.dart


試したこと

現在のUI

1回目のスクロール

https://user-images.githubusercontent.com/82624334/148186434-03e01574-865b-439a-8883-4649274e7bcd.mp4

↑ 最初に下端まで到達した際に記事を追加することができていないため、一度少し上までスクロールした後に再度下端までスクロールしないと記事が追加できない

2回目以降のスクロール

https://user-images.githubusercontent.com/82624334/148186378-4588f2e1-3f46-48e6-9e7d-d28ed60286c6.mp4

↑ 2回目以降は問題なく無限スクロールできている

ShuheiYoshidaJP commented 2 years ago

columnとlistviewの違い スクロールするならlistviewを使う方が実装が楽だと思います。

KobayashiYoh commented 2 years ago

コメントありがとうございます。 修正してみます。

KobayashiYoh commented 2 years ago

FutureBuilderの返り値をColumnからListViewに変更したのですが、改善しませんでした。

変更した結果、2回目以降のスクロールも上手く記事が読み込めないという現象も低確率で起こるようになってしまいました。 また、Columnと違ってListViewだとCircularProgressIndicatorの表示が上手くできませんでした。 変更のやり方に問題があると思うのですが、どこをどのように修正すべきだったのでしょうか。

いずれにしても、1回目のスクロールは改善しないままです。 他に考えられる原因がございましたら教えていただけないでしょうか。 ご回答よろしくお願いします。

変更点

013989c

1回目

https://user-images.githubusercontent.com/82624334/148525556-1e7c5c35-e681-40f9-8f16-246dc810c217.mp4


2回目以降

https://user-images.githubusercontent.com/82624334/148525599-3d16ddb8-fb79-4ca7-8a16-4831fb71d5a6.mp4

ShuheiYoshidaJP commented 2 years ago

問題点の予想

lib/pages/feed_page.dart:153と lib/pages/feed_page.dart:154の間に以下のコードを挟んでみた

print('${_scrollController.position.pixels}/${_scrollController.position.maxScrollExtent}');

フィードページでスクロールした結果が以下のようになった

flutter: 50.0/1022.0
flutter: 67.0/1028.6666666666665
flutter: 100.33332824707031/1028.6666666666665
flutter: 115.33332824707031/1028.6666666666665
flutter: 129.0/1034.3076923076924
flutter: 141.66665649414062/1034.3076923076924
flutter: 154.3333282470703/1034.3076923076924
flutter: 169.0/1034.3076923076924
flutter: 184.3333282470703/1034.3076923076924
flutter: 197.3333282470703/1034.3076923076924
flutter: 220.66665649414062/1039.142857142857
flutter: 231.0/1039.142857142857
flutter: 250.3333282470703/1039.142857142857
flutter: 256.6666564941406/1039.142857142857
flutter: 262.0/1039.142857142857
flutter: 269.0/1039.142857142857
flutter: 277.3333282470703/1039.142857142857
flutter: 304.6666564941406/1039.142857142857
flutter: 329.6666564941406/1011.3333333333335
flutter: 335.6666564941406/1011.3333333333335
flutter: 339.6666564941406/1011.3333333333335
flutter: 341.6666564941406/1009.7142857142858
flutter: 342.6666564941406/1009.7142857142858
flutter: 343.3333282470703/1009.7142857142858
flutter: 343.0/1009.7142857142858
flutter: 344.1666666666667/1009.7142857142858
flutter: 352.8333384195964/1015.8666666666668
flutter: 359.8333384195964/1015.8666666666668
flutter: 365.8333384195964/1015.8666666666668
flutter: 371.1666666666667/1015.8666666666668
flutter: 375.1666666666667/1015.8666666666668
flutter: 381.1666666666667/1015.8666666666668
flutter: 383.8333384195964/1015.8666666666668
flutter: 390.8333384195964/1015.8666666666668
flutter: 393.50001017252606/1015.8666666666668
flutter: 396.50001017252606/1015.8666666666668
flutter: 400.1666666666667/1015.8666666666668
flutter: 403.1666666666667/1015.8666666666668
flutter: 407.50001017252606/1021.4285714285716
flutter: 413.8333384195964/1021.4285714285716
flutter: 416.8333384195964/1021.4285714285716
flutter: 422.50001017252606/1021.4285714285716
flutter: 425.1666666666667/1021.4285714285716
flutter: 432.1666666666667/1021.4285714285716
flutter: 440.1666666666667/1025.2
flutter: 444.1666666666667/1025.2
flutter: 449.50001017252606/1025.2
flutter: 458.8333384195964/1025.2
flutter: 464.1666666666667/1025.2
flutter: 471.8333384195964/1025.2
flutter: 473.8333384195964/1025.2
flutter: 477.1666666666667/1025.2
flutter: 478.1666666666667/1025.2
flutter: 479.50001017252606/1025.2
flutter: 480.1666666666667/1025.2
flutter: 480.50001017252606/1025.2
flutter: 481.50001017252606/1025.2
flutter: 481.8333384195964/1025.2
flutter: 482.1666666666667/1025.2
flutter: 482.50001017252606/1025.2
flutter: 482.8333384195964/1025.2
flutter: 484.00000508626306/1025.2
flutter: 496.00000508626306/1025.2
flutter: 508.33333333333337/1024.5714285714287
flutter: 521.0000050862631/1024.5714285714287
flutter: 532.6666615804037/1000.9333333333334
flutter: 538.0000050862631/1000.9333333333334
flutter: 546.6666615804037/1000.9333333333334
flutter: 549.6666615804037/1000.9333333333334
flutter: 551.3333333333334/1000.9333333333334
flutter: 553.3333333333334/1000.9333333333334
flutter: 554.6666615804037/1000.9333333333334
flutter: 556.3333333333334/1000.9333333333334
flutter: 557.0000050862631/1000.9333333333334
flutter: 557.6666615804037/1000.9333333333334
flutter: 558.0000050862631/1000.9333333333334
flutter: 559.1666717529297/1000.9333333333334
flutter: 563.8333282470703/1000.9333333333334
flutter: 572.5/1000.9333333333334
flutter: 576.8333282470703/1000.9333333333334
flutter: 582.5/1000.9333333333334
flutter: 585.1666717529297/1000.2857142857142
flutter: 589.8333282470703/1005.4666666666667
flutter: 592.5/1005.4666666666667
flutter: 598.1666717529297/1005.4666666666667
flutter: 600.8333282470703/1005.4666666666667
flutter: 604.1666717529297/1005.4666666666667
flutter: 607.8333282470703/1005.4666666666667
flutter: 611.8333282470703/1005.4666666666667
flutter: 619.1666717529297/1005.4666666666667
flutter: 623.1666717529297/1005.4666666666667
flutter: 628.5/1005.4666666666667
flutter: 639.1666717529297/1005.4666666666667
flutter: 653.5/1005.4666666666667
flutter: 670.5/1006.8571428571429
flutter: 694.1666717529297/1006.8571428571429
flutter: 714.8333282470703/1010.0
flutter: 722.8333282470703/1010.0
flutter: 734.5/1010.0
flutter: 738.5/1010.0
flutter: 744.1666717529297/1010.0
flutter: 748.8333282470703/1010.0
flutter: 751.1666717529297/1010.0
flutter: 752.8333282470703/1010.0
flutter: 754.8333282470703/1010.0
flutter: 758.5/1010.0
flutter: 761.5/1010.0
flutter: 763.1666717529297/1010.0
flutter: 763.8333282470703/1010.0
flutter: 764.8333282470703/1010.0
flutter: 765.8333282470703/1010.0
flutter: 767.8333282470703/1010.0
flutter: 770.8333282470703/1010.0
flutter: 775.1666717529297/1010.0
flutter: 779.1666717529297/1010.0
flutter: 782.8333282470703/1010.0
flutter: 784.8333282470703/1010.0
flutter: 787.5/1010.0
flutter: 792.5/1010.0
flutter: 797.1666717529297/1010.0
flutter: 802.8333282470703/1010.0
flutter: 806.5/1010.0
flutter: 810.1666717529297/1010.0
flutter: 811.1666717529297/1010.0
flutter: 813.1666717529297/1010.0
flutter: 814.1666717529297/1010.0
flutter: 815.3333384195963/1010.0
flutter: 847.3333384195963/1010.0
flutter: 870.6666666666666/1010.0
flutter: 877.6666666666666/1010.0
flutter: 893.000010172526/1010.0
flutter: 898.3333384195963/1010.0
flutter: 903.6666666666666/1010.0
flutter: 912.6666666666666/1010.0
flutter: 916.6666666666666/1010.0
flutter: 923.3333384195963/1010.0
flutter: 927.000010172526/1010.0
flutter: 934.000010172526/1010.0
flutter: 939.6666666666666/1010.0
flutter: 943.6666666666666/1010.0
flutter: 950.6666666666666/1010.0
flutter: 953.6666666666666/1010.0
flutter: 957.6666666666666/1010.0
flutter: 961.6666666666666/1010.0
flutter: 965.6666666666666/1010.0
flutter: 971.000010172526/1010.0
flutter: 976.000010172526/1010.0
flutter: 987.3333384195963/1010.0
flutter: 1000.6666666666666/1010.0
flutter: 1014.6666666666666/1010.0
flutter: 1025.896983108241/1010.0
flutter: 1030.18852469534/1010.0
flutter: 1033.6085888577884/1010.0
flutter: 1037.4754282350323/1010.0
flutter: 1043.5235089888508/1010.0
flutter: 1048.3632777304024/1010.0
flutter: 1051.898623187427/1010.0
flutter: 1053.2662801153842/1010.0
flutter: 1054.4765861233707/1010.0
flutter: 1056.2849000642518/1010.0
flutter: 1057.1837350205737/1010.0
flutter: 1057.6318327491908/1010.0
flutter: 1057.9301287026615/1010.0
flutter: 1056.8068629267148/1010.0
flutter: 1054.118313581789/1010.0
flutter: 1042.9024662434745/1010.0
flutter: 1039.1772450917952/1010.0
flutter: 1032.5209588322966/1010.0
flutter: 1027.1027230175848/1010.0
flutter: 1022.8591751645237/1010.0
flutter: 1019.6083032263718/1010.0
flutter: 1010.0/1010.0

154行目で以下の条件を設置している

if (_scrollController.position.pixels >=
          _scrollController.position.maxScrollExtent) {

少しでもスクロールすると上記の条件がtrueとなり 現在原因が分かりませんが… _moreLoadが呼ばれる →リクエスト作成が一定時間に大量に行われてアクセス制限に引っかかり403 1ページ分スクロールすると403になったのは確認されました。

ShuheiYoshidaJP commented 2 years ago

提案

のフローを見直してみましょう。 頭の中で整理ではなく書き出してみると いいですね。

ShuheiYoshidaJP commented 2 years ago

一つ確認方法として 提案したいのが、

print('${_scrollController.position.pixels}/${_scrollController.position.maxScrollExtent}');

でスクロール量の確認

156行目にデバックをはって _moreLoadが意図したときに 呼ばれるかどうかを確認する必要があります。

確認方法わからなければ連絡ください。

KobayashiYoh commented 2 years ago

@ShuheiYoshidaJP とても丁寧に解説してくださり本当にありがとうございます。 提案していただいたことを基に試してみます。

KobayashiYoh commented 2 years ago

スクロール量の確認

lib/pages/feed_page.dart:153とlib/pages/feed_page.dart:154の間に以下のコードを用いて確認しました。

print('${_scrollController.position.pixels}/${_scrollController.position.maxScrollExtent}');

実行結果

I/flutter ( 6629): 12.276785714285666 / 1180.904761904762
I/flutter ( 6629): 22.276785714285666 / 1180.904761904762
I/flutter ( 6629): 29.419642857142833 / 1180.904761904762
I/flutter ( 6629): 39.41964285714283 / 1180.904761904762
I/flutter ( 6629): 49.70982142857139 / 1180.904761904762
I/flutter ( 6629): 59.70982142857139 / 1180.904761904762
I/flutter ( 6629): 71.71874999999994 / 1180.904761904762
I/flutter ( 6629): 81.98660714285711 / 1180.904761904762
I/flutter ( 6629): 91.98660714285711 / 1180.904761904762
I/flutter ( 6629): 101.98660714285711 / 1180.904761904762
I/flutter ( 6629): 112.27678571428567 / 1156.6623376623374
I/flutter ( 6629): 126.27232142857139 / 1156.6623376623374
I/flutter ( 6629): 139.41964285714283 / 1156.6623376623374
I/flutter ( 6629): 149.7098214285714 / 1156.6623376623374
I/flutter ( 6629): 158.57142857142856 / 1156.6623376623374
I/flutter ( 6629): 168.8616071428571 / 1156.6623376623374
I/flutter ( 6629): 177.9910714285714 / 1156.6623376623374
I/flutter ( 6629): 187.9910714285714 / 1156.6623376623374
I/flutter ( 6629): 197.9910714285714 / 1156.6623376623374
I/flutter ( 6629): 207.1428571428571 / 1156.6623376623374
I/flutter ( 6629): 215.13392857142856 / 1156.6623376623374
I/flutter ( 6629): 221.4285714285714 / 1156.6623376623374
I/flutter ( 6629): 228.28124999999994 / 1156.6623376623374
I/flutter ( 6629): 235.4241071428571 / 1156.6623376623374
I/flutter ( 6629): 243.41517857142856 / 1127.5714285714284
I/flutter ( 6629): 249.7098214285714 / 1127.5714285714284
I/flutter ( 6629): 254.5535714285714 / 1127.5714285714284
I/flutter ( 6629): 258.57142857142856 / 1127.5714285714284
I/flutter ( 6629): 260.5580357142857 / 1127.5714285714284
I/flutter ( 6629): 263.7053571428571 / 1127.5714285714284
I/flutter ( 6629): 266.85267857142856 / 1127.5714285714284
I/flutter ( 6629): 267.70089285714283 / 1127.5714285714284
I/flutter ( 6629): 269.70982142857144 / 1127.5714285714284
I/flutter ( 6629): 270.84821428571433 / 1139.8791208791208
I/flutter ( 6629): 271.6964285714286 / 1139.8791208791208
I/flutter ( 6629): 273.7053571428572 / 1139.8791208791208
I/flutter ( 6629): 271.4164387885608 / 1139.8791208791208
I/flutter ( 6629): 271.2306629247422 / 1139.8791208791208
I/flutter ( 6629): 297.52530578188504 / 1139.8791208791208
I/flutter ( 6629): 354.08780578188504 / 1139.8791208791208
I/flutter ( 6629): 421.8110200675993 / 1139.8791208791208
I/flutter ( 6629): 466.0967343533136 / 1139.8791208791208
I/flutter ( 6629): 521.5208414961708 / 1139.8791208791208
I/flutter ( 6629): 571.2306629247422 / 1162.032967032967
I/flutter ( 6629): 627.7931629247422 / 1162.032967032967
I/flutter ( 6629): 672.0788772104565 / 1162.032967032967
I/flutter ( 6629): 717.5029843533136 / 1162.032967032967
I/flutter ( 6629): 756.944948639028 / 1162.032967032967
I/flutter ( 6629): 776.0744129247423 / 1162.032967032967
I/flutter ( 6629): 790.3601272104565 / 1162.032967032967
I/flutter ( 6629): 801.4985200675994 / 1162.032967032967
I/flutter ( 6629): 802.6369129247423 / 1162.032967032967
I/flutter ( 6629): 851.1697442697912 / 1170.6483516483518
I/flutter ( 6629): 941.3256604522452 / 1175.5714285714284
I/flutter ( 6629): 979.8119615414323 / 1175.5714285714284
I/flutter ( 6629): 996.9849139728756 / 1175.5714285714284
I/flutter ( 6629): 1027.4751732639334 / 1175.5714285714284
I/flutter ( 6629): 1040.8003280340008 / 1175.5714285714284
I/flutter ( 6629): 1064.3366811850196 / 1175.5714285714284
I/flutter ( 6629): 1084.0027829048424 / 1175.5714285714284
I/flutter ( 6629): 1100.4610014911268 / 1175.5714285714284
I/flutter ( 6629): 1114.2340154592962 / 1175.5714285714284
I/flutter ( 6629): 1125.7297938489885 / 1175.5714285714284
I/flutter ( 6629): 1130.6958529355818 / 1175.5714285714284
I/flutter ( 6629): 1135.232826444721 / 1175.5714285714284
I/flutter ( 6629): 1139.3516199669239 / 1175.5714285714284
I/flutter ( 6629): 1143.0368463981852 / 1175.5714285714284
I/flutter ( 6629): 1149.3792340947534 / 1175.5714285714284
I/flutter ( 6629): 1152.0233797794856 / 1175.5714285714284
I/flutter ( 6629): 1156.4472198116505 / 1175.5714285714284
I/flutter ( 6629): 1158.2494727109663 / 1175.5714285714284
I/flutter ( 6629): 1159.7967381095323 / 1175.5714285714284
I/flutter ( 6629): 1162.154682418836 / 1175.5714285714284
I/flutter ( 6629): 1163.6441094107884 / 1175.5714285714284
I/flutter ( 6629): 1164.0930042303346 / 1175.5714285714284
I/flutter ( 6629): 1164.3571238626378 / 1175.5714285714284
I/flutter ( 6629): 1164.4254706511795 / 1175.5714285714284
I/flutter ( 6629): 1166.4343992226081 / 1175.5714285714284
I/flutter ( 6629): 1168.4210063654652 / 1175.5714285714284
I/flutter ( 6629): 1169.5593992226081 / 1175.5714285714284
I/flutter ( 6629): 1171.5683277940366 / 1175.5714285714284
I/flutter ( 6629): 1174.4254706511795 / 1175.5714285714284
I/flutter ( 6629): 1175.5714285714284 / 1175.5714285714284

デバッグ

lib/pages/feed_page.dart:156をブレークポイントとしてデバッグを行った結果、以下の画像のように、_currentPageNumberのみ1ずつ増加していたことがわかりました。

スクリーンショット (337) スクリーンショット (338) スクリーンショット (339) スクリーンショット (340)

KobayashiYoh commented 2 years ago

現状整理

実行した際のフロー

  1. 一番最初にinitState内でfetchArticle(1, '')が呼び出される
  2. スクロールの下端に到達したら_moreLoad()が呼び出される
  3. _moreLoad()内の_curentPageNumber++が実行されて_curentPageNumber=2になる
  4. _moreLoad()内のfetchAtricle(2, '')が呼び出される
  5. そのままスクロールできないので一度上へスクロール
  6. スクロールの下端に到達したら_moreLoad()が呼び出される
  7. _moreLoad()内の_curentPageNumber++が実行されて_curentPageNumber=3になる
  8. _moreLoad()内のfetchAtricle(3, '')が呼び出される
  9. 以降はスクロール可能

実行結果

I/flutter ( 8947): initState()
I/flutter ( 8947): fetchArticle(1, )
I/flutter ( 8947): bottom edge
I/flutter ( 8947): _moreLoad()
I/flutter ( 8947): _currentPageNumber++
I/flutter ( 8947): _currentPageNumber: 2
I/flutter ( 8947): fetchArticle(2, )
I/flutter ( 8947): bottom edge
I/flutter ( 8947): _moreLoad()
I/flutter ( 8947): _currentPageNumber++
I/flutter ( 8947): _currentPageNumber: 3
I/flutter ( 8947): fetchArticle(3, )
KobayashiYoh commented 2 years ago

デバッグ(2回目)

再びlib/pages/feed_page.dart:156をブレークポイントとしてデバッグを行った結果、前回とは違う結果になりました。

前回と同じ点

前回と異なる点

考察

2回目のデバッグを行った結果、最初の_moreLoad()実行時に_scrollControllerのoffsetが変更されていないことがわかった。 もし、offsetが変更できたら改善するのでしょうか。

デバッグの結果

スクリーンショット (342) スクリーンショット (343) スクリーンショット (344) スクリーンショット (345)

ShuheiYoshidaJP commented 2 years ago

直接的な解決にはならないのですが addListenerに問題あるかもしれませんね。 lib/pages/feed_page.dart:153

    _scrollController.addListener(() {
      if (_scrollController.position.pixels >=
          _scrollController.position.maxScrollExtent) {
        print('more load');
        //_moreLoad();
      }
    });

↑これで実験してみてください。 一番下までスクロールすると more loadが大量に呼ばれていると思います。 ここに問題があるのではと思いました。

ShuheiYoshidaJP commented 2 years ago

more loadが大量に呼ばれている原因に関して考えてみました。

僕が立てた仮説

UIが更新される(新しい記事情報が取得され、ListView内に表示される)まで永遠とmore loadが呼ばれるのではないか? ↑これは間違っている気がする

理由

そもそも_moreLoadをコメントアウトしているため 新しい記事情報は取得されないからListView内のUIは更新されないはず

ShuheiYoshidaJP commented 2 years ago

スクロール検知のロジックに問題あり? https://kwmt27.net/2018/09/03/flutter-scroll/ ↑こちらで使っているスクロール検知の条件はいかがでしょうか?

懸念点

現状での原因が解決されない

KobayashiYoh commented 2 years ago

lib/pages/feed_page.dart:153

    _scrollController.addListener(() {
      if (_scrollController.position.pixels >=
          _scrollController.position.maxScrollExtent) {
        print('more load');
        //_moreLoad();
      }
    });

↑これで実行して一番下までスクロールしてみたのですが、_moreLoadが大量に呼ばれる現象は確認できませんでした。

実行結果

I/flutter (12430): more load
KobayashiYoh commented 2 years ago

https://kwmt27.net/2018/09/03/flutter-scroll/ ↑ 次はこれを試してみようと思います。

ちなみに、ここまで無限スクロールを実装するにあったって参考にしたのは以下の動画です。 https://www.youtube.com/watch?v=EXJboDdIpEA https://www.youtube.com/watch?v=umIztKrk-AY

ShuheiYoshidaJP commented 2 years ago

もしかして挙動が違うんですかね⁈

https://user-images.githubusercontent.com/62702170/149136232-2400e807-1d8b-4afc-8fe0-ac7b14ec4df2.mov

この動画見た感じだと 最下の_articleWidgetにたどりついてもなお 下にスクロールしようとすると追加で_moreLoadが呼ばれることがわかりました。

_scrollController.position.pixels を使用するとスクロールした量を忠実に出力してしまうのが_moreLoadが大量に呼ばれる原因だと考えました。

今回は要件としては 最下の_articleWidgetに到達した瞬間さえ分かればいいので 忠実なスクロール量を計測する必要がないのだと僕は考えます。

解決策は二つあって 一つ目は https://kwmt27.net/2018/09/03/flutter-scroll/ ↑このサイトで使っている条件のようにスクロール量に閾値を設定するという手法 二つ目は lib/pages/feed_page.dart:76 の

itemBuilder: (context, index) {

indexを使用して条件分岐を設定する必要があるかと思います。