Describe the bug
useProvider triggers pointless rebuild when the value is collection type.
To Reproduce
At 0.6.0-dev+2, executed this.
build log will be printed each time PRESS ME button is pressed.
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() => runApp(const ProviderScope(child: App()));
class App extends StatelessWidget {
const App({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends HookWidget {
const HomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('build');
useProvider(_stateProvider.select((value) => value.state));
return Scaffold(
appBar: AppBar(),
body: RaisedButton(
child: const Text('PRESS ME'),
onPressed: () => context.read(_stateProvider).state = [42],
),
);
}
}
final _stateProvider = StateProvider((_) => [42]);
If [42](List<int>) is changed to 42(int), the problem doesn't occurs, so collection comparison seems broken.
Expected behavior
build should be printed at first time when the UI appeared, and shouldn't be printed when PRESS ME button pressed.
At 0.5.1, it works well without any problems.
This line seems to cause the problem 馃憖
https://github.com/rrousselGit/river_pod/blob/4c0889e6893d9c4e051d7ce8b828c9fcb98152d3/packages/riverpod/lib/src/framework/select.dart#L46
This was removed voluntarily
Collection comparison are quite expensive, so it is usually inefficient
Is there any way to compare collection like previously?
I sometimes prefer this kind of optimization.
class _ListView extends HookWidget {
const _ListView({ Key key }): super(key: key);
@override
Widget build(BuildContext context) {
final ids = useProvider(someProvider.state.select((s) => s.keys.toList()));
return ListView.builder(
itemCount: ids.length,
itemBuilder: (context, index) => _Tile(id: ids[index]),
);
}
}
But I know this kind of optimization is not much needed, so it is okay if collection comparison is impossible.
There's a different solution:
final someLengthProvider = Provider((ref) {
return ref.watch(someProvider).keys.length;
});
return ListView.builder(
itemCount: useProvider(someLengthProvider),
);
Thanks, but the detection of ids's reorder is needed 馃
You could have :
itemBuilder: (context, index) {
return HookBuilder(builder: (context) {
final id = useProvider(someProvider.select((s) => s.keys.elementAt(index))
return TodoItem(id);
});
})
Thanks, it suppresses the pointless rebuild and works well 馃憤
I think this change should be documented(although you may already be planning on it).
The problem was fixed, so I'm closing the issue, thanks again!
Such code is how Todo and Marvel deal with lists, with using ScopedProvider on the top of that to make the list item constant
Thanks, that's much better 馃憤
Indeed I was concerned the rebuild of each item rather than ListView itself.
So, I reverted the change, and made _Tile as const.
Most helpful comment
You could have :