-
Notifications
You must be signed in to change notification settings - Fork 214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change status listener and page request listeners to final #7
Conversation
Do you mind sharing a toy project showcasing the error? |
This is the stack trace of the error:
I can provide a quick example of how the code looks like. Sorry, it is not a working example: import 'dart:async';
import 'package:flutter/material.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
class FooListScreen extends StatefulWidget {
@override
State createState() => _FooListScreenState();
}
class _FooListScreenState extends State<FooListScreen> {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(50.0),
child: AppBar(
backgroundColor: Colors.white,
bottom: TabBar(
labelColor: Colors.black,
tabs: <Widget>[
Tab(text: 'Status A'),
Tab(text: 'Status B'),
Tab(text: 'Status C'),
],
),
),
),
body: Container(
child: TabBarView(
children: <Widget>[
_FooListView(FooStatus.A),
_FooListView(FooStatus.B),
_FooListView(FooStatus.C),
],
),
),
),
);
}
}
class _FooListView extends StatefulWidget {
final int status;
_FooListView(this.status);
@override
State createState() => _FooListViewState();
}
class _FooListViewState extends State<_FooListView> {
final PagingController<int, Foo> _pagingController =
PagingController(firstPageKey: 0);
@override
initState() {
_pagingController.addPageRequestListener((pageKey) => _fetchPage(pageKey));
super.initState();
}
@override
dispose() {
_pagingController.dispose();
super.dispose();
}
Future<void> _fetchPage(int pageKey) async {
final pageSize = 10;
final newItems = await RemoteAPI.getFoosByStatus(
widget.status,
limit: pageSize,
offset: pageKey,
);
final isLastPage = newItems.length < pageSize;
if (isLastPage) {
_pagingController.appendLastPage(newItems);
} else {
final nextPageKey = pageKey + newItems.length;
_pagingController.appendPage(newItems, nextPageKey);
}
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () => Future.sync(() => _pagingController.refresh()),
child: PagedListView<int, Foo>(
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<Foo>(
itemBuilder: (context, item, index) => _FooListItem(item),
newPageProgressIndicatorBuilder: (context) => Center(
child: CircularProgressIndicator(),
),
),
),
);
}
}
class _FooListItem extends StatelessWidget {
final Foo _foo;
_FooListItem(this._foo);
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Card(
child: ListTile(
title: Padding(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
_foo.name,
textAlign: TextAlign.left,
),
],
),
Icon(Icons.keyboard_arrow_right),
],
),
),
onTap: () {},
),
),
],
);
}
} |
When I used |
Alright! Why is that happening? When you switch tabs without the So, the problem is not the error being thrown, but the fact that your widget shouldn't execute code after being disposed. class _FooListViewState extends State<_FooListView> {
final PagingController<int, Foo> _pagingController =
PagingController(firstPageKey: 0);
Object _activeCallbackIdentity;
@override
initState() {
_pagingController.addPageRequestListener((pageKey) => _fetchPage(pageKey));
super.initState();
}
@override
dispose() {
_pagingController.dispose();
_activeCallbackIdentity = null;
super.dispose();
}
Future<void> _fetchPage(int pageKey) async {
final pageSize = 10;
final callbackIdentity = Object();
_activeCallbackIdentity = callbackIdentity;
try {
final newItems = await RemoteAPI.getFoosByStatus(
widget.status,
limit: pageSize,
offset: pageKey,
);
if (callbackIdentity == _activeCallbackIdentity) {
final isLastPage = newItems.length < pageSize;
if (isLastPage) {
_pagingController.appendLastPage(newItems);
} else {
final nextPageKey = pageKey + newItems.length;
_pagingController.appendPage(newItems, nextPageKey);
}
}
} catch (error) {
if (callbackIdentity == _activeCallbackIdentity) {
_pagingController.error = error;
}
}
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () => Future.sync(() => _pagingController.refresh()),
child: PagedListView<int, Foo>(
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<Foo>(
itemBuilder: (context, item, index) => _FooListItem(item),
newPageProgressIndicatorBuilder: (context) => Center(
child: CircularProgressIndicator(),
),
),
),
);
}
} This mechanism protects you against memory leaks and unexpected behavior whenever you’re working with Future calls. It’s nothing specific to pagination or infinite scrolling. FutureBuilder‘s implementation, for example, relies on it too. Also, I took the liberty to add the error handling to your sample code. You can reach me on our chat or my e-mail if you have any doubts! Your PR quiets the error but doesn't solve the issue, that's why I'm closing it. Thank you so much for taking the time! |
This causes an error when using the
TabBarView
widget, which calls thedispose
method when switching between tabs.