Closed kylianSalomon closed 8 months ago
I'd need to have a reproducible example in order to find out what is going on. It might be a bug in your code or in SuperSliverList
. It's not clear from the stacktrace.
I think I managed to reproduce an interesting the error in a similar context :
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
final listProvider =
FutureProvider.family<List<String>, String>((ref, query) async {
await Future.delayed(const Duration(seconds: 2));
if (query.isNotEmpty) {
return List.generate(200, (index) => 'Query result $query $index');
}
return List.generate(100, (index) => 'Item $index');
});
void main() {
runApp(const ProviderScope(child: MaterialApp(home: MainApp())));
}
class MainApp extends HookWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
final textController = useTextEditingController();
useListenable(textController);
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (context, hasScrolled) {
return [
SliverAppBar(
pinned: true,
title: TextField(
controller: textController,
),
)
];
},
body: CustomScrollView(
slivers: [
PaginatedSliverListView(
limit: 10,
providerListBuilder: listProvider(textController.text),
listCount: textController.text.isEmpty ? 100 : 200,
)
],
),
),
);
}
}
class PaginatedSliverListView<T> extends ConsumerWidget {
const PaginatedSliverListView({
super.key,
required this.limit,
required this.providerListBuilder,
required this.listCount,
});
final int limit;
final ProviderListenable<AsyncValue<List<T>>> providerListBuilder;
final int listCount;
@override
Widget build(BuildContext context, WidgetRef ref) {
return SuperSliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final itemIndexInPage = index % limit;
final list = ref.watch(providerListBuilder);
return list.when(
loading: () {
if (itemIndexInPage == 0) {
return const Center(child: CircularProgressIndicator());
}
return null;
},
data: (list) {
if (index == 0 && list.isEmpty) {
return const Text('no result');
}
if (itemIndexInPage < list.length) {
return ListTile(
title: Text(list[itemIndexInPage].toString()),
);
}
return null;
},
error: (error, __) {
if (index == 0) {
return const Text('An error occurred');
}
return null;
},
);
},
childCount: listCount,
),
);
}
}
This example needs flutter_riverpod and flutter_hooks packages to work (sorry for those dependencies, I tried to make a quick example and I'm used to develop with this packages).
To reproduce the error you can let the first list loads, then scroll to the bottom of the page and type something in the TextField in the AppBar. You should get an error like this :
Unexpected trailing child.
'package:super_sliver_list/src/render_object.dart':
Failed assertion: line 532 pos 18: 'newChild != null'
This is not exactly the same error but I found that this is due to the return null;
in the loading state where a null return in a list view should stop the list view for being built.
As a result, I'm no longer sure that those errors are related to your packages but may be the way I implemented the SuperSliverList.
You both
a) specify childCount to SliverChildBuilderDelegate
b) return null from the build method
The documentation of SliverChildBuilderDelegate
does not specify how that should be handled, but I can imagine this can be causing problems. I'd recommend always setting childCount
to actual items in the list.
The ref.watch
should probably be outside of the delegate.
I used a simplified version here for testing but normally I pass to the listProvider
the current page value calculated from the sliver index and a arbitrary limit. The purpose is to create a lazy list view with paginated data.
So if I understand well your previous message, I should either set the childCount
, either return null
when dealing with SliverChildBuilderDelegate
?
In my perspective, specifying the childCount
when it is possible was a way of reducing performance cost in infinite list. But I'm maybe wrong.
If childCount
is not specified, SliverMultiBoxAdaptorElement
will determine child count using binary search by calling the childBuilder repeatedly seeing which indices return null. I'd not recommend using this if you have any way to actually determine child count (which you seem to do).
However when childCount
is specified, as it is in your case, the build must be able to return valid child for any index between 0 and childCount-1
, which is seems to be where your code fails.
If you have lazy list with paginated data, and the list loads more data when scrolled to end, then the childCount
should be number of loaded items, not number of total (not loaded yet) items.
Ok I see now where I have probably made an error : instead of returning null
in the loading state to give the information that I no longer want the sliverList to render any widget, I should return a SizedBox.shrink()
because the SliverMultiBoxAdaptorElement
expect the same amount of items regardless of the state for a given childCount
.
That is not a very good solution, as you'll end up materializing elements unecessarily. The correct solution is for childCount
to always reflect the exact number of actually available items.
You're right, I replace the child count value by 1
when my state is loading
and I no longer get the error.
Thanks for the advice and for the package ! 💪
I got this error during build of a SuperSliverList :
This error happened in a search page while updating a list rendered in the SuperSliverList (the list has been updated due to search query changes). I remember having the same error with the Flutter SliverList when updating the given list but the error was something like : earliestUsefulChild !=null is not true if I remember well.
Is this a bug from the package or is it related to a bad practice dealing with sliver list ? Should I rebuild the entire SliverList widget when my list changes ?