River_pod: [0.6.0-dev] `useProvider` triggers pointless rebuild when the value type is collection

Created on 3 Aug 2020  路  9Comments  路  Source: rrousselGit/river_pod

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.

bug

Most helpful comment

You could have :

itemBuilder: (context, index) {
  return HookBuilder(builder: (context) {
    final id = useProvider(someProvider.select((s) => s.keys.elementAt(index)) 
    return TodoItem(id);
  });
})

All 9 comments

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 馃憤

https://github.com/mono0926/riverpod_example/blob/5a69ef38ffbf5e9654821d4727da52374c1b7217/lib/pages/counters_page/counters_page.dart#L21-L35

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.

Was this page helpful?
0 / 5 - 0 ratings